Gearman實戰第一彈:非同步處理結算單

freephp發表於2020-10-11

昨天夢迴jm,醒來之後看著窗外萬里晴空,想大聲喊一句:爺青回!
我想起之前使用gearman的歲月。不知不覺也過了快5年,想總結一篇關於gearman的技術文章算是一種對青春的祭奠,再不寫的話更少有phper用過這個強大的分散式任務分發框架,畢竟這個時代已經屬於swoole。

先講一下Gearman,它是一款用C++編寫的分散式任務分發框架,通過暴露API給使用方來完任務委託,在多臺機器上負載均衡且併發地執行任務。特別是密集型計算,可以使用Gearman去非同步地完成任務排程。

Gearman的結構分成三個角色:
Client: 客戶端,可以是不同的程式語言,如php,java,python等.
Job Server: 任務伺服器,負責分派任務,不負責業務邏輯。
worker: 執行任務的節點,可以是不同的程式語言實現,不一定和clent端的語言一樣,如java,php, python等。

整個流程圖如下圖所示:

下一步開始安裝gearman和相關擴充套件,這次我們還是使用php來作為程式語言。之前我寫過一篇文章講在伺服器上安裝gearman,這裡只講一下在macpro上如何安裝gearman和php擴充套件。

比起linux上面的編譯安裝,mac上安裝很簡單,使用brew命令如下:

brew install gearman

# 為了讓上一步安裝的gearman能直接在終端呼叫,需要先建立下面這個目錄
sudo mkdir /usr/local/sbin
# 讓當前使用者成為gearman安裝目錄下的sbin的所有者
sudo chown -R $(whoami) $(brew --prefix)/sbin

# 把link指向剛才安裝的gearman
brew link gearman
# 這一步很關鍵,讓終端可以直接呼叫gearmand命令
ln -s /usr/local/opt/gearman/sbin/gearmand /usr/local/bin

先介紹一下背景:
假設我們有一個場景,很多商家通過我們的結算平臺進行天結算,然後結算單需要複雜的計算,然後結算完成後非同步地把計算結果以附件excel的方式傳送給使用者。假設有十萬商家,每天訂單有上萬每戶,那麼這是比較大的資料量,且都是在某個時間段開放結算,有併發的壓力。

這個時候使用者(也就是商家)非常想快速完成結算並看到結果,對於處理過程的等待是非常低容忍度的,我們需要考慮可以利用多個worker非同步去處理這些結算任務。

首先編寫worker端的程式碼:

<?php

$worker = new GearmanWorker();
$worker->addServer('127.0.0.1', 4730); // 可以註冊多個server,server可以在不同的機器上。
// 非阻塞方式執行
$worker->addOptions(GEARMAN_WORKER_NON_BLOCKING);

$worker->addFunction("calculate", "calculatePayment");

$worker->addFunction('send', "sendEmail");

while($worker->work());
// 計算結算單
function calculatePayment($job) {
    $data = json_decode($job->workload());

    // 開始複雜的計算 todo

    return $data;

}

// 傳送郵件
function sendEmail($job) {
    // todo

    $data = $job->workload();
    send_email_with_attachment($data->email, $data->content);
    return true;
}

然後是編寫客戶端,命名為client.php,程式碼如下:

<?php

$client = new GearmanClient();

$client->addServer();
echo "start the calling";
$paymentList = [['order_id' => '110112', 'product_id' => [2323,4455,4455], 'pay_money' => '4423.00'], [['order_id' => '110113', 'product_id' => [223,45,67], 'pay_money' => '1400.00']]];
$data = $client->addTask('calculate', json_encode($paymentList));
// 後臺方式執行,因為畢竟只是發郵件
$client->addTaskBackground("send", $data);

echo "finish\r\n";

然後可以先把gearmand跑起來,也就是job server這一塊,命令很簡單:


但是因為有可能任務執行失敗或者gearmand服務因為各種原因掛掉,所以建議結合mysql做持久化,也就是把執行情況記錄到mysql中,一旦出現問題down掉,重新執行的時候可以根據mysql中的執行記錄從失敗的記錄開始重跑。

為了做持久化,先建立用於持久化的資料庫和表:


CREATE DATABASE gearman;
 
CREATE TABLE `gearman_queue` (
`unique_key` varchar(64) NOT NULL,
`function_name` varchar(255) NOT NULL,
`priority` int(11) NOT NULL,
`data` longblob NOT NULL,
`when_to_run` int(11),
PRIMARY KEY (`unique_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

執行gearmand的時候可以使用到mysql方式作為持久化,命令如下所示:

gearmand -q mysql \
--mysql-host=127.0.0.1 \
--mysql-port=3306 \
--mysql-user=mine \
--mysql-password=xxxxx \
--mysql-db=gearman \
--mysql-table=gearman_queue

然後可以把worker執行起來,可以多開幾個終端,例如跑三個worker,命令如下:

php /path/to/folder/worker.php

然後再執行client去觸發任務:

php /path/to/folder/client.php

剩下就交給gearman去分發任務了,worker開始併發地高效處理任務啦!

當然我們也要考慮監控和管理job server,可以用shell去監聽,也可以自己編寫一個Gearman manager工具。這是後面可以聊的,到時候另開一篇文章吧。
除此之外,其他我們對於worker部分完全可以考慮使用一些異構語言,如java或者golang,PHP只做客戶端使用,有人已經編寫了grpc去實現了。

今年gearman的作者還在不斷迭代,目前最新版本是1.1.19.1, 我收回之前的話,還是很多人在為這個框架付出,聽說10年前左右雅虎已經大面積使用這個gearman框架來做新聞聚合了,當然jm也有用。

gearman=swoole+任務分發,老驥伏櫪,志在千里罷了。

相關文章