一般用 PHP 實作 background task,大多都是先將 task 處理後再處理 user request。
所以程式大概是長這樣:
<?php class Contrller { public function handle() { if (Background::hasTask()) { Background::run(); } $data = List::whereIn('id', [1, 4, 6])->get(); view($data); }
這樣做會有一些缺點,若背景程式跑了很久才結束,會讓使用者有一種「點了按鍵卻感覺沒有回應」的錯覺。
若有時候真的累積不少 background task 需要處理時,為了不讓使用者等太久,通常會 trigger 一些專為 background task 設定的程式去執行,像是一群的 consumer,或者會 trigger gearman 去處理。
而我再某一次很罕見的狀況下必須馬上讓使用者先收到 reponse 之後才去處理 background task。問題來了:通常都要 PHP return / exit 以後,response 才會傳回 client,那有什麼辦法先給已經處理好的 response,讓 client 繼續瀏覽以後再來跑 background task?
沒想到還真的讓我找到解法:fastcgi_finish_request()
。
fastcgi_finish_request()
這個函式會先通知 fastcgi 的上層 (我這邊是 Nginx reverse proxy) 要給 client 的資料都送出去囉,可以 ending 啦。然後 PHP 偷偷摸摸繼續在後面弄東西。
程式上寫起來會有點不一樣:
<?php class Contrller { public function handle() { $data = List::whereIn('id', [1, 4, 6])->get(); view($data); fastcgi_finish_request(); // 這之後不管發生什麼事,client 都不會再收到訊息 Background::run() }
不過要注意,因為不管如何,client 都不再收到訊息,因此若有錯誤發生是很難 debug 的。
用 catch
也好,用 logger
也好,甚至你要請到 register_shutdown_function()
出場也都好,如果不這樣做,基本上沒什麼 debug 手段。
而我使用 fastcgi_finish_request()
也只有在 Nginx + php-fpm 時成功,我不曉得會做其他形式的 web server 架構是否可以使用。如果網友嘗試過,不如回覆讓其他人知道一下。
總之不得已才使用這個方法。