使用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界面:

bash 复制代码
sponge run

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

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

yaml 复制代码
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服务:

bash 复制代码
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接口相关代码。

bash 复制代码
make proto

(2) 添加连接mysql代码。

bash 复制代码
make patch TYPE=mysql-init

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

yaml 复制代码
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:

bash 复制代码
make run

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

配置和运行优惠券服务coupon

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

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

bash 复制代码
make run

配置和运行支付服务pay

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

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

bash 复制代码
make run

配置和运行订单服务order

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

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

yaml 复制代码
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代码:

bash 复制代码
make update-config

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

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

bash 复制代码
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目录,执行命令:

bash 复制代码
make copy-proto SERVER=../order

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

yaml 复制代码
grpcClient:
  - name: "order"
    host: "127.0.0.1"
    port: 8282
    registryDiscoveryType: ""
    enableLoadBalance: false

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

bash 复制代码
make proto

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

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

bash 复制代码
make run

测试分布式事务

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

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

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

测试成功提交订单场景

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

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

测试失败提交订单场景

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

在请求参数不变情况下,

json 复制代码
{
  "userId": 1,
  "productId": 1,
  "amount": 1100,
  "productCount": 1,
  "couponId": 1
}

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

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

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

json 复制代码
{
  "userId": 1,
  "productId": 1,
  "amount": 1100000,
  "productCount": 1000,
  "couponId": 0
}

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

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

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

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

json 复制代码
{
  "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等服务模块组成一个电商平台。

相关推荐
星染xr2 小时前
kafka 生产经验——数据积压(消费者如何提高吞吐量)
分布式·kafka
东方巴黎~Sunsiny2 小时前
如何监控Kafka消费者的性能指标?
分布式·kafka
飞升不如收破烂~2 小时前
kafka
分布式·kafka
龙哥·三年风水3 小时前
群控系统服务端开发模式-应用开发-前端个人信息功能
分布式·vue·群控系统
小码哥呀4 小时前
RabbitMQ集群搭建
分布式·rabbitmq
材料苦逼不会梦到计算机白富美4 小时前
golang分布式缓存项目 Day6 防止缓存击穿
分布式·缓存·golang
Yz98766 小时前
Kafka面试题
大数据·分布式·zookeeper·kafka·big data
林子漾8 小时前
【paper】分布式无人水下航行器围捕智能目标
分布式·wpf
郝同学的测开笔记9 小时前
云原生探索系列(十二):Go 语言接口详解
后端·云原生·go
阿川20159 小时前
引领企业未来数字基础架构浪潮,中国铁塔探索超大规模分布式算力
分布式·浪潮信息·中国铁塔