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
// routes/api.php
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
// config/services.php
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. 保持服务独立

1
2
3
4
5
6
7
// 好的做法:每个服务有自己的数据库
// user-service -> user_db
// order-service -> order_db

// 不好的做法:共享数据库
// user-service -> shared_db
// order-service -> shared_db

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 进行容器化部署。微服务架构适合大型复杂应用,对于小型应用,单体架构可能更合适。