分佈式事務一直是微服務的一個難點。相關的解決方案和框架大部分是 java 的,那麼 php 該如何解決呢?下面一步一步講解如何用 php 解決分佈式事務。
單機單數據源事務
首先從單機事務開始。
大概邏輯如下 :
try {
// 開始事務
$db->beginTransaction();
// 執行你的操作
// ...
// 提交事務
$db->commit();
} catch (Exception $e) {
// 執行失敗 回滾
$db->rollBack();
}
單機多個數據源事務
如果你業務涉及到多個數據庫,事務大概邏輯是這個樣子:
try {
// 開始事務
$db1->beginTransaction();
$db2->beginTransaction();
// 執行你的操作
// ...
// 提交事務
$db1->commit();
$db2->commit();
} catch (Exception $e) {
// 執行失敗 回滾
$db1->rollBack();
$db2->rollBack();
}
多機多數據源事務(分佈式事務)
如果你的數據源和業務代碼都是分開的(微服務)這就是我們今天的核心。
由前面兩種情況來看,大概邏輯是差不多的,主要也分爲 4 個步驟。
- 開始事務
- 執行邏輯代碼
- 提交事務
- 回滾事務
有些文章也稱爲 tcc
也就是 234 步驟。
我們用一個常用的例子:下單。
主要 3 個步驟:
- 創建訂單
- 修改庫存
- 修改用戶積分
假設訂單,庫存,用戶都是獨立的服務。
按照前面的經驗大概分爲 4 個步驟,我們以用戶爲例 代碼如下:
class User
{
// 開始事務
public function beginTransaction()
{
$db->beginTransaction();
return $this;
}
// 執行代碼
public function doTransaction()
{
// 執行你的操作
// ...
return $this;
}
public function commit()
{
$db->commit();
}
public funtion rollBack()
{
$db->rollBack();
}
}
庫存 (stock),訂單 (order) 和上面類似,也需要這 4 個方法,我就不寫了。
難點在於我們沒法直接操作數據源,只能通過 rpc 調用相應的服務來操作。依次執行上面的方法就好了。代碼如下:
try {
// 開始事務
$user = new User();
$stock = new Stock();
$order = new Order();
$user = $user->beginTransaction();
$stock = $stock->beginTransaction();
$order = $order->beginTransaction();
// 執行你的操作
$user = $user->doTransaction();
$stock = $stock->doTransaction();
$order = $order->doTransaction();
// 提交事務
$user->commit();
$stock->commit();
$order->commit();
} catch (Exception $e) {
// 執行失敗 回滾
$user->rollBack();
$stock->rollBack();
$order->rollBack();
}
到這裏可能有人看出問題來了,正常情況下這樣肯定是不行的。要上面這段代碼成立需要滿足 1 個條件:User
分別調用了 3 次,也就是 3 個請求。要保證這 3 個請求是調用的同一個實例化後的對象。Stock
和 Order
一樣。
User
調用邏輯如下:
// 第一次請求調用
$user = new User();
$user = $user->beginTransaction();
// 第二次請求調用 複用的第一次 $user
$user = $user->doTransaction();
// 第三次請求調用 複用的第一次 $user
$user->commit();
//或者
$user->rollBack();
注意: 雖然調用了 3 次但是隻 new
了一次, 第二次和第三次請求是複用的第一次的對象。要滿足這個條件 服務供方必須 常駐內存 ,而且提供的 rpc服務
必須支持鏈式調用
的功能。
one
框架 https://github.com/lizhichao/one
極簡。高性能。松耦合。分佈式。可運行於多種環境
one
框架完美支持上面的要求。只需要把上面的 User
、Stock
和 Order
添加爲 rpc 服務即可。還需要注意 beginTransaction
和 doTransaction
方法必須返回 $this
提供給後面的方法調用。
user 服務如下:
RpcServer::add(User::class);
其他兩個類似。到此分佈式事務問題就搞定了,可能覺得這麼簡單嗎?這主要由於 one框架
的 rpc 服務提供了鏈式調用(多個請求複用同一個對象)的功能。
可能有人要問:如果因爲網絡問題或者其他問題導致最後一個服務的最後一次調用失敗了怎麼辦?
解決方案就是事務補償
,你可以把這類極端的情況下的錯誤,放到一個隊列裏 起一服務來專門處理這裏問題。