Go Engineer System Course 013 [Study Notes]

Order transactions, whether deducting inventory first or later, will both affect inventory and orders. Therefore, distributed transactions must be used to address business issues (e.g., unpaid orders). One approach is to deduct inventory only after successful payment (e.g., an order was placed, but there was no inventory at the time of payment). Another common method is to deduct inventory when the order is placed, but if payment isn't made, the order is returned/released upon timeout.

Transactions and Distributed Transactions
1. What is a transaction?
A transaction is an important concept in database management systems. It is a collection of database operations, which either all execute successfully, or all...

Table of Contents

Order Transactions

  • Whether inventory is deducted before or after, it will affect both inventory and orders, so distributed transactions must be used.
  • Business (order placed but not paid) business problem.
  • Deduct inventory after successful payment (order placed, but out of stock at payment time).
  • Order deduction, no payment (order timeout and return) [common method].

Transactions and Distributed Transactions

1. What is a Transaction?

A transaction is an important concept in database management systems. It is a collection of database operations that are either all successfully executed or all rolled back upon failure.

1.1 ACID Properties of Transactions

  • Atomicity: All operations within a transaction either succeed completely or fail completely; there is no partial success.
  • Consistency: A transaction transforms the database from one consistent state to another consistent state before and after its execution.
  • Isolation: Concurrent transactions are isolated from each other; the execution of one transaction should not affect other transactions.
  • Durability: Once a transaction is committed, its results are permanently stored in the database.

1.2 Transaction Isolation Levels

  1. Read Uncommitted: The lowest level, may read dirty data.
  2. Read Committed: Can only read committed data.
  3. Repeatable Read: Multiple reads within the same transaction yield consistent results.
  4. Serializable: The highest level, transactions are executed completely serially.

2. What is a Distributed Transaction?

A distributed transaction refers to transaction operations involving multiple databases or services, requiring data consistency across multiple nodes.

2.1 Challenges of Distributed Transactions

  • Network Partition: Network failures lead to communication interruptions between nodes.
  • Node Failure: A node crashes or restarts.
  • Clock Skew: Inconsistent times across different nodes.
  • Data Consistency: How to ensure data consistency across nodes.

2.2 CAP Theorem

  • Consistency: All nodes see the same data at the same time (after an update returns to the client).
  • Availability: The system remains continuously available and operations do not fail.
  • Partition Tolerance: The system can tolerate network partition failures.

CAP Theorem: In a distributed system, it is impossible to simultaneously satisfy all three of Consistency, Availability, and Partition Tolerance.

2.3 BASE Theory (Engineering Trade-offs with CAP)

  • Basically Available: In the event of a failure, the system is allowed to degrade and provide limited functionality (e.g., slower response, partial unavailability).
  • Soft state: The system state is allowed to exist in an intermediate state for a period of time (not strongly consistent).
  • Eventual consistency: Data eventually reaches consistency after a period of time (or through retries/compensations).

In engineering practice: most internet businesses choose AP → guided by BASE theory, sacrificing strong consistency for high availability and scalability, achieving eventual consistency through "compensation, retries, deduplication, and reconciliation".

3. Distributed Transaction Solutions

3.1 Two-Phase Commit (2PC)

Principle:

  1. Prepare Phase: The coordinator asks all participants if they can commit.
  2. Commit Phase: Decides to commit or roll back based on participant responses.

Advantages: Strong consistency
Disadvantages: Poor performance, single point of failure, blocking issues

Detailed Process (示意):

  1. The coordinator sends a prepare request to participants. Each participant reserves resources, writes pre-commit logs, and returns yes/no.
  2. The coordinator aggregates: all yes → sends commit; any no/timeout → sends rollback.
  3. Participants commit or roll back according to the instruction and acknowledge the coordinator.

Common issues: single point of failure for the coordinator, participant blocking (holding locks for a long time), complex recovery during network partitions.

sequenceDiagram
  participant C as 协调者(Coordinator)
  participant P1 as 参与者1
  participant P2 as 参与者2

  C->>P1: prepare
  C->>P2: prepare
  P1-->>C: yes/预提交成功
  P2-->>C: yes/预提交成功
  alt 全部yes
    C->>P1: commit
    C->>P2: commit
    P1-->>C: ack
    P2-->>C: ack
  else 任一no/超时
    C->>P1: rollback
    C->>P2: rollback
  end

3.2 Three-Phase Commit (3PC)

Adds a pre-commit phase on top of 2PC to reduce blocking time, but still suffers from single point of failure issues.

3.3 TCC (Try-Confirm-Cancel)

Principle:

  • Try: Attempt to execute business, reserve resources.
  • Confirm: Confirm business execution, commit resources.
  • Cancel: Cancel business execution, release resources.

Advantages: Good performance, non-blocking
Disadvantages: Complex implementation, requires business compensation

Key Implementation Points (taking order-inventory-payment as an example):

  • Try: Create order pre-status, pre-occupy inventory (deduct available inventory, increase pre-occupied inventory), pre-order payment.
  • Confirm (payment success callback or asynchronous confirmation): Order status changes to paid, inventory moves from pre-occupied to formally deducted.
  • Cancel (payment failure/timeout): Order cancelled, pre-occupied inventory released.

Implementation details: idempotent interfaces (deduplication table/unique business key), handling null rollback/hanging, transaction log recording and retry tasks.

sequenceDiagram
  participant Order as 订单服务
  participant Inv as 库存服务
  participant Pay as 支付服务

  rect rgb(230,250,230)
  Note over Order,Inv: Try 阶段(预留资源)
  Order->>Inv: Try 预占库存
  Order->>Pay: Try 预下单/冻结
  end

  alt 支付成功
    rect rgb(230,230,255)
    Note over Order,Inv: Confirm 阶段
    Pay-->>Order: 支付成功回调
    Order->>Inv: Confirm 扣减库存
    Order->>Pay: Confirm 确认扣款
    end
  else 失败/超时
    rect rgb(255,230,230)
    Note over Order,Inv: Cancel 阶段
    Order->>Inv: Cancel 释放预占
    Order->>Pay: Cancel 解冻/撤销
    end
  end

3.4 Message-Based Eventual Consistency

Principle:

  1. Execute local transaction.
  2. Send message to message queue.
  3. Consumer processes message, ensuring eventual consistency.

Advantages: Good performance, relatively simple implementation
Disadvantages: Only guarantees eventual consistency

3.4.1 Local Message Table (Outbox Pattern)

Process: Write business data and outbox message table within the same local transaction → background forwarder polls and delivers to MQ → consumer processes and persists to database → sends confirmation/reconciliation.

Key points:

  • Strong consistency on the producer side (business + message in the same database transaction)
  • Idempotent forwarding (deliver by message ID, consumer deduplication)
  • Failure retry and dead-letter queue, manual reconciliation and repair
flowchart LR
  A[应用/业务服务] -->|同库同事务| B[(业务表 + Outbox表)]
  B -->|后台转发器扫描/拉取| MQ[消息队列]
  MQ --> C[下游服务]
  C --> D[(消费落库/去重表)]

  subgraph 重试与对账
    E[失败重投/死信队列]
    F[对账/人工修复]
  end
  MQ --> E
  E --> F
3.4.2 Reliable Message-Based Eventual Consistency (Common)

Process:

  1. The business requests a "prepare message/half message" from MQ.
  2. After the local business commit is successful, it calls MQ to confirm (commit), otherwise it rolls back.
  3. MQ suspends unconfirmed half messages and queries (check) the business's final status to decide whether to commit or discard.

Key points:

  • Relies on MQ's transaction message/callback capabilities (RocketMQ, etc.)
  • Both producer and consumer sides need idempotent processing.
sequenceDiagram
  participant Biz as 业务服务
  participant MQ as MQ(事务消息)
  participant D as 下游服务

  Biz->>MQ: 发送半消息(Prepare)
  Biz->>Biz: 执行业务本地事务
  alt 成功
    Biz->>MQ: Commit 确认
  else 失败
    Biz->>MQ: Rollback 撤销
  end
  MQ->>D: 投递正式消息
  D-->>MQ: Ack/重试

  MQ->>Biz: 事务回查(Check) 未确认半消息
  Biz-->>MQ: 返回最终状态(提交/回滚)
3.4.3 Best-Effort Notification

Process: After an event occurs, a notification is sent to downstream systems (HTTP/MQ). If it fails, it retries several times according to a strategy. If the threshold is exceeded, it enters manual processing.

Applicable: Scenarios with relatively relaxed consistency requirements (e.g., SMS, in-app messages, point issuance).

flowchart LR
  A[事件源] --> B{通知}
  B -->|HTTP/MQ| C[下游]
  B --> R1[重试1]
  R1 --> R2[重试2]
  R2 --> R3[重试N]
  R3 --> DLQ[死信/人工补偿]
  C --> Idem[去重/幂等处理]

4. Transaction Processing in Order Systems

4.1 Inventory Deduction Issue

In an order system, inventory deduction is a critical operation:

// 库存扣减示例
func (s *InventoryServer) Sell(ctx context.Context, req *proto.SellInfo) (*emptypb.Empty, error) {
    // 开启事务
    tx := global.DB.Begin()
    if tx.Error != nil {
        return nil, status.Error(codes.Internal, "开启事务失败")
    }

    // 遍历所有商品
    for _, goodsInfo := range req.GoodsInvInfo {
        var inv model.Inventory
        // 使用行锁查询
        result := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
            Where("goods_id = ?", goodsInfo.GoodsId).
            First(&inv)

        // 检查库存是否充足
        if inv.Stock < goodsInfo.Num {
            tx.Rollback()
            return nil, status.Error(codes.ResourceExhausted, "库存不足")
        }

        // 使用乐观锁更新库存
        updateResult := tx.Model(&model.Inventory{}).
            Where("goods_id = ? AND version = ?", goodsInfo.GoodsId, inv.Version).
            Updates(map[string]interface{}{
                "stock":   inv.Stock - goodsInfo.Num,
                "version": inv.Version + 1,
            })
    }

    // 提交事务
    if err := tx.Commit().Error; err != nil {
        return nil, status.Error(codes.Internal, "提交事务失败")
    }
    return &emptypb.Empty{}, nil
}

4.2 Distributed Locks to Solve Concurrency Issues

// 基于Redis的分布式锁
func (s *InventoryServer) SellWithDistributedLock(ctx context.Context, req *proto.SellInfo) (*emptypb.Empty, error) {
    // 获取分布式锁
    lockKey := fmt.Sprintf("inventory_lock_%d", req.GoodsInvInfo[0].GoodsId)
    lock := s.redisClient.NewMutex(lockKey, time.Second*10)

    if err := lock.Lock(); err != nil {
        return nil, status.Error(codes.Internal, "获取锁失败")
    }
    defer lock.Unlock()

    // 执行库存扣减逻辑
    return s.Sell(ctx, req)
}

5. Business Scenario Analysis

5.1 Order Placement Without Payment Issue

Problem: Users place orders but do not pay, leading to inventory being held.

Solutions:

  1. Order Timeout Mechanism: Set an order timeout period, after which the order is automatically canceled.
  2. Inventory Pre-occupation: Pre-occupy inventory when an order is placed, confirm deduction after successful payment.
  3. Scheduled Task: Periodically clean up timed-out orders and release inventory.

5.2 Insufficient Inventory During Payment Issue

Problem: Inventory is sufficient when the order is placed, but insufficient at the time of payment.

Solutions:

  1. Inventory Pre-occupation: Pre-occupy inventory when an order is placed to prevent overselling.
  2. Re-check at Payment: Re-verify inventory before payment.
  3. Compensation Mechanism: Provide alternative solutions when inventory is insufficient.

6. Best Practices

  1. Judicious Use of Transactions: Avoid long transactions to reduce lock contention.
  2. Choose Appropriate Isolation Levels: Select based on business requirements.
  3. Use Optimistic Locking: Reduce lock contention and improve concurrency performance.
  4. Implement Retry Mechanisms: Handle transient failures.
  5. Monitoring and Alerting: Timely detection and handling of issues.

7. Summary

Transactions and distributed transactions are important mechanisms for ensuring data consistency. In a microservice architecture,

主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://walker-learn.xyz/archives/4786

(0)
Walker的头像Walker
上一篇 Nov 25, 2025 13:00
下一篇 Nov 25, 2025 11:00

Related Posts

  • In-depth Understanding of ES6 001 [Study Notes]

    Block-Level Scope Binding
    Previously, `var` variable declarations, regardless of where they were declared, were considered to be declared at the top of their scope. Since functions are first-class citizens, the typical order was `function functionName()`, followed by `var variable`.

    Block-Level Declarations
    Block-level declarations are used to declare variables that cannot be accessed outside the scope of a specified block. Block-level scope exists in:
    - Inside functions
    - Within blocks (the region between `{` and `}`)

    Temporal Dead Zone
    When the JavaScript engine scans code and finds variable declarations, it either hoists them to the top of the scope...

    Personal Mar 8, 2025
    1.6K00
  • In-depth Understanding of ES6 011 [Learning Notes]

    Promises and Asynchronous Programming

    Because the execution engine is single-threaded, it needs to track the code that is about to run. This code is placed in a task queue. Whenever a piece of code is ready to execute, it is added to the task queue, and whenever a piece of code in the engine finishes execution, the event loop executes the next task in the queue. A Promise acts as a placeholder for the result of an asynchronous operation. It doesn't subscribe to an event or pass a callback function to the target function. Instead, it allows the function to return a Promise, like this...

    Personal Mar 8, 2025
    1.1K00
  • In-depth Understanding of ES6 007 [Study Notes]

    Set and Map Collections. In JS, there is an `in` operator that can determine if a property exists in an object without needing to read the object's value, returning true if it exists. However, the `in` operator also checks the object's prototype chain, so using this method is only relatively safe when the object's prototype is null. Set Collection: `let set = new Set()` `set.add(5)` `set.add("5")` `console.log(s…`

    Personal Mar 8, 2025
    1.2K00
  • Go Engineer's Comprehensive Course 017: Learning Notes

    Introduction to Rate Limiting, Circuit Breaking, and Degradation (with Sentinel Practical Application)
    Based on the key video points from Chapter 3 (3-1 to 3-9) of the courseware, this guide compiles a service protection introduction for beginners, helping them understand "why rate limiting, circuit breaking, and degradation are needed," and how to quickly get started with Sentinel.
    Learning Path at a Glance
    3-1 Understanding Service Avalanche and the Background of Rate Limiting, Circuit Breaking, and Degradation
    3-2 Comparing Sentinel and Hystrix to clarify technology selection
    3-3 Sen...

    Personal Nov 25, 2025
    18000
  • In-depth Understanding of ES6 013 [Study Notes]

    Code Encapsulation with Modules

    JavaScript loads code using a "share everything" approach to loading code, which is one of the most error-prone and confusing aspects of the language. Other languages use concepts like packages to define code scope. Before ES6, everything defined in every JavaScript file within an application shared a single global scope. As web applications became more complex and the amount of JavaScript code grew, this practice led to issues such as naming conflicts and security concerns. One of ES6's goals was to address the scoping issue…

    Personal Mar 8, 2025
    1.1K00
EN
简体中文 繁體中文 English
欢迎🌹 Coding never stops, keep learning! 💡💻 光临🌹