Sidekiq(部分基础,有几个使用案例和active_job的用法)
Sidekiq (8700✨)
git : https://github.com/mperham/sidekiq
https://www.cnblogs.com/richard1234/p/3829074.html (一篇文章,讲的比较清楚)
https://wdxtub.com/2016/07/06/sidekiq-guide/ (wiki的翻译)
进阶精华贴:https://ruby-china.org/topics/31470; https://ruby-china.org/topics/36825
Sidekiq 是Ruby社区最受欢迎的多线程的异步任务框架之一,几乎是Rails项目标配。
非常优秀的后台任务处理软件,其依赖Redis实现队列任务的增加,重试和调度等。
在Web请求中,有很多任务是可以放到后台执行的,比如用户购买商品付款成功后,就可以直接向用户购买成功,相应的短信发送,物流通知等等就可以放到后台任务去做,不用在用户购买的同时立即执行,这些任务也称作异步任务。
基本概念:
Job
在 Sidekiq 中的 Job 指的是某一个任务的一次执行, 注意与我们通常意义上说 “一个 Job” 指的是一类 Job.(一类任务)
Worker
因为 Sidekiq
是使用 Celluoid 来完成其多线程的控制的, 而 Celluoid 是 Ruby 中的多线程模式 Actor 模式的实现, 所以在 Sidekiq 中的 Worker 我们以拟人的方式去理解. 我拥有一个工人, 不过有一点要区分的是这些 Worker 都是按照”操作手册”在执行任务, 所以他不会被限制在某一类任务上.
Queue
队列的意义在于区分任务并且让任务排队, Sidekiq 中将每一类的任务使用一个 Queue 来区分开.
按照视频步骤操作失败。 谷歌中文的帖子,最后成功。
- 根据教材安装gem 'sidekiq'
- 进行相关配置,下载的rails模版已经在sidekiq.rb中配置,并在routs.rb中加入 monitoring监控。
- rails g sidekiq:worker Hard
- 然后打开在方法perform(参数)内设置如puts("xxx")等等,
- 在控制器action,models中创建一个job。job是指某一个任务的一次执行,通常我们说一个job🈯️的是一类job。
class HomeController < ApplicationController def index HardWorker.perform_async('北京天安门', 5) end end
⚠️ :也可以创建一个未来处理的job:HardWorker.perform_in(1.minutes, '北京王府', 5)
6. 启动服务器rails server。启动bundle exec sidekiq 还要启动redis-server.
7. 打开网页localhost:3000,及监控localhost:3000/sidekiq
8. 在terminal的bundle(fserver_watch),就是打开sidekig的那个窗,可看到每次处理job时的输出:
❌,从rails console中进行操作,提示错误 (已解决✅原因可能是没有rails db:migrate)。
)
✅返回 => "350788261bbeb33e6a7b187c"
如何访问 http://localhost:3000/sidekiq 进入监控页面
步骤:在routes.rb中设置:
- require 'sidekiq/web'
- 在Rails.application.routes.draw的块内,添加mount Sidekiq::Web => '/sidekiq'
Rails.application.routes.draw do require 'sidekiq/web' mount Sidekiq::Web => '/sidekiq' root to: 'addresses#index' resources :addresses end
Sidekiq 管理 UI
在实际网站经营中,只有管理员才能够看监控,因此需要加上管理员的权限验证。在第2步:
config/routes.rb Rails.application.routes.draw do + require 'sidekiq/web' + authenticate :user, lambda { |u| u.is_admin? } do + mount Sidekiq::Web => '/sidekiq' + end
is_admin?是user.rb中的验证方法。
mount(app, options=nil)是增加一个Rack-based application。例子:
mount(SomeRackApp => "some_route", as: '客制化helper's name')
高级用法:
新增config/sidekiq.yml
The Sidekiq configuration file is a YAML file that Sidekiq server uses to configure itself, by default located at config/sidekiq.yml
.
只有在使用高级功能时才需要创建这个文件,比如并发池such as concurrency pool size, 命名named queues, PID file location, etc.
---
:concurrency: 5
staging:
:concurrency: 10
production:
:concurrency: 20
:queues:
- critical
- default
- low
Activejob可以和Sidekiq一起使用
以前的博客: https://www.cnblogs.com/chentianwei/p/9123322.html
操作文档: https://github.com/mperham/sidekiq/wiki/Active+Job
首先:
rails g job Example
然后在config/application.rb中的 Application类中添加:
class Application < Rails::Application # ... config.active_job.queue_adapter = :sidekiq end
或者在config/environments/developments或者 production.rb中添加。
也可以直接在在每个job Basis内添加self.active_job = XXX
最后,在任意位置添加job queue.
ExamleJob.perform_later(参数)
Activejob补充:
入队一个等待performed的job,当queueing system空闲的时候,就会立即执行:
CompanyJob.perform_later(参数)
入队一个在未来某个时间执行的job:
CompanyJob.set(wait_until: Date.tomorrow.noon).perform_later(参数)
立即执行一个job
CompanyJob.perform_now(参数)
set()方法:创建一个预制选项的job。根据选择中的参数来调用perform_later方法入队job。
option:
- wait: 指定的延长多少时间后执行入队job,如wait: 5.minutes
- wait_until: 指定到未来的某个时间点时,入队job
- priority: 10 入队这个job并设定优先级
- queue: :some_queue 入队这个job到指定的队列queue, 写队列名字
VideoJob.set(queue: :some_queue, wait: 5.minutes, priority: 10).perform_later(Video.last)
queue_as :default : 设置队列的名字,默认在jobs/XXX_job.rb中自动生成,可以改成类名。
queue_name: 这个方法会调用队列的名字,如CompanyJob.queue_name返回:default
Active Job Callbacks (API)
6个钩子,after, before, around. 针对enqueue和perform
retry_job
ActiveJob::Exceptions中的实例方法。配合rescue_from使用。当从你的job中,营救一个exception,你可以让Active Job再尝试执行这个job.⚠️和set()方法的option一样。
客制化 错误处理
Activejob不支持丰富的Sidekiq's retry feature.
它使用一个简单的抽象方式来解析retries,基于遭遇的指定的例外。
⚠️,rails server后,在网页上出现❌提示pasting
NameError in HomeController#index
uninitialized constant ExampleJob::ErrorLoadingSite
从rescue_frome()中,去掉ErrorLoadingSite参数,则可以执行。
限制
Rails允许对象作为参数传入perform方法。
def perform(user)
user.send_welcome_email!
end
但是如果在job 入队后,perform方法调用之前,user记录被删除的情况下,会升起raise
因此:需要加上rescue_from
rescue_from ActiveJob::DeserializationError do |exception| # handle a deleted user record end
✅使用标准的Sidekiq写法,则是:
def perform(user_id) user = User.find_by(id: user_id) if user user.send_welcome_email! else # handle a deleted user record end end
Action Mailer(没看)
Job ID (不知道用处)
ActiveJob有自己的Job ID。Rails5,可以得到Sidekiq's JID
> com = CompanyJob.perform_later
Enqueued CompanyJob (Job ID: 0c4eb163-60fc-4b91-a4d5-1098f50c8c65) to Sidekiq(default)
=>
#<CompanyJob:0x00007f858ef6c190 @arguments=[], @job_id="0c4eb163-60fc-4b91-a4d5-1098f50c8c65", @queue_name="default", @priority=nil, @executions=0, @provider_job_id="5c8f31d1e02d6deca5371bfa">
> com.provider_job_id =>"5c8f31d1e02d6deca5371bfa"
provider_job_id就是Sidekiq's JID
❌Activejob没有perform_in()方法
我不知道如何使用,延时功能。
The Basics
Sidekiq是一个后台任务进程的框架。 通过在后台perform work,它让你测量你的app。
这需要3部分:
Client
Sidekiq客户端运行在Ruby应用进程中, 允许我们创建jobs之后,过一段时间再进行处理。
2个方法来创建一个job:
# 常规调用
MyWorker.perform_async(1, 2, 3)
# lower-level generic API
Sidekiq::Client.push('class' => MyWorker, 'args' => [1, 2, 3])
|
2个方法等价,创建一个Hash代表这个job,序列化Hash为一个JSON string,并把string入栈(in Redis)。这意味参数只能是简单的JSON 数据类型,复杂的Ruby对象(Date,Time, ActiveRecord)会出现无法正常序列化sserialize的问题。
⚠️使用Activejob的话,可以传递记录对象作为参数。不过设置上要小心。
Redis
It holds all the job data along with runtime and historical data to power Sidekiq's Web UI. Redis 为Sidekiq提供数据存储。
See Using Redis for info about connecting to Redis.(暂时忽略)
Rails5.2提供了Redis Cache Store内建支持(使用哪个缓存存储器由使用者决定。点击看博客
Redis 是一种小型数据结构key-value结构,作为搭配用的数据库来使用。我们在百宝箱用 sidekiq 实作异步时看过它。
Server
每个Sidekiq服务进程会从在Redis中的队列拉任务,并处理它们。和你的web进程一样,Sidekiq boots Rails,这样所有的jobs和workers就能使用全部的Rails API。服务器将实例化the worker并调用其中的perform方法及其参数。
Best Practices
1.让参数小而简单,如果使用Activejob,就能传递复杂对象。
2让job满足事物性。⚠️不明白
3.拥抱并发性。
Sidekiq是并发设计的,可以同时执行大量jobs.
你可以使用连接池来限制总共的连接数量,如果你的Sidekqi进程占用太多流量。
Concurrency 并发
https://github.com/mperham/sidekiq/wiki/Advanced-Options#concurrency
默认一个进程创建25个threads。如果crushing你的机器i/o,你可以调整它:
sidekiq -c 10
另外不要设高于50的threads。在config/database.yml中,设置连接pool的数量最好和threads的数量相等。
production: adapter: mysql2 database: foo_production pool: 25
Workers (点击看文档)
提供一些初始设置,给queue换个名字,设置retry的最大次数
- queue : use a named queue for this Worker, default 'default'
- retry : enable the RetryJobs middleware for this Worker, default true. Alternatively, you can specify the max. number of times a job is retried (ie. :retry => 3)
- backtrace : whether to save any error backtrace in the retry payload to display in web UI, can be true, false or an integer number of lines to save, default false. (不太懂)
class HardWorker include Sidekiq::Worker sidekiq_options :queue => :crawler, :retry => false, :backtrace => true def perform(name, count) end end
Redis的使用: (点击看详细文档)
Sidekiq 使用 Redis 来保存所有的 job 和操作数据。默认会去连接位于 localhost:6379
的 Redis 服务器。但是在生产环境中需要自定义地址。
具体需要修改 config/initializers/sidekiq.rb
文件
如果要使用云服务器的话:具体看文档。
使用sidekiq的地方:
异步汇出简介
数据汇出也是一个常用异步来处理的任务,流程和汇入其实87分像:
- 建立 RegistraionExport model,这个 model 会纪录是那个 user 做汇出、是哪个 event 要汇出,以及存储最后汇出的档案
- 建立一个 RegistrationExports controller,这个 controller 让用户可以新增汇出纪录,以及浏览汇出纪录
- 建立 ExportWorkerJob,这个异步任务会执行汇出操作,并将汇出的档案放到 RegistraionExport model 上
- 异步任务最后完成时,可以寄 E-mail 通知用户汇出的档案已经准备好了
最后,汇出和汇入的功能要完整实做的话,还需要考虑档案存储的位置。我们用 carrierwave 上传的档案,默认是公开的。但是汇出和汇入的档案,应该也必须要检查有没有权限才行。这部分的实作牵扯到我们使用哪种档案服务器:
- 存储在本机的话,请参考 How To: Secure Upload (carrierwave的,可不看)
- 存储在七牛云,请参考 下载凭证
- 存储在 AWS S3,请参考 Serving Private Content
报名15分钟内没完成自动取消
异步处理可以设定延迟时间set方法
rails g job check_registration
app/jobs/check_registration_job.rb
class CheckRegistrationJob < ApplicationJob
queue_as :default
+ def perform(registration_id)
+ registration = Registration.find(registration_id)
+
+ if registration.status != "confirmed"
+ registration.status = "cancalled"
+ registration.save!
+ end
app/controllers/registrations_controller.rb
class RegistrationsController < ApplicationController
before_action :find_event
def create
@registration = @event.registrations.new(registration_params)
@registration.ticket = @event.tickets.find( params[:registration][:ticket_id] )
@registration.status = "pending"
@registration.user = current_user
@registration.current_step = 1
if @registration.save
flash[:notice] = "报名成功,请在15分钟内完成操作!"
+ CheckRegistrationJob.set( wait: 15.minutes ).perform_later(@registration.id)
注册过程被分解为3step,第一步是create,此时提示用户在"15分钟内完成操作。因为15分钟后会执行队列中的CheckRegistrationJob类中的方法perform。根据注册的状态status来判断是否确认。见⬆️。