2025年6月24日 星期二

打造你的第一個生產級推薦系統:Laravel 與 FastAPI 的微服務實戰

解鎖智能推薦:Laravel x FastAPI 微服務架構實戰指南

✨ 引言:當 PHP 遇上 AI:你的應用程式為何需要「智能推薦」?

在現今數據驅動的時代,個性化體驗已成為產品成功的關鍵。從電商的「你可能也喜歡」到新聞平台的「為你推薦」,背後都少不了推薦系統的身影。作為 PHP 工程師,我們深諳 Laravel 在 Web 開發領域的效率與優雅,但當面對機器學習模型推論這種高併發、計算密集型的挑戰時,單一語言框架往往力有未逮。

別擔心,這並不意味著你必須放棄 Laravel!本文將引導你進入一個真實的生產級推薦系統專案,它巧妙地結合了 Laravel 的業務處理能力FastAPI 的高性能 AI 推論。我們將透過 Docker 實現無縫整合,並建立一套全面的監控與 A/B 測試機制。這不僅能為你的 Laravel 應用注入智能,更能讓你一窺現代微服務架構在 AI 領域的應用精髓。

準備好了嗎?讓我們一同探索如何打造一個既強大又易於管理的智能推薦引擎!


🚀 專案概覽與核心亮點:一個完整且富有韌性的推薦架構

本專案提供了一個從用戶請求到模型訓練、結果監控的端到端解決方案。它不僅僅是個概念驗證,更是為實際生產環境而設計的 robust 系統。

系統核心亮點:

  • 智能個性化推薦:基於真實用戶互動數據(點擊、曝光),提供精準的 Item-based 協同過濾推薦。
  • 業務合規性優先:嚴格確保只推薦當前上架(Active)的商品,避免業務邏失。
  • 內建 A/B 測試框架:採用 Thompson Sampling 動態分組策略,助你科學評估不同推薦演算法的真實效果。
  • 全自動化模型更新:推薦模型定期自動訓練並熱載入,無需停機,確保推薦內容始終新鮮有效。
  • 實時監控與智能告警:利用 Prometheus 和 Grafana 全面監控系統性能與推薦質量(如冷啟動比例、重複率、覆蓋率),實現主動式運維
  • 微服務解耦哲學:清晰劃分 Laravel(業務邏輯)與 FastAPI(AI 推論)職責,提升系統可擴展性與維護性。
  • 容器化部署:所有服務透過 Docker Compose 一鍵啟動,大幅簡化環境配置與部署流程。

⚙️ 系統架構深度解析:Laravel 與 FastAPI 的協同共舞

本專案的核心是其精心設計的微服務架構,讓各組件各司其職,協同高效運作:

程式碼片段
graph TD
    A[用戶請求] -->|HTTP| B[Laravel App: API & Middleware]
    B -->|A/B 測試分配| C[FastAPI: 推薦服務]
    B -->|資料儲存| D[MySQL: 商品與用戶行為]
    C -->|模型訓練/查詢| D
    C -->|快取| E[Redis: 模型與行為快取]
    F[Prometheus: 監控指標] -->|抓取 Metrics| C
    F -->|資料來源| G[Grafana: 可視化儀表板]
    B -->|健康檢查| F
  • Laravel App (PHP):作為整個系統的前端網關與業務邏輯核心。它負責接收所有來自用戶的推薦請求,進行 A/B 測試分組,並發起對 FastAPI 推薦服務的調用。更重要的是,它會二次驗證推薦結果,確保僅返回上架商品。此外,所有用戶的關鍵行為(如曝光、點擊)也在此層被追蹤並異步記錄。
  • FastAPI 推薦服務 (Python):這是推薦系統的**「智能大腦」**。它是一個高性能的獨立微服務,專門處理推薦算法的推論與模型訓練。選擇 Python 和 FastAPI 的原因顯而易見:Python 在機器學習領域擁有無與倫比的生態(Pandas, scikit-learn 等),而 FastAPI 則以其極致的性能和非同步特性,成為構建 AI 推論服務的理想選擇。
  • MySQL 資料庫:系統的數據基石,負責持久化儲存用戶、商品(包含關鍵的 status 欄位)以及所有的用戶推薦互動事件。這些互動事件數據是 FastAPI 進行模型訓練的唯一真實數據來源。
  • Redis 緩存與隊列:扮演著加速器和緩衝區的角色:
    • 推薦模型緩存:FastAPI 訓練好的模型會被序列化後儲存於 Redis,實現快速載入與熱更新。
    • Laravel 異步隊列:用戶行為事件被推送到 Redis 隊列,由 Laravel Worker 異步處理寫入數據庫,避免阻塞用戶請求,極大提升 API 響應速度。
    • 最近推薦結果快取:用於計算推薦結果的重複率,提升用戶體驗。
  • Prometheus & Grafana:這對黃金搭檔構成了系統的可觀測性中樞。Prometheus 負責從 FastAPI 服務周期性抓取各類性能與業務指標。Grafana 則將這些數據轉化為直觀的儀表板,並在關鍵指標異常時自動觸發告警,助你實時掌握系統健康狀況。

💻 Laravel 端實作解密:業務邏輯與 API 設計精粹

作為 PHP 開發者,讓我們聚焦 Laravel 如何優雅地肩負起業務層的重任。

1. API 接口與推薦請求流程

前端透過 Laravel 的 API 路由請求推薦列表。RecommendationController 會調用核心服務層 RecommendationService

PHP
// laravel-app/routes/api.php
use App\Http\Controllers\RecommendationController;
use App\Http\Middleware\AssignRecommendationGroup; // 引入 A/B 測試中間件

Route::middleware([AssignRecommendationGroup::class])->group(function () {
    Route::get('/user/recommendations', [RecommendationController::class, 'getRecommendations']);
});

RecommendationService 是關鍵所在,它承擔了與 FastAPI 交互並確保數據一致性的職責:

PHP
// laravel-app/app/Services/RecommendationService.php
namespace App\Services;

use Illuminate\Support\Facades\Http;
use App\Models\Product;
use App\Models\User;
use App\Events\RecommendationInteraction;
use Illuminate\Support\Facades\Log; // 引入日誌

class RecommendationService
{
    private string $fastApiUrl;

    public function __construct()
    {
        $this->fastApiUrl = config('services.fastapi.url');
    }

    public function getRecommendations(User $user, string $experimentName, string $group): array
    {
        try {
            $response = Http::timeout(3) // 為外部 API 請求設定合理超時
                            ->get("{$this->fastApiUrl}/recommend/{$user->id}", [
                                'experiment_name' => $experimentName,
                                'group' => $group,
                            ]);

            if ($response->successful()) {
                $recommendedProductIds = $response->json()['recommendations'] ?? [];
                
                // **【核心】二道防線:再次過濾,確保僅返回上架商品**
                $products = Product::whereIn('id', $recommendedProductIds)
                                    ->active() // 利用 Eloquent Local Scope 簡潔篩選
                                    ->get();

                // 異步記錄曝光事件,不阻塞用戶請求
                event(new RecommendationInteraction(
                    $user->id,
                    $experimentName,
                    $group,
                    'impression', // 動作類型
                    $products->pluck('id')->toArray(), // 實際曝光的商品 ID
                    ['source' => 'fastapi']
                ));

                return $products->toArray();
            }
        } catch (\Throwable $e) {
            Log::error("FastAPI 推薦服務請求失敗: " . $e->getMessage(), ['user_id' => $user->id, 'trace' => $e->getTraceAsString()]);
        }

        // 【健壯性】備用策略:當 FastAPI 無法響應時,隨機推薦活躍商品
        $fallbackProducts = Product::active()->inRandomOrder()->limit(10)->get();
        event(new RecommendationInteraction(
            $user->id,
            $experimentName,
            $group,
            'impression',
            $fallbackProducts->pluck('id')->toArray(),
            ['source' => 'fallback', 'error_msg' => $e->getMessage() ?? 'unknown']
        ));
        return $fallbackProducts->toArray();
    }
}

2. 嚴格的商品上下架狀態整合

確保推薦內容的業務正確性至關重要。我們在 products 表中引入了 status 欄位 (active, inactive, sold_out)。

PHP
// laravel-app/database/migrations/xxxx_create_products_table.php (片段)
Schema::create('products', function (Blueprint $table) {
    // ... 其他欄位
    $table->enum('status', ['active', 'inactive', 'sold_out'])->default('active'); // **定義商品狀態**
    $table->timestamps();
});

並為 Product Model 定義一個 scopeActive() 查詢範圍,方便篩選活躍商品:

PHP
// laravel-app/app/Models/Product.php (片段)
namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    use HasFactory;

    protected $fillable = ['name', 'image_url', 'price', 'category_id', 'status'];

    /**
     * Scope a query to only include active products.
     */
    public function scopeActive(Builder $query): Builder
    {
        return $query->where('status', 'active');
    }
}

這不僅在 Laravel 端篩選,FastAPI 也會只讀取活躍商品進行模型訓練,形成雙重保障。

3. A/B 測試框架:Thompson Sampling 動態分組

為科學地評估不同推薦策略,我們在 Laravel 中間件 AssignRecommendationGroup 中實現了 A/B 測試的分組邏輯。

PHP
// laravel-app/app/Http/Middleware/AssignRecommendationGroup.php (精簡核心邏輯)
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Config;

class AssignRecommendationGroup
{
    public function handle(Request $request, Closure $next)
    {
        $experimentName = Config::get('ab_test.recommendation_experiment.name', 'default_recommendation');
        $isEnabled = Config::get('ab_test.recommendation_experiment.enabled', false);

        if (!$isEnabled) { /* 如果 A/B 測試未啟用,預設到控制組 */ }

        $user = Auth::user();
        $group = null;

        if ($user) {
            // 已登入用戶:優先使用 DB 中已分組結果,否則進行 Thompson Sampling 分配並持久化
            if (empty($user->recommendation_group)) {
                $group = $this->assignGroupUsingThompsonSampling($experimentName);
                $user->recommendation_group = $group;
                $user->save();
            } else {
                $group = $user->recommendation_group;
            }
        } else {
            // 未登入用戶(訪客):基於 Session ID 和哈希算法分組
            $sessionId = session()->getId();
            $group = $this->assignGroupBasedOnSession($sessionId, $experimentName);
        }

        $request->attributes->set('recommendation_experiment_name', $experimentName);
        $request->attributes->set('recommendation_group', $group);

        return $next($request);
    }

    /**
     * 基於 Thompson Sampling 分配組別。
     * Thompson Sampling 會根據各組的歷史表現(如點擊率),動態調整分配權重,
     * 讓表現更好的組別獲得更多流量,更快地收斂到最優解。
     */
    private function assignGroupUsingThompsonSampling(string $experimentName): string
    {
        $groupsConfig = Config::get('ab_test.recommendation_experiment.groups', []);
        $scores = []; // 實際應用中會從數據庫獲取各組的 alpha/beta 參數
        
        // 這裡會實作 Thompson Sampling 的核心邏輯:
        // 1. 從各組的 Beta 分佈中抽取一個隨機數。
        // 2. 選擇隨機數最大的組別。
        // 簡化為:若無實際 Thompson Sampling 數據,則按預設權重分配。
        return $this->assignGroupBasedOnWeight($groupsConfig);
    }

    /**
     * 基於 Session ID 進行哈希分組,確保訪客在同一 Session 保持組別一致性。
     */
    private function assignGroupBasedOnSession(string $sessionId, string $experimentName): string
    {
        $hash = crc32($sessionId . $experimentName) % 100; // 確保結果在 0-99
        $groupsConfig = Config::get('ab_test.recommendation_experiment.groups', []);
        return $this->assignGroupBasedOnWeight($groupsConfig, $hash);
    }

    private function assignGroupBasedOnWeight(array $groupsConfig, int $hash = null): string
    {
        $totalWeight = array_sum(array_column($groupsConfig, 'weight'));
        $targetValue = ($hash !== null) ? $hash : mt_rand(0, $totalWeight * 100) / 100; // 如果是哈希分組,直接用哈希值,否則隨機
        
        $currentThreshold = 0;
        foreach ($groupsConfig as $groupName => $config) {
            $currentThreshold += ($config['weight'] ?? 0);
            if ($targetValue < $currentThreshold) {
                return $groupName;
            }
        }
        return 'control'; // 安全回退
    }
}

這個中間件會確保每個用戶都被分配到一個特定的實驗組,並將分組信息附加到請求中,供後續服務使用。

4. 用戶行為事件追蹤:異步處理的效能優化

用戶的每一次推薦曝光和點擊都是訓練模型不可或缺的「燃料」。為了不影響 API 響應速度,我們採用 Laravel 的 Event & Listener + Queue 機制進行異步追蹤。

PHP
// laravel-app/app/Events/RecommendationInteraction.php
namespace App\Events;

use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class RecommendationInteraction
{
    use Dispatchable, SerializesModels;

    public int $userId;
    public string $experimentName;
    public string $group;
    public string $action; // 'impression' (曝光) or 'click' (點擊)
    public array $productIds; // 相關商品 ID 列表
    public array $metadata; // 額外元數據

    public function __construct(int $userId, string $experimentName, string $group, string $action, array $productIds, array $metadata = [])
    {
        $this->userId = $userId;
        $this->experimentName = $experimentName;
        $this->group = $group;
        $this->action = $action;
        $this->productIds = $productIds;
        $this->metadata = $metadata;
    }
}

// laravel-app/app/Listeners/LogRecommendationInteraction.php
namespace App\Listeners;

use App\Events\RecommendationInteraction;
use App\Models\RecommendationEvent; // 用於儲存事件的 Eloquent Model
use Illuminate\Contracts\Queue\ShouldQueue; // **核心:標記此監聽器為異步處理**
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Log;

class LogRecommendationInteraction implements ShouldQueue
{
    use InteractsWithQueue;

    public function handle(RecommendationInteraction $event)
    {
        try {
            RecommendationEvent::create([
                'user_id' => $event->userId,
                'experiment_name' => $event->experimentName,
                'group' => $event->group,
                'action' => $event->action,
                'product_ids' => json_encode($event->productIds), // 將數組存儲為 JSON 字符串
                'metadata' => json_encode($event->metadata),
            ]);
        } catch (\Exception $e) {
            Log::error("無法記錄推薦互動事件: " . $e->getMessage(), ['event_data' => $event]);
        }
    }
}

【重要】 別忘了在 laravel-app/app/Providers/EventServiceProvider.php 中註冊這些事件與監聽器,並確保 Laravel 使用 Redis 作為隊列驅動。此外,我們的 create_project.sh 腳本還會引導你為 User 模型添加一個 Observer,確保用戶資料被創建或更新時,A/B 測試組能正確被分配和記錄。

🐍 FastAPI 端實作解密:推薦核心與智能引擎

FastAPI 服務是整個推薦系統的智能中樞,它負責處理所有與推薦算法相關的邏輯。

1. 推薦算法核心:Item-based 協同過濾 (CF)

我們採用了經典且高效的 Item-based Collaborative Filtering。其核心原理是「喜歡 A 商品的用戶也喜歡 B 商品,則 A 與 B 相似」,從而推薦與用戶歷史行為中商品相似的新商品。

Python
# ai-recommender-service/recommender.py (簡化核心邏輯)
import pandas as pd
from sklearn.metrics.pairwise import cosine_similarity
from typing import List, Dict, Any
import logging # 引入日誌

logger = logging.getLogger(__name__)

class Recommender:
    def __init__(self):
        self.item_similarity_df = pd.DataFrame() # 儲存商品相似度矩陣
        self.products = {} # 儲存活躍商品 {id: product_data}
        self.redis_client = None # Redis 連接實例
        # ... 其他初始化

    def _load_user_interactions_from_mysql(self) -> pd.DataFrame:
        """從 MySQL 加載用戶互動數據,轉換為 Pandas DataFrame。"""
        # 實際實作會透過數據庫連接池從 recommendation_events 表查詢數據
        # 並進行適當的數據清洗和格式轉換
        logger.info("正在從 MySQL 加載用戶互動數據...")
        # 假設返回 DataFrame 包含 'user_id', 'product_id', 'action'
        # 轉換成 user-item 矩陣
        # ...
        pass

    def _load_products_from_mysql(self) -> Dict[int, Any]:
        """從 MySQL 加載所有狀態為 'active' 的商品數據。"""
        # 實際實作會透過數據庫連接池從 products 表查詢數據
        # **關鍵:WHERE status = 'active'**
        logger.info("正在從 MySQL 加載活躍商品數據...")
        # 假設返回 {product_id: product_info}
        pass

    def train_and_save_model(self):
        """定時觸發:從數據庫加載數據、訓練模型並持久化。"""
        logger.info("開始訓練推薦模型...")
        try:
            # 1. 更新活躍商品數據
            self.products = self._load_products_from_mysql()
            active_product_ids = list(self.products.keys())
            if not active_product_ids:
                logger.warning("無活躍商品數據,模型訓練中止。")
                return

            # 2. 加載用戶互動數據
            user_interactions = self._load_user_interactions_from_mysql()
            if user_interactions.empty:
                logger.warning("無用戶互動數據,模型訓練中止。")
                return

            # 3. 構建用戶-商品互動矩陣
            # 確保矩陣只包含活躍商品
            user_item_matrix = user_interactions.pivot_table(index='user_id', columns='product_id', values='action').fillna(0)
            user_item_matrix = user_item_matrix[user_item_matrix.columns.intersection(active_product_ids)]

            if user_item_matrix.empty or user_item_matrix.shape[1] < 2:
                logger.warning("用戶-商品矩陣不足以訓練模型,可能活躍商品少於2個或無有效互動。")
                return

            # 4. 計算商品相似度:核心算法
            self.item_similarity_df = pd.DataFrame(cosine_similarity(user_item_matrix.T),
                                                   index=user_item_matrix.columns,
                                                   columns=user_item_matrix.columns)
            
            # 5. 模型持久化:保存到本地文件和 Redis
            # ... 序列化 self.item_similarity_df 到 .pkl 文件
            # ... 將 .pkl 內容存儲到 Redis
            logger.info("模型訓練並保存成功!")
        except Exception as e:
            logger.exception(f"模型訓練失敗: {e}")

    def get_recommendations(self, user_id: int, product_count: int = 10) -> List[int]:
        """獲取指定用戶的推薦商品 ID 列表。"""
        # 1. 獲取用戶最近瀏覽或點擊的商品 (從數據庫或 Redis 緩存)
        # 2. 根據這些商品和 item_similarity_df 計算相似商品
        # 3. **【核心】確保只從 `self.products` (活躍商品字典) 中選擇推薦結果**
        # 4. 冷啟動處理:若推薦數量不足,從活躍商品中隨機補充
        # 5. 確保推薦結果不重複且符合數量要求
        logger.info(f"為用戶 {user_id} 生成推薦結果...")
        # ...
        pass

2. 模型自動訓練與熱載入:確保推薦時效性

FastAPI 服務啟動時會透過 APScheduler 定義定時任務,實現模型的自動化訓練與數據同步:

Python
# ai-recommender-service/main.py (啟動 APScheduler)
from fastapi import FastAPI # ...
from apscheduler.schedulers.background import BackgroundScheduler
from recommender import Recommender # 引入 Recommender 類
import logging # 引入日誌

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

app = FastAPI()
recommender_instance = Recommender()
scheduler = BackgroundScheduler()

@app.on_event("startup")
async def startup_event():
    """應用啟動時的初始化任務。"""
    logger.info("FastAPI 推薦服務啟動中...")
    recommender_instance.connect_to_redis() # 連接 Redis
    recommender_instance.load_model() # 啟動時加載最新模型 (從 Redis 或文件)
    recommender_instance.update_product_data() # 立即同步活躍商品數據

    # 定義定時任務
    scheduler.add_job(recommender_instance.train_and_save_model, 'interval', hours=6, id='model_retrain', replace_existing=True)
    scheduler.add_job(recommender_instance.update_product_data, 'interval', hours=1, id='product_data_sync', replace_existing=True)
    scheduler.start()
    logger.info("APScheduler 定時任務已啟動。")

@app.on_event("shutdown")
def shutdown_event():
    """應用關閉時的清理任務。"""
    scheduler.shutdown()
    recommender_instance.disconnect_from_redis()
    logger.info("FastAPI 推薦服務已關閉。")

# ... FastAPI 路由定義,例如 /recommend/{user_id}

這種設計確保了推薦模型能夠定期反映最新的用戶行為和商品狀態,且在模型更新時,無需中斷服務,實現無縫熱載入

3. 冷啟動與推薦多樣性

冷啟動處理:對於沒有足夠歷史互動數據的用戶(新用戶),協同過濾模型無法有效推薦。FastAPI 服務會智能判斷,若協同過濾無法產生足夠的推薦數量,則會從當前所有活躍商品中隨機補充,確保用戶總能看到推薦結果。

推薦多樣性:目前主要透過冷啟動時的隨機補充來增加多樣性。未來可以進一步引入更複雜的多樣性算法,例如最大化邊際相關性 (MMR) 等,以平衡推薦的相關性與新穎性。

4. Prometheus 指標暴露:讓推薦服務數據化

FastAPI 服務透過 prometheus_client 庫,將內部的運行狀態和業務指標標準化暴露/metrics HTTP 端點,供 Prometheus 抓取。

Python
# ai-recommender-service/main.py (部分指標定義與使用)
from prometheus_client import generate_latest, Counter, Histogram, Gauge

# HTTP 請求指標
REQUEST_COUNT = Counter('http_requests_total', 'Total HTTP Requests', ['method', 'endpoint'])
REQUEST_LATENCY = Histogram('http_request_duration_seconds', 'HTTP Request Latency', ['method', 'endpoint'])

# 推薦業務指標 (核心!)
RECOMMENDATION_SUCCESS_TOTAL = Counter('recommendation_success_total', 'Total successful recommendations served')
RECOMMENDATION_COLD_START_TOTAL = Counter('recommendation_cold_start_total', 'Total cold start recommendations served')
RECOMMENDATION_REPETITION_RATIO = Gauge('recommendation_repetition_ratio', 'Ratio of repeated recommendations', ['user_id', 'experiment_name', 'group'])
RECOMMENDATION_CATALOG_COVERAGE_RATIO = Gauge('recommendation_catalog_coverage_ratio', 'Ratio of unique recommended products to total active products')

這些指標是後續監控和告警的基礎,也是評估推薦系統效果的量化依據

📚 數據庫與緩存策略:速度與可靠性的黃金平衡

  • MySQL (持久化):作為所有核心數據(用戶、商品、推薦事件)的最終一致性來源。它的穩定性和 ACID 特性確保了數據的完整性與可靠性。
  • Redis (高性能緩存與隊列)
    • 極速模型載入:序列化後的推薦模型儲存於 Redis,FastAPI 啟動時能瞬間加載,無需等待文件讀取。
    • 非同步事件處理:Laravel 將用戶行為事件推送到 Redis 隊列,由後台 Worker 非同步處理,將數據庫寫入的延遲從用戶請求路徑中剝離。
    • 實時重複率計算:快速讀寫特性使 Redis 成為記錄和對比用戶最近推薦結果的理想選擇。

📈 可觀測性:Prometheus & Grafana,讓你的系統「會說話」

一個好的系統不僅要能工作,更要「會說話」。我們透過 Prometheus 和 Grafana,賦予推薦系統強大的可觀測性。

  • Prometheus:它會定期從 FastAPI 服務的 /metrics 端點「抓取」(scrape) 各類指標數據。
    • 系統層指標:HTTP 請求總數、請求延遲、服務錯誤率等,反映服務運行健康。
    • 業務層指標
      • recommendation_success_total:成功響應的推薦請求總數。
      • recommendation_cold_start_total:冷啟動用戶的推薦請求次數。
      • recommendation_repetition_ratio:推薦結果重複的比例,衡量內容新鮮度
      • recommendation_catalog_coverage_ratio:推薦出的商品種類覆蓋整個商品庫的比例,衡量推薦廣度
  • Grafana:作為 Prometheus 的強大可視化介面。我們可以在 Grafana 中建立客製化的儀表板 (Dashboard),將這些複雜的指標以直觀的圖表呈現。

最關鍵的是,Grafana 配置了告警規則。例如,當:

  • 冷啟動比例 (rate(recommendation_cold_start_total[5m]) / rate(http_requests_total[5m])) 在 5 分鐘內持續超過 30% 時,觸發「WARN」警告。
  • 推薦重複率 (recommendation_repetition_ratio) 在 10 分鐘內持續高於 80% 時,觸發「CRITICAL」告警。

一旦觸發,Grafana 會立即透過郵件、Slack 或其他通知渠道發送告警,實現真正的「主動式運維」。這意味著我們能夠在用戶察覺問題之前,就及時發現、定位並解決問題,極大降低了平均恢復時間 (MTTR),保障了用戶體驗和業務連續性。

🚀 快速啟動與親身體驗:運行你的專案!

親手運行這個專案,是你理解其精髓的最佳方式。所有服務都已容器化,啟動流程極為簡便:

  1. 環境準備:確保您的系統已安裝 DockerDocker Compose
  2. 克隆專案
    Bash
    git clone https://github.com/BpsEason/recommendation-system.git
    cd recommendation-system
    
  3. 啟動 Docker 服務:這會啟動所有 Laravel、FastAPI、MySQL、Redis、Prometheus、Grafana 容器。
    Bash
    docker compose up --build -d
    
  4. 初始化 Laravel 應用 (重要步驟!)
    Bash
    docker compose exec laravel-app php artisan key:generate --show # 複製輸出,貼到 laravel-app/.env 的 APP_KEY=
    docker compose exec laravel-app php artisan migrate
    docker compose exec laravel-app php artisan db:seed --class=ProductSeeder
    docker compose exec laravel-app php artisan db:seed --class=UserSeeder
    
  5. 【關鍵手動配置】綁定 Laravel 事件監聽器 編輯 laravel-app/app/Providers/EventServiceProvider.php,在 $listen 陣列中添加以下內容:
    PHP
    'App\\Events\\RecommendationInteraction' => [
        'App\\Listeners\\LogRecommendationInteraction',
    ],
    
    並在 boot() 方法中添加 UserObserver 的註冊:
    PHP
    \App\Models\User::observe(\App\Observers\UserObserver::class);
    
    完成後,請儲存檔案
  6. 啟動 Laravel 隊列 Worker:確保異步事件處理正常運行。
    Bash
    docker compose exec laravel-app php artisan queue:work --queue=default --tries=3
    

現在,你的推薦系統已準備就緒!你可以訪問:

  • Laravel App (主要業務 API):http://localhost:8000
  • FastAPI Recommender (推薦服務接口):http://localhost:8001
  • Prometheus (監控數據收集):http://localhost:9090
  • Grafana (可視化儀表板與告警):http://localhost:3000 (默認用戶/密碼:admin/admin)

展望未來:推薦系統的無限進化之路

雖然這個專案已具備生產級應用的基礎,但推薦系統的進化永無止境。對於有志於深度探索的你,以下是一些值得思考和實踐的未來方向:

  • 更前沿的推薦算法
    • 探索基於深度學習的模型(如 DSSM, DIN, YoutubeDNN, Transformer)來處理更複雜的用戶行為和物品特徵。
    • 引入圖神經網絡(GNN)處理用戶-物品交互圖,挖掘更深層次的關係。
  • 實時推薦:透過整合消息隊列(如 Kafka)和流處理框架(如 Flink/Spark Streaming),實現用戶行為的實時捕獲、特徵提取和模型更新,提供秒級響應的推薦。
  • 多樣性與可解釋性優化:在追求推薦精準度之外,加入更多樣性指標,並探索如何讓推薦結果更具「可解釋性」(Explainable AI),增強用戶信任感。
  • 完善 MLOps 流程:將模型的數據收集、訓練、評估、部署、監控整個生命週期全面自動化,實現持續集成/持續部署 (CI/CD) for ML。
  • 特徵平台 (Feature Store):為了解耦特徵計算與模型訓練,建立統一的特徵儲存和服務層,加速新模型的實驗和迭代。

結語:跨越 PHP 與 AI 的鴻溝

本專案提供了一個清晰的藍圖,展示了 PHP 工程師如何利用其在 Laravel 上的深厚功底,與 Python 的 AI 生態無縫協作,共同打造強大而智能的應用程式。這不僅是技術棧的整合,更是微服務架構思維、跨語言協作、以及數據驅動決策的實際應用。

希望這篇文章能激發你的靈感,鼓勵你跳出舒適區,探索更廣闊的技術領域。智能推薦的未來充滿可能性,期待你能在這條路上創造更多精彩!

歡迎訪問我的 GitHub 倉庫:https://github.com/BpsEason/recommendation-system.git,深入代碼細節,一同學習成長!


沒有留言:

張貼留言

網誌存檔