天天看點

Gearmand異步處理就安全了嗎?不!

之前使用gearman的時候,遇到過一個卡頓的問題。今天微網誌上又有人問我是否遇到過此類問題。這個問題,當時是伯誠老師解決的。我把他的文章搬過來。希望能給遇到此類問題的人一點參考。

使用gearman作為異步消息進行中間件是卻沒有想象中的順利。我們多次發現gearmand程序會将php的請求hold住,不做任何響應,即便php在gearmanclient發起連接配接時設定了連接配接逾時時間,也不會逾時。這對于php的工作方式來說,是很危險的。

對于這個問題,我們問了google許多次,自己也在伺服器上跟蹤了許久,終于将問題定位為在gearman的worker程序在通過 addfunction 方法注冊任務時,如果使用了timeout參數,那麼就會複現這個問題,但這個問題的複現是随機發生的。

首先,gearmand程序正常工作時,通過pstack檢視其工作線程狀态如下:

可以看到,正常工作時,gearmand程序中會有7個工作線程。而當工作不正常時,也就是php請求被hold住時,可以看到這裡面的線程數是少于7個的。

這個問題在起官方網站的issue中也有人提起。

使用的gearmand的版本是1.1.8依然有這個問題。追溯到0.32版本發現無法重制這個問題,而0.33至1.1.8都存在此問題。後來閱讀了源碼以及檢視了相關changelog發現0.33之後的版本增加了對worker的timeout處理。而從上面官網的一些使用者的回報來看,也确實和addfunction的timeout參數有關。于是我們先将addfunction時的timeout參數設定為0。問題果然不在重制,那麼這個timeout到底為什麼會導緻這個問題呢?這需要進一步分析gearmand的源碼,分析的重點如下:

首先是線程數為什麼會減少?

libgearman-server/gearmand_thread.cc: _thread方法中

在問題發生的時候,會導緻event_base_loop執行完畢,導緻退出。檢視了libevent的相關資料,可以看到。libevent 1.4.x是非線程安全的,不能跨線程執行event_add。而libevent 2.0.x通過線程鎖做到了線程安全,可以通過執行evthread_use_pthreads跨線程執行event_add。而gearmand的代碼中沒有使用evthread_use_pthreads。而gearmand的官方代碼中,應該就是對于timeout特性進行處理時發生的問題。我們可以看到如下源碼中

libgearman-server/connection.cc:gearman_server_con_add_job_timeout 方法中

gearman_server_con_add_job_timeout 這個方法,是在main thread中執行的,而下面這條指令

​event_base_set(dcon->thread->base, con->timeout_event);操作的是dcon->thread 對象上的event base對象,是以屬于跨線程操作了event_base對象。

​我們随後對gearman_server_con_add_job_timeout這個方法中的event_base_set調用進行了修改。将dcon->thread->base對象改到了_global_gearmand->base。