Rails Go 写个小程序,替换掉 Sidekiq

moliliang · 2017年05月15日 · 最后由 IChou 回复于 2017年05月20日 · 4872 次阅读

ruby 真是内存大户啊~

我的小破站运行在阿里云 1g 内存上,mysql+sidekiq+puma 这三个内存大户吃了大部分的内存。

时不时从 swap 交换数据的时候,整个系统的负载就飙升,top NO.1 只看到个 kswap[xxx] 的进程。

所以我决定将 MEM NO.1 的 sidekiq 去掉。

  • 同时不需要对 rails 中的队列业务做任何改动
  • 也不需要对一直的定时任务做任何修改

于是就用写了这个程序 https://github.com/molisoft/rkejob/

除了是一个队列(感觉这样改进后,更像是将请求异步化了),还是一个定时任务工具(因为我之前用 Sidetiq 作为定时任务工具)

之前的 rails 的队列流程如下:

rails  -> redis -> sidekiq(Runing)

现在的流程:

rails -> redis -> rkejob ---(POST)---> rails

什么?!最后请求又请求回来了?没错啊,最后又是 rails web 端来执行队列任务了。所以相当于将“请求异步化”了~~~

这样的设计,其实就注定不适合做那种分分钟耗时数分钟,数十分钟的任务了~~~ 请酌情使用~~~ 保平安

rkejob 如何配置使用

在 rkejob 程序的同目录下新建配置 config.yml

redis:
  host: localhost
  port: 6379

queue:
  pool: 30
  concurrency: 3
  namespace: "namespace_sidekiq"
  database: 0
  queues:
    - default

job:
  url: "https://www.xxx.com/myjobs"

crons:
  -
    name: "CheckServerStatus"
    url: "https://www.xxx.com/mycrons"
    spec: "0 0 3 * * *"   # 每天凌晨3点运行
  -
    name: "CheckXXXXXStatus"
    url: "https://www.xxx.com/mycrons"
    spec: "@every 10m"

这样每一个队列任务都会被请求到 job.url 这个配置中,顺便会 sidekiq 请求过去的参数也一并请求到 rails-web 中了,下面会介绍 rails-web 端怎么处理这些数据。

crons 就是配置定时任务了~

rails-web 端要做的事

rails 这边需要做呢?几乎不需要做任何任务改动,只需要加 2 个 action,分别处理 job 和 cron。

require 'yaml'

# 这个比较重要哦~ 因为写入到redis中的数据是可以直接被yaml解析成ruby代码的,会出问题,所以这个加个猴子补丁~~
Psych::Visitors::ToRuby.prepend Module.new {
  def resolve_class(klass_name)
    klass_name && klass_name.safe_constantize || super
  end
}

class MyJobsController < ApplicationController

  skip_before_action :verify_authenticity_token, :only => [:myjobs, :mycron]

  before_action :only_local

  # 队列
  #
  def myjobs
    request_body = request.raw_post
    (target, method_name, args) = YAML.load(request_body)

    if target.to_s == 'Mailer'
      eval("#{target}.#{method_name}(*args)").deliver_now()
    else
      eval("#{target}.new.#{method_name}(*args)")
    end
    render :text => 'success'
  end

  # 定时任务
  #
  def mycron
    target = request.raw_post
    eval("#{target}.new.perform()")
    render :text => 'success'
  end

  private

  # 这里最好加验证,禁止外网请求 :)你懂的
  def only_local
    # if request.remote_ip != '127.0.0.1'
    #   redirect_to root_path
    # end
  end
end

后话

已在我的小破站上跑了几天了~~ 看日志来看~~~ 还可以~~~

golang 真是不错啊~~~

重要的事,只说一遍…… 这玩意不适合跑非常耗时的任务哦~~~

1G 内存,节约了 5 刀

这。。。

sidekiq 本来就已经异步 + 并发了啊。

你这么做,最终任务还是落在 ruby 上处理,并不会使用更少内存。

不如直接用 golang 把后台任务重写了。https://github.com/jrallison/go-workers

但是既然只是个 1G 内存的机器,说明你的业务并没有太值钱到需要重写的地步。多花 $5 看来比较划算。

可以在需要执行后台任务的地方 Thread.new 然后在内部用 perform_now 的方式执行,或者直接用 https://rubygems.org/gems/sucker_punch 这样的库直接帮你把异步任务在一个线程池内搞定,这你连 Redis 依赖都不需要了。

我可以认为你只是找个借口写 Golang.

Rei 回复

主要是折腾·· 日积月累也不少,哈哈哈

ch3n 回复

完全不一样啊,sidekiq 会在后台一直驻留吃内存啊。 就是不想重写业务才这么整。

zlx_star 回复

差别还是很大的。如果都是轻量级的任务,比如发邮件,发短信什么的,几秒钟的任务用这个来做还是很方便的。耗时的任务当然还是用 golang 重写业务啦。这个文中已阐述~~~ 😅

lgn21st 回复

噗,很有道理,突然觉得 @zlx_star 说的也有道理。。hoho,但是看起来不屌啊~~~ 😭 定时任务也可以直接写个 ruby 脚本放 crontab 中,然后 post 吗~~~ = =!

lgn21st 回复

我再一想,不对啊,这样搞我得重写多少代码啊~~~ 😂

话说需要缓冲的请求,通常都怎么处理?都用队列?

jetspeed 回复

看实际需求嘛,发邮件,发短信这种耗时几秒几十秒的,可以用这些啊

看看 active job 的后端,有的后端是不用开新进程的。

moliliang 回复

你试试看 sucker_punch 你就会发现比现在要少写很多代码。

lgn21st 回复

看了下这玩意确实不错,基于线程的。 那定时任务呢~

理论上来说,你这么做没有意义

  1. Sidekiq 耗内存,原因是因为 Sidekiq 的进程载入了整个 Rails 项目的代码,用于执行 Rails 以及应用的代码。
  2. 你当然你这么做当然能省内存,原因是因为 Go 的进程不再有 Rails 项目的代码,但是... 你的业务逻辑得重新实现,或像你现在这样调用 Rails 的接口来实现。如果是这样,那用 Go 来写 Sidekiq 要做的事情没有任何意义,你 Sidekiq 也可以独立跑,不加载 Rails 的东西,内存就少了。
  3. Sidekiq 也是并发的实现,而大量耗费资源的地方是处理逻辑,不是 Sidekiq 本身,单纯改实现来说,提升的效果几乎可以忽略(除非所有业务实现用 Go 来写)
moliliang 回复

定时任务你自己不是已经有答案了么?写一个 rake task,用 cron job 去调用呀。

lgn21st 回复

很久之前就这样做过了,每次 rake 也会载入整个 rails,其实依然慢

moliliang 回复

5 美刀一个月,其实就是一杯星巴克的事,看你纠结成这个样子......

huacnlee 回复

1、这个我当然知道呀~ 2、嗯 3、整个 sidekiq 的 cpu 的占用是非常低的,完全就是 占内存。我的初衷是减少内存占用,而不是降低 cpu 资源。

sidekiq 只所以占内存是因为会载入 rails,这个大家都知道。 可是你说这样做没意义,我不认同。

之前 sidekiq+puma 服务器,就要载入 2 份 rails,也就是 rails 会吃 2 份内存(一份几百 M)。现在只有 puma 会载入 rails,也就是少了一份内存占用。puma 本身也会有更的内存来提高效能。

而执行相关任务时的内存占用,几乎可以忽略不计。

所以,我不觉得这样做没意义:)

lgn21st 回复

我擦,钱能解决的事当然都是小事。但是你说一个宅男宅家里,除了撸代码还能干啥

moliliang 回复

我的答案是玩《赛尔达传说荒野之息》

用 ActiveJob 的 async adapter 多好

lgn21st 回复

😂 家里的 xbox 都懒得玩~~~

happy hacking😂

语法高亮可以指定 ruby 的

666,还是第一次看到有把异步任务再 post 回来处理的。。。

折腾折腾是对的,哈哈哈。最近我也在研究用 go 重写 Ruby

宅会玩。

@moliliang 我觉得对于你的问题来说,你的解决方法很新颖,而且很简洁,需要赞一个!

lgn21st 回复

你这个回复让我笑了一路

blueplanet 回复

感觉你在黑我

lithium4010 回复

谢谢提醒,哈哈

hging 回复

很幽默的

hanluner 回复

折腾并学习嘛

Crystal 版的 sidekiq,同一个作者:sidekiq.cr

wmzsonic 回复

1、你的头像好赞哦 2、这玩意能无缝使用 sidekiq 中的业务代码?应该不能载入 rails 吧? 3、看了下源码,真是跟 ruby 一模一样啊~~

用 crystal 写 worker 不是更合适吗,可参见:https://ruby-china.org/topics/32893

赞 挺有意思的小东西,虽然很少有应用场景,哈哈

机器内存不够,又不想多花钱的痛我懂,当年阿里云 1G 内存的机器上 cap 内存不够,逼得我去用 shell 写了个部署脚本

不过我的话,估计会把 sidekiq 的消费者抽出来用纯 ruby 重写,然后你就发现它不是辣么吃内存了😅

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