apache / incubator-seata-go

Go Implementation For Seata
https://seata.apache.org/
Apache License 2.0
1.53k stars 269 forks source link

Is AT mode working for multiple services invoke chain? #660

Closed smiletrl closed 8 months ago

smiletrl commented 8 months ago

What happened:

I'm assuming one scenario for service invoke chain: OrderService -> ProductService -> UserService. Three services will share one global tx. Maybe I'm wrong, but it seems this is impossible to achieve with current AT mode ^?

The follow func will start a new global tx (providing ctx doesn't hold any global tx information), and directly commit or rollback this new global tx. No chances are left to pass this newly created global tx to downstream services (product service, user service).

func sampleInsert(ctx context.Context) {
    tm.WithGlobalTx(ctx, &tm.GtxConfig{
        Name:    "ATSampleLocalGlobalTx_Insert",
        Timeout: time.Second * 30,
    }, insertData)
}

commitOrRollback is within tm.WithGlobalTx already.

What you expected to happen:

Global tx XID can be passed from init order service to downstream user service, and when this service invoke chain has finished, the init order service can decide whether commit or rollback this global tx.

Something like this


func main() {
    client.InitPath("../../conf/seatago.yml")
    db = util.GetAtMySqlDb()
    orderCtx := context.Background()

    defer func() {
        // isSuccess is determined by the returned result from product service and
        // user service. If all goes well, isSuccess = true, otherwise isSuccess = false.
        tm.CommitOrRollback(orderCtx, isSuccess)
    }()

    // order service needs an insert
    orderServiceInsert(orderCtx)

    // order service sends rpc call to product service
    time.Sleep(2 * time.Second)

    // product service has received this rpc request.
    // This request has included order service's global tx's XID, so product service can
    // implement its actions within the same global tx.
    productCtx := context.Background()
    tm.SetXID(productCtx, "{XID received from order service}")
    productServiceInsert(productCtx)

    // product service sends rpc call to user service.
    time.Sleep(2 * time.Second)

        // user service has received this rpc request.
    // This request has included product service's global tx's XID, so user service can
    // implement its actions within the same global tx.
    userCtx := context.Background()
    tm.SetXID(userCtx, "{XID received from product service}")
    userServiceInsert(userCtx)

    <-make(chan struct{})
}

func orderServiceInsert(ctx context.Context) {
    tm.WithGlobalTx(ctx, &tm.GtxConfig{
        Name:    "ATSampleLocalGlobalTx_Insert",
        Timeout: time.Second * 30,
    }, insertData)
}

func productServiceInsert(ctx context.Context) {
    tm.WithGlobalTx(ctx, &tm.GtxConfig{
        Name: "ATSampleLocalGlobalTx_Insert",
        // This action must be under a global tx scope.
        // For example, it must be invoked from order service's rpc request
        Propagation: tm.Mandatory,
        Timeout:     time.Second * 30,
    }, insertData)
}

func userServiceInsert(ctx context.Context) {
    tm.WithGlobalTx(ctx, &tm.GtxConfig{
        Name: "ATSampleLocalGlobalTx_Insert",
        // This action must be under a global tx scope.
        // For example, it must be invoked from product service's rpc request
        Propagation: tm.Mandatory,
        Timeout:     time.Second * 30,
    }, insertData)
}

How to reproduce it (as minimally and precisely as possible):

Anything else we need to know?:

smiletrl commented 8 months ago

Never mind. AT mode is not for such case. TCC and Saga is used across services.