Laravel 13 微服务架构实践
微服务架构是一种将应用程序拆分为小型、独立服务的架构风格。本文将介绍如何使用 Laravel 13 构建微服务架构。
微服务概述
单体架构 vs 微服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 单体架构: ┌─────────────────────────────────┐ │ 应用程序 │ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │ │用户 │ │订单 │ │支付 │ │ │ └─────┘ └─────┘ └─────┘ │ │ ┌─────────┐ │ │ │ 数据库 │ │ │ └─────────┘ │ └─────────────────────────────────┘
微服务架构: ┌─────────┐ ┌─────────┐ ┌─────────┐ │用户服务 │ │订单服务 │ │支付服务 │ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ │用户数据库│ │订单数据库│ │支付数据库│ └─────────┘ └─────────┘ └─────────┘
|
服务拆分
按业务领域拆分
1 2 3 4 5 6
| ├── user-service/ # 用户服务 ├── order-service/ # 订单服务 ├── payment-service/ # 支付服务 ├── inventory-service/ # 库存服务 ├── notification-service/# 通知服务 └── api-gateway/ # API 网关
|
API 网关
创建 API 网关
1 2 3 4 5 6 7 8 9 10 11 12 13
| Route::prefix('v1')->group(function () { Route::prefix('users')->group(function () { Route::get('/', [UserController::class, 'index']); Route::get('/{id}', [UserController::class, 'show']); Route::post('/', [UserController::class, 'store']); });
Route::prefix('orders')->group(function () { Route::get('/', [OrderController::class, 'index']); Route::post('/', [OrderController::class, 'store']); }); });
|
服务代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| <?php
namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Support\Facades\Http;
class ServiceProxyController extends Controller { protected $services = [ 'users' => 'http://user-service:8001', 'orders' => 'http://order-service:8002', 'payments' => 'http://payment-service:8003', ];
public function proxy(Request $request, $service, $path) { $baseUrl = $this->services[$service] ?? null;
if (!$baseUrl) { return response()->json(['error' => 'Service not found'], 404); }
$response = Http::withHeaders($request->headers->all()) ->send( $request->method(), $baseUrl . '/' . $path, [ 'query' => $request->query(), 'body' => $request->getContent(), ] );
return response($response->body(), $response->status()) ->withHeaders($response->headers()); } }
|
服务间通信
HTTP 通信
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
class UserServiceClient { protected string $baseUrl;
public function __construct() { $this->baseUrl = config('services.user.url'); }
public function getUser(int $id): array { return Http::get("{$this->baseUrl}/users/{$id}")->json(); }
public function createUser(array $data): array { return Http::post("{$this->baseUrl}/users", $data)->json(); } }
|
消息队列通信
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| use App\Jobs\ProcessOrder;
class OrderController extends Controller { public function store(Request $request) { $order = Order::create($request->validated());
ProcessOrder::dispatch($order);
return new OrderResource($order); } }
class UpdateInventory implements ShouldQueue { public function handle(OrderCreated $event) { foreach ($event->order->items as $item) { Inventory::where('product_id', $item->product_id) ->decrement('quantity', $item->quantity); } } }
|
服务发现
配置服务发现
1 2 3 4 5 6 7 8 9 10 11 12
| return [ 'user' => [ 'url' => env('USER_SERVICE_URL', 'http://user-service'), ], 'order' => [ 'url' => env('ORDER_SERVICE_URL', 'http://order-service'), ], 'payment' => [ 'url' => env('PAYMENT_SERVICE_URL', 'http://payment-service'), ], ];
|
服务健康检查
1 2 3 4 5 6 7
| Route::get('/health', function () { return response()->json([ 'status' => 'healthy', 'service' => config('app.name'), 'timestamp' => now()->toIso8601String(), ]); });
|
分布式追踪
使用请求 ID
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <?php
namespace App\Http\Middleware;
use Closure; use Illuminate\Http\Request; use Illuminate\Support\Str;
class RequestIdMiddleware { public function handle(Request $request, Closure $next) { $requestId = $request->header('X-Request-ID') ?? Str::uuid()->toString();
$request->headers->set('X-Request-ID', $requestId);
$response = $next($request);
$response->headers->set('X-Request-ID', $requestId);
return $response; } }
|
日志追踪
1 2 3 4 5 6 7 8 9 10 11
| use Illuminate\Support\Facades\Log;
Log::withContext([ 'request_id' => request()->header('X-Request-ID'), 'service' => config('app.name'), ]);
Log::info('Processing request', [ 'method' => request()->method(), 'url' => request()->url(), ]);
|
数据一致性
分布式事务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Http;
class OrderService { public function createOrder(array $data) { return DB::transaction(function () use ($data) { $order = Order::create($data);
$inventoryResponse = Http::post(config('services.inventory.url') . '/reserve', [ 'order_id' => $order->id, 'items' => $data['items'], ]);
if (!$inventoryResponse->successful()) { throw new \Exception('Inventory reservation failed'); }
$paymentResponse = Http::post(config('services.payment.url') . '/charge', [ 'order_id' => $order->id, 'amount' => $order->total, ]);
if (!$paymentResponse->successful()) { Http::post(config('services.inventory.url') . '/release', [ 'order_id' => $order->id, ]); throw new \Exception('Payment failed'); }
return $order; }); } }
|
最终一致性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| class ProcessOrder implements ShouldQueue { public function handle() { try { $this->reserveInventory(); $this->processPayment(); $this->confirmOrder(); } catch (\Exception $e) { $this->compensate(); throw $e; } }
protected function reserveInventory() { }
protected function processPayment() { }
protected function confirmOrder() { }
protected function compensate() { } }
|
容错处理
断路器模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| <?php
namespace App\Services;
use Illuminate\Support\Facades\Cache;
class CircuitBreaker { protected string $service; protected int $failureThreshold = 5; protected int $resetTimeout = 60;
public function __construct(string $service) { $this->service = $service; }
public function call(callable $callback) { if ($this->isOpen()) { throw new \Exception("Circuit breaker is open for {$this->service}"); }
try { $result = $callback(); $this->recordSuccess(); return $result; } catch (\Exception $e) { $this->recordFailure(); throw $e; } }
protected function isOpen(): bool { $failures = Cache::get("circuit:{$this->service}:failures", 0); $lastFailure = Cache::get("circuit:{$this->service}:last_failure");
if ($failures >= $this->failureThreshold) { if ($lastFailure && now()->diffInSeconds($lastFailure) < $this->resetTimeout) { return true; } $this->reset(); }
return false; }
protected function recordFailure(): void { Cache::increment("circuit:{$this->service}:failures"); Cache::put("circuit:{$this->service}:last_failure", now()); }
protected function recordSuccess(): void { $this->reset(); }
protected function reset(): void { Cache::forget("circuit:{$this->service}:failures"); Cache::forget("circuit:{$this->service}:last_failure"); } }
|
重试机制
1 2 3 4 5
| use Illuminate\Support\Facades\Http;
$response = Http::retry(3, 100, function ($exception) { return $exception instanceof ConnectionException; })->get('http://user-service/users');
|
Docker 部署
Dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| FROM php:8.3-fpm-alpine
WORKDIR /var/www/html
COPY composer.json composer.lock ./ RUN composer install --no-dev --optimize-autoloader
COPY . .
RUN php artisan config:cache RUN php artisan route:cache
EXPOSE 8000
CMD ["php", "artisan", "serve", "--host=0.0.0.0", "--port=8000"]
|
Docker Compose
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| version: '3.8'
services: api-gateway: build: ./api-gateway ports: - "8000:8000" depends_on: - user-service - order-service
user-service: build: ./user-service ports: - "8001:8000" depends_on: - user-db - redis
order-service: build: ./order-service ports: - "8002:8000" depends_on: - order-db - redis
user-db: image: mysql:8.0 environment: MYSQL_DATABASE: users MYSQL_ROOT_PASSWORD: secret
order-db: image: mysql:8.0 environment: MYSQL_DATABASE: orders MYSQL_ROOT_PASSWORD: secret
redis: image: redis:alpine
|
最佳实践
1. 保持服务独立
2. 使用 API 版本控制
1 2 3 4 5 6 7
| Route::prefix('v1')->group(function () { Route::get('/users', [UserController::class, 'index']); });
Route::prefix('v2')->group(function () { Route::get('/users', [V2\UserController::class, 'index']); });
|
3. 实现优雅降级
1 2 3 4 5 6 7 8
| public function getUser(int $id) { try { return $this->userServiceClient->getUser($id); } catch (\Exception $e) { return Cache::get("user:{$id}:fallback"); } }
|
总结
Laravel 13 可以很好地支持微服务架构。通过合理使用 API 网关、服务间通信、分布式追踪和容错处理,可以构建出可扩展、可维护的微服务系统。记住保持服务的独立性、使用消息队列实现异步通信、实现断路器模式处理故障,并使用 Docker 进行容器化部署。微服务架构适合大型复杂应用,对于小型应用,单体架构可能更合适。