需求場景:
我們公司有個產品,使用者可以通過第三方應用中的積分進行消費抵扣,好比用肯德基的積分去買麥當勞。每次積分交易都要去第三方系統同步積分餘額進行校驗,本地沒有最新的積分餘額資訊。
負責運營的同學要定期統計第三方系統中使用者剩餘積分餘額,技術同學為此做了一個批量同步功能,基本思路就是從資料庫表裡撈出全量資料,做一個大迴圈,每條使用者記錄呼叫同步介面把第三方的積分餘額同步過來,再匯出成Excel給運營。
早期,這樣的實現沒有問題,但是隨著使用者記錄越來越多,運營同學統計的等候時間越來越慢,跑完一次同步需要很長時間,更嚴重的是引起鎖表。
解決問題:
為了解決這個問題,提出幾個優化目標:
- 資料統計不能影響正常業務,減少對資料庫的壓力
- 資料處理進度可見
- 按時提取,自動生成結果資料
優化方案
公司技術框架剛轉Laravel,凡事先考慮Laravel現有框架是否能有方案支援,解題思路如下:
- Redis:把待同步的使用者基礎資料放在Redis佇列中,通過LPOP方法,每次冒泡處理一個記錄,減少處理過程中對資料庫的依賴
- 通過對對佇列長度的判斷和資料總數,計算當前進度
- 以上邏輯用Artisan 命令列方式寫成元件,可手動執行,也可以放在任務中定時執行
- 結果資料用外掛 maatwebsite/excel 生成Excel檔案供運營下載
實踐
用到的Laravel框架知識棧:
Redis操作
Artisan 命令列
任務排程
整個方案核心就一個命令列元件,關於如何寫Artisan命令列,可以看這篇 Laravel 5.1 Artisan 命令列實戰
public function handle()
{
//命令列引數 --pre為預處理
$pre = $this->option('pre');
if ($pre) {
$this->info('sync pre points!');
$users=User::all();
//進度條
$bar = $this->output->createProgressBar(count($users));
//待處理記錄放進佇列
foreach ($users as $user){
Redis::RPUSH('sync_points',$user->youzan_wx_id);
$bar->advance();
}
$bar->finish();
} else {
$total=Redis::LLEN('sync_points');
if($total>0){
$bar = $this->output->createProgressBar($total);
for ($i = 0; $i < $total; $i++) {
$youzan_id=Redis::LPOP('sync_points');
//同步資料
$this->service->syncUser($youzan_id);
$bar->advance();
}
$bar->finish();
}
//呼叫另外一個命令列元件匯出excel
$this->callSilent('export:points');
}
}
命令編寫完成後,需要註冊 Artisan 後才能使用。註冊檔案為 app/Console/Kernel.php。
敲這個命令的時候,運維瞬間感覺到了優雅
php artisan sync:points --pre
尤其是看到這個進度表:
Artisan 命令列的進度條實現簡直太方便:
$users = App\User::all();
// 多少個任務
$bar = $this->output->createProgressBar(count($users));
foreach ($users as $user) {
$this->performTask($user);
// 一個任務處理完了,可以前進一點點了
$bar->advance();
}
$bar->finish();
通過這個命令,同步資料可以手動執行,也可以放在定時任務中在業務不繁忙的夜間自動同步
如何把命令列放在定時任務中執行,看這裡:任務排程
有了定時任務自動處理,每天早上,運營同學都可以在後臺,自動拿到一份昨晚同步好的積分餘額清單,瞬間也感覺到了優雅
總結
以上需求,從提出目標到實現,也就一個小時時間,結果皆大歡喜,大家都覺得很優雅。當然,關於處理這種場景,肯定還有更優化的方案,我在這裡總結出來,僅僅是從具體場景分享一下Laravel框架的便捷和高度整合,能夠快速響應業務需求,並且保持簡潔,是我喜歡Laravel的一萬個理由,沒有之一。