PHP队列场景以及实现代码实例详解


Posted in PHP onFebruary 26, 2021

为了降低单点压力,通常会根据业务情况进行分表分库,将表分布在不同的库中(库可能分布在不同的机器上),但是一个业务场景可能会同时处理两个表的操作。在这种场景下,事务的提交会变得相对复杂,因为多个节点(库)的存在,可能存在部分节点提交失败的情况,即事务的ACID特性需要在各个不同的数据库实例中保证。比如更新db1库的A表时,必须同步更新db2库的B表,两个更新形成一个事务,要么都成功,要么都失败。

那么我们如何利用mysql实现分布式数据库的事务呢?

mysql是从5.0开始支持分布式事务

这里先声明两个概念:

资源管理器(resource manager):用来管理系统资源,是通向事务资源的途径。数据库就是一种资源管理器。资源管理还应该具有管理事务提交或回滚的能力。
事务管理器(transaction manager):事务管理器是分布式事务的核心管理者。事务管理器与每个资源管理器(resource
manager)进行通信,协调并完成事务的处理。事务的各个分支由唯一命名进行标识。
mysql在执行分布式事务(外部XA)的时候,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' 是用户给的,全局唯一在一台mysql中开启一个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 transaction 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 commitxa 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 username='$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];
  }else {
    return ['state' => 'no', '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队列场景以及实现内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

PHP 相关文章推荐
PHP4之真OO
Oct 09 PHP
高亮度显示php源代码
Oct 09 PHP
PHP 检查扩展库或函数是否可用的代码
Apr 06 PHP
PHP中对于浮点型的数据需要用不同的方法解决
Mar 11 PHP
php限制文件下载速度的代码
Oct 20 PHP
PHP中抽象类和抽象方法概念与用法分析
May 24 PHP
完美解决Thinkphp3.2中插入相同数据的问题
Aug 01 PHP
PHP receiveMail实现收邮件功能
Apr 25 PHP
PHP5.5基于mysqli连接MySQL数据库和读取数据操作实例详解
Feb 16 PHP
php抽象类和接口知识点整理总结
Aug 02 PHP
php和js实现根据子网掩码和ip计算子网功能示例
Nov 09 PHP
PHP 裁剪图片
Mar 09 PHP
PHP实现长轮询消息实时推送功能代码实例讲解
Feb 26 #PHP
php的对象传值与引用传值代码实例讲解
Feb 26 #PHP
php并发加锁问题分析与设计代码实例讲解
Feb 26 #PHP
PHP内存溢出优化代码详解
Feb 26 #PHP
php自动加载代码实例详解
Feb 26 #PHP
PHP的重载使用魔术方法代码实例详解
Feb 26 #PHP
PHP解密支付宝小程序的加密数据、手机号的示例代码
Feb 26 #PHP
You might like
re0第二季蕾姆被制作组打入冷宫!艾米莉亚女主扶正,原因唏嘘
2020/04/02 日漫
php学习 函数 课件
2008/06/15 PHP
PHP 彩色文字实现代码
2009/06/29 PHP
ajax php传递和接收变量实现思路及代码
2012/12/19 PHP
PHP使用strrev翻转中文乱码问题的解决方法
2017/01/13 PHP
php微信公众号开发之快递查询
2018/10/20 PHP
PHP的mysqli_stmt_init()函数讲解
2019/01/24 PHP
一个分享按钮的插件使用介绍(可扩展,内附开发制作流程)
2011/09/19 Javascript
JS 屏蔽键盘不可用与鼠标右键不可用的方法
2013/11/18 Javascript
深入学习AngularJS中数据的双向绑定机制
2016/03/04 Javascript
微信JS接口大全
2016/08/25 Javascript
js 获取本地文件及目录的方法(推荐)
2016/11/10 Javascript
使用jquery模拟a标签的click事件无法实现跳转的解决
2018/12/04 jQuery
jQuery+Ajax+js实现请求json格式数据并渲染到html页面操作示例
2020/06/02 jQuery
利用Vue的v-for和v-bind实现列表颜色切换
2020/07/17 Javascript
解决idea开发遇到javascript动态添加html元素时中文乱码的问题
2020/09/29 Javascript
python中的代码编码格式转换问题
2015/06/10 Python
Python使用爬虫猜密码
2016/02/19 Python
python实现决策树、随机森林的简单原理
2018/03/26 Python
对pandas进行数据预处理的实例讲解
2018/04/20 Python
在Qt5和PyQt5中设置支持高分辨率屏幕自适应的方法
2019/06/18 Python
python logging模块的使用总结
2019/07/09 Python
Python通过正则库爬取淘宝商品信息代码实例
2020/03/02 Python
Python多线程threading join和守护线程setDeamon原理详解
2020/03/18 Python
如何基于Django实现上下文章跳转
2020/09/16 Python
python 如何用urllib与服务端交互(发送和接收数据)
2021/03/04 Python
Monnier Frères美国官网:法国知名奢侈品网站
2016/11/22 全球购物
个人实用的自我评价范文
2013/11/23 职场文书
保安拾金不昧表扬信
2014/01/15 职场文书
工程开工庆典邀请函
2014/02/01 职场文书
广告传媒专业应届生求职信
2014/03/01 职场文书
英文推荐信格式范文
2014/05/09 职场文书
一小时学会TensorFlow2之基本操作2实例代码
2021/09/04 Python
Elasticsearch 索引操作和增删改查
2022/04/19 Python
Win10多屏显示如何设置?Win10电脑多屏显示设置操作方法
2022/07/07 数码科技
react中useState使用:如何实现在当前表格直接更改数据
2022/08/05 Javascript