# 13. listen/notify 之消息隊列
#### 1. 消息隊列的簡介
什么是消息隊列呢?隊列就是排隊,就像在銀行辦理業務排隊一樣,排在最前面的先處理,后面的后處理,按照順序來,先進先出。這個隊列可以是程序,可以是數據,也可以是任務,是任何你可以存儲的東西。消息隊列就是給隊列傳遞消息。這么來說,打個比方,我們在一個網站上注冊了賬號,系統可能會給你發送一封注冊郵件,同時在頁面上提示你"稍等幾分鐘后會收到一封郵件",發郵件這個事是通過操作系統的調用,例如linux的sendmail,或者接口來發送的,發郵件是通過排隊來發的,先到的先發,假如很多郵件等著發,那就得像銀行那樣排隊了,所以未必就能實時,總有延遲。總結來說,發郵件這個事是有延遲的,是需要等待之后用戶才能收到郵件的。然而,這種延遲對用戶的體驗還有操作并不影響啊。在網站上的其他應用他還是照樣用,沒有任何影響。對這種對用戶沒直接影響或者有延遲的任務就可以扔到消息隊列處理。所以,發郵件,發短信,捕獲異常等任務都可以扔到消息隊列。也就是消息隊列是獨立于web進程的另一個進程,因為它有可能耗時很長的,所以要另開一個進程來處理,對web進程沒有任務影響,用戶還是照常訪問網站。這么來說,假如網站有一個需要扔的消息隊列叫A,但用戶觸發了A,就把A扔到消息隊列,這時給用戶感覺是這個A任務是一瞬間完成,其實它是給消息隊列那個進程發送了消息,可能跟它說,我要發短信,就把發短信這個指令,加上短信的內容一并傳給消息隊列的進程,消息隊列收到消息后,就把這個任務放到隊列中進行排隊,因為前面還有一堆任務沒處理,所以要慢慢處理,輪到A的時候才處理A,由于A是耗時的動作所以就慢慢處理就好,反正對用戶不太影響。
前面說到,消息隊列的進程要把任務放入隊列中,由于有很多任務,需要排隊,所以這些任務是需要存儲起來的。在ruby中,有很多gem可以實現后臺的消息隊列,但它們的存儲方式有區別,比如[delayed\_job](https://github.com/collectiveidea/delayed_job)就是用數據庫(MySQL,Sqlite,PostgreSQL等)來存的,它會先讓你創建表,如果有任務進來,就會插入到表中作為一條記錄,要處理的時候就會取出這條記錄。像[resque](https://github.com/resque/resque)和[sidekiq](https://github.com/mperham/sidekiq)就是用redis來存儲數據的,redis是存儲在計算機的內存中的。比較一下,就知道resque和sidekiq在存儲方式上比delayed\_job有優勢,而delayed\_job的好處是能直接利用數據庫,不用額外安裝redis。
消息隊列是另外的一個進程,任務進入消息隊列中,一個接一個的處理,也就是說,A進程在被處理時,必須等前面的任務被處理完才能輪到它。這種方式體現在delayed\_job和resque中。sidekiq的處理方式是多線程的,它是基于celluloid的,用Actor作為并發模型,它能同時處理多個任務。
值得一提的是Ruby on Rails從4.2開始加上了[active\_job](http://edgeguides.rubyonrails.org/active_job_basics.html)。因為有各種各樣的消息隊列的解決方案,active\_job就是提供了統一的接口和調用,要用到消息隊列還是會用到上面提到的幾個。這個東西就像activerecord一樣,要指定數據庫那也是很簡單的,只要換相應的gem和改配置文件就行了,而active\_job也正是這樣。
不過,這一篇文章不會詳說上面的三種消息隊列的實現,只會說到特用于PostgreSQL的消息隊列[queue\_classic](https://github.com/QueueClassic/queue_classic)
#### 2. PostgreSQL的listen/notify
[queue\_classic](https://github.com/QueueClassic/queue_classic)是基于PostgreSQL的listen/notify來實現的,列隊在等任務進來就是用的listen,把任務放入隊列就是notify。
PostgreSQL的[listen](http://www.postgresql.org/docs/9.4/static/sql-listen.html)/[notify](http://www.postgresql.org/docs/9.4/static/sql-notify.html),也就是一種消息的訂閱/發布模式,也就是類似那種生產者/消費者模式。這種模式很常見,例如redis的[pub/sub](http://redis.io/topics/pubsub)模式、rails的[Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html)組件。
懂了PostgreSQL的listen/notify,也就等于懂了其他的訂閱/發布模式。
它很簡單,就相當于一種廣播機制,比如,你定閱雜志,還有其他人也訂閱了,這個過程就叫listen,也就是監聽,等雜志有更新了,或者有新的雜志出來,它就會廣播,就會送一份給訂閱雜志的人,這個過程就是notify,也就是通知。
listen/notify的使用很簡單。
首先是listen(監聽),只接監聽的通道的名稱,這個名稱自己定義。
```
rails365_pro=# LISTEN virtual;
LISTEN
```
這個時候可以直接執行notify。
```
rails365_pro=# NOTIFY virtual;
NOTIFY
Asynchronous notification "virtual" received from server process with PID 4996.
```
表示監聽的通道已知收到消息了。
還可以傳參數。
```
rails365_pro=# NOTIFY virtual, 'This is the payload';
NOTIFY
Asynchronous notification "virtual" with payload "This is the payload" received from server process with PID 4996.
```
也可以結合sql語句來使用。
```
LISTEN foo;
SELECT pg_notify('fo' || 'o', 'pay' || 'load');
```
只要連接到同一個數據庫的所有session都會接到監聽通道傳過來的信息。
可以嘗試另開一個psql進程。然后notify,再回到之前的psql執行listen就可以測試的,如果顯示正確的pid就成功的。
下一章: [PostgreSQL的listen/notify之queue\_classic(二)](http://www.rails365.net/articles/2015-10-12-postgresql-listen-notify-queue-classic-er)
完結。