(四)从0开始写框架—V1.0说明文档

概述

  通过前几章的技术铺垫和思路设计,我们最终把该分布式事务框架实现出来了,但由于以文章的形式很难描述整个写代码的过程,所以我们采取的方式是:先把框架写完,然后大家下载源码,结合说明文档一起阅读的方式了解整个框架。该框架的GitHub地址为:

https://github.com/luohaipeng/rmdt

  该框架取名为:RMDT,意思其实是:可靠消息分布式事务(Reliable Message Distributed Transaction)的英文首字母简写。

技术选型

开发工具

  • IDEA
  • Maven
  • Git

框架

  • SpringBoot
  • Dubbo
  • Mybatis

中间件

  • MySQL
  • Zookeeper
  • ActiveMQ

其他工具

  • Lombok
  • Disruptor

补充说明
  对于技术选型有几点需要说明的,第一点是:该框架直接使用Springboot构建,简化了我们构建项目和开发过程。同时使用了Spring IOC,并没有自己实现IOC,虽然这样会对Spring框架强依赖,但是我们要关注的核心是分布式事务,而不是IOC。
  第二点是:我们知道,市面上流行的分布式应用框架有很多,比如Dubbo、SpringCloud、Motan等,不同的框架有不同的实现细节,要让我们这个分布式事务框架支持市面上流行的分布式应用框架,那就必须做很多对应的适配工作,而我们时间有限,第一个版本先支持Dubbo,后续有时间再做扩展,或者大家可以贡献适配的代码。
  第三点是:Mybatis和Zookeeper并不是框架本身使用的,而是Demo项目使用的,框架本身操作关系型数据库没有依赖任何第三方ORM框架,而是直接使用JDBC操作。至于Zookeeper,就是Demo项目使用Dubbo搭建的一个分布式应用,服务的发现和注册使用Zookeeper中间件。
  第四点是:框架本身需要MySQL和ActiveMQ中间件支持,MySQL用于存储事务日志数据,ActiveMQ用于发送事务消息,但是框架内部并没有使用硬编码的方式集成这两个中间件,而是支持动态扩展,换句话说就是:存储事务日志的中间件可以通过配置的方式,切换为任意的存储技术,比如可以切换为Redis、MangoDB等。同理,消息中间件也是可以通过配置切换为常用的RabbitMQ、RocketMQ、Kafka等。具体如何配置看下文。

模块说明

  整个项目包含以下几个子模块:rmdt-core,rmdt-common, rmdt-annotation,rmdt-dubbo,rmdt-demo。

项目模块

我们来看看每个模块的作用:

  • rmdt-core
    顾名思义,它就是存放核心代码的模块,整个项目所有处理流程和逻辑类都放到这里。
  • rmdt-common
    存放一些各个子模块有可能会用到的公共文件,公共类和公共的配置。
  • rmdt-annotation
    存放注解的模块,我们之前说了,要让别人用起来简单,最好贴个注解就能有对应的功能,所以我们涉及到的注解的定义都放到这里。但实际上,整个项目肯定不可能有很多注解,把这为数不多的注解放到一个模块中,最主要的目的是,别人在使用框架时,可以导入最少依赖。什么意思呢?举个例子:如果是使用Dubbo作为分布式框架,那么肯定会有API这样的子项目,API项目中放的是对外提供服务的接口类,那就有可能我们的注解就需要贴在这些接口类的抽象方法上,而这种API项目是不会有具体实现逻辑的,所以它能用的上的就只有注解,不会使用我们的core、common,这些模块中的类,那这样的话,这个API项目就只导入annotation这个模块的依赖就行了。所以,这就是为什么几个注解类,也要单独放一个模块。
  • rmdt-dubbo
    我们在之前的设计稿中可以知道,调用远程RPC方法前,需要给RPC地址添加参数,但是每种具体的分布式应用框架传参都是不一样的,比如Dubbo和SpringCloud就有很大的区别,SpringCloud相对来说要简单很多,因为它就是一个RESTful资源路径而已,往该资源路径再追加一个参数很简单,而如果大家对Dubbo的源码有了解的话,就知道往RPC地址加参数,需要做比较多的事情。所以,为了后面框架的扩展,每一种分布式应用框架的支持,都单独创建一个模块,比如,现在我们框架需要支持Dubbo,那么就创建一个rmdt-dubbo的模块,用户存放处理远程方法调用和参数传递的类和文件。同理,如果对框架扩展,让它也能支持SpringCloud,就需要再创建一个rmdt-springcloud的模块。
  • rmdt-demo
    该模块主要是用于方便测试功能的,其实就是我们经常说的业务项目了,它不是框架的一部分,但为了能让自己开发方便,和别人测试使用方便,就把demo放到了框架中。其中,我们可以看到,rmdt-demo模块下多了7个子模块,对Dubbo项目比较熟悉的小伙伴,应该不用过多的解析了,这7个模块都是业务类的项目,他们分别为:
  • rmdt-demo-client
    作为服务消费者项目,也就是我们说的客户端。
  • rmdt-demo-goods-api和rmdt-demo-goods-server
    共同构成商品系统项目,其中api为对外提供的服务接口,server为具体的服务实现。
  • rmdt-demo-member-api和rmdt-demo-member-server
    共同构成会员系统项目,其中api为对外提供的服务接口,server为具体的服务实现。
  • rmdt-demo-order-api和rmdt-demo-order-server
    共同构成订单系统项目,其中api为对外提供的服务接口,server为具体的服务实现。

功能演示说明

  我们可以运行框架中的demo项目,了解框架的功能效果。但在运行前,先简单解析一下这个demo项目的业务(注意:demo只是模拟业务需求,并发真实逻辑):客户端发起RPC请求,调用远程订单系统中的付款方法makePayment。在makePayment方法中有两个操作:分别是发送两个RPC请求,调用远程会员系统的付款方法payment,和远程商品系统的扣库存方法decrease,在这个模拟的业务需求中,我们可以测试出分布式事务的问题,因为payment和decrease分别做付款和扣库存的操作,这两个操作是同一个事务的,要么两个都成功,要么两个都失败,但是由于现在的架构是分布式应用,他们各自都运行在自己的JVM中,这就不能确保事务一致性了。而使用了RMDT框架后,就能确保分布式事务的一致性了,实现原理在之前的分析文章中已经讲过了,这里就不再提及。那么接下来,我们来启动demo项目。

  • 1、导入代码
    把GitHub上的该项目clone下来,导入到IDEA或者Eclipse(推荐使用IDEA),稍等片刻,让开发工具把环境build好。
  • 2、导入测试数据
    项目clone下来后,在rmdt-demo模块中下有3个SQL文件:

    在自己本地MySQL服务创建3个数据库rmdt-demo-goods,rmdt-demo-member,rmdt-demo-order,分别是给商品系统,会员系统和订单系统使用,创建好这3个数据库后,把这3个SQL文件分别导入到以上3个数据库中:
  • 3、启动zookeeper
    由于使用到了zookeeper作为服务发现和注册中心,而配置文件中,连接的zookeeper地址是本地,所以需要在自己本地电脑启动zookeeper服务
  • 4、启动ActiveMQ
    ActiveMQ用于发送事务消息,给框架做事务补偿提供保障。在配置文件中,也是连接本地ActiveMQ服务,所以需要在本地启动一个ActiveMQ。
  • 5、运行springboot程序
    分别把demo项目的4个springboot应用启动起来(直接运行main方法即可):
    这个4个程序分别是rmdt-demo-client模块下的ClientApp类、rmdt-demo-goods-server模块下的GoodsApp类、rmdt-demo-member-server模块下的MemberApp类、rmdt-demo-order-server模块下的OrderApp类:
  • 6、发送请求
    使用postman测试demo项目模拟的业务方法。postman访问client应用中的/api/orders资源路径,设置的参数为count和price,也就是购买数量和商品价格,点击send按钮,发送请求:
  • 7、验证正常情况下的结果
    可以看到,请求成功,并且我们查看rmdt-demo-goods,rmdt-demo-member,rmdt-demo-order这3个数据库中的数据是有改变的,order_info表增加了一条订单,user_account表中的balance字段扣了1,inventory表中的total_inventory字段减了1。
  • 8、验证错误情况下的结果
    到这里,我们就把demo项目启动起来,并且可以正常执行模拟的业务流程了,以上执行的情况是没有出错的情况,那现在我们来演示其中一个远程服务出错的情况:把商品系统服务停掉,然后再执行postman的send请求,此时我们再查看数据,订单生成了,并且用户余额也扣了,而我们模拟商品系统宕机,导致库存扣减失败,这时就出现了数据不一致的情况了:
  • 9、事务最终一致性
    通过第8步,我们已经模拟出分布式事务问题出来了,而要让改demo项目数据最终一致性很简单,只需要重新启动商品系统即可:

    注:这里只演示了某个服务宕机的情况。如果是服务没有宕机,但是执行业务方法过程中,由于各种原因导致抛出异常,导致数据不一致的情况,框架也是支持数据恢复一致性的,大家可以自行演示

使用说明

  功能效果刚刚已经看到了,那接下来看看如何使用。RMDT框架使用起来比较简单,我们还是通过内置的demo项目来看看框架的使用。

  • 1、导入框架依赖
    在需要使用RMDT框架的项目导入框架的相关依赖,如Demo项目:rmdt-demo-member-server、rmdt-demo-goods-server、rmdt-demo-order-server这3个模块需要导入rmdt-dubbo依赖。rmdt-demo-member-api、rmdt-demo-goods-api、rmdt-demo-order-api这3个模块需要导入rmdt-annotation依赖。如下图:

    由于api项目只用到注解,不需要使用具体功能逻辑,所以api项目只需要导入存放注解的rmdt-annotation依赖即可。这里以rmdt-demo-order-server和rmdt-demo-order-api模块举例,其他模块同理。
  • 2、配置框架扫描的包
    在各个服务提供者系统中的SpringBoot主启动类,添加框架包的扫描范围:

    需要扫描的包路径如有多个则用英文逗号隔开。这里以订单系统举例,其他服务系统同理。
  • 3、配置初始化参数
    在各个服务提供者系统中,创建Spring的Configuration配置对象,配置框架初始化参数:

    这里以订单系统举例,其他服务系统同理。
  • 4、贴上分布式事务注解
    在需要开启分布式事务的业务方法上,贴上@Rmdt注解,抽象方法和实现方法都需要贴:

    通过上图可以发现,订单系统的makePayment方法上的@Rmdt注解是没有destination属性的,那是由于destination属性是用于发送事务消息的,订单系统的makePayment是事务发起者,不需要发送事务消息,所以不需要配置destination属性。
  • 5、引用远程服务
    在Dubbo的@Reference注解中,添加一个proxy属性,属性值为"rmdtProxyFactory",意思为使用RMDT框架自定义的代理工厂:

    但这里需要注意的是:业务方法开启了分布式事务处理,才需要配置proxy = "rmdtProxyFactory",否则不用配置还是使用Dubbo默认的代理工厂即可。

扩展说明

  框架中的事务日志存储组件使用的是JDBC操作mysql,而MQ消息组件使用的是ActiveMQ,但框架中并没有硬编码的写死这些实现,而是以SPI的方式动态扩展,尽量做到黑盒扩展,而不是白盒修改。具体看以下扩展说明:

事务日志存储组件扩展

1、创建一个配置Bean类,继承框架的BaseRepositoryConfig,给该类添加存储技术需要的属性,比如连接地址,用户名、密码和连接池大小等。
2、创建一个类,实现事务日志存储组件SPI(RmdtTransactionRepository),该实现类需要自己写代码,实现数存储。
3、然后在resources/META-INF目录下,创建services子目录,在services目录中新建名为org.rmdt.core.repository.RmdtTransactionRepository的文本文件。
4、该文件内容为实现了RmdtTransactionRepository接口的类全限定名。
5、在初始化配置类RmdtConfig中,设置repositoryName的值为实现类中,与getRepositoryName方法返回值相同的字符串。并且new出自定义的BaseRepositoryConfig子类配置对象,设置好RmdtTransactionRepository实现类需要的相关属性。
具体操作如下:

MQ消息组件扩展

MQ消息组件扩展的方式与事务日志存储组件扩展的方式是一样的,这里就不详细说明了,类比学习即可。

架构说明

总体执行流程

流程图说明:

  • 图中左边淡蓝色区域为事务发起者执行流程中涉及到的接口和类,右边淡绿色区域为事务参与者执行流程中涉及到的接口和类,而中轴虚线上的接口和类是双方都需要的。
  • 图中从上到下共分成9层,分别对应着Rmdt框架的7组件。其中Interceptor DubboProxy和Interceptor AOP同属Interceptor组件,Business不属于框架组件,而是框架使用者的业务类。
  • 图中绿色小方块代表接口,蓝色小方块代表实现类,其中RmdtTransactionRepository和RmdtTransactionMessage为SPI,其他的都是API。
  • 图中从黄色圆圈开始,蓝色虚线连接的地方是框架初始化时的操作,即启动时组装链,并且如果分布式应用框架使用的是Dubbo,那么Dubbo框架还有它自己的初始化操作,这部分在左上角蓝色虚线粗略描绘,详细的流程可以看Dubbo的源码解析文档。黑色实线为方法同步调用过程,深红色虚线为方法异步调用过程,即运行时调用链。线上的文字为调用的方法名。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • 今年的冬天显得特别迷人,穿上厚厚的衣服,戴上粉色的帽子,想要去户外找寻呼吸的自由。 这个年过了还没有多久,整个人都...
    卜嫼妞阅读 241评论 0 0
  • https://www.cnblogs.com/yaosutu/p/7352642.htmllib里面需要cani...
    胡儒清阅读 247评论 0 0
  • 大声的唱着歌 大踏步的向前去 去爱,去恨,去享受,去生活 去拥抱生活的荆棘 去享受生活的忧伤 带着伤痕 带着成长 ...
    jscfc阅读 77评论 0 0