好的,我们开始今天的讲座,主题是:PHP在大规模分布式事务中的应用:TCC或Saga模式的用户态实现。
引言
在微服务架构日益普及的今天,分布式事务成为一个不可回避的挑战。PHP虽然在单体应用开发中表现出色,但在分布式环境中处理事务需要一些额外的考量。今天,我们将探讨如何使用TCC(Try-Confirm-Cancel)和 Saga 模式在 PHP 中实现用户态的分布式事务管理。我们将深入研究这些模式的原理,并提供具体的代码示例,帮助大家理解如何在实际项目中应用它们。
分布式事务的挑战
传统的ACID事务模型在单数据库环境中表现良好,但在分布式系统中却面临以下挑战:
- 跨数据库/服务: 事务涉及多个独立的数据库或服务,难以保证原子性。
- 网络延迟: 分布式系统中的网络延迟可能导致事务执行时间过长,影响性能。
- 数据一致性: 在部分服务失败的情况下,如何保证数据最终一致性是一个难题。
TCC (Try-Confirm-Cancel) 模式
TCC 是一种补偿型事务,其核心思想是将每个业务操作分成三个阶段:
- Try 阶段: 尝试执行业务,预留所需的资源。这个阶段通常不进行实际的业务变更,而是进行资源的锁定或预分配。
- Confirm 阶段: 在 Try 阶段成功的情况下,确认执行业务。这个阶段会执行真正的业务操作,并释放 Try 阶段预留的资源。
- Cancel 阶段: 在 Try 阶段失败或 Confirm 阶段无法执行的情况下,取消执行业务,释放 Try 阶段预留的资源。
TCC 模式的优点:
- 资源锁定: Try 阶段预留资源,减少了并发冲突的可能性。
- 最终一致性: 通过 Confirm 和 Cancel 操作,保证了数据最终一致性。
TCC 模式的缺点:
- 开发复杂度高: 需要为每个业务操作实现 Try、Confirm 和 Cancel 三个阶段的逻辑。
- 幂等性要求: Confirm 和 Cancel 操作需要保证幂等性,以应对网络异常或重复调用。
- 可能存在资源锁定时间过长的问题: 如果 Try 阶段预留资源后长时间未进行 Confirm 或 Cancel 操作,可能会导致资源被长时间占用。
TCC 模式在 PHP 中的实现
下面是一个使用 PHP 实现 TCC 模式的示例。假设我们有一个在线购物系统,用户购买商品涉及以下两个服务:
- 订单服务: 创建订单。
- 库存服务: 扣减库存。
1. 订单服务 (Order Service)
<?php
class OrderService
{
private $db; // 数据库连接
public function __construct(PDO $db)
{
$this->db = $db;
}
public function tryCreateOrder(int $userId, int $productId, int $quantity): string
{
// 1. 预留订单,生成订单ID,状态设置为 'pending'
$orderId = uniqid('order_');
$sql = "INSERT INTO orders (order_id, user_id, product_id, quantity, status) VALUES (?, ?, ?, ?, ?)";
$stmt = $this->db->prepare($sql);
$stmt->execute([$orderId, $userId, $productId, $quantity, 'pending']);
return $orderId; // 返回订单ID,供库存服务使用
}
public function confirmCreateOrder(string $orderId): bool
{
// 2. 确认创建订单,将订单状态设置为 'completed'
$sql = "UPDATE orders SET status = ? WHERE order_id = ?";
$stmt = $this->db->prepare($sql);
$stmt->execute(['completed', $orderId]);
return true;
}
public function cancelCreateOrder(string $orderId): bool
{
// 3. 取消创建订单,将订单状态设置为 'cancelled'
$sql = "UPDATE orders SET status = ? WHERE order_id = ?";
$stmt = $this->db->prepare($sql);
$stmt->execute(['cancelled', $orderId]);
return true;
}
}
?>
2. 库存服务 (Inventory Service)
<?php
class InventoryService
{
private $db; // 数据库连接
public function __construct(PDO $db)
{
$this->db = $db;
}
public function tryDeductInventory(int $productId, int $quantity): bool
{
// 1. 尝试扣减库存,预留库存
$sql = "UPDATE inventory SET reserved_quantity = reserved_quantity + ? WHERE product_id = ? AND quantity - reserved_quantity >= ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$quantity, $productId, $quantity]);
return $stmt->rowCount() > 0; // 如果预留成功,返回 true,否则返回 false
}
public function confirmDeductInventory(int $productId, int $quantity): bool
{
// 2. 确认扣减库存,将 reserved_quantity 转移到 used_quantity
$sql = "UPDATE inventory SET quantity = quantity - ?, reserved_quantity = reserved_quantity - ? WHERE product_id = ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$quantity, $quantity, $productId]);
return true;
}
public function cancelDeductInventory(int $productId, int $quantity): bool
{
// 3. 取消扣减库存,释放预留的库存
$sql = "UPDATE inventory SET reserved_quantity = reserved_quantity - ? WHERE product_id = ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$quantity, $productId]);
return true;
}
}
?>
3. TCC 协调器 (TCC Coordinator)
TCC 协调器负责协调各个服务的 TCC 操作。它可以是一个独立的微服务,也可以嵌入到某个服务中。
<?php
class TCCCoordinator
{
private $orderService;
private $inventoryService;
public function __construct(OrderService $orderService, InventoryService $inventoryService)
{
$this->orderService = $orderService;
$this->inventoryService = $inventoryService;
}
public function purchase(int $userId, int $productId, int $quantity): bool
{
// 1. Try 阶段
$orderId = $this->orderService->tryCreateOrder($userId, $productId, $quantity);
if (!$orderId) {
return false; // 创建订单失败
}
$inventoryReserved = $this->inventoryService->tryDeductInventory($productId, $quantity);
if (!$inventoryReserved) {
// 库存预留失败,需要 Cancel 订单
$this->orderService->cancelCreateOrder($orderId);
return false;
}
// 2. Confirm 阶段
try {
$this->orderService->confirmCreateOrder($orderId);
$this->inventoryService->confirmDeductInventory($productId, $quantity);
return true; // 购买成功
} catch (Exception $e) {
// Confirm 阶段失败,需要 Cancel 所有操作
$this->orderService->cancelCreateOrder($orderId);
$this->inventoryService->cancelDeductInventory($productId, $quantity);
return false;
}
}
}
?>
4. 使用示例
<?php
// 假设已经创建了数据库连接 $db
$orderService = new OrderService($db);
$inventoryService = new InventoryService($db);
$tccCoordinator = new TCCCoordinator($orderService, $inventoryService);
$userId = 123;
$productId = 456;
$quantity = 1;
$success = $tccCoordinator->purchase($userId, $productId, $quantity);
if ($success) {
echo "购买成功!";
} else {
echo "购买失败!";
}
?>
重要注意事项:
- 异常处理: 在 TCC 的每个阶段都需要进行完善的异常处理,确保在任何情况下都能正确执行 Confirm 或 Cancel 操作。
- 幂等性: Confirm 和 Cancel 操作必须保证幂等性。可以采用以下方法:
- 状态机: 记录事务的执行状态,只有在特定状态下才允许执行操作。
- 唯一标识: 为每个事务生成一个唯一标识,只有在未处理过该标识的情况下才执行操作。
- 超时机制: 为 TCC 操作设置超时时间,避免长时间锁定资源。如果超时,则自动执行 Cancel 操作。
- 消息队列: 可以使用消息队列来异步执行 Confirm 和 Cancel 操作,提高系统的吞吐量。
Saga 模式
Saga 模式是另一种常用的分布式事务解决方案。它将一个大的事务分解成一系列小的本地事务,每个本地事务都由一个 Saga 参与者(Participant)执行。Saga 模式通过事件驱动的方式协调各个参与者,以保证数据最终一致性。
Saga 模式的两种实现方式:
- 编排式 Saga (Orchestration-based Saga): 由一个中心化的编排器(Orchestrator)负责协调各个 Saga 参与者。编排器会根据预定义的流程,依次调用各个参与者的本地事务。
- 协同式 Saga (Choreography-based Saga): 各个 Saga 参与者通过发布和订阅事件进行协作。每个参与者监听特定的事件,并在接收到事件后执行相应的本地事务。
Saga 模式的优点:
- 松耦合: 各个 Saga 参与者之间通过事件进行通信,降低了耦合度。
- 易于扩展: 可以方便地添加新的 Saga 参与者,扩展系统的功能。
- 适用于长事务: 适用于需要跨多个服务、长时间执行的事务。
Saga 模式的缺点:
- 数据不一致性: 在 Saga 执行过程中,可能存在数据不一致的情况。需要通过补偿操作来保证数据最终一致性。
- 复杂性: Saga 的实现相对复杂,需要处理各种异常情况和补偿操作。
Saga 模式在 PHP 中的实现
我们继续使用上面的在线购物系统示例,使用编排式 Saga 模式实现分布式事务。
1. 定义 Saga 参与者接口
<?php
interface SagaParticipantInterface
{
public function execute(array $data): bool;
public function compensate(array $data): bool;
}
?>
2. 实现订单服务 Saga 参与者
<?php
class OrderSagaParticipant implements SagaParticipantInterface
{
private $orderService;
public function __construct(OrderService $orderService)
{
$this->orderService = $orderService;
}
public function execute(array $data): bool
{
// 创建订单
$userId = $data['user_id'];
$productId = $data['product_id'];
$quantity = $data['quantity'];
$orderId = uniqid('order_');
// 创建订单的逻辑 (与 TCC 的 tryCreateOrder 类似)
try {
$sql = "INSERT INTO orders (order_id, user_id, product_id, quantity, status) VALUES (?, ?, ?, ?, ?)";
$stmt = $this->orderService->db->prepare($sql); // 直接访问数据库连接,简化示例
$stmt->execute([$orderId, $userId, $productId, $quantity, 'completed']);
return true;
} catch (Exception $e) {
return false;
}
}
public function compensate(array $data): bool
{
// 取消订单
$orderId = $data['order_id'];
// 取消订单的逻辑 (与 TCC 的 cancelCreateOrder 类似)
try {
$sql = "UPDATE orders SET status = ? WHERE order_id = ?";
$stmt = $this->orderService->db->prepare($sql);
$stmt->execute(['cancelled', $orderId]);
return true;
} catch (Exception $e) {
return false;
}
}
}
?>
3. 实现库存服务 Saga 参与者
<?php
class InventorySagaParticipant implements SagaParticipantInterface
{
private $inventoryService;
public function __construct(InventoryService $inventoryService)
{
$this->inventoryService = $inventoryService;
}
public function execute(array $data): bool
{
// 扣减库存
$productId = $data['product_id'];
$quantity = $data['quantity'];
// 扣减库存的逻辑 (与 TCC 的 confirmDeductInventory 类似)
try {
$sql = "UPDATE inventory SET quantity = quantity - ? WHERE product_id = ?";
$stmt = $this->inventoryService->db->prepare($sql); // 直接访问数据库连接,简化示例
$stmt->execute([$quantity, $productId]);
return true;
} catch (Exception $e) {
return false;
}
}
public function compensate(array $data): bool
{
// 增加库存
$productId = $data['product_id'];
$quantity = $data['quantity'];
// 增加库存的逻辑 (与 TCC 的 cancelDeductInventory 的反向操作类似)
try {
$sql = "UPDATE inventory SET quantity = quantity + ? WHERE product_id = ?";
$stmt = $this->inventoryService->db->prepare($sql);
$stmt->execute([$quantity, $productId]);
return true;
} catch (Exception $e) {
return false;
}
}
}
?>
4. 实现 Saga 编排器
<?php
class SagaOrchestrator
{
private $participants = [];
public function addParticipant(SagaParticipantInterface $participant): void
{
$this->participants[] = $participant;
}
public function run(array $data): bool
{
$executedParticipants = [];
foreach ($this->participants as $participant) {
if ($participant->execute($data)) {
$executedParticipants[] = $participant;
} else {
// 执行补偿操作
$this->compensate($executedParticipants, $data);
return false;
}
}
return true;
}
private function compensate(array $executedParticipants, array $data): void
{
// 倒序执行补偿操作
foreach (array_reverse($executedParticipants) as $participant) {
$participant->compensate($data);
}
}
}
?>
5. 使用示例
<?php
// 假设已经创建了数据库连接 $db
$orderService = new OrderService($db);
$inventoryService = new InventoryService($db);
$orderSagaParticipant = new OrderSagaParticipant($orderService);
$inventorySagaParticipant = new InventorySagaParticipant($inventoryService);
$sagaOrchestrator = new SagaOrchestrator();
$sagaOrchestrator->addParticipant($orderSagaParticipant);
$sagaOrchestrator->addParticipant($inventorySagaParticipant);
$userId = 123;
$productId = 456;
$quantity = 1;
$data = [
'user_id' => $userId,
'product_id' => $productId,
'quantity' => $quantity,
'order_id' => uniqid('order_'), // 生成一个订单ID,传递给补偿操作
];
$success = $sagaOrchestrator->run($data);
if ($success) {
echo "购买成功!";
} else {
echo "购买失败!";
}
?>
选择 TCC 还是 Saga?
| 特性 | TCC | Saga |
|---|---|---|
| 耦合度 | 较高,需要修改现有服务的逻辑 | 较低,通过事件驱动,服务间解耦 |
| 复杂度 | 较高,需要实现 Try/Confirm/Cancel | 较高,需要处理补偿逻辑和事件驱动 |
| 数据一致性 | 强最终一致性 | 最终一致性,可能存在中间状态 |
| 适用场景 | 资源锁定要求较高的场景 | 长事务、跨多个服务的场景 |
| 开发难度 | 较高 | 较高 |
| 性能 | 可能会因为资源锁定影响性能 | 性能较好,异步执行 |
总结:用户态分布式事务的PHP实现
用户态的TCC和Saga模式,通过PHP代码可以在分布式系统中实现事务。TCC需要修改现有服务,而Saga通过事件驱动实现解耦,选择哪种模式取决于具体业务场景和需求。