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

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

漫画之前使用了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

共收到 83 条回复

👍👍👍👍👍

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可以解决。

哈哈,所以这是个悬案!

#73楼 @haoqu 有理有据

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

@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

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