PHP佇列場景以及實現程式碼例項詳解
為了降低單點壓力,通常會根據業務情況進行分表分庫,將表分佈在不同的庫中(庫可能分佈在不同的機器上),但是一個業務場景可能會同時處理兩個表的操作。在這種場景下,事務的提交會變得相對複雜,因為多個節點(庫)的存在,可能存在部分節點提交失敗的情況,即事務的ACID特性需要在各個不同的資料庫例項中保證。比如更新db1庫的A表時,必須同步更新db2庫的B表,兩個更新形成一個事務,要麼都成功,要麼都失敗。
那麼我們如何利用mysql實現分散式資料庫的事務呢?
mysql是從5.0開始支援分散式事務
這裡先宣告兩個概念:
資源管理器(resource manager):用來管理系統資源,是通向事務資源的途徑。資料庫就是一種資源管理器。資源管理還應該具有管理事務提交或回滾的能力。
manager)進行通訊,協調並完成事務的處理。事務的各個分支由唯一命名進行標識。
mysql在執行分散式事務(外部X程式設計客棧A)的時候,mysql伺服器相當於xa事務資源管理器,與mysql連結的客戶端相當於事務管理器。
分散式事務原理:分段式提交
分散式事務通常採用2PC協議,全稱Two Phase Commitment Protocol
。該協議主要為了解決在分散式資料庫場景下,所有節點間資料一致性的問題。分散式事務通過2PC協議將提交分成兩個階段:
prepare;commit/rollback
階段一為準備(prepare)階段。即所有的參與者準備執行事務並鎖住需要的資源。參與者ready時,向transaction manager報告已準備就緒。
階段二為提交階段(commit)。當transaction manager確認所有參與者都ready後,向所有參與者傳送commit命令。
事務協調者transaction manager
因為XA 事務是基於兩階段提交協議的,所以需要有一個事務協調者(transaction manager)來保證所有的事務參與者都完成了準備工作(第一階段)。如果事務協調者(transaction manager)收到所有參與者都準備好的訊息,就會通知所有的事務都可以提交了(第二階段)。MySQL 在這個XA事務中扮演的是參與者的角色,而不是事務協調者(transaction manager)。
Mysql的XA事務分為外部XA和內部XA
外部程式設計客棧XA用於跨多MySQL例項的分散式事務,需要應用層作為協調者,通俗的說就是比如我們在php中寫程式碼,那麼PHP書寫的邏輯就是協調者。應用層負責決定提交還是回滾,崩潰時的懸掛事務。MySQL資料庫外部XA可以用在分散式資料庫代理層,實現對MySQL資料庫的分散式事務支援,例如開源的代理工具:網易的DDB,淘寶的TDDL等等。
內部XA事務用於同一例項下跨多引擎事務,由Binlog作為協調者,比如在一個儲存引擎提交時,需要將提交資訊寫入二進位制日誌,這就是一個分散式內部XA事務,只不過二進位制日誌的參與者是MySQL本身。Binlog作為內部XA的協調者,在binlog中出現的內部xid,在crash recover時,由binlog負責提交。(這是因為,binlog不進行prepare,只進行commit,因此在binlog中出現的內部xid,一定能夠保證其在底層各儲存引擎中已經完成prepare)。
mysql xa事務的語法
1、首先要確保mysql開啟XA事務支援
SHOW VARIABLES LIKE '%xa%'
如果innodb_support_xa
的值是ON就說明mysql已經開啟對XA事務的支援了。 如果不是就執行:
SET innodb_support_xa = ON
主要有:
XA START 'any_unique_id'; // 'any_unique_id' 是使用者給的,全域性唯一在一臺my程式設計客棧sql中開啟一個XA事務
XA END 'any_unique_id '; //標識XA事務的操作結束
XA PREPARE 'any_unique_id'; //告知mysql 準備提交這個xa事務
XA COMMIT 'any_unique_id'; //告知mysql提交這個xa事務
XA ROLLBACK 'any_unique_id'; //告知mysql回滾這個xa事務
XA RECOVER;//檢視本機mysql目前有哪些xa事務處於prepare狀態
XA事務恢復
如果執行分散式事務的mysql crash了,mysql 按照如下邏輯進行恢復:
a. 如果這個xa事務commit了,那麼什麼也不用做
b. 如果這個xa事務還沒有prepare,那麼直接回滾它
c. 如果這個xa事務prepare了,還沒commit, 那麼把它恢復到prepare的狀態,由使用者去決定commit或rollback
當mysql crash後重新啟動之後,執行“XA RECOVER;”檢視當前處於prepare狀態的xa事務,然後commit或rollback它們。
使用限制
a. XA事務和本地事務以及鎖表操作是互斥的
開啟了xa事務就無法使用本地事務和鎖表操作
mysql> xa start 't1xa'; Query OK,0 rows affected (0.04 sec) mysql> begin; ERROR 1399 (XAE07): XAER_RMFAIL: The command cannot be executed when global transacthttp://www.cppcns.comion is in the ACTIVE state mysql> lock table t1 read; ERROR 1399 (XAE07): XAER_RMFAIL: The command cannot be executed when global transaction is in the ACTIVE state
開啟了本地事務就無法使用xa事務
mysql> begin; Query OK,0 rows affected (0.00 sec) mysql> xa start 'rrrr'; ERROR 1400 (XAE09): XAER_OUTSIDE: Some work is done outside global transaction
b. xa start
之後必須xa end
, 否則不能執行xa commit
和xa rollback
所以如果在執行xa事務過程中有語句出錯了,你也需要先xa end
一下,然後才能xarollback
。
注意事項
a. mysql只是提供了xa事務的介面,分散式事務中的mysql例項之間是互相獨立的不感知的。 所以使用者必須自己實現分散式事務的排程器
b. xa事務有一些使用上的bug, 參考http://www.mysqlops.com/2012/02/24/mysql-xa-optimize.html
主要是
“MySQL資料庫的主備資料庫的同步,通過Binlog的複製完成。而Binlog是MySQL資料庫內部XA事務的協調者,並且MySQL資料庫為binlog做了優化——binlog不寫prepare日誌,只寫commit日誌。
所有的參與節點prepare完成,在進行xa commit前crash。crash recover如果選擇commit此事務。由於binlog在prepare階段未寫,因此主庫中看來,此分散式事務最終提交了,但是此事務的操作並未 寫到binlog中,因此也就未能成功複製到備庫,從而導致主備庫資料不一致的情況出現。
而crash recover如果選rollback,那麼就會出現全域性不一致(該分散式事務對應的節點,部分已經提交,無法回滾,而部分節點回滾。最終導致同一分散式事務,在各參與節點,最終狀態不一致)”
參考的那篇blog中給出的辦法是修改mysql程式碼,這個無法在DBScale中使用。 所以可選的替代方案是不使用
主從複製進行備份,而是直接使用xa事務實現同步寫來作為備份。
php+mysql實現分散式事務案例
保證資料表是innodb的
//db_finance庫下 CREATE TABLE `t_user_account` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',`username` varchar(255) NOT NULL DEFAULT '' COMMENT '使用者名稱',`money` int(11) NOT NULL DEFAULT '0' COMMENT '賬戶金額',PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
//db_order庫下 CREATE TABLE `t_user_orders` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',`username` varchar(255) NOT NULL DEFAULT '',`money` int(11) NOT NULL DEFAULT '0' COMMENT '訂單扣款金額',PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=44 DEFAULT CHARSET=utf8;
php程式碼
$username = '公眾號PHP開源社群';
$order_money = 100;
$addOrder_success = addOrder($username,$order_money);
$upAccount_success = updateAccount($username,$order_money);
if($addOrder_success['state'] =="yes" && $upAccount_success['state']=="yes"){
commitdb($addOrder_success['xa']);
commitdb1($upAccount_success['xa']);
}else{
rollbackdb($addOrder_success['xa']);
rollbackdb1($upAccount_success['xa']);
}
die;
function addOrder ($username,$order_money){
$xa = uniqid("");
$sql_xa = "XA START '$xa'";
$db = Yii::app()->dborder_readonly;
$db->createCommand($sql_xa)->execute();
$insert_sql = "INSERT INTO t_user_orders (`username`,`money`) VALUES ($username,$order_money)";
$id = $db->createCommand($insert_sql)->execute();
$db->createCommand("XA END '$xa'")->execute();
if ($id) {
$db->createCommand("XA PREPARE '$xa'")->execute();
return ['state' => 'yes','xa' => $xa];
}else {
return ['state' => 'no','xa' => $xa];
}
}
function updateAccount($username,$order_money){
$xa = uniqid("");
$sql_xa = "XA START '$xa'";
$db = Yii::app()->db_finance;
$db->createCommand($sql_xa)->execute();
$sql = "update t_user_account set money=money-".$order_money." where usernhttp://www.cppcns.comame='$username'";
$id = $db->createCommand($sql)->execute();
$db->createCommand("XA END '$xa'")->execute();
if ($id) {
$db->createCommand("XA PREPARE '$xa'")->execute();
return ['state' => 'yes','xa' => $xa];
}
}
//提交事務!
function commitdb($xa){
$db = Yii::app()->dborder_readonly;
return $db->createCommand("XA COMMIT '$xa'")->execute();
}
//回滾事務
function rollbackdb($xa){
$db = Yii::app()->dborder_readonly;
return $db->createCommand("XA COMMIT '$xa'")->execute();
}
//提交事務!
function commitdb1($xa){
$db = Yii::app()->db_finance;
return $db->createCommand("XA COMMIT '$xa'")->execute();
}
//回滾事務
function rollbackdb1($xa){
$db = Yii::app()->db_finance;
return $db->createCommand("XA ROLLBACK '$xa'")->execute();
到此這篇關於PHP佇列場景以及實現程式碼例項詳解的文章就介紹到這了,更多相關PHP佇列場景以及實現內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!