3
头图

Preamble

We will show you a go-zero microservice example in detail through a series of articles. The whole series is divided into ten articles. The directory structure is as follows:

  1. Environment construction
  2. service split
  3. User service
  4. product service
  5. Order service
  6. payment service
  7. RPC Service Auth Authentication
  8. service monitoring
  9. link tracking
  10. Distributed Transactions (this article)

I hope that through this series, you can quickly develop a mall system using go-zero in the Docker environment on the local machine, so that you can quickly get started with microservices.

Complete example code: https://github.com/nivin-studio/go-zero-mall

First, let's take a look at the overall service split diagram:

10.1 DTM Introduction

DTM is a distributed transaction manager developed by golang , which solves the problem of consistency of updating data across databases, services, and language stacks.

Most of the transactions of the order system will cross services, so there is a need to update data consistency, and the architecture can be greatly simplified through DTM to form an elegant solution.

Moreover, DTM has cooperated deeply and natively supports distributed transactions in go-zero. Let's explain in detail how to use DTM to help our order system solve the consistency problem.

10.2 go-zero use DTM

First, let's review the order service in Chapter 5 order rpc service Create interface processing logic. The method determines the legitimacy of users and products, and whether the product inventory is sufficient, and finally creates a new order through OrderModel , and calls product rpc service Update The interface updates the product's inventory.

 func (l *CreateLogic) Create(in *order.CreateRequest) (*order.CreateResponse, error) {
    // 查询用户是否存在
    _, err := l.svcCtx.UserRpc.UserInfo(l.ctx, &user.UserInfoRequest{
        Id: in.Uid,
    })
    if err != nil {
        return nil, err
    }

    // 查询产品是否存在
    productRes, err := l.svcCtx.ProductRpc.Detail(l.ctx, &product.DetailRequest{
        Id: in.Pid,
    })
    if err != nil {
        return nil, err
    }
    // 判断产品库存是否充足
    if productRes.Stock <= 0 {
        return nil, status.Error(500, "产品库存不足")
    }

    newOrder := model.Order{
        Uid:    in.Uid,
        Pid:    in.Pid,
        Amount: in.Amount,
        Status: 0,
    }

    res, err := l.svcCtx.OrderModel.Insert(&newOrder)
    if err != nil {
        return nil, status.Error(500, err.Error())
    }

    newOrder.Id, err = res.LastInsertId()
    if err != nil {
        return nil, status.Error(500, err.Error())
    }

    _, err = l.svcCtx.ProductRpc.Update(l.ctx, &product.UpdateRequest{
        Id:     productRes.Id,
        Name:   productRes.Name,
        Desc:   productRes.Desc,
        Stock:  productRes.Stock - 1,
        Amount: productRes.Amount,
        Status: productRes.Status,
    })
    if err != nil {
        return nil, err
    }

    return &order.CreateResponse{
        Id: newOrder.Id,
    }, nil
}

As we said before, there is a data consistency problem in the processing logic here. It is possible that the order was created successfully, but it may fail when updating the product inventory. At this time, the order creation is successful and the product inventory is not reduced.

Because the product inventory update here is operated across services, and there is no way to use local transactions to handle it, we need to use distributed transactions to handle it. Here we need to use the DTM SAGA protocol of ---c8c2a5bb72d506742572b3c504f4aeb4--- to realize the cross-service distributed transaction operation of order creation and product inventory update.

You can move to the document DTM and then take over the SAGA transaction mode .

10.2.1 Add DTM service configuration

See Chapter 1 Environment Construction , modify the dtm->config.yml configuration file. As long as we modify MicroService in Target , EndPoint configuration to the dtm registered to etcd in.

 # ......

# 微服务
MicroService:
  Driver: 'dtm-driver-gozero'           # 要处理注册/发现的驱动程序的名称
  Target: 'etcd://etcd:2379/dtmservice' # 注册 dtm 服务的 etcd 地址
  EndPoint: 'dtm:36790'

# ......

10.2.2 Added dtm_barrier Datasheet

Microservices are a distributed system, so various exceptions may occur, such as repeated requests caused by network jitter, which can make business processing extremely complicated. In DTM , the sub-transaction barrier technology was pioneered. Using this technology, abnormal problems can be solved very conveniently, which greatly reduces the threshold for the use of distributed transactions.

Using the sub-transaction barrier technology provided by DTM requires the creation of sub-transaction barrier-related tables in the business database. The table creation statement is as follows:

 create database if not exists dtm_barrier
/*!40100 DEFAULT CHARACTER SET utf8mb4 */
;
drop table if exists dtm_barrier.barrier;
create table if not exists dtm_barrier.barrier(
  id bigint(22) PRIMARY KEY AUTO_INCREMENT,
  trans_type varchar(45) default '',
  gid varchar(128) default '',
  branch_id varchar(128) default '',
  op varchar(45) default '',
  barrier_id varchar(45) default '',
  reason varchar(45) default '' comment 'the branch type who insert this record',
  create_time datetime DEFAULT now(),
  update_time datetime DEFAULT now(),
  key(create_time),
  key(update_time),
  UNIQUE key(gid, branch_id, op, barrier_id)
);
Note: Do not modify the library name and table name. If you customize the table name, please call dtmcli.SetBarrierTableName before use.

10.2.3 Modification of OrderModel and ProductModel

In each sub-transaction, a lot of operation logic needs to use local transactions, so we add some sub-transaction barriers model method compatible DTM

 $ vim mall/service/order/model/ordermodel.go
 package model

......

type (
    OrderModel interface {
        TxInsert(tx *sql.Tx, data *Order) (sql.Result, error)
        TxUpdate(tx *sql.Tx, data *Order) error
    FindOneByUid(uid int64) (*Order, error)
    }
)

......

func (m *defaultOrderModel) TxInsert(tx *sql.Tx, data *Order) (sql.Result, error) {
    query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?)", m.table, orderRowsExpectAutoSet)
    ret, err := tx.Exec(query, data.Uid, data.Pid, data.Amount, data.Status)

    return ret, err
}

func (m *defaultOrderModel) TxUpdate(tx *sql.Tx, data *Order) error {
    productIdKey := fmt.Sprintf("%s%v", cacheOrderIdPrefix, data.Id)
    _, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
        query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, orderRowsWithPlaceHolder)
        return tx.Exec(query, data.Uid, data.Pid, data.Amount, data.Status, data.Id)
    }, productIdKey)
    return err
}

func (m *defaultOrderModel) FindOneByUid(uid int64) (*Order, error) {
    var resp Order

    query := fmt.Sprintf("select %s from %s where `uid` = ? order by create_time desc limit 1", orderRows, m.table)
    err := m.QueryRowNoCache(&resp, query, uid)

    switch err {
    case nil:
        return &resp, nil
    case sqlc.ErrNotFound:
        return nil, ErrNotFound
    default:
        return nil, err
    }
}
 $ vim mall/service/product/model/productmodel.go
 package model

......

type (
    ProductModel interface {
        TxAdjustStock(tx *sql.Tx, id int64, delta int) (sql.Result, error)
    }
)

......

func (m *defaultProductModel) TxAdjustStock(tx *sql.Tx, id int64, delta int) (sql.Result, error) {
    productIdKey := fmt.Sprintf("%s%v", cacheProductIdPrefix, id)
    return m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
        query := fmt.Sprintf("update %s set stock=stock+? where stock >= -? and id=?", m.table)
        return tx.Exec(query, delta, delta, id)
    }, productIdKey)
}

10.2.4 Modification product rpc Service

  • Added DecrStock , DecrStockRevert interface method

    We need to add DecrStock and DecrStockRevert two interface methods for the product rpc service, which are used for product inventory update and product inventory update compensation.

 $ vim mall/service/product/rpc/product.proto
 syntax = "proto3";

package productclient;

option go_package = "product";

......

// 减产品库存
message DecrStockRequest {
    int64 id = 1;
    int64 num = 2;
}
message DecrStockResponse {
}
// 减产品库存

service Product {
    ......
    rpc DecrStock(DecrStockRequest) returns(DecrStockResponse);
    rpc DecrStockRevert(DecrStockRequest) returns(DecrStockResponse);
}
Tip: Use the goctl tool to regenerate the code after modification.
  • Implement DecrStock interface method

    Here only when the inventory is insufficient, we do not need to retry and roll back directly.

 $ vim mall/service/product/rpc/internal/logic/decrstocklogic.go
 package logic

import (
    "context"
    "database/sql"

    "mall/service/product/rpc/internal/svc"
    "mall/service/product/rpc/product"

    "github.com/dtm-labs/dtmcli"
    "github.com/dtm-labs/dtmgrpc"
    "github.com/tal-tech/go-zero/core/logx"
    "github.com/tal-tech/go-zero/core/stores/sqlx"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
)

type DecrStockLogic struct {
    ctx    context.Context
    svcCtx *svc.ServiceContext
    logx.Logger
}

func NewDecrStockLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DecrStockLogic {
    return &DecrStockLogic{
        ctx:    ctx,
        svcCtx: svcCtx,
        Logger: logx.WithContext(ctx),
    }
}

func (l *DecrStockLogic) DecrStock(in *product.DecrStockRequest) (*product.DecrStockResponse, error) {
    // 获取 RawDB
    db, err := sqlx.NewMysql(l.svcCtx.Config.Mysql.DataSource).RawDB()
    if err != nil {
        return nil, status.Error(500, err.Error())
    }

    // 获取子事务屏障对象
    barrier, err := dtmgrpc.BarrierFromGrpc(l.ctx)
    if err != nil {
        return nil, status.Error(500, err.Error())
    }
    // 开启子事务屏障
    err = barrier.CallWithDB(db, func(tx *sql.Tx) error {
        // 更新产品库存
        result, err := l.svcCtx.ProductModel.TxAdjustStock(tx, in.Id, -1)
        if err != nil {
            return err
        }

        affected, err := result.RowsAffected()
        // 库存不足,返回子事务失败
        if err == nil && affected == 0 {
            return dtmcli.ErrFailure
        }

        return err
    })

    // 这种情况是库存不足,不再重试,走回滚
    if err == dtmcli.ErrFailure {
        return nil, status.Error(codes.Aborted, dtmcli.ResultFailure)
    }

    if err != nil {
        return nil, err
    }

    return &product.DecrStockResponse{}, nil
}
  • Implement DecrStockRevert interface method

    In the DecrStock interface method, the product inventory is minus the specified quantity, and here we add it back. In this way, the product inventory is returned to the previous quantity in the DecrStock interface method minus the previous quantity.

 $ vim mall/service/product/rpc/internal/logic/decrstockrevertlogic.go
 package logic

import (
    "context"
    "database/sql"

    "mall/service/product/rpc/internal/svc"
    "mall/service/product/rpc/product"

    "github.com/dtm-labs/dtmcli"
    "github.com/dtm-labs/dtmgrpc"
    "github.com/tal-tech/go-zero/core/logx"
    "github.com/tal-tech/go-zero/core/stores/sqlx"
    "google.golang.org/grpc/status"
)

type DecrStockRevertLogic struct {
    ctx    context.Context
    svcCtx *svc.ServiceContext
    logx.Logger
}

func NewDecrStockRevertLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DecrStockRevertLogic {
    return &DecrStockRevertLogic{
        ctx:    ctx,
        svcCtx: svcCtx,
        Logger: logx.WithContext(ctx),
    }
}

func (l *DecrStockRevertLogic) DecrStockRevert(in *product.DecrStockRequest) (*product.DecrStockResponse, error) {
    // 获取 RawDB
    db, err := sqlx.NewMysql(l.svcCtx.Config.Mysql.DataSource).RawDB()
    if err != nil {
        return nil, status.Error(500, err.Error())
    }

    // 获取子事务屏障对象
    barrier, err := dtmgrpc.BarrierFromGrpc(l.ctx)
    if err != nil {
        return nil, status.Error(500, err.Error())
    }
    // 开启子事务屏障
    err = barrier.CallWithDB(db, func(tx *sql.Tx) error {
        // 更新产品库存
        _, err := l.svcCtx.ProductModel.TxAdjustStock(tx, in.Id, 1)
        return err
    })

    if err != nil {
        return nil, err
    }

    return &product.DecrStockResponse{}, nil
}

10.2.5 Modify order rpc Service

  • Add CreateRevert interface method

    order rpc service already has Create interface method, we need to create its compensation interface method CreateRevert .

 $ vim mall/service/order/rpc/order.proto
 syntax = "proto3";

package orderclient;

option go_package = "order";

......

service Order {
    rpc Create(CreateRequest) returns(CreateResponse);
    rpc CreateRevert(CreateRequest) returns(CreateResponse);
    ......
}
Tip: Use the goctl tool to regenerate the code after modification.
  • Modify Create interface method

    The original Create interface method in the product inventory judgment and update operations, we have already implemented in the product rpc DecrStock interface method, so we only need to create an order here. Can.

 $ vim mall/service/order/rpc/internal/logic/createlogic.go
 package logic

import (
    "context"
    "database/sql"
    "fmt"

    "mall/service/order/model"
    "mall/service/order/rpc/internal/svc"
    "mall/service/order/rpc/order"
    "mall/service/user/rpc/user"

    "github.com/dtm-labs/dtmgrpc"
    "github.com/tal-tech/go-zero/core/logx"
    "github.com/tal-tech/go-zero/core/stores/sqlx"
    "google.golang.org/grpc/status"
)

type CreateLogic struct {
    ctx    context.Context
    svcCtx *svc.ServiceContext
    logx.Logger
}

func NewCreateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateLogic {
    return &CreateLogic{
        ctx:    ctx,
        svcCtx: svcCtx,
        Logger: logx.WithContext(ctx),
    }
}

func (l *CreateLogic) Create(in *order.CreateRequest) (*order.CreateResponse, error) {
    // 获取 RawDB
    db, err := sqlx.NewMysql(l.svcCtx.Config.Mysql.DataSource).RawDB()
    if err != nil {
        return nil, status.Error(500, err.Error())
    }

    // 获取子事务屏障对象
    barrier, err := dtmgrpc.BarrierFromGrpc(l.ctx)
    if err != nil {
        return nil, status.Error(500, err.Error())
    }
    // 开启子事务屏障
    if err := barrier.CallWithDB(db, func(tx *sql.Tx) error {
        // 查询用户是否存在
        _, err := l.svcCtx.UserRpc.UserInfo(l.ctx, &user.UserInfoRequest{
            Id: in.Uid,
        })
        if err != nil {
            return fmt.Errorf("用户不存在")
        }

        newOrder := model.Order{
            Uid:    in.Uid,
            Pid:    in.Pid,
            Amount: in.Amount,
            Status: 0,
        }
        // 创建订单
        _, err = l.svcCtx.OrderModel.TxInsert(tx, &newOrder)
        if err != nil {
            return fmt.Errorf("订单创建失败")
        }

        return nil
    }); err != nil {
        return nil, status.Error(500, err.Error())
    }

    return &order.CreateResponse{}, nil
}
  • Implement CreateRevert interface method

    In this interface, we query the order just created by the user and change the status of the order to 9(无效状态) .

 $ vim mall/service/order/rpc/internal/logic/createrevertlogic.go
 package logic

import (
    "context"
    "database/sql"
    "fmt"

    "mall/service/order/rpc/internal/svc"
    "mall/service/order/rpc/order"
    "mall/service/user/rpc/user"

    "github.com/dtm-labs/dtmgrpc"
    "github.com/tal-tech/go-zero/core/logx"
    "github.com/tal-tech/go-zero/core/stores/sqlx"
    "google.golang.org/grpc/status"
)

type CreateRevertLogic struct {
    ctx    context.Context
    svcCtx *svc.ServiceContext
    logx.Logger
}

func NewCreateRevertLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateRevertLogic {
    return &CreateRevertLogic{
        ctx:    ctx,
        svcCtx: svcCtx,
        Logger: logx.WithContext(ctx),
    }
}

func (l *CreateRevertLogic) CreateRevert(in *order.CreateRequest) (*order.CreateResponse, error) {
    // 获取 RawDB
    db, err := sqlx.NewMysql(l.svcCtx.Config.Mysql.DataSource).RawDB()
    if err != nil {
        return nil, status.Error(500, err.Error())
    }

    // 获取子事务屏障对象
    barrier, err := dtmgrpc.BarrierFromGrpc(l.ctx)
    if err != nil {
        return nil, status.Error(500, err.Error())
    }
    // 开启子事务屏障
    if err := barrier.CallWithDB(db, func(tx *sql.Tx) error {
        // 查询用户是否存在
        _, err := l.svcCtx.UserRpc.UserInfo(l.ctx, &user.UserInfoRequest{
            Id: in.Uid,
        })
        if err != nil {
            return fmt.Errorf("用户不存在")
        }
        // 查询用户最新创建的订单
        resOrder, err := l.svcCtx.OrderModel.FindOneByUid(in.Uid)
        if err != nil {
            return fmt.Errorf("订单不存在")
        }
        // 修改订单状态9,标识订单已失效,并更新订单
        resOrder.Status = 9
        err = l.svcCtx.OrderModel.TxUpdate(tx, resOrder)
        if err != nil {
            return fmt.Errorf("订单更新失败")
        }

        return nil
    }); err != nil {
        return nil, status.Error(500, err.Error())
    }

    return &order.CreateResponse{}, nil
}

10.2.6 Modification order api Service

We order rpc Services Create , CreateRevert interface methods, product rpc Services DecrStock , DecrStockRevert order api DecrStockRevert a distributed transaction operation with SAGA事务模式 in the service.

  • Add pproduct rpc dependency configuration

     $ vim mall/service/order/api/etc/order.yaml
 Name: Order
Host: 0.0.0.0
Port: 8002

......

OrderRpc:
  Etcd:
    Hosts:
    - etcd:2379
    Key: order.rpc

ProductRpc:
  Etcd:
    Hosts:
    - etcd:2379
    Key: product.rpc
  • Added pproduct rpc Instantiation of service configuration

     $ vim mall/service/order/api/internal/config/config.go
 package config

import (
    "github.com/tal-tech/go-zero/rest"
    "github.com/tal-tech/go-zero/zrpc"
)

type Config struct {
    rest.RestConf

    Auth struct {
        AccessSecret string
        AccessExpire int64
    }

    OrderRpc   zrpc.RpcClientConf
    ProductRpc zrpc.RpcClientConf
}
  • Dependency of registration service context pproduct rpc

     $ vim mall/service/order/api/internal/svc/servicecontext.go
 package svc

import (
    "mall/service/order/api/internal/config"
    "mall/service/order/rpc/orderclient"
    "mall/service/product/rpc/productclient"

    "github.com/tal-tech/go-zero/zrpc"
)

type ServiceContext struct {
    Config config.Config

    OrderRpc   orderclient.Order
    ProductRpc productclient.Product
}

func NewServiceContext(c config.Config) *ServiceContext {
    return &ServiceContext{
        Config:     c,
        OrderRpc:   orderclient.NewOrder(zrpc.MustNewClient(c.OrderRpc)),
        ProductRpc: productclient.NewProduct(zrpc.MustNewClient(c.ProductRpc)),
    }
}
  • Add import gozero dtm driver

     $ vim mall/service/order/api/order.go
 package main

import (
    ......

    _ "github.com/dtm-labs/driver-gozero" // 添加导入 `gozero` 的 `dtm` 驱动
)

var configFile = flag.String("f", "etc/order.yaml", "the config file")

func main() {
    ......
}
  • Modify order api Create interface method

     $ vim mall/service/order/api/internal/logic/createlogic.go
 package logic

import (
    "context"

    "mall/service/order/api/internal/svc"
    "mall/service/order/api/internal/types"
    "mall/service/order/rpc/order"
    "mall/service/product/rpc/product"

    "github.com/dtm-labs/dtmgrpc"
    "github.com/tal-tech/go-zero/core/logx"
    "google.golang.org/grpc/status"
)

type CreateLogic struct {
    logx.Logger
    ctx    context.Context
    svcCtx *svc.ServiceContext
}

func NewCreateLogic(ctx context.Context, svcCtx *svc.ServiceContext) CreateLogic {
    return CreateLogic{
        Logger: logx.WithContext(ctx),
        ctx:    ctx,
        svcCtx: svcCtx,
    }
}

func (l *CreateLogic) Create(req types.CreateRequest) (resp *types.CreateResponse, err error) {
    // 获取 OrderRpc BuildTarget
    orderRpcBusiServer, err := l.svcCtx.Config.OrderRpc.BuildTarget()
    if err != nil {
        return nil, status.Error(100, "订单创建异常")
    }

    // 获取 ProductRpc BuildTarget
    productRpcBusiServer, err := l.svcCtx.Config.ProductRpc.BuildTarget()
    if err != nil {
        return nil, status.Error(100, "订单创建异常")
    }

    // dtm 服务的 etcd 注册地址
    var dtmServer = "etcd://etcd:2379/dtmservice"
    // 创建一个gid
    gid := dtmgrpc.MustGenGid(dtmServer)
    // 创建一个saga协议的事务
    saga := dtmgrpc.NewSagaGrpc(dtmServer, gid).
        Add(orderRpcBusiServer+"/orderclient.Order/Create", orderRpcBusiServer+"/orderclient.Order/CreateRevert", &order.CreateRequest{
            Uid:    req.Uid,
            Pid:    req.Pid,
            Amount: req.Amount,
            Status: 0,
        }).
        Add(productRpcBusiServer+"/productclient.Product/DecrStock", productRpcBusiServer+"/productclient.Product/DecrStockRevert", &product.DecrStockRequest{
            Id:  req.Pid,
            Num: 1,
        })

    // 事务提交
    err = saga.Submit()
    if err != nil {
        return nil, status.Error(500, err.Error())
    }

    return &types.CreateResponse{}, nil
}
Tip: SagaGrpc.Add The first parameter of the method action is the method path accessed by the grpc . This method path needs to be found in the following files.

mall/service/order/rpc/order/order.pb.go

mall/service/product/rpc/product/product.pb.go

Search by keyword Invoke to find it.

10.3 Test go-zero + DTM

10.3.1 Test the normal flow of distributed transactions

  • Use the postman --- interface to call /api/product/create interface to create a product that inventory stock as 1 .

  • Use the postman --- interface to call /api/order/create interface to create an order with product ID pid as 1 .

  • We can see that the product inventory has changed from the original 1 to 0 .

  • Let's look at the data in the sub-transaction barrier table barrier , we can see that the operations of the two services have been completed.

10.3.2 Test Distributed Transaction Failure Process 1

  • Then the above test results, the product ID at this time is 1 the inventory is already 0 , use postman to call /api/order/create interface, 2 Create an order.

  • Let's see that there is a data in the order data table whose ID is 2 the product ID is 1 , and its order data status is 9 .

  • Let's look at the data in the sub-transaction barrier table barrier , we can see that (gid = fqYS8CbYbK8GkL8SCuTRUF) the first service (branch_id = 01) the sub-transaction barrier operation is normal, Service (branch_id = 02) barrier operation failed, compensation required. So both services have a compensating operation record.

  • The operation flow of this distributed transaction

    1. First DTM service will be transferred order rpc Create interface to create order processing.
    2. After creating the order is completed DTM service and then transferred product rpc DecrStock interface that's by pid update product inventory, due to lack of inventory , throws transaction failure.
    3. DTM service initiates the compensation mechanism, and adjusts the order rpc CreateRevert interface for order compensation processing.
    4. DTM service initiates the compensation mechanism and adjusts the product rpc DecrStockRevert interface to perform compensation processing for product inventory updates. However, because within the sub-transaction barrier of the product rpc DecrStock interface, the business processing did not succeed. So in the DecrStockRevert interface, the business logic in the sub-transaction barrier will not be executed.

10.3.3 Test Distributed Transaction Failure Process 2

  • We manually change the product ID in the database to 1 the inventory to 100, and then artificially create an exception outside the sub-transaction barrier in the product rpc DecrStock interface method. .

  • Use the postman --- interface to call /api/order/create interface, and create an order with the product ID pid as 1 .

  • Let's look at the order data table and the product data table respectively. The order data table ID is 3 , and its order data status is 9 . The product data sheet ID is 1 , its inventory is still 100 and the data update time has also changed.

  • Let's look at the data in the sub-transaction barrier table barrier , we can see that (gid = ZbjYHv2jNra7RMwyWjB5Lc) the first service (branch_id = 01) the sub-transaction barrier operation is normal, the second Service (branch_id = 02) barrier operation is also normal. Because outside the neutron transaction barrier of the product rpc DecrStock interface method, we artificially fail to create an exception, so the two services have a compensation operation record.

You can compare the difference between testing distributed transaction failure process 1 and testing distributed transaction failure process 2, can you find and appreciate the power of this sub-transaction barrier technology DTM .

The sub-transaction barrier will automatically identify whether the forward operation has been executed. The failed process 1 does not execute the business operation, so when it is compensated, it will not execute the compensated business operation; the failed process 2 executes the business operation, so the compensation will also be executed. business operations.

project address

https://github.com/zeromicro/go-zero

Welcome go-zero and star support us!

WeChat exchange group

Follow the official account of " Microservice Practice " and click on the exchange group to get the QR code of the community group.


kevinwan
931 声望3.5k 粉丝

go-zero作者