Laravel 應用實踐:如何優雅的完成全量資料同步

老財發表於2017-03-13

需求場景:

我們公司有個產品,使用者可以通過第三方應用中的積分進行消費抵扣,好比用肯德基的積分去買麥當勞。每次積分交易都要去第三方系統同步積分餘額進行校驗,本地沒有最新的積分餘額資訊。

負責運營的同學要定期統計第三方系統中使用者剩餘積分餘額,技術同學為此做了一個批量同步功能,基本思路就是從資料庫表裡撈出全量資料,做一個大迴圈,每條使用者記錄呼叫同步介面把第三方的積分餘額同步過來,再匯出成Excel給運營。

早期,這樣的實現沒有問題,但是隨著使用者記錄越來越多,運營同學統計的等候時間越來越慢,跑完一次同步需要很長時間,更嚴重的是引起鎖表。

解決問題:

為了解決這個問題,提出幾個優化目標:

  1. 資料統計不能影響正常業務,減少對資料庫的壓力
  2. 資料處理進度可見
  3. 按時提取,自動生成結果資料

優化方案

公司技術框架剛轉Laravel,凡事先考慮Laravel現有框架是否能有方案支援,解題思路如下:

  1. Redis:把待同步的使用者基礎資料放在Redis佇列中,通過LPOP方法,每次冒泡處理一個記錄,減少處理過程中對資料庫的依賴
  2. 通過對對佇列長度的判斷和資料總數,計算當前進度
  3. 以上邏輯用Artisan 命令列方式寫成元件,可手動執行,也可以放在任務中定時執行
  4. 結果資料用外掛 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

尤其是看到這個進度表:
file

Artisan 命令列的進度條實現簡直太方便:

$users = App\User::all();

// 多少個任務
$bar = $this->output->createProgressBar(count($users));

foreach ($users as $user) {
    $this->performTask($user);

    // 一個任務處理完了,可以前進一點點了
    $bar->advance();
}

$bar->finish();

通過這個命令,同步資料可以手動執行,也可以放在定時任務中在業務不繁忙的夜間自動同步
如何把命令列放在定時任務中執行,看這裡:任務排程
有了定時任務自動處理,每天早上,運營同學都可以在後臺,自動拿到一份昨晚同步好的積分餘額清單,瞬間也感覺到了優雅
file

總結

以上需求,從提出目標到實現,也就一個小時時間,結果皆大歡喜,大家都覺得很優雅。當然,關於處理這種場景,肯定還有更優化的方案,我在這裡總結出來,僅僅是從具體場景分享一下Laravel框架的便捷和高度整合,能夠快速響應業務需求,並且保持簡潔,是我喜歡Laravel的一萬個理由,沒有之一。

相關文章