# 14. listen/notify 之 queue\_classic
#### 1. 介紹
[queue\_classic](https://github.com/QueueClassic/queue_classic)是一個ruby的gem,用來實現PostgreSQL的消息隊列。它基于[PostgreSQL的listen/notify](http://www.rails365.net/articles/2015-10-12-postgresql-listen-notify-xiao-xi-dui-lie-lie-yi),有很多接口,使用起來比較簡單。
如果你有使用過[sidekiq](https://github.com/mperham/sidekiq),[resque](https://github.com/resque/resque),[delayed\_job](https://github.com/collectiveidea/delayed_job),就會發現基本每個這種消息隊列的gem的使用方法都是類似的,里面的概念也是差不多,也就是說,學會了[queue\_classic](https://github.com/QueueClassic/queue_classic)就等于會其中三種,只要掌握思想就好了。
#### 2. 安裝
假如我們已經有一個rails項目了。
在Gemfile添加下面這行。
```
gem "queue_classic", "~> 3.0.0"
```
執行`bundle install`
正如我們上面所說的,任務是需要存儲的。所以要創建相應的表來存。
```
# 創建queue_classic_jobs表
rails generate queue_classic:install
# username替換為你自己的數據庫的用戶名,password是數據庫的密碼,rails365_dev是數據庫名
export QC_DATABASE_URL="postgres://username:password@localhost/rails365_dev"
bundle exec rake db:migrate
```
#### 3. 測試
我們先在`rails console`里測試。
前面說過,消息隊列是跑在一個進程里的。所以要啟動那個進程。
```
bundle exec rake qc:work
```
你會發現delayed\_job,sidekiq也是差不多的啟動方法。
還可以指定隊列來啟動。
```
QUEUES="priority_queue,secondary_queue" bundle exec rake qc:work
```
啟動好后,我們進入`rails console`中,執行下面這行語句。
```
? rails365 git:(master) ? rails c
Loading development environment (Rails 4.2.3)
2.2.2 :001 > QC.enqueue("Kernel.puts", "hello world")
nil
```
你會在進程里看到類似這樣的輸出,就說明成功了。
```
? rails365 git:(master) ? bundle exec rake qc:work
hello world
```
QC.enqueue就是后面的命令加上參數作為任務push到隊列中。
上面只是個最簡單的例子,還有可以在指定時間,指定隊列來執行,具體更為詳細的命令要看官方的readme文檔。
如果需要調試或查看日志,可以開啟調試功能,有兩種不同的日志,分別是:
```
export QC_MEASURE="true"
# or
export DEBUG="true"
```
在運行`rake qc:work`之前運行,具體的效果,嘗試下就知道的。
#### 4. 在rails中使用
以[本站](https://github.com/yinsigan/rails365)為例,是一個放博客的網站,文章是存放在articles這張表,當時為了查看哪篇文章最受歡迎,就在articles存了一個字段叫visit\_count,但每次用戶查看文章時,就會往這個字段加1。這個動作是用rails的[ActiveSupport::Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html)配合sidekiq的消息隊列來做的,現在要改成用queue\_classic來做。
我們來看下相關的代碼。
```
# app/workers/update_article_visit_count_worker.rb
class UpdateArticleVisitCountWorker
include Sidekiq::Worker
def perform(article_id)
logger.info 'update article visit count begin'
@article = Article.find(article_id)
@article.visit_count += 1
@article.save!(validate: false)
logger.info 'update article visit count end'
end
end
```
在哪里調用呢,我們是結合ActiveSupport::Notifications來做的,這個先不管,你也可以在articles\_controller的show action直接調用。
```
# config/initializers/notification.rb
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |name, started, finished, unique_id, payload|
Rails.logger.info payload
if payload[:controller] == "ArticlesController" && payload[:action] == "show"
UpdateArticleVisitCountWorker.perform_async(payload[:params]["id"]) if payload[:params]["id"].present?
end
end
```
上文提過,queue\_classic主要是利用`QC.enqueue`這條命令把任務push到隊列中的。只需要把這行`UpdateArticleVisitCountWorker.perform_async(payload[:params]["id"]) if payload[:params]["id"].present?`改成我們需要的就可以了。
把增加visit\_count的值的邏輯移動model中去,然后在`ActiveSupport::Notifications.subscribe`用`QC.enqueue`中調用就好了。
改造之后是這樣的。
```
# app/models/article.rb
class Article < ActiveRecord::Base
def self.update_article_visit_count(article_id)
article = Article.find(article_id)
article.visit_count += 1
article.save!(validate: false)
end
end
```
```
# config/initializers/notification.rb
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |name, started, finished, unique_id, payload|
Rails.logger.info payload
if payload[:controller] == "ArticlesController" && payload[:action] == "show"
QC.enqueue "Article.update_article_visit_count",payload[:params]["id"] if payload[:params]["id"].present?
end
end
```
現在需要重啟下`rails server`和`bundle exec rake qc:work`。
重啟`rails server`運行好`export QC_DATABASE_URL="postgres://username:password@localhost/rails365_dev"`這個命令。
為了讓`rake qc:work`更能明顯地看到日志信息,在運行`rake qc:work`前先執行`export QC_MEASURE="true"`。
現在可以去頁面上測試的。
#### 5. 注意事項
第一點是關于環境變量,也就是`QC_DATABASE_URL`、`QC_MEASURE`、`DEBUG`這三個,當部署到線上環境時,就要把這三個變量寫進shell的配置文件,如比ubuntu系統,就寫進~/.bashrc\_profile就好了。
第二點是關于錯誤的任務,任務也是有可能會報錯的,但是我們不知道哪個任務報錯了,所以很不方便,其實官方提供了接口的,你自己可以捕獲那個錯誤信息,捕獲后就可以進行自己想要的處理了。其實錯誤的任務都會一直存在表queue\_classic\_jobs中,這樣查看就好,至于那個接口,就是[worker.rb](https://github.com/QueueClassic/queue_classic/blob/master/lib/queue_classic/worker.rb)中的handle\_failure方法,官方readme文檔也有示例,這里不再深究。
完結。