乐观锁 - 状态机流转控制
目录
1. 概述
1.1 什么是状态机?
状态机(State Machine)是一种数学模型,用于描述系统在其生命周期中经历的各种状态以及状态之间的转换规则。在业务系统中,状态机广泛应用于订单、支付、工作流等场景。
核心概念:
- 状态(State):系统在某一时刻的状态
- 事件(Event):触发状态转换的动作或条件
- 转换(Transition):从一个状态到另一个状态的变迁
- 守卫条件(Guard Condition):状态转换的前置条件
- 动作(Action):状态转换时执行的操作
1.2 为什么需要状态机 + 乐观锁?
在并发场景下,多个请求可能同时尝试修改同一资源的状态,导致:
- 重复处理:同一订单被多次发货
- 状态跳跃:跳过中间关键状态
- 数据不一致:状态与业务数据不匹配
解决方案:
- 使用状态机定义合法的状态转换路径
- 使用乐观锁保证状态转换的原子性
- 两者结合确保并发安全 + 业务正确性
1.3 典型应用场景
| 场景 | 状态流转示例 | 并发风险 |
|---|---|---|
| 订单系统 | 待支付 → 已支付 → 已发货 → 已完成 | 重复发货、状态回退 |
| 支付系统 | 初始化 → 处理中 → 成功/失败 | 重复扣款、状态不一致 |
| 审批流程 | 草稿 → 审批中 → 通过/驳回 | 多人同时审批 |
| 库存管理 | 可用 → 预占 → 已出库 | 超卖、库存负数 |
2. 状态机设计基础
2.1 状态枚举定义
csharp
/// <summary>
/// 订单状态枚举
/// </summary>
public enum OrderStatus
{
/// <summary>
/// 待支付
/// </summary>
Pending = 0,
/// <summary>
/// 已支付
/// </summary>
Paid = 1,
/// <summary>
/// 已发货
/// </summary>
Shipped = 2,
/// <summary>
/// 已完成
/// </summary>
Completed = 3,
/// <summary>
/// 已取消
/// </summary>
Cancelled = -1,
/// <summary>
/// 退款中
/// </summary>
Refunding = -2,
/// <summary>
/// 已退款
/// </summary>
Refunded = -3
}
/// <summary>
/// 订单事件枚举
/// </summary>
public enum OrderEvent
{
Pay, // 支付
Ship, // 发货
Confirm, // 确认收货
Cancel, // 取消订单
RequestRefund, // 申请退款
ApproveRefund, // 批准退款
CompleteRefund // 完成退款
}2.2 状态转换规则定义
csharp
/// <summary>
/// 状态转换规则
/// </summary>
public class StateTransitionRule
{
/// <summary>
/// 起始状态
/// </summary>
public OrderStatus FromState { get; set; }
/// <summary>
/// 目标状态
/// </summary>
public OrderStatus ToState { get; set; }
/// <summary>
/// 触发事件
/// </summary>
public OrderEvent TriggerEvent { get; set; }
/// <summary>
/// 守卫条件(可选)
/// </summary>
public Func<Order, bool> GuardCondition { get; set; }
/// <summary>
/// 转换前执行的动作
/// </summary>
public Action<Order> BeforeTransition { get; set; }
/// <summary>
/// 转换后执行的动作
/// </summary>
public Action<Order> AfterTransition { get; set; }
}
/// <summary>
/// 状态机配置
/// </summary>
public class StateMachineConfig
{
private readonly Dictionary<(OrderStatus, OrderEvent), StateTransitionRule> _rules = new();
public void AddRule(StateTransitionRule rule)
{
var key = (rule.FromState, rule.TriggerEvent);
_rules[key] = rule;
}
public bool TryGetRule(OrderStatus fromState, OrderEvent triggerEvent, out StateTransitionRule rule)
{
return _rules.TryGetValue((fromState, triggerEvent), out rule);
}
/// <summary>
/// 验证状态转换是否合法
/// </summary>
public bool IsValidTransition(OrderStatus fromState, OrderEvent triggerEvent)
{
return _rules.ContainsKey((fromState, triggerEvent));
}
}2.3 构建状态转换图
csharp
public class OrderStateMachineConfigurator
{
public static StateMachineConfig Configure()
{
var config = new StateMachineConfig();
// 待支付 -> 已支付
config.AddRule(new StateTransitionRule
{
FromState = OrderStatus.Pending,
ToState = OrderStatus.Paid,
TriggerEvent = OrderEvent.Pay,
GuardCondition = order => order.PaymentAmount > 0,
BeforeTransition = order =>
{
order.PaidAt = DateTime.UtcNow;
order.StatusMessage = "订单已支付";
},
AfterTransition = order =>
{
// 发送支付成功通知
Console.WriteLine($"订单 {order.Id} 支付成功");
}
});
// 已支付 -> 已发货
config.AddRule(new StateTransitionRule
{
FromState = OrderStatus.Paid,
ToState = OrderStatus.Shipped,
TriggerEvent = OrderEvent.Ship,
GuardCondition = order => order.InventoryReserved,
BeforeTransition = order =>
{
order.ShippedAt = DateTime.UtcNow;
order.StatusMessage = "订单已发货";
},
AfterTransition = order =>
{
// 发送发货通知
Console.WriteLine($"订单 {order.Id} 已发货");
}
});
// 已发货 -> 已完成
config.AddRule(new StateTransitionRule
{
FromState = OrderStatus.Shipped,
ToState = OrderStatus.Completed,
TriggerEvent = OrderEvent.Confirm,
BeforeTransition = order =>
{
order.CompletedAt = DateTime.UtcNow;
order.StatusMessage = "订单已完成";
}
});
// 待支付 -> 已取消
config.AddRule(new StateTransitionRule
{
FromState = OrderStatus.Pending,
ToState = OrderStatus.Cancelled,
TriggerEvent = OrderEvent.Cancel,
BeforeTransition = order =>
{
order.CancelledAt = DateTime.UtcNow;
order.StatusMessage = "订单已取消";
},
AfterTransition = order =>
{
// 释放库存
Console.WriteLine($"订单 {order.Id} 已取消,释放库存");
}
});
// 已支付 -> 退款中
config.AddRule(new StateTransitionRule
{
FromState = OrderStatus.Paid,
ToState = OrderStatus.Refunding,
TriggerEvent = OrderEvent.RequestRefund,
BeforeTransition = order =>
{
order.RefundRequestedAt = DateTime.UtcNow;
order.StatusMessage = "退款申请处理中";
}
});
// 退款中 -> 已退款
config.AddRule(new StateTransitionRule
{
FromState = OrderStatus.Refunding,
ToState = OrderStatus.Refunded,
TriggerEvent = OrderEvent.ApproveRefund,
BeforeTransition = order =>
{
order.RefundedAt = DateTime.UtcNow;
order.StatusMessage = "订单已退款";
},
AfterTransition = order =>
{
// 执行退款
Console.WriteLine($"订单 {order.Id} 退款完成");
}
});
return config;
}
}3. 数据库层面的状态机实现
3.1 订单表设计(PostgreSQL)
sql
-- 创建订单表
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
order_no VARCHAR(50) NOT NULL UNIQUE,
user_id UUID NOT NULL,
-- 状态字段
status SMALLINT NOT NULL DEFAULT 0, -- 对应 OrderStatus 枚举
version INTEGER NOT NULL DEFAULT 1, -- 乐观锁版本号
-- 金额信息
payment_amount DECIMAL(10, 2) NOT NULL DEFAULT 0,
-- 库存信息
inventory_reserved BOOLEAN NOT NULL DEFAULT FALSE,
-- 时间戳
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
paid_at TIMESTAMP WITH TIME ZONE,
shipped_at TIMESTAMP WITH TIME ZONE,
completed_at TIMESTAMP WITH TIME ZONE,
cancelled_at TIMESTAMP WITH TIME ZONE,
refund_requested_at TIMESTAMP WITH TIME ZONE,
refunded_at TIMESTAMP WITH TIME ZONE,
-- 状态消息
status_message TEXT,
-- 审计字段
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_by VARCHAR(100)
);
-- 创建索引
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_order_no ON orders(order_no);
CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_orders_created_at ON orders(created_at DESC);
-- 创建复合索引(查询用户订单列表)
CREATE INDEX idx_orders_user_status_created ON orders(user_id, status, created_at DESC);
-- 添加约束:状态值范围检查
ALTER TABLE orders ADD CONSTRAINT chk_order_status
CHECK (status IN (-3, -2, -1, 0, 1, 2, 3));
-- 添加约束:状态转换合理性检查(部分)
ALTER TABLE orders ADD CONSTRAINT chk_paid_requires_payment
CHECK (status != 1 OR payment_amount > 0);
-- 注释
COMMENT ON COLUMN orders.status IS '订单状态: -3=已退款, -2=退款中, -1=已取消, 0=待支付, 1=已支付, 2=已发货, 3=已完成';
COMMENT ON COLUMN orders.version IS '乐观锁版本号,用于并发控制';3.2 状态转换日志表
sql
-- 创建状态转换日志表
CREATE TABLE order_status_history (
id BIGSERIAL PRIMARY KEY,
order_id UUID NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
-- 状态转换信息
from_status SMALLINT NOT NULL,
to_status SMALLINT NOT NULL,
event VARCHAR(50) NOT NULL, -- 触发事件
-- 执行信息
executed_by VARCHAR(100),
executed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
-- 附加信息
metadata JSONB, -- 额外上下文信息
error_message TEXT,
-- 乐观锁版本号变化
old_version INTEGER NOT NULL,
new_version INTEGER NOT NULL
);
-- 创建索引
CREATE INDEX idx_order_status_history_order_id ON order_status_history(order_id);
CREATE INDEX idx_order_status_history_executed_at ON order_status_history(executed_at DESC);
-- 注释
COMMENT ON TABLE order_status_history IS '订单状态转换历史日志';3.3 使用存储过程保证原子性
sql
-- 创建状态转换函数
CREATE OR REPLACE FUNCTION update_order_status(
p_order_id UUID,
p_from_status SMALLINT,
p_to_status SMALLINT,
p_event VARCHAR(50),
p_executed_by VARCHAR(100),
p_metadata JSONB DEFAULT NULL
)
RETURNS TABLE(success BOOLEAN, new_version INTEGER, error_message TEXT) AS $$
DECLARE
v_current_version INTEGER;
v_current_status SMALLINT;
v_new_version INTEGER;
BEGIN
-- 开启事务
BEGIN
-- 获取当前状态和版本号(加行锁)
SELECT status, version
INTO v_current_status, v_current_version
FROM orders
WHERE id = p_order_id
FOR UPDATE;
-- 检查订单是否存在
IF NOT FOUND THEN
RETURN QUERY SELECT FALSE, 0::INTEGER, 'Order not found'::TEXT;
RETURN;
END IF;
-- 检查状态是否匹配
IF v_current_status != p_from_status THEN
RETURN QUERY SELECT FALSE, v_current_version,
format('Status mismatch: expected %s, actual %s', p_from_status, v_current_status);
RETURN;
END IF;
-- 计算新版本号
v_new_version := v_current_version + 1;
-- 更新订单状态
UPDATE orders
SET
status = p_to_status,
version = v_new_version,
updated_at = NOW(),
updated_by = p_executed_by,
-- 根据目标状态设置相应的时间戳
paid_at = CASE WHEN p_to_status = 1 THEN NOW() ELSE paid_at END,
shipped_at = CASE WHEN p_to_status = 2 THEN NOW() ELSE shipped_at END,
completed_at = CASE WHEN p_to_status = 3 THEN NOW() ELSE completed_at END,
cancelled_at = CASE WHEN p_to_status = -1 THEN NOW() ELSE cancelled_at END,
refunded_at = CASE WHEN p_to_status = -3 THEN NOW() ELSE refunded_at END
WHERE id = p_order_id AND version = v_current_version;
-- 记录状态转换历史
INSERT INTO order_status_history (
order_id, from_status, to_status, event,
executed_by, executed_at, metadata, old_version, new_version
) VALUES (
p_order_id, p_from_status, p_to_status, p_event,
p_executed_by, NOW(), p_metadata, v_current_version, v_new_version
);
-- 返回成功
RETURN QUERY SELECT TRUE, v_new_version, ''::TEXT;
EXCEPTION
WHEN OTHERS THEN
RETURN QUERY SELECT FALSE, 0::INTEGER, SQLERRM;
END;
END;
$$ LANGUAGE plpgsql;
-- 使用示例
SELECT * FROM update_order_status(
'123e4567-e89b-12d3-a456-426614174000'::UUID,
0, -- from_status: Pending
1, -- to_status: Paid
'Pay',
'system',
'{"payment_method": "credit_card"}'::JSONB
);3.4 使用触发器自动记录状态变更
sql
-- 创建触发器函数
CREATE OR REPLACE FUNCTION log_order_status_change()
RETURNS TRIGGER AS $$
BEGIN
-- 如果状态发生变化,自动记录到历史表
IF OLD.status != NEW.status THEN
INSERT INTO order_status_history (
order_id, from_status, to_status, event,
executed_by, executed_at, old_version, new_version
) VALUES (
NEW.id, OLD.status, NEW.status, 'AUTO_LOG',
COALESCE(NEW.updated_by, 'system'),
NOW(),
OLD.version,
NEW.version
);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 创建触发器
CREATE TRIGGER trg_order_status_change
AFTER UPDATE ON orders
FOR EACH ROW
WHEN (OLD.status != NEW.status)
EXECUTE FUNCTION log_order_status_change();4. C# 状态机实现
4.1 订单实体类
csharp
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Idempotency.OrderManagement.Models
{
[Table("orders")]
public class Order
{
[Key]
[Column("id")]
public Guid Id { get; set; }
[Required]
[Column("order_no")]
[StringLength(50)]
public string OrderNo { get; set; }
[Column("user_id")]
public Guid UserId { get; set; }
[Column("status")]
public OrderStatus Status { get; set; }
[ConcurrencyCheck]
[Column("version")]
public int Version { get; set; }
[Column("payment_amount")]
public decimal PaymentAmount { get; set; }
[Column("inventory_reserved")]
public bool InventoryReserved { get; set; }
[Column("created_at")]
public DateTime CreatedAt { get; set; }
[Column("paid_at")]
public DateTime? PaidAt { get; set; }
[Column("shipped_at")]
public DateTime? ShippedAt { get; set; }
[Column("completed_at")]
public DateTime? CompletedAt { get; set; }
[Column("cancelled_at")]
public DateTime? CancelledAt { get; set; }
[Column("refund_requested_at")]
public DateTime? RefundRequestedAt { get; set; }
[Column("refunded_at")]
public DateTime? RefundedAt { get; set; }
[Column("status_message")]
public string? StatusMessage { get; set; }
[Column("updated_at")]
public DateTime UpdatedAt { get; set; }
[Column("updated_by")]
public string? UpdatedBy { get; set; }
// 导航属性
public ICollection<OrderStatusHistory> StatusHistories { get; set; } = new List<OrderStatusHistory>();
}
}4.2 状态转换结果
csharp
namespace Idempotency.OrderManagement.Core
{
/// <summary>
/// 状态转换结果
/// </summary>
public class TransitionResult
{
/// <summary>
/// 是否成功
/// </summary>
public bool Success { get; set; }
/// <summary>
/// 新版本号
/// </summary>
public int NewVersion { get; set; }
/// <summary>
/// 错误消息
/// </summary>
public string? ErrorMessage { get; set; }
/// <summary>
/// 转换前的状态
/// </summary>
public OrderStatus FromStatus { get; set; }
/// <summary>
/// 转换后的状态
/// </summary>
public OrderStatus ToStatus { get; set; }
/// <summary>
/// 触发的事件
/// </summary>
public OrderEvent TriggerEvent { get; set; }
public static TransitionResult CreateSuccess(int newVersion, OrderStatus fromStatus, OrderStatus toStatus, OrderEvent triggerEvent)
{
return new TransitionResult
{
Success = true,
NewVersion = newVersion,
FromStatus = fromStatus,
ToStatus = toStatus,
TriggerEvent = triggerEvent
};
}
public static TransitionResult CreateFailure(string errorMessage, OrderStatus currentStatus, OrderEvent triggerEvent)
{
return new TransitionResult
{
Success = false,
ErrorMessage = errorMessage,
FromStatus = currentStatus,
ToStatus = currentStatus,
TriggerEvent = triggerEvent
};
}
}
}4.3 状态机服务实现
csharp
using Microsoft.EntityFrameworkCore;
using Idempotency.OrderManagement.Models;
using Idempotency.OrderManagement.Core;
namespace Idempotency.OrderManagement.Services
{
/// <summary>
/// 订单状态机服务
/// </summary>
public interface IOrderStateMachineService
{
/// <summary>
/// 执行状态转换
/// </summary>
Task<TransitionResult> TransitionAsync(Guid orderId, OrderEvent triggerEvent, string executedBy, object? metadata = null);
/// <summary>
/// 验证状态转换是否合法
/// </summary>
bool CanTransition(Guid orderId, OrderEvent triggerEvent);
/// <summary>
/// 获取当前状态可触发的事件列表
/// </summary>
List<OrderEvent> GetAvailableEvents(Guid orderId);
}
public class OrderStateMachineService : IOrderStateMachineService
{
private readonly OrderDbContext _dbContext;
private readonly StateMachineConfig _config;
private readonly ILogger<OrderStateMachineService> _logger;
public OrderStateMachineService(
OrderDbContext dbContext,
ILogger<OrderStateMachineService> logger)
{
_dbContext = dbContext;
_logger = logger;
_config = OrderStateMachineConfigurator.Configure();
}
/// <summary>
/// 执行状态转换(带乐观锁)
/// </summary>
public async Task<TransitionResult> TransitionAsync(
Guid orderId,
OrderEvent triggerEvent,
string executedBy,
object? metadata = null)
{
using var transaction = await _dbContext.Database.BeginTransactionAsync();
try
{
// 1. 查询订单并加锁(悲观锁,防止并发读取)
var order = await _dbContext.Orders
.Where(o => o.Id == orderId)
.FirstOrDefaultAsync();
if (order == null)
{
return TransitionResult.CreateFailure(
"Order not found",
OrderStatus.Pending,
triggerEvent);
}
var fromStatus = order.Status;
// 2. 验证状态转换是否合法
if (!_config.TryGetRule(fromStatus, triggerEvent, out var rule))
{
return TransitionResult.CreateFailure(
$"Invalid transition: {triggerEvent} from {fromStatus}",
fromStatus,
triggerEvent);
}
// 3. 检查守卫条件
if (rule.GuardCondition != null && !rule.GuardCondition(order))
{
return TransitionResult.CreateFailure(
$"Guard condition failed for transition: {triggerEvent}",
fromStatus,
triggerEvent);
}
// 4. 执行转换前动作
rule.BeforeTransition?.Invoke(order);
// 5. 更新状态和版本号(乐观锁)
var oldVersion = order.Version;
order.Status = rule.ToState;
order.Version++;
order.UpdatedAt = DateTime.UtcNow;
order.UpdatedBy = executedBy;
// 6. 保存更改(EF Core 会自动在 WHERE 中添加版本号检查)
var affectedRows = await _dbContext.SaveChangesAsync();
if (affectedRows == 0)
{
await transaction.RollbackAsync();
return TransitionResult.CreateFailure(
"Concurrent modification detected, please retry",
fromStatus,
triggerEvent);
}
// 7. 记录状态转换历史
var history = new OrderStatusHistory
{
OrderId = orderId,
FromStatus = fromStatus,
ToStatus = rule.ToState,
Event = triggerEvent.ToString(),
ExecutedBy = executedBy,
ExecutedAt = DateTime.UtcNow,
Metadata = metadata != null ? System.Text.Json.JsonSerializer.SerializeToElement(metadata) : null,
OldVersion = oldVersion,
NewVersion = order.Version
};
await _dbContext.StatusHistories.AddAsync(history);
await _dbContext.SaveChangesAsync();
// 8. 执行转换后动作
rule.AfterTransition?.Invoke(order);
await transaction.CommitAsync();
_logger.LogInformation(
"Order {OrderId} status changed from {FromStatus} to {ToStatus} by event {Event}, version: {OldVersion} -> {NewVersion}",
orderId, fromStatus, rule.ToState, triggerEvent, oldVersion, order.Version);
return TransitionResult.CreateSuccess(
order.Version,
fromStatus,
rule.ToState,
triggerEvent);
}
catch (DbUpdateConcurrencyException ex)
{
await transaction.RollbackAsync();
_logger.LogWarning(ex, "Concurrency conflict when updating order {OrderId}", orderId);
return TransitionResult.CreateFailure(
"Concurrent modification detected",
OrderStatus.Pending,
triggerEvent);
}
catch (Exception ex)
{
await transaction.RollbackAsync();
_logger.LogError(ex, "Error transitioning order {OrderId} with event {Event}", orderId, triggerEvent);
return TransitionResult.CreateFailure(
$"Internal error: {ex.Message}",
OrderStatus.Pending,
triggerEvent);
}
}
/// <summary>
/// 验证状态转换是否合法
/// </summary>
public async Task<bool> CanTransition(Guid orderId, OrderEvent triggerEvent)
{
var order = await _dbContext.Orders.FindAsync(orderId);
if (order == null) return false;
return _config.IsValidTransition(order.Status, triggerEvent);
}
/// <summary>
/// 获取当前状态可触发的事件列表
/// </summary>
public async Task<List<OrderEvent>> GetAvailableEvents(Guid orderId)
{
var order = await _dbContext.Orders.FindAsync(orderId);
if (order == null) return new List<OrderEvent>();
var availableEvents = new List<OrderEvent>();
foreach (var kvp in _config.GetAllRules())
{
if (kvp.Key.Item1 == order.Status)
{
// 检查守卫条件
if (kvp.Value.GuardCondition == null || kvp.Value.GuardCondition(order))
{
availableEvents.Add(kvp.Key.Item2);
}
}
}
return availableEvents;
}
}
}4.4 使用 ASP.NET Minimal API 暴露接口
csharp
using Idempotency.OrderManagement.Core;
using Idempotency.OrderManagement.Services;
var builder = WebApplication.CreateBuilder(args);
// 注册服务
builder.Services.AddDbContext<OrderDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("Default")));
builder.Services.AddScoped<IOrderStateMachineService, OrderStateMachineService>();
var app = builder.Build();
// 执行状态转换
app.MapPost("/api/orders/{orderId}/events", async (
Guid orderId,
OrderEventRequest request,
IOrderStateMachineService stateMachineService) =>
{
var result = await stateMachineService.TransitionAsync(
orderId,
request.Event,
request.ExecutedBy ?? "system",
request.Metadata);
if (!result.Success)
{
return Results.BadRequest(result);
}
return Results.Ok(result);
})
.WithName("TriggerOrderEvent")
.WithOpenApi();
// 查询可用事件
app.MapGet("/api/orders/{orderId}/available-events", async (
Guid orderId,
IOrderStateMachineService stateMachineService) =>
{
var events = await stateMachineService.GetAvailableEvents(orderId);
return Results.Ok(events);
})
.WithName("GetAvailableEvents")
.WithOpenApi();
app.Run();
public record OrderEventRequest(
OrderEvent Event,
string? ExecutedBy,
object? Metadata = null
);5. 并发场景下的状态机优化
5.1 重试机制(处理乐观锁冲突)
csharp
public class ResilientOrderStateMachineService : IOrderStateMachineService
{
private readonly IOrderStateMachineService _innerService;
private readonly Polly.IAsyncPolicy<TransitionResult> _retryPolicy;
private readonly ILogger<ResilientOrderStateMachineService> _logger;
public ResilientOrderStateMachineService(
IOrderStateMachineService innerService,
ILogger<ResilientOrderStateMachineService> logger)
{
_innerService = innerService;
_logger = logger;
// 配置重试策略:最多重试3次,指数退避
_retryPolicy = Polly.Policy<TransitionResult>
.HandleResult(r => !r.Success && r.ErrorMessage?.Contains("Concurrent") == true)
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: retryAttempt => TimeSpan.FromMilliseconds(Math.Pow(2, retryAttempt) * 100),
onRetry: (result, timeSpan, retryCount, context) =>
{
_logger.LogWarning(
"Retry {RetryCount} after concurrent conflict, waiting {TimeSpan}ms",
retryCount, timeSpan.TotalMilliseconds);
});
}
public async Task<TransitionResult> TransitionAsync(
Guid orderId,
OrderEvent triggerEvent,
string executedBy,
object? metadata = null)
{
return await _retryPolicy.ExecuteAsync(async () =>
{
return await _innerService.TransitionAsync(orderId, triggerEvent, executedBy, metadata);
});
}
public async Task<bool> CanTransition(Guid orderId, OrderEvent triggerEvent)
{
return await _innerService.CanTransition(orderId, triggerEvent);
}
public async Task<List<OrderEvent>> GetAvailableEvents(Guid orderId)
{
return await _innerService.GetAvailableEvents(orderId);
}
}5.2 批量状态转换(减少数据库往返)
csharp
public class BatchOrderStateMachineService
{
private readonly OrderDbContext _dbContext;
private readonly StateMachineConfig _config;
public async Task<List<TransitionResult>> BatchTransitionAsync(
List<OrderTransitionRequest> requests,
string executedBy)
{
var results = new List<TransitionResult>();
using var transaction = await _dbContext.Database.BeginTransactionAsync();
try
{
// 批量加载订单
var orderIds = requests.Select(r => r.OrderId).Distinct().ToList();
var orders = await _dbContext.Orders
.Where(o => orderIds.Contains(o.Id))
.ToDictionaryAsync(o => o.Id);
foreach (var request in requests)
{
if (!orders.TryGetValue(request.OrderId, out var order))
{
results.Add(TransitionResult.CreateFailure(
"Order not found", OrderStatus.Pending, request.Event));
continue;
}
// 验证并执行转换...
// (省略与单个转换相同的逻辑)
}
// 批量保存
await _dbContext.SaveChangesAsync();
await transaction.CommitAsync();
}
catch (Exception ex)
{
await transaction.RollbackAsync();
// 处理异常
}
return results;
}
}
public record OrderTransitionRequest(Guid OrderId, OrderEvent Event, object? Metadata = null);5.3 缓存状态机配置(提升性能)
csharp
public class CachedStateMachineService
{
private static readonly Lazy<StateMachineConfig> _configLazy = new Lazy<StateMachineConfig>(() =>
{
return OrderStateMachineConfigurator.Configure();
});
private static StateMachineConfig Config => _configLazy.Value;
private readonly IMemoryCache _cache;
private readonly OrderDbContext _dbContext;
public CachedStateMachineService(IMemoryCache cache, OrderDbContext dbContext)
{
_cache = cache;
_dbContext = dbContext;
}
public async Task<Order?> GetOrderWithCacheAsync(Guid orderId)
{
var cacheKey = $"order:{orderId}";
return await _cache.GetOrCreateAsync(cacheKey, async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);
return await _dbContext.Orders.FindAsync(orderId);
});
}
public async Task InvalidateOrderCacheAsync(Guid orderId)
{
var cacheKey = $"order:{orderId}";
_cache.Remove(cacheKey);
}
}6. 分布式系统中的状态机
6.1 使用 Redis 分布式锁
csharp
using StackExchange.Redis;
public class DistributedOrderStateMachineService
{
private readonly IConnectionMultiplexer _redis;
private readonly OrderDbContext _dbContext;
private readonly StateMachineConfig _config;
private readonly ILogger<DistributedOrderStateMachineService> _logger;
public DistributedOrderStateMachineService(
IConnectionMultiplexer redis,
OrderDbContext dbContext,
ILogger<DistributedOrderStateMachineService> logger)
{
_redis = redis;
_dbContext = dbContext;
_config = OrderStateMachineConfigurator.Configure();
_logger = logger;
}
public async Task<TransitionResult> TransitionWithLockAsync(
Guid orderId,
OrderEvent triggerEvent,
string executedBy,
TimeSpan? lockTimeout = null,
object? metadata = null)
{
var db = _redis.GetDatabase();
var lockKey = $"lock:order:{orderId}";
var lockValue = Guid.NewGuid().ToString();
var lockAcquired = false;
try
{
// 尝试获取分布式锁
lockAcquired = await db.StringSetAsync(
lockKey,
lockValue,
expiry: lockTimeout ?? TimeSpan.FromSeconds(10),
when: When.NotExists);
if (!lockAcquired)
{
return TransitionResult.CreateFailure(
"Failed to acquire lock, another operation is in progress",
OrderStatus.Pending,
triggerEvent);
}
// 在锁保护下执行状态转换
return await ExecuteTransitionAsync(orderId, triggerEvent, executedBy, metadata);
}
finally
{
// 释放锁(只有持有者才能释放)
if (lockAcquired)
{
var currentValue = await db.StringGetAsync(lockKey);
if (currentValue == lockValue)
{
await db.KeyDeleteAsync(lockKey);
}
}
}
}
private async Task<TransitionResult> ExecuteTransitionAsync(
Guid orderId,
OrderEvent triggerEvent,
string executedBy,
object? metadata)
{
// 实现与第4节相同的状态转换逻辑
// ...
throw new NotImplementedException();
}
}6.2 Saga 模式中的状态机
csharp
public class OrderSagaStateMachine
{
private readonly IOrderService _orderService;
private readonly IPaymentService _paymentService;
private readonly IInventoryService _inventoryService;
public async Task ExecuteSagaAsync(CreateOrderRequest request)
{
var sagaId = Guid.NewGuid();
var compensations = new Stack<Func<Task>>();
try
{
// Step 1: 创建订单(Pending)
var order = await _orderService.CreateOrderAsync(request);
compensations.Push(async () => await _orderService.CancelOrderAsync(order.Id));
// Step 2: 转换到 Paid(调用支付服务)
var paymentResult = await _paymentService.ProcessPaymentAsync(order.Id, order.PaymentAmount);
if (!paymentResult.Success)
{
throw new Exception("Payment failed");
}
await _orderService.TransitionAsync(order.Id, OrderEvent.Pay, "saga");
compensations.Push(async () => await _paymentService.RefundAsync(paymentResult.PaymentId));
// Step 3: 预占库存
var stockResult = await _inventoryService.ReserveStockAsync(order.Items);
if (!stockResult.Success)
{
throw new Exception("Insufficient stock");
}
await _orderService.UpdateInventoryReservedAsync(order.Id, true);
compensations.Push(async () => await _inventoryService.ReleaseStockAsync(order.Items));
// Step 4: 转换到 Shipped
await _orderService.TransitionAsync(order.Id, OrderEvent.Ship, "saga");
// Saga 完成
_logger.LogInformation("Saga {SagaId} completed successfully", sagaId);
}
catch (Exception ex)
{
_logger.LogError(ex, "Saga {SagaId} failed, executing compensations", sagaId);
// 执行补偿操作(反向状态转换)
foreach (var compensation in compensations)
{
try
{
await compensation();
}
catch (Exception compEx)
{
_logger.LogError(compEx, "Compensation failed in saga {SagaId}", sagaId);
}
}
throw;
}
}
}7. 最佳实践与常见问题
7.1 状态机设计原则
✅ 推荐做法
明确定义状态转换规则
csharp// 清晰的规则定义 config.AddRule(new StateTransitionRule { FromState = OrderStatus.Paid, ToState = OrderStatus.Shipped, TriggerEvent = OrderEvent.Ship });使用守卫条件保护转换
csharpGuardCondition = order => order.PaymentAmount > 0 && order.InventoryReserved记录完整的状态转换历史
sqlINSERT INTO order_status_history (...) VALUES (...);使用乐观锁处理并发
csharp[ConcurrencyCheck] public int Version { get; set; }实现重试机制
csharp_retryPolicy.ExecuteAsync(async () => await TransitionAsync(...));
❌ 避免的做法
直接修改状态字段
csharp// ❌ 错误:绕过状态机 order.Status = OrderStatus.Shipped; await _dbContext.SaveChangesAsync();忽略并发冲突
csharp// ❌ 错误:没有版本检查 UPDATE orders SET status = 2 WHERE id = xxx;允许任意状态转换
csharp// ❌ 错误:没有验证 if (newStatus != currentStatus) { order.Status = newStatus; }
7.2 常见问题及解决方案
问题1:状态转换死锁
场景:两个订单互相依赖,形成死锁
解决方案:
csharp
// 按固定顺序获取锁(例如按订单ID排序)
var sortedOrders = orderIds.OrderBy(id => id).ToList();
foreach (var orderId in sortedOrders)
{
await AcquireLockAsync(orderId);
}问题2:长时间运行的状态转换
场景:状态转换涉及外部API调用,耗时过长
解决方案:
csharp
// 异步化处理
await _orderService.TransitionAsync(orderId, OrderEvent.Pay, "system");
// 立即返回,后台处理后续逻辑
_backgroundTaskQueue.QueueBackgroundWorkItem(async ct =>
{
await _notificationService.SendEmailAsync(orderId);
});问题3:状态回退如何处理?
场景:需要支持"撤销"操作
解决方案:
csharp
// 定义明确的回退规则
config.AddRule(new StateTransitionRule
{
FromState = OrderStatus.Shipped,
ToState = OrderStatus.Paid,
TriggerEvent = OrderEvent.Unship,
GuardCondition = order => !order.HasDeliveryConfirmation
});7.3 监控与告警
csharp
public class StateMachineMetrics
{
private readonly Counter<long> _transitionsTotal;
private readonly Counter<long> _transitionsFailed;
private readonly Histogram<double> _transitionDuration;
public async Task RecordTransitionAsync(
Guid orderId,
OrderEvent triggerEvent,
Func<Task<TransitionResult>> transitionFunc)
{
var stopwatch = Stopwatch.StartNew();
try
{
var result = await transitionFunc();
if (result.Success)
{
_transitionsTotal.Add(1);
}
else
{
_transitionsFailed.Add(1);
}
}
finally
{
stopwatch.Stop();
_transitionDuration.Record(stopwatch.Elapsed.TotalMilliseconds);
}
}
}7.4 性能优化建议
使用连接池
csharpservices.AddDbContext<OrderDbContext>(options => options.UseNpgsql(connectionString, builder => { builder.EnableRetryOnFailure(3); builder.CommandTimeout(30); }));批量操作
csharp// 使用 EF Core 批量更新 await _dbContext.Orders .Where(o => orderIds.Contains(o.Id)) .ExecuteUpdateAsync(setters => setters.SetProperty(o => o.Version, o => o.Version + 1));缓存热点数据
csharp// 使用 Redis 缓存订单状态 var cachedStatus = await cache.GetStringAsync($"order_status:{orderId}");
总结
状态机 + 乐观锁是解决并发场景下状态一致性问题的强大组合:
- 状态机:定义清晰的状态转换路径,防止非法操作
- 乐观锁:保证并发环境下的数据一致性,避免丢失更新
- 历史记录:完整的审计追踪,便于排查问题
- 重试机制:优雅处理并发冲突,提升用户体验
通过本文的实现方案,你可以构建一个高可靠、高性能的订单状态管理系统,能够应对各种并发挑战。