COSCUP 2016(講稿)

這邊老樣子先來一段自我介紹
各位COSCUP的會眾大家好
我是Michael,目前任職於時間軸科技,擔任PHP主任工程師
自己本身是php-resque-pool的contributor,目前在維護該專案
同時也是SITCON2016的講者,所以算上這次的話已經是我第二次像這樣站上講台了,如果平時有在關注學生年會的話應該知道我是誰

為了表示我是一個專業的php工程師,我後面的內容都會以php的為基準

每一個演講後面都一定有一段故事

那這個故事要追溯到很早以前,還記得菜鳥時代的時候在某段code看到類似的程式碼,當然這部份的不是公司的,只是真的有一段幾乎一樣,這段是為了簡報需要所以從網路上面拿下來的示意圖
這部份主要的功能是說系統要發信給一個使用者,使用者按完送信後會有收到信的效果,那這段code有使用到fastcgi_finish_request去做一個提早回覆的動作

然而就我們對mail server及php的理解,php對mail server做出請求到完成得到回應,都會一直卡在那裡,那這個時間可能相當冗長
所以這裡使用了fastcgi_finish_request來達到說我要及時給使用者目前的畫面,強制server吐出處理到一半的資料
之後才做寄信的動作,這樣就不會讓使用者有一個卡的感覺

但是到後面發現效果很糟,就結果論而言算是收到了相當糟糕的回饋,甚至覺得根本不該讓這樣的code出現在正式站裡

造成的原因主要是fastcgi_finish_request的特性
他的特性除了剛剛有提到過的讓使用者以為完成了
還有,他也算是一個偽異步執行的方式
可能你做了送這封信的動作,但是信件一直莫名的卡在那裡,造成我的php其實一直卡在執行的狀態
另外一個issue的點就是實際上很難debug,因為頁面已經強制先顯示給使用者了,所以後面如果發生Exception也不會被抓到
然後另外一個問題是他只能在CGI使用,像是php-cgi跟php-fpm,一般在php的話是沒辦法用的,像是laravel的話

如果做網站不去管fastcgi_finish_request的濫用的話就會像這樣,php-fpm越來越多,甚至不去限制pool數量的話會整個爆炸

這時候就要把電腦關掉,雙手合十,走到公司門外一直念著阿彌陀佛阿彌陀佛,「假的」

拿一個微型網站的例子來說好了
如果在座各位有興趣去玩google cloud的話,他有一個micro instance,提供600MB的記憶體
那一個php-fpm吃掉大概5~8MB的記憶體,這部分的話可能在不同os上面的數字不一樣就是,如果有人連續針對這個發送120次請求,你的server大概就掛了
或許你可能會說:「啊我就有設定php-fpm pool的大小啊」
可是php-fpm如果已經滿的話網頁也不會有任何反應

後面就一直在討論說該怎麼解決這個問題
於是就有了一個想法是說,這裡我們會需要一隻程式去跑像是寄信或是報表等可能需要耗費比較久時間的背景工作
通通都要丟到一個統一的地方執行,那數量必須是在可以控制的範圍內,免得像是之前的狀況,我一直去拿server上面的所有資源去拼那個寄送email的動作
在那時候選了gearman這個solution,他分為兩個模組,client跟server,其中client用來送job,server的話去執行client送過來的job

用gearman的好處是我需要scaling到多大的規模我可以自己控制,依照設定可以做相關的擴充

可是問題又來了,在導入了時候遇到了一點問題
像是我可能需要寄送電子報,這份電子報可能是突然舉行的活動,所以我需要在某個時間去執行這段程式碼
或是我可能要依照權責規劃,寄送email的事情統一由A server負責,B server可能效能比較好,那我可能要指派B server去做一個產生報表的動作
這些在gearman都是比較困難去達成的
或許多寫幾個判斷可以幫助你去完成這些相關的動作,但是整體來說後續維護會是一個相當大的技術債

再來還有
gearman對很多junior其實是不太友善的
其中相關的function甚至多達100多個,每次要使用的時候都要去翻文件,學習曲線相對較高
而且gearman根據官方的說法,在某些情況會有掉job的風險

當時就在尋找一個完美的解決方法
畢竟長期來說繼續用gearman下去的話會遇到不少問題,該解決的還是得去解決

  1. 所以要找一個能解決剛剛那種大量請求
  2. 不讓使用者覺得產品的網站很慢啊
  3. 而且junior能上手又能擴充的service
  4. 我要隨時都可以擴充他的server
  5. 最好是要php

所以整個人就開始崩潰了,畢竟相關經驗還不夠多又菜

後面也硬著頭皮讀了不少套件,除了原本在ruby上面眾多的類似套件,python也去找了
譬如MQ、各種的queue方式

那最後就決定了resque的做法
resque的話是在2009年由defunkt發起的github專案,那在2010的時候被chrisboulton改版成php的版本
採用的原因很簡單
最主要的原因還是因為有php的版本,畢竟時間軸如大家所知道的也算是一個堅持php為主的公司,那時候是想說能儘量不要脫離php的範圍,一方面也有要保持一致的原因在,所以resque那時候是在考量範圍的
此外還有queue優先權的設定跟定時執行的功能一直是需求上面能夠meet的
加上採用的時間點版本已經相對穩定,自然而然的resque就被採用了

那介紹開始之前,先來說明一些比較特別的用語

  1. graceful-shutdown 如果有在玩shell的話應該大部份都知道,當你按下ctrl+c的話程式可以去捕捉你按下的中斷訊號,進而去處理不讓資料流失,簡單來說就是等到手邊的工作結束之後才去嘗試關閉程式
  2. FIFO 指的是stack裡面的先進先出
  3. 第三的話就是MessageQueue 訊息儲列

首先resque的角色 job
job的話顧名思義就是你要執行的程式碼,他會被worker執行,他這裡是整個resque的週期裡面最為重要的一部份,而resque底下的job都是由class封裝成的,分為三個生命週期

  • setUp
    在job被執行的時候第一個被處理的function,像是準備環境變數、建立預先執行的檔案,都應該在裡面執行

  • perform
    perform的話就是第二個會被處理的順位,他這裡是描述程式主要會被執行的區塊,基本上如果你要寄信什麼啊或是處理報表之類的啊基本上都要寫在這裡

  • tearDown
    這部分的話是在執行完job的時候你可能有一些需要回收的變數或是一些檔案

再來是構築整個resque核心的部份worker
worker的話是用來執行剛剛的job,他會依照job定義的生命週期做執行,從setUpperformtearDown

另外的話就是每一個worker都會去做graceful-shutdown,來確保我執行的工作不會遺失

再來是跟gearman比較不一樣的設計就是他有queue name
那queue name的話他的主要功能就是去讓worker去區別說我需要去執行什麼樣的工作,你也可以同時讓很多worker去執行同一種queue
那每個job執行的時候也都會被指派要由什麼樣的worker去做執行,那這部分的話是走先進先出,job在沒辦法去做一個插隊的動作,必須要前面的執行完畢後才可以動作

最後想提一下resque-scheduler,認真的說resque-scheduler算是resque的子專案,他可以直接相容於resque,那在整個resque家族的部份是扮演一種傳遞訊息的角色
不會去執行任何工作,實作上scheduler沒辦法獨立存在,必須倚賴resque才可以
那比較特別的是resque-scheduler因為只有去搬運job到resque的特性,所以沒有特別去做graceful-shutdown

那再來是有人可能會問啦,我現在的resque我也是跟fastcgi_finish_resquet一樣看不到錯誤訊息啊,那看不到的話我怎麼去處理bug
沒關係,resque也很貼心的幫你想到了這個問題,他推出了resque-web
但是可惜的是resque-web並沒有pure php的版本,雖然在資料結構上ruby跟php的resque都是一樣的,所以能直接使用resque官方的專案就好了
而且有個好處是resque-web可以看到及時的資料狀態

OK….
講到這裡,一定有人想問剛剛那張圖片是三小,看不懂啊
可以再簡單一點好嗎,畫成這樣最好是看的懂在幹嘛

來一點有感的code
這部分的話是resque執行前定義的一個叫做My_Job的工作,那他會照著順序從setUp -> perform -> tearDown到結束整個job

再來我們要做的工作很簡單,首先要去執行resque
那執行的做法可以直接去執行resque專案底下的bin/resque,並且直接帶入APP_INCLUDE
這樣的話你寫好的像是那些寄信啊或是報表啊或是一些可能要背景處理使用者資料工作的部份都可以直接使用

接著在test.php直接呼叫Resque::enqueue

其實MQ也有一樣的效果,不管是RabbitMQ或是MQTT大部份的做法會類似像這樣
這裡舉例的是由Phalcon實作的beanstalk的用法,beanstalk的話構思上面是以RabbitMQ的架構去做實作的
基本上來說跟resque的worker一樣可以取job出來做,不過因為每個執行的時間有長有短,甚至可能會造成collision問題
後面維護的成本更是難以估計
如果我今天需要100個job去做我網頁各式各樣的處理,那我要把全部的job取出的工作全部寫在同一個檔案裡面
久了搞不好會造成另外一種世界奇觀

可以想像一下你需要維護上百個job在同一個檔案的情境嗎?

那導入resque後也有蠻多問題需要解決,像是一部份比較有規模/規矩的公司可能會訂好多個環境去做測試,確保在正式給使用者的時候是完美無虞的
那我的環境可能有development/beta/production甚至更多,那基於不同的設定檔我需要不同的指令去開啟各種環境該對應的resque,對後面維護的人是一個稍微大的負擔

那後來提出來的一個新的解決辦法php-resque-poolphp-resque-pool的話是從原本的nevans 的 resque-pool移植出來的版本,他沿襲了Resque的架構,甚至你只需要一個設定檔就可以解決在resque遇到的問題
而且有多了一個容錯機制,如果resque-pool下的worker掛掉的話可以針對掛掉的worker重啓

(後面簡報內容)