使用go效率工具一小时轻松搭建一个简单可靠的订单系统,使用dtm解决分布式事务超级简单

订单系统简介

订单系统是交易平台的核心系统,涉及多个方面的复杂任务,需要仔细考虑业务需求、性能、可扩展性和安全性等因素。把订单系统拆分为订单服务、库存服务、优惠券服务、支付服务等等,每个服务都有自己独立数据库。订单处理过程中必然会涉及到分布式事务,例如创建订单与扣减库存需要保证原子性,在分布式系统中,保证这些操作的原子性,会遇到不少难题需要克服,例如进程crash问题幂等问题回滚问题精准补偿问题等。

在单体服务订单系统中,使用数据库的本身支持的事务很容易解决,服务化之后必须考虑分布式系统问题,目前常见的解决分布式事务有消息队列方案状态机方案,两种解决方案都比较重,使得订单系统变得更复杂。而dtm作为另一种解决分布式事务方案,极大的简化了订单系统架构,使用dtm优雅的解决了分布式事务中的数据一致性问题。

当前端请求grpc网关服务order_gw提交订单api接口,服务端完成以下操作:

  • 订单服务order:在订单表中创建订单,订单id作为唯一键。
  • 库存服务stock:在库存表中扣减库存,如果库存不足,全局事务自动回滚。
  • 优惠券服务coupon:在优惠券表中标记优惠券已使用,如果优惠券无效,全局事务自动回滚。
  • 支付服务pay:在支付表中创建支付单,最后告诉用户跳转到支付页付款。


下面从0开始搭建一个简单的订单系统,这是按照下面步骤搭建的订单系统源码


准备工作

(1) 准备一个mysql服务,使用脚本docker-compose.yaml快速启动一个mysql服务。


(2) 把准备好的sql导入到mysql。


(3) 准备proto文件。


(4) 安装工具 sponge

安装完工具sponge后,执行命令打开生成代码的UI界面:

sponge run


(5) 启动分布式事务管理器dtm服务。

使用docker-compose.yml脚本运行一个dtm服务。

version: '3'
services:
  dtm:
    image: yedf/dtm
    container_name: dtm
    restart: always
    environment:
      STORE_DRIVER: mysql
      STORE_HOST: '192.168.3.37'
      STORE_USER: root
      STORE_PASSWORD: '123456'
      STORE_PORT: 3306
    #volumes:
    #  - /etc/localtime:/etc/localtime:ro
    #  - /etc/timezone:/etc/timezone:ro
    ports:
      - '36789:36789'
      - '36790:36790'

修改STORE_xxx相关环境变量值,然后启动dtm服务:

docker-compose up -d


快速创建订单系统相关的微服务

生成订单、库存、优惠券、支付、grpc网关5个服务代码

进入sponge的UI界面,点击左边菜单栏【Protobuf】–>【创建微服务项目】,填写参数,分别生成订单、库存、优惠券、支付服务代码。

快速创建订单服务order,如下图所示:


快速创建库存服务stock,如下图所示:


快速创建优惠券服务coupon,如下图所示:


快速创建支付服务pay,如下图所示:


快速创建grpc网关服务order_gw,点击左边菜单栏【Protobuf】–>【创建grpc网关服务】,填写参数,点击下载代码按钮即可,如下图所示:


把生成的5个服务名称分别修改为order、stock、coupon、pay、order_gw,并打开5个终端,每个服务对应一个终端。


配置和运行库存服务stock

切换到库存服务stock目录,按下面步骤操作:

(1) 生成与自动合并api接口相关代码。

make proto


(2) 添加连接mysql代码。

make patch TYPE=mysql-init


(3) 打开配置文件configs/stock.yml,修改mysql地址和账号信息,修改默认的grpc服务端口,主要是为了避免端口冲突。

mysql:
  dsn: "root:123456@(192.168.3.37:3306)/eshop_stock?parseTime=true&loc=Local&charset=utf8mb4"

grpc:
  port: 28282
  httpPort: 28283


(4) 在生成的模板代码上添加扣减库存和补偿库存的业务逻辑代码,点击查看代码internal/service/stock.go


(5) 编译和启动库存服务stock:

make run


这是根据上面步骤完成的库存服务stock源码


配置和运行优惠券服务coupon

切换到优惠券服务coupon目录,操作步骤与上面的配置和运行库存服务stock一样,除了业务逻辑代码。这是优惠券服务coupon源码

配置和填写完具体的业务逻辑代码后,编译和启动优惠券服务coupon:

make run


配置和运行支付服务pay

切换到支付服务pay目录,操作步骤与上面的配置和运行库存服务stock一样,除了业务逻辑代码。这是支付服务pay源码

配置和填写完具体的业务逻辑代码码后,编译和启动支付服务pay:

make run


配置和运行订单服务order

切换到订单服务order目录,操作步骤与上面的配置和运行库存服务stock一样,除了业务逻辑代码。这是订单服务order源码

因为提交订单时候需要把订单服务order、库存服务stock、优惠券服务coupon、支付服务pay的grpc服务地址告诉dtm服务,让dtm服务协调管理分布式事务,所以需要配置这些地址,打开配置文件configs/order.yml,添加订单相关的服务地址和dmt服务地址配置,如下所示:

grpcClient:
  - name: "order"
    host: "127.0.0.1"
    port: 8282
  - name: "coupon"
    host: "127.0.0.1"
    port: 18282
    registryDiscoveryType: ""
    enableLoadBalance: false
  - name: "stock"
    host: "127.0.0.1"
    port: 28282
    registryDiscoveryType: ""
    enableLoadBalance: false
  - name: "pay"
    host: "127.0.0.1"
    port: 38282
    registryDiscoveryType: ""
    enableLoadBalance: false

dtm:
  addr: "127.0.0.1:36790"

配置文件添加了新字段,需要更新到对应的go结构体代码:

make update-config


在生成的模板代码上添加的提交订单、创建订单、取消订单业务逻辑代码,点击查看代码internall/service/order.go


配置和填写完业务逻辑代码码后,编译和启动订单服务:

make run


配置和运行grpc网关服务order_gw

(1) 生成grpc服务连接代码。

grpc网关服务order_gw作为请求入口,因为前端是http请求,而后端是grpc服务,需要把http转为grpc请求,因此需要生成连接order服务的代码,如果有必要也可以按照同样步骤添加其他服务(stock、coupon、pay)的grpc连接代码。进入sponge的UI界面,点击左边菜单栏【Public】–>【生成grpc服务连接代码】,填写参数生成grpc服务连接代码,如下图所示:

解压代码,把internal目录移动到grpc网关服务order_gw服务目录下。


(2) 复制proto文件。

因为grpc网关服务order_gw需要知道订单服务order有哪些api接口可以调用,因此需要把订单服务order的proto文件复制过来,打开终端,切换到order_gw目录,执行命令:

make copy-proto SERVER=../order


(3) 打开配置文件configs/order_gw.yml,配置订单服务order地址。

grpcClient:
  - name: "order"
    host: "127.0.0.1"
    port: 8282
    registryDiscoveryType: ""
    enableLoadBalance: false


(4) 生成与自动合并api接口相关代码。

make proto


(5) 填写业务逻辑代码,也就是http请求转为grpc请求,这里可以直接使用已经生成的模板代码示例即可。点击查看代码internal/service/order_gw.go


配置和填写完业务逻辑代码码后,编译和启动grpc网关服务order_gw:

make run


测试分布式事务

在浏览器打开swagger界面 http://localhost:8080/apis/swagger/index.html,测试提交订单api接口。

在dtm的管理界面 http://localhost:36789 可以查看分布式事务状态和详情。

在各个服务的终端可以查看日志信息了解dtm协调调用的api接口情况。


测试成功提交订单场景

在swagger界面上,填写请求参数。

点击Execute按钮进行测试,提交订单成功,从dtm的管理界面和各个服务日志可以看到。


测试失败提交订单场景

(1) 优惠券无效造成订单失败。

在请求参数不变情况下,

{
  "userId": 1,
  "productId": 1,
  "amount": 1100,
  "productCount": 1,
  "couponId": 1
}

直接点击Execute按钮测试,虽然返回了订单id(这不表示订单成功,实际需要获取到订单成功状态再执行后面操作),从dtm的管理界面和优惠券服务coupon日志可以看到,订单状态是失败的,因为优惠券已经被使用,返回了Aborted错误,dtm收到Aborted错误信息之后,会对已经创建订单扣减库存分支事务进行补偿,保证数据最终一致。


(2) 库存不足造成订单失败。

填写请求参数,字段productCount值为1000确定大于了库存数量,把参数couponId设置为0表示不使用优惠券。

{
  "userId": 1,
  "productId": 1,
  "amount": 1100000,
  "productCount": 1000,
  "couponId": 0
}

点击Execute按钮测试,虽然返回了订单id(这不表示订单成功),从dtm的管理界面和库存服务stock日志可以看到,订单状态是失败的,因为库存不足原因,返回了Aborted错误,dtm收到Aborted错误信息之后,会对创建订单分支事务进行补偿,保证数据最终一致。

后续添加支付业务逻辑代码之后,可以测试账号余额不足导致订单失败,dtm会补偿分支事务确保数据最终一致。


测试模拟进程crash,恢复后成功提交订单场景

停止库存服务stock,然后在swagger界面填写请求参数:

{
  "userId": 1,
  "productId": 1,
  "amount": 1100,
  "productCount": 1,
  "couponId": 0
}

点击Execute按钮测试,虽然返回了订单id(这不表示订单成功),从dtm的管理界面看到订单状态是submitted状态,dtm会一直重试连接库存服务stock,重试默认是指数退避算法,可以修改为固定时间间隔重试。启动库存服务,dtm连接库存服务成功之后,接着完成后续分支事务,成功完成订单,保证数据最终一致。根据业务需求也可以做超时主动强制取消订单处理,dtm收到强制取消订单后,会对创建订单分支事务进行补偿,也保证数据最终一致。


总结

本文介绍了从0开始快速搭建一个简单的订单系统,使用sponge很容易构建微服务,使用dtm优雅的解决提交订单的分布式事务,开发一个订单系统变得很简单,让开发人员把精力用在业务开发上。

各个服务的常用服务治理功能也是具备的,例如服务注册与发现、限流、熔断、链路跟踪、监控、性能分析、资源统计、CICD等,这些功能统一在yml配置文件开启或关闭。

这不是完整的订单系统,只有一个提交订单业务逻辑,如果需要构建一个自己的订单系统,可以作为一个参考。根据上面操作步骤,很容易添加商品服务product、物流服务logistics、用户服务user等服务模块组成一个电商平台。


本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 3
yourself

dtm如果用mysql驱动,数据积累问题如何解决?

2个月前 评论
zhuyasen (楼主) 1个月前
yourself

感谢

1个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
未填写
文章
10
粉丝
3
喜欢
21
收藏
19
排名:1288
访问:3091
私信
所有博文
社区赞助商