Web開發系列(九):訊息佇列,非同步任務
有這樣一個需求,第三方請求向我們的使用者傳送一個推送訊息。我們必須儘快響應第三方:你的請求我們收到了,但是第三方又想知道 結果。一種辦法是等,第三方等我們的系統處理好了,然後返回結果給他。這樣做有個優點,程式碼邏輯簡單,但是缺點似乎更大,因為 使用者要等待結果,所以這個TCP連線是不會斷掉的,也就意味著,如果請求的併發量比較大,那麼對我們的系統負載是非常高的,因為要 維護很多個TCP連線。此外對第三方的系統來說也是如此,假設這個請求是從移動端發來的,那影響則更甚。
所以我們需要另外一種方法,非同步任務。
Python中,非同步任務的首選似乎是Celery,不過我在生產環境中遇到過Celery的問題是無故假死,一直卡在futex鎖上。後來切換到rq就 沒有再出現過這個問題了,但是Python-rq的問題是使用的併發模型是來一個任務fork一次,對系統性能消耗特別大,所以我改了一下Worker, 加入了Gevent:
class GeventWorker(Worker): def execute_job(self, job, queue): self.set_state(WorkerStatus.BUSY) self.log.debug("gonna spawn a greenlet to execute the given job.") gevent.spawn(self.perform_job, job, queue).join() self.log.debug("greenlet executed.") self.set_state(WorkerStatus.IDLE) def gevent_worker(queues): client = Client(config.SENTRY_DSN, transport=HTTPTransport) worker = GeventWorker( queues=queues, connection=StrictRedis.from_url(config.WORKER_BROKER) ) register_sentry(client, worker) worker.work()
然後再pre-fork多個程序,每個程序中使用協程處理任務,本來還可以改成併發處理多個任務的,但是因為沒有這麼高的併發要求, 所以就沒有進一步改的更復雜。
而Python-rq
的原理我也在這一篇 文章中
有說過,即生產者進行enqueue,而消費者監聽對應的queue,一有任務到來便開始進行消費。中間的queue,也叫broker,是用來儲存
在生產者和消費者傳遞的訊息用的,Celery可以選rabbit-mq, redis等好幾種作為broker,而rq則只支援redis一種。
此外,從嚴格意義上來說,Redis並不能算上是正統的訊息佇列