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

zhufuyi · · 1059 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

### 订单系统简介 订单系统是交易平台的核心系统,涉及多个方面的复杂任务,需要仔细考虑业务需求、性能、可扩展性和安全性等因素。把订单系统拆分为订单服务、库存服务、优惠券服务、支付服务等等,每个服务都有自己独立数据库。订单处理过程中必然会涉及到分布式事务,例如创建订单与扣减库存需要保证原子性,在分布式系统中,保证这些操作的原子性,会遇到不少难题需要克服,例如`进程crash问题`、`幂等问题`、`回滚问题`、`精准补偿问题`等。 在单体服务订单系统中,使用数据库的本身支持的事务很容易解决,服务化之后必须考虑分布式系统问题,目前常见的解决分布式事务有`消息队列方案`和`状态机方案`,两种解决方案都比较重,使得订单系统变得更复杂。而[dtm](https://github.com/dtm-labs/dtm)作为另一种解决分布式事务方案,极大的简化了订单系统架构,使用dtm优雅的解决了分布式事务中的数据一致性问题。 ![order-system.png](https://static.golangjob.cn/231205/203cafb0c9e8b19aca46e7c0481424f5.png) 当前端请求**grpc网关服务order_gw**提交订单api接口,服务端完成以下操作: - **订单服务order**:在订单表中创建订单,订单id作为唯一键。 - **库存服务stock**:在库存表中扣减库存,如果库存不足,全局事务自动回滚。 - **优惠券服务coupon**:在优惠券表中标记优惠券已使用,如果优惠券无效,全局事务自动回滚。 - **支付服务pay**:在支付表中创建支付单,最后告诉用户跳转到支付页付款。 <br> 下面从0开始搭建一个简单的订单系统,这是按照下面步骤搭建的[订单系统源码](https://github.com/zhufuyi/sponge_examples/tree/main/9_order-grpc-distributed-transaction)。 <br> ### 准备工作 (1) 准备一个mysql服务,使用脚本[docker-compose.yaml](https://github.com/zhufuyi/sponge/blob/main/test/server/mysql/docker-compose.yaml)快速启动一个mysql服务。 <br> (2) 把准备好的sql导入到mysql。 - dtm相关sql - [dtmcli.barrier.mysql.sql](https://github.com/dtm-labs/dtm/blob/main/sqls/dtmcli.barrier.mysql.sql) - [dtmsvr.storage.mysql.sql](https://github.com/dtm-labs/dtm/blob/main/sqls/dtmsvr.storage.mysql.sql) - 订单相关sql - [order.sql](https://github.com/zhufuyi/sponge_examples/tree/main/9_order-grpc-distributed-transaction/order/test/sql/order.sql) - [stock.sql](https://github.com/zhufuyi/sponge_examples/tree/main/9_order-grpc-distributed-transaction/stock/test/sql/stock.sql) - [coupon.sql](https://github.com/zhufuyi/sponge_examples/tree/main/9_order-grpc-distributed-transaction/coupon/test/sql/coupon.sql) - [pay.sql](https://github.com/zhufuyi/sponge_examples/tree/main/9_order-grpc-distributed-transaction/pay/test/sql/pay.sql) <br> (3) 准备proto文件。 - [order.proto](https://github.com/zhufuyi/sponge_examples/tree/main/9_order-grpc-distributed-transaction/order/api/order/v1/order.proto) 用来创建订单服务order。 - [stock.proto](https://github.com/zhufuyi/sponge_examples/tree/main/9_order-grpc-distributed-transaction/stock/api/stock/v1/stock.proto) 用来创建库存服务stock。 - [coupon.proto](https://github.com/zhufuyi/sponge_examples/tree/main/9_order-grpc-distributed-transaction/coupon/api/coupon/v1/coupon.proto) 用来创建优惠券服务coupon。 - [pay.proto](https://github.com/zhufuyi/sponge_examples/tree/main/9_order-grpc-distributed-transaction/pay/api/pay/v1/pay.proto) 用来创建支付服务pay。 - [order_gw.proto](https://github.com/zhufuyi/sponge_examples/tree/main/9_order-grpc-distributed-transaction/order_gw/api/order_gw/v1/order_gw.proto) 用来创建grpc网关服务order_gw。 <br> (4) 安装工具 [sponge](https://github.com/zhufuyi/sponge/blob/main/assets/install-cn.md)。 安装完工具sponge后,执行命令打开生成代码的UI界面: ```bash sponge run ``` <br> (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 ``` <br> ### 快速创建订单系统相关的微服务 #### 生成订单、库存、优惠券、支付、grpc网关5个服务代码 进入sponge的UI界面,点击左边菜单栏【Protobuf】-->【创建微服务项目】,填写参数,分别生成订单、库存、优惠券、支付服务代码。 快速创建订单服务order,如下图所示: ![order-grpc-pb-order.png](https://static.golangjob.cn/231205/28023dd147d5945b91ad0ca2e0c2dc55.png) <br> 快速创建库存服务stock,如下图所示: ![order-grpc-pb-stock.png](https://static.golangjob.cn/231205/e13901e23f2c6c3c4372bded5a44f8f1.png) <br> 快速创建优惠券服务coupon,如下图所示: ![order-grpc-pb-coupon.png](https://static.golangjob.cn/231205/6617614849212712723b097af84e9f9d.png) <br> 快速创建支付服务pay,如下图所示: ![order-grpc-pb-pay.png](https://static.golangjob.cn/231205/d9134a47f45299fd8d75f457696dfca2.png) <br> 快速创建grpc网关服务order_gw,点击左边菜单栏【Protobuf】-->【创建grpc网关服务】,填写参数,点击下载代码按钮即可,如下图所示: ![order-http-pb-order-gw.png](https://static.golangjob.cn/231205/4cfcc5f1b68535bc23f8c9e41c1901b6.png) <br> 把生成的5个服务名称分别修改为order、stock、coupon、pay、order_gw,并打开5个终端,每个服务对应一个终端。 <br> #### 配置和运行库存服务stock 切换到库存服务stock目录,按下面步骤操作: (1) 生成与自动合并api接口相关代码。 ```bash make proto ``` <br> (2) 添加连接mysql代码。 ```bash make patch TYPE=mysql-init ``` <br> (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 ``` <br> (4) 在生成的模板代码上添加扣减库存和补偿库存的业务逻辑代码,点击查看代码[internal/service/stock.go](https://github.com/zhufuyi/sponge_examples/blob/main/9_order-grpc-distributed-transaction/stock/internal/service/stock.go)。 <br> (5) 编译和启动库存服务stock: ```bash make run ``` <br> 这是根据上面步骤完成的[库存服务stock源码](https://github.com/zhufuyi/sponge_examples/tree/main/9_order-grpc-distributed-transaction/stock)。 <br> #### 配置和运行优惠券服务coupon 切换到优惠券服务coupon目录,操作步骤与上面的**配置和运行库存服务stock**一样,除了业务逻辑代码。这是[优惠券服务coupon源码](https://github.com/zhufuyi/sponge_examples/tree/main/9_order-grpc-distributed-transaction/coupon)。 配置和填写完具体的业务逻辑代码后,编译和启动优惠券服务coupon: ```bash make run ``` <br> #### 配置和运行支付服务pay 切换到支付服务pay目录,操作步骤与上面的**配置和运行库存服务stock**一样,除了业务逻辑代码。这是[支付服务pay源码](https://github.com/zhufuyi/sponge_examples/tree/main/9_order-grpc-distributed-transaction/pay)。 配置和填写完具体的业务逻辑代码码后,编译和启动支付服务pay: ```bash make run ``` <br> #### 配置和运行订单服务order 切换到订单服务order目录,操作步骤与上面的**配置和运行库存服务stock**一样,除了业务逻辑代码。这是[订单服务order源码](https://github.com/zhufuyi/sponge_examples/tree/main/9_order-grpc-distributed-transaction/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 ``` <br> 在生成的模板代码上添加的提交订单、创建订单、取消订单业务逻辑代码,点击查看代码[internall/service/order.go](https://github.com/zhufuyi/sponge_examples/blob/main/9_order-grpc-distributed-transaction/order/internal/service/order.go)。 <br> 配置和填写完业务逻辑代码码后,编译和启动订单服务: ```bash make run ``` <br> #### 配置和运行grpc网关服务order_gw (1) 生成grpc服务连接代码。 grpc网关服务order_gw作为请求入口,因为前端是http请求,而后端是grpc服务,需要把http转为grpc请求,因此需要生成连接order服务的代码,如果有必要也可以按照同样步骤添加其他服务(stock、coupon、pay)的grpc连接代码。进入sponge的UI界面,点击左边菜单栏【Public】-->【生成grpc服务连接代码】,填写参数生成grpc服务连接代码,如下图所示: ![order-grpc-conn.png](https://static.golangjob.cn/231205/2a187726ea3913d94e7ca9a22a655ca5.png) 解压代码,把internal目录移动到grpc网关服务order_gw服务目录下。 <br> (2) 复制proto文件。 因为grpc网关服务order_gw需要知道订单服务order有哪些api接口可以调用,因此需要把订单服务order的proto文件复制过来,打开终端,切换到order_gw目录,执行命令: ```bash make copy-proto SERVER=../order ``` <br> (3) 打开配置文件`configs/order_gw.yml`,配置订单服务order地址。 ```yaml grpcClient: - name: "order" host: "127.0.0.1" port: 8282 registryDiscoveryType: "" enableLoadBalance: false ``` <br> (4) 生成与自动合并api接口相关代码。 ```bash make proto ``` <br> (5) 填写业务逻辑代码,也就是http请求转为grpc请求,这里可以直接使用已经生成的模板代码示例即可。点击查看代码[internal/service/order_gw.go](https://github.com/zhufuyi/sponge_examples/blob/main/9_order-grpc-distributed-transaction/order_gw/internal/service/order_gw.go)。 <br> 配置和填写完业务逻辑代码码后,编译和启动grpc网关服务order_gw: ```bash make run ``` <br> ### 测试分布式事务 在浏览器打开swagger界面 `http://localhost:8080/apis/swagger/index.html`,测试提交订单api接口。 在dtm的管理界面 `http://localhost:36789` 可以查看分布式事务状态和详情。 在各个服务的终端可以查看日志信息了解dtm协调调用的api接口情况。 <br> #### 测试成功提交订单场景 在swagger界面上,填写请求参数。 ![order-http-pb-order-gw-swagger.png](https://static.golangjob.cn/231205/39b4ff691251eff5eac6e8ec31221349.png) 点击Execute按钮进行测试,提交订单成功,从dtm的管理界面和各个服务日志可以看到。 <br> #### 测试失败提交订单场景 (1) 优惠券无效造成订单失败。 在请求参数不变情况下, ```json { "userId": 1, "productId": 1, "amount": 1100, "productCount": 1, "couponId": 1 } ``` 直接点击Execute按钮测试,虽然返回了订单id(这不表示订单成功,实际需要获取到订单成功状态再执行后面操作),从dtm的管理界面和优惠券服务coupon日志可以看到,订单状态是失败的,因为优惠券已经被使用,返回了`Aborted`错误,dtm收到`Aborted`错误信息之后,会对已经**创建订单**和**扣减库存**分支事务进行补偿,保证数据最终一致。 <br> (2) 库存不足造成订单失败。 填写请求参数,字段productCount值为1000确定大于了库存数量,把参数couponId设置为0表示不使用优惠券。 ```json { "userId": 1, "productId": 1, "amount": 1100000, "productCount": 1000, "couponId": 0 } ``` 点击Execute按钮测试,虽然返回了订单id(这不表示订单成功),从dtm的管理界面和库存服务stock日志可以看到,订单状态是失败的,因为库存不足原因,返回了`Aborted`错误,dtm收到`Aborted`错误信息之后,会对**创建订单**分支事务进行补偿,保证数据最终一致。 后续添加支付业务逻辑代码之后,可以测试账号余额不足导致订单失败,dtm会补偿分支事务确保数据最终一致。 <br> #### 测试模拟进程crash,恢复后成功提交订单场景 停止库存服务stock,然后在swagger界面填写请求参数: ```json { "userId": 1, "productId": 1, "amount": 1100, "productCount": 1, "couponId": 0 } ``` 点击Execute按钮测试,虽然返回了订单id(这不表示订单成功),从dtm的管理界面看到订单状态是`submitted`状态,dtm会一直重试连接库存服务stock,重试默认是指数退避算法,可以修改为固定时间间隔重试。启动库存服务,dtm连接库存服务成功之后,接着完成后续分支事务,成功完成订单,保证数据最终一致。根据业务需求也可以做超时主动强制取消订单处理,dtm收到强制取消订单后,会对**创建订单**分支事务进行补偿,也保证数据最终一致。 <br> ### 总结 本文介绍了从0开始快速搭建一个简单的订单系统,使用sponge很容易构建微服务,使用dtm优雅的解决提交订单的分布式事务,开发一个订单系统变得很简单,让开发人员把精力用在业务开发上。 各个服务的常用服务治理功能也是具备的,例如服务注册与发现、限流、熔断、链路跟踪、监控、性能分析、资源统计、CICD等,这些功能统一在yml配置文件开启或关闭。 这不是完整的订单系统,只有一个提交订单业务逻辑,如果需要构建一个自己的订单系统,可以作为一个参考。根据上面操作步骤,很容易添加商品服务product、物流服务logistics、用户服务user等服务模块组成一个电商平台。 <br>

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1059 次点击  ∙  1 赞  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传