【7】JMicro分布式事务Demo
本文先从使用者角度说明JMicro分布式事务的使用方式,下一篇将再解析JMicro分布式事务实现细节。
1. 部署图
1)图中每个结点表示一个进程实例,可以部署于同一物理机或者不同物理机,网络能联通即可;
2)图中每个进程实例可启用主备服务,图中除Api网关实例外都只画主实例;
3)ZK及Redis被全部JVM实例依赖,图中没有一一连线;
4)LogServer,Repository,Security被其他(包括其自身)全部JVM依赖,以分别提供日志,资源,安全服务;
5)此Demo我们只关注Shop,Order,Payment这3个类型服务;
6)Shop,Order,Payment可以同时存在多个实例或多个主从实例同时提供服务,此样例只使用一个作为Demo;
2. 核心接口及实现类图
1)服务方法名带Asy后缀表示异步方法,与相应不带Asy方法名实现同样功能,只有同步和异步区别;
2)3个服务实现分别部署于Shop,Order,Payment 3个类型的JVM,3者之前通过RPC远程调用交互;
3. 店铺服务实现
Shop服务实现代码GitHub路径如下:
https://github.com/mynewworldyyl/jmicro/blob/master/example/expjmicro.tx/expjmicro.tx.shop/src/main/java/cn/expjmicro/tx/shop/TxShopServiceImpl.java
其中异步方法代码如下:
1 /** 2 * 异步实现下单服务 3 * @see txType=TxConstants.TYPE_TX_DISTRIBUTED 启用分布式服务 4 * @see txPhase=TxConstants.TX_3PC 3PC事务提交方案 5 * @see txIsolation=Connection.TRANSACTION_READ_COMMITTED 事务隔离级别为读提交 6 * 7 */ 8 @Override 9 @SMethod(txType=TxConstants.TYPE_TX_DISTRIBUTED,timeout=3*60*1000, 10 txIsolation=Connection.TRANSACTION_READ_COMMITTED,txPhase=TxConstants.TX_3PC) 11 public IPromise<Resp<Boolean>> buyAsy(int goodId,int num) { 12 13 PromiseImpl<Resp<Boolean>> p = new PromiseImpl<>(); 14 15 Resp<Boolean> r = new Resp<>(Resp.CODE_FAIL,false); 16 p.setResult(r); 17 18 Good g = goodMapper.selectById(goodId); 19 if(g == null) { 20 r.setMsg("Good not found!"); 21 p.done(); 22 return p; 23 } 24 25 if(g.getUsableCnt() < num) {//判断是否还有充足库存 26 r.setMsg("Good num not egnogh!"); 27 p.done(); 28 return p; 29 } 30 31 if(LG.isLoggable(MC.LOG_INFO)) { 32 String msg = "decGoodNum cur: "+g.getUsableCnt()+" txid: " + 33 (JMicroContext.get().getLong(TxConstants.TYPE_TX_KEY, -1L)); 34 LG.log(MC.LOG_INFO, TxShopServiceImpl.class, msg); 35 logger.info(msg); 36 } 37 38 int un = goodMapper.decGoodNum(goodId, num);//扣减库存 39 if(un < 1) { 40 r.setMsg("fail to update good num!"); 41 LG.log(MC.LOG_ERROR, TxShopServiceImpl.class, "Fail to dec good num cur:" + g.getUsableCnt()); 42 p.done(); 43 return p; 44 } 45 46 Req req = new Req(); 47 req.setGoodId(goodId); 48 req.setNum(num); 49 req.setTxid(JMicroContext.get().getLong(TxConstants.TYPE_TX_KEY, -1L)); 50 reqMapper.saveReq(req); 51 52 if(LG.isLoggable(MC.LOG_INFO)) { 53 String msg = "Invoke order service takeOrderAsy txid: " + 54 (JMicroContext.get().getLong(TxConstants.TYPE_TX_KEY, -1L)); 55 LG.log(MC.LOG_INFO, TxShopServiceImpl.class, msg); 56 logger.info(msg); 57 } 58 return orderSrv.takeOrderAsy(g,num);//调用远程下单服务 59 }
SMethod注解与事务相关3个属性为txType,txPhase和txIsolation。
txType表示事务类型,默认为不启用事务,值分别为TYPE_TX_NO不支持事务,TYPE_TX_LOCAL本地事务,TYPE_TX_DISTRIBUTED分布式事务
txPhase表示事务分段,默认2PC,目前支持2PC及3PC,3PC在2PC基础上,在事务提交之前,询问事务参与者是否可以提交,而2PC只要参与者全部投票通过后就直接提交,回滚不需要3PC。
txIsolation表示事务隔离级别,与JDBC定义一致。分别有TRANSACTION_READ_UNCOMMITTED读未提交,TRANSACTION_READ_COMMITTED读提交,
TRANSACTION_REPEATABLE_READ可重复读,TRANSACTION_SERIALIZABLE序列化事务。默认为TRANSACTION_READ_COMMITTED
4. 订单服务实现
GitHub路径如下:
https://github.com/mynewworldyyl/jmicro/blob/master/example/expjmicro.tx/expjmicro.tx.order/src/main/java/cn/expjmicro/tx/order/TxOrderServiceImpl.java
其中异步方法代码如下:
1 /** 2 * 异步下单服务, 被商店服务调用 3 * TxConstants.TYPE_TX_DISTRIBUTED 启用分布式服务 4 */ 5 @Override 6 @SMethod(txType=TxConstants.TYPE_TX_DISTRIBUTED) 7 public IPromise<Resp<Boolean>> takeOrderAsy(Good g,int num) { 8 9 Order o = new Order(); 10 o.setGoodId(g.getId()); 11 o.setNum(num); 12 o.setAmount(o.getNum()*g.getPrice()); 13 o.setId(idServer.getLongId(Order.class)); 14 o.setTxid(JMicroContext.get().getLong(TxConstants.TYPE_TX_KEY, -1L)); 15 16 LG.log(MC.LOG_INFO, this.getClass(), "Save order"); 17 //保存订单 18 om.saveOrder(o); 19 20 Payment p = new Payment(); 21 p.setId(idServer.getLongId(Order.class)); 22 p.setAmount(o.getAmount()); 23 p.setOrderId(o.getId()); 24 p.setTxid(o.getTxid()); 25 26 LG.log(MC.LOG_INFO, this.getClass(), "Before invoke pay service"); 27 28 //调用支付服务 29 return paymentSrv.payAsy(p); 30 }
5. 支付服务实现
GitHub路径如下:
https://github.com/mynewworldyyl/jmicro/blob/master/example/expjmicro.tx/expjmicro.tx.payment/src/main/java/cn/expjmicro/tx/payment/TxPaymentServiceImpl.java
其中异步方法代码如下:
/** * 异步实现支付服务 */ @Override @SMethod(txType=TxConstants.TYPE_TX_DISTRIBUTED) public IPromise<Resp<Boolean>> payAsy(Payment p) { Resp<Boolean> r = pay(p); PromiseImpl<Resp<Boolean>> pr = new PromiseImpl<>(); pr.setResult(r); pr.done(); return pr; }
6. 运行
1)确保下载完整代码
https://github.com/mynewworldyyl/jmicro
2)构建
mvn clean install -Dmaven.test.skip=true
3)Eclipse工具运行项目配置,运行全部项目此配置都相同,后面不再一一列举
VM参数配置javaagent,运行全部项目此配置都相同,后面不再一一列举
-javaagent:D:\opensource\github\jmicro\agent\target\jmicro.agent-0.0.2-SNAPSHOT.jar
4)运行Security配置
对应文本
-DsysLogLevel=3 -DclientId=0 -DadminClientId=0 -Dlog4j.configuration=../../log4j.xml -Dpwd=0 -DpriKeyPwd=私钥密码 -DinstanceName=security
5)运行MNG管理后台后
参数文本
-DsysLogLevel=4 -DclientId=0 -DadminClientId=0 -DpriKeyPwd=私钥密码 -D/mongodb/username=MongoDB用户名 -D/mongodb/password=MONGODB密码 -Dlog4j.configuration=../../log4j.xml -Dpwd=0
6)运行API网关
程序参数(Program arguments)
-DinstanceName=apigateway -DexportHttpIP=192.168.56.1 -DexportSocketIP=192.168.56.1 -DlistenHttpIP=0.0.0.0 -DlistenSocketIP=0.0.0.0 -D/NettySocketServer/nettyPort=9092 -D/nettyHttpPort=9090 -DapiGatewayExportHttpIP=0.0.0.0 -DinitGatewayAndMng=false -D/StaticResourceHttpHandler/staticResourceRoot_mng=D:/opensource/github/jmicro/mng.web/public -DclientId=0 -DadminClientId=0 -DsysLogLevel=5 -DpriKeyPwd=API网关私钥 -Dlog4j.configuration=../../log4j.xml -Dpwd=0
exportHttpIP:外网要访问API网关的HTTP IP地址,因为我们测试环境,只需要一个IP即可;
exportSocketIP:外网要访问API网关的SOCKET地址,因为我们测试环境,只需要一个IP即可;
/NettySocketServer/nettyPort:Socket端口
/nettyHttpPort: HTTP端口
/StaticResourceHttpHandler/staticResourceRoot_mng:后台管理页面地址,本地测试可以直接NPM启动页面
priKeyPwd:API网关SSL私钥密码,参考别的章节说明相关配置方式
7)日志监控服务
-DsysLogLevel=4 -DclientId=0 -DadminClientId=0 -Dlog4j.configuration=../../log4j.xml -D/mongodb/username=MongoDB用户名 -D/mongodb/password=MONGODB密码 -Dpwd=0
8)事务协调器
-DsysLogLevel=3 -DclientId=0 -DadminClientId=0 -Dlog4j.configuration=../../log4j.xml -Dpwd=0
9)店铺服务
-DsysLogLevel=4 -DclientId=25500 -DadminClientId=0 -Dlog4j.configuration=../../../log4j.xml -Dpwd=1
此时clientId=25500表示以普通账号启动用户级服务,clientId=0表示以高级账号启动系统级的服务,两种服务资源隔离
10) 订单服务
-DsysLogLevel=4 -DclientId=25500 -DadminClientId=0 -Dlog4j.configuration=../../../log4j.xml -Dpwd=1
11) 支付服务
-DsysLogLevel=4 -DclientId=25500 -DadminClientId=0 -Dlog4j.configuration=../../../log4j.xml -Dpwd=1
12)启动管理后台页面
命令行进入D:\opensource\github\jmicro\mng.web目录 ,运行
npm run serve
13) 浏览器打开
http://localhost:8081/,如下图
在此页面调用一下Shop服务的buyAsy方法,并查看事务日志
选择主菜单“监控/服务”,弹出如下侧栏服务列表,并按红框选择buyAsy方法
编辑区往下拖,看到到如下卡片
在Testing Args输入框输入[1,1],表示buyASy方法两个参数值,第一个是商品ID,第二个是购买数量
点“Start”,将发起服务方法的调用,在Testting Result输出[object Object]
14)查看调用日志
选择主菜单“监控/监听日志”打开日志视图,如下图
在打开的视图将鼠标移到最左侧,自动显示查询条件输入框,输入如下红框内容,并点“Query”,如下图
在日志视图看到事务相关日志,注意,最前面日志最新,如要看开始事务日志,要滚动到视图最下面,如下图
提交事图日志
15)链路日志
现在想看看刚发起的购买动作相关的整个调用链路日志,而不是离散的日志,选择主菜单“监控/调用链”,如下图
视图区将打开如下图
最左边为每个RPC调用的唯一标识,每个调用花费时间。点最右边的“Detail”按钮,可以查看更详细的信息,如下图
基于JMicro开发的服务除了可以独立运行JVM外,还可以托管到jmicro.cn平台,实现自动化部署和运维,避免传统购买云服务及日常运维的高成本开销。
Github:https://github.com/mynewworldyyl/jmicro
Demo: http://jmicro.cn/
作者:mynewworldyyl
邮箱:mynewworldyyl@gmail.com