瞎扯淡 [12 台减至 3 台] 用 Golang 重写 Sidekiq 的 worker

shawnyu · 2015年08月05日 · 最后由 benzhang 回复于 2017年03月24日 · 18021 次阅读
本帖已被设为精华帖!

漫画之前使用了 12 台 8 核 16G 机器 跑 sidekiq 的任务

每次重启 sidekiq 都会造成大量积压 而且要很久才能跑完,所以一般都不太敢重启 sidekiq

7 月初@tency 带头用 golang 重写了大部分 worker 逻辑 用了这个库 https://github.com/jrallison/go-workers

最终还没有重构完的 ruby worker 占两台,go worker 只用了一台 而且基本上没什么负载

有些事情确实不是 ruby 的强项。

大家关注点有点歪啊, 第一时间怀疑我们业务设计有问题么。 其实主要问题还是量大 每天跑 800 万任务

每秒

抛个问题,像这样的重型 worker 系统, 大家怎么选方案?

最后最后,有人想来一起维护这个嘛? yuchanghong@baozou.com

评论区几个有价值的观点:

  1. @luikore :worker 一次取多个任务, 然后把 io 读写都 batch。https://github.com/gzigzigzeo/sidekiq-grouping

  2. @quakewang :重度网络 IO 试试 异步的 http client

👍👍👍👍👍

12 台 8 核 16G 机器 跑 sidekiq 的任务

我想知道有多少任务在跑。

jjburst

没看代码。这是用 GO 做 Worker 然后跑 Ruby 代码么

竟然要用这么多机器跑,能说说是用来跑什么任务吗?

是计算密集型的任务吧? 我们也是用 Golang 重写了部分 worker,其实用 Java、C# 之类重写也可以,都会比 Ruby 省资源。

#6 楼 @chrisloong 各种 io 和计算的都有

9楼 已删除

为什么不先换 jruby 试试

#11 楼 @shawnyu jruby 性能不差

#12 楼 @steven_yue 不能保证使用的 gem 性能很好,比如说如果 gem 没有单独用 java 写用到 java.nio 包的话即使用了 jruby 在 io 方面性能也好不到哪去

#10 楼 @steven_yue

同问,

  1. 为啥不考虑 jruby?

  2. 如果用 golang 重写的话,所有的业务逻辑是不是都要重新实现一遍?而且要和 Rails 中的业务逻辑始终保持一致,维护的工作量是不是有点大?

15楼 已删除
16楼 已删除

对于 worker 系统 golang 先天并发优势,再加上语言效率, 肯定是首选

不过,希望能够普及一下改造的背景,实例说明等,

#18 楼 @huobazi go_workers 兼容 sidekiq api, Rails 端通过 sidkeiq client push 任务, go 这边执行任务。golang 这边要把之前的 worker 逻辑重写一遍

#21 楼 @shawnyu 明白些了, 谢谢。

很好奇,这些机器是专门跑 sidekiq 吗? 还是说上边也有 app server,然后同时还跑 sidekiq?

#23 楼 @tony612 当然是单独的,跟 appserver 放在一起 肯定互相影响啊。

我还是习惯性的想为 ruby 辩解几句,sidekiq_worker(重构前的 worker) = sidekiq + 暴走 rails app, go_worker(重构后的 worker) = go_workers + 精简的暴走业务代码, 看到区别了没有,一个是拖家带口臃肿不堪,一个是轻装上阵如履平地。按楼主提供的信息: 8 核 16G 机器 跑 sidekiq 的任务, 我保守猜测一下,一台机器启动了 8 * 2 = 16 个 workers, 每个 worker 消耗 500M 内存 (我现在有一个中等规模的 rails 项目,每个 woker 平均消耗 400 M 内存,以暴走的规模来看应该比我们消耗的内存多些), 总共 8 G 内存, 什么任务都没有跑就已经吃掉 8G 内存了,但这不是 ruby 的错,是 rails 的错。其实如果脱离 rails, 单纯用 ruby 来写这些后台业务代码,效果也会不错的。

#25 楼 @kayakjiang 是的 ruby 本身不慢,我们 api 接口,响应时间 80ms 吞吐量 70kpm, 单就 worker 系统,在暴走漫画 ruby 已经不适合了。

#25 楼 @kayakjiang 另外再说下 sidekiq 的情况, 硬件资源只能用到 50%, 如果在提高并发量,由于 GIL 的存在 每个任务都会更慢 我们试验过多次。 现在比较理想的是 150 并发

这个量级的任务, 我猜是要按照订阅生成 feeds timeline? worker 一次取多个任务, 然后把 io 读写都 batch 就嗷嗷快了 如果是做矩阵计算做个性化推荐或者聚类, 都用的 C 实现, 换语言也不会快

https://github.com/gzigzigzeo/sidekiq-grouping

#28 楼 @luikore :plus1: 其实主要是三方服务,比如诸葛数据的反馈,推送服务。

#27 楼 @shawnyu 能从 12 台机器减到 3 台,这本身是一个了不起的成功,我看你们的技术迁移也是非常接地气的,做了很多的试验,不是脑袋一热,拍下脑门就做出的决定,这个是非常值得我们这些同行学习。

其实 elixir 来写 worker 也不错。

worker 是不是 io 操作居多,比如调用 http 服务或者 socket 服务?调用这些的 ruby lib 用的是什么?

#29 楼 @shawnyu IO 操作部分语言的差别不会太大呀

如果用 erlang 会怎样?

#33 楼 @huacnlee 是的 io 时间肯定是差不多,比如一个调诸葛接口的 worker,时间上差不多 因为逻辑简单只有一个调用, 整体上之所以能快 应该是并发优势。

#34 楼 @rasefon 考虑到语言成本 pass 了

#32 楼 @quakewang ruby 这边基本上走第三方接口调用都是 RestClient, io 确实多的。

#36 楼 @shawnyu 是不是 erlang 不好招人啊。。。

#35 楼 @shawnyu 这个 go-worker 的并发量是多少呢?

#38 楼 @rasefon 不好学。 目前的重构是 ruby 组 花了 1 个半小时讲了一遍语法, 然后给了几个写好的 demo,就这样 大家都完成了 2-3 个 worker 的重写工作,golang 非常简单 容易上手,但是 erlang 的语法和并发机制有点难懂

#40 楼 @shawnyu golang 语法好像和 c 几乎差不多确实容易不少。

#37 楼 @shawnyu

800 万一天是一台服务器要处理的量?平均每秒要处理 100 个不到的任务,估算到高峰是 700~800 每秒的任务处理。 根据经验,对 8 核 16G 的服务器,可以开 100 个左右 worker,处于 20%~30% 的负载水平。 也就是一个 worker 在高峰时间每秒要处理 7~8 个,就算用了 persistent client,对第 3 方服务的响应时间也要在 100ms 以下。 超过这个时间,就会积压,可以考虑换异步的 http client 试试看,或者类似你们干脆换语言。

#43 楼 @quakewang 我们实际的情况是,高峰时期远远不止每秒处理 800 个,用户一个操作会触发 2~3 个任务,并且这些任务还需要尽快产生结果,所以我们原有的 12 台机器,是为了应对峰值,而非平均的处理一天的量来搭建的。 另外一个情况就是,sidekiq 的实际处理速度,单机是有瓶颈的,纯粹的数据库+redis 操作,一般也要 50ms 左右,并且还存在大量消耗数据库连接的问题。 最后就是,ruby 的执行速度,确实不是很快。

所以干脆换语言重新实现。上面说的数据库+redis 操作的 task,在新系统下只有 10ms 左右,提升非常明显。

#44 楼 @tency 高峰时期远远不止每秒处理 800 个是指单台机器嘛?

#45 楼 @serco 整个 worker 系统,现在 95%的 task,都由 go worker 在 1 台机器上运行,并且负载很轻,在 30% 以下。

#43 楼 @quakewang 现在 1000 多万了,是整个系统, 暴漫峰值 在晚上下班时,这个时候任务量特别大。 异步 request 确实好思路。

48楼 已删除

@shawnyu 说的好爽,写的好痛苦

回答一下为啥不考虑 jruby 的问题

  1. 因为不想引入 jvm,不想去测试第三方库在 jruby 下的兼容性。
  2. 我编译一个 go 项目,执行程序只有 10m,一个执行文件搞定,干净、简洁。
  3. 想弄个大新闻

jruby 是用来做什么的 求科普贴~

#29 楼 @shawnyu 如果多数任务是三方服务,用 EventMachine 应该也比较快的。

运算性能、并发都是 ruby 的弱点,加上 rails 对内存的消耗,应付这种局面自然悲剧。确实更适合考虑引入新的高性能并发编程工具。

@shawnyu @tency 以下引用自 sidekiq FAQ 页面,所以数据可能会有水分。

The largest customers I'm aware of are processing ~500,000 jobs/min with Sidekiq with one customer reporting a peak of ~50,000 jobs/sec. Note that on dedicated hardware, Redis should be able to handle about 7000-8000 jobs/sec. After that, you'll need to shard your application to use multiple Redis instances or use multiple independent applications.

我不是特别能理解,后台任务多是 IO 相关,但是为什么使用sidekiqgo-works会有这么大的差距。

Queue 方面

两者的 queue 都是利用了redisBRPOPLPUSH,这方面应该差距不大。

Consumer 方面

假设单台机器 8x25 个 thread, 200 个 sidekiq 的 worker,并发上应该问题也不大。

有没有可能是某些任务特别慢(比如网络连接有问题,单纯在等 timeout 时间),占住了 worker,或者其他什么原因呢?

如果两位有时间,能不能介绍下相关情况,让我们选型时可以参考一下。

#54 楼 @serco 先介绍下我们实际项目的情况,暴走漫画采用的是 RoR 框架,sidekiq 也是集成在项目内,也就是说 web 和 sidekiq 共用一套代码。我们的 sidekiq 系统无法满足我们的处理需求的原因,我分析下面几个原因:

  1. Ruby 本身的处理速度有劣势,sidekiq 的多线程模式会有全局锁的问题,并发性能有先天劣势。
  2. 采用全局的 redis 对象,而 Redis 库采用的是 mon_synchronize 来解决线程安全问题,造成每次操作都会阻塞其他线程。
  3. 全集成的系统,让 sidekiq 载入了大量不需要使用的第三方库,拖慢性能,额外占用内存。

可能还有别的原因造成整个系统有处理瓶颈,你说的网络 io、timeout 的问题,都会导致单个 sidekiq 线程暂停,不过这个问题在 go 系统中也存在,所以在比较两个系统差异上,这个不是核心。

简单来说,两方对比,语言执行效率、内存占用、并发模型、业务核心库的线程安全处理上,让新的 go 系统的处理效率远远高于 sidekiq。

学习了,之前公司在实际过程中也发现 sidekiq 占用 cpu 过高,负载比较大的情况

部分同意观点,主要还是 sidekiq 和 rails 业务代码相关联,带了很多不必要的库,而且 go 的并发性能有很大提升

但是用 go 重写推送部分的业务逻辑的话,整体的性能也会有所提高吧?对业务代码进行改造,会不会也在一定程度上提高了 worker 的性能呢?也就是大概多少比例是语言本身的优势,哪些是业务简化,代码优化带来的提升?

还有个问题,这个改造的成本会有点高吧?比如之前用 sidekiq 的 proxy 写的代码必须转换成全部都是传递数据的形似,比如 ruby-china 社区的源码就大量采用了 dalayed 这种写法,会把 ruby 的调用转成 yaml 序列化,go 用来做 worker 的话这块就完全没法用了吧?

#55 楼 @tency 多谢解释。

第一点有影响但问题没那么严重,如果你指的是 GIL,那么 IO 并发还是没有太大影响的。 之前没有了解仔细看过 redis-rb 的代码,我觉得你说的第二点应该是最致命的。

timeout 的问题在 go 系统中应该没有太大影响,sidekiq 一般一开始就初始化了 25 个左右的 worker,卡住一个就会损失一部分处理能力。Go 的话,轻量级进程卡住就卡住了,完全不影响其他请求的处理。

#57 楼 @serco redis 部分是 redis 本身单线程的问题,这个换 go 还使用 redis 的话也是不会有性能提升的

#58 楼 @kingwkb 当然不一样,redis server 是单线程,并不意味着 redis 的 client 也必须是单线程。

#56 楼 @lujiajing1126

  1. 业务代码没有做任何简化,Go 代码量高于 Ruby 的。新系统做的只是技术重构,不是业务重构。
  2. sidekiq 是使用 json 序列化的,Go 能直接解析,我们采用的 go-workers 库,完全兼容 sidekiq。

#57 楼 @serco Go 系统最大的好处是轻量、快速,之前我也说过,单个 task 的处理速度提升是巨大的,现在单机 150 左右的并发量和 sidekiq 的单机并发基本一致,但在内存、处理速度上的巨大优势让 Go 系统得以处理之前几台机器的工作。可以这么说,Go 系统能让我在并发处理中不丢失 CPU 执行效率,达到单机最大化的硬件利用率。

#35 楼 @shawnyu 如果都是 IO,Ruby 你跑多线程,效果也是一样的呀,慢无非在序列化之类的运算动作上面。

#25 楼 @kayakjiang 看这个说法,应该最大的问题是 Ruby Sidekiq 启动起来带了 Rails 应用的内存暂用,内存费太多,workers 数量无法提升上去

#63 楼 @huacnlee 实际情况是内存用不满, 但是开多了 worker 导致很多任务时间骤增,这个是上不去的原因。

挺好的。用 sidekiq 搭配 redis 实现了 mq 作用的东西。 其实 sidekiq 的任务是可以和主项目分离的,用 sidekiq 推任务。 另写一个轻量级的 ruby 项目来完成任务。

主要原因还是有点不明确,是 redis-rb 的问题还是 ruby GIL 的问题。是不是升级 ruby 版本可以解决 GIL 的问题? 我们现在把涉及外部的都用 rabbitmq 做成异步的调用,有时候是调用其他项目的 api 有时候是推入某个队列。

猜测一下:是不是 GO 使用的事件驱动模型,ruby 用的是线程模型,大量慢速 IO(网络请求)下,ruby 只能增加线程数量,增加线程数量又会带来线程切换开销。这也是楼主说增加线程数量,处理能力反而下降的原因。 BTW:这种场景 Go 确实很不错,曾经让小弟用 ruby 实现了一个百万连接的消息推送服务,连接是可以,但是大并发推送消息就挂了。用 Go 重写了,问题解决。Go 在这方面还是很有优势的。

golang 的确大有前途

这个项目的源代码,好歹用目录结构分类一下吧。我发现目前好多开源的 go 项目都没有分类的习惯,一上来就是一堆代码,不知道要从哪里开始看。

#67 楼 @lujiajing1126 这个没法处理,项目中别用这种用法就好了。

新手问下,如果用 go worker 的话,CRUD 是不是都得用 golang 的库了?ActiveRecord 就没法用了? 楼主公司 golang 用的什么 ORM?或者直接是 SQL?

Ruby 在这里是有两个弱点 1、内存占用多。 2、如果涉及大量运算,会稍慢一些。 关于第一点,内存占用多式 Rails 的问题。所以剥离开就好了。 关于第二点,貌似你没有提到你们有大量的运算。 所以你们的问题,用 Ruby 完全可以解决(也许 Sidekiq 不适合)。 当然你们用 Go 也是没有问题,因为恰好你们有人会用,而且解决了你们的问题。

所以目前我能看到的就是你们还是没有找到根本原因,没有找到本质问题,你当然解决不了。 而且你们用的 Go,也是恰巧解决了你们的问题,但是你们还是不知道为啥 Go 可以解决。

哈哈,所以这是个悬案!

代码逻辑方便发出来吗?正好做下性能测试

@tency 哟。居然能在这里看见你。你们 golang 用得不错呀。

#72 楼 @imwildcat 用了 jinzhu 大哥的gorm

#76 楼 @sai 谢谢夸奖 😄

#73 楼 @haoqu 你可以看我之前的回复,至于为啥不在 ruby 上继续做文章,我是觉得一步到位比剥离再测试再优化要强很多。虽然 go 不是银弹,但在我们这个应用环境中就是银弹。

好像有人之前就做过类似的项目 http://www.goworker.org/

#79 楼 @tency 我想弱弱的问一下,你们是在 ror 里利用 sidekiq 把一些数据序列化到 redis 中,然后启 golang 的 worker 来读取,然后用 golang 来处理这部分 worker 的逻辑吧?所以 ror 中 sidekiq 的作用就只是往 redis 中压数据?

#81 楼 @wpzero 是这样没错,你的理解完全正确。

85楼 已删除

今天忽然看到这个帖子。看了一下我们也是用 sidekiq,一天 800w 任务左右。一部 15G 2vCPU 的虚拟服务器就搞定了。开 12 个线程。

benzhang 回复

Ruby worker or go worker?

imwildcat 回复

Ruby worker 啊. Rails 4.1 Ruby 2.2.4

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册