用 ASP.NET + Redis + PostgreSQL 實作購票系統的併發控制
前兩篇分別談了樂觀鎖與悲觀鎖的理論,以及購票系統各環節的設計決策,這篇將直接進入程式碼實作,把設計思路轉化成可以實際運行的實作。 資料模型 先定義三個核心 Entity,各自對應不同的鎖策略。 Ticket 代表票種與庫存,Entity 上掛的 RowVersion(對應 PostgreSQL 的 xmin 系統欄位)是樂觀鎖機制,用於後台修改票種名稱、調整票價等一般更新場景。 Order 代表訂單,其中狀態流轉使用樂觀鎖(Optimistic Lock),手動維護 Version 整數欄位,方便在 SQL 層直接用條件更新防止重複付款。 Seat 代表對號座的每一個座位,記錄目前是否被鎖定、被誰鎖定、以及鎖定到什麼時間;它的鎖定狀態由 Redis 的分散式鎖(Distributed Lock)主導,LockedByUserId 和 LockedUntil 則作為資料庫層的備份紀錄,用於查詢座位狀態或在 Redis 異常時做補救。 1// Entities/Ticket.cs 2public class Ticket 3{ 4 public int Id { get; set; } 5 public int EventId { get; set; } 6 public string TicketType { get; set; } = default!; 7 public int AvailableQty { get; set; } 8 9 // EF Core 樂觀鎖:使用 PostgreSQL xmin 系統欄位,免額外欄位 10 // xmin 是每次 row 被更新時自動遞增的系統欄位,完全不需要手動維護 11 [Timestamp] 12 public byte[] RowVersion { get; set; } = default!; 13} 14 15// Entities/Order.cs 16public class Order 17{ 18 public int Id { get; set; } 19 public string UserId { get; set; } = default!; 20 public OrderStatus Status { get; set; } 21 22 // 手動版本號,用於訂單狀態流轉的樂觀鎖 23 public int Version { get; set; } 24} 25 26// Entities/Seat.cs 27public class Seat 28{ 29 public string SeatId { get; set; } = default!; // e.g. "A-12" 30 public int EventId { get; set; } 31 public SeatStatus Status { get; set; } 32 public string? LockedByUserId { get; set; } 33 public DateTime? LockedUntil { get; set; } 34} 35 36public enum OrderStatus { Pending, Paid, Cancelled, Expired } 37public enum SeatStatus { Available, Locked, Sold } 1// Data/AppDbContext.cs 2public class AppDbContext : DbContext 3{ 4 public DbSet<Ticket> Tickets => Set<Ticket>(); 5 public DbSet<Order> Orders => Set<Order>(); 6 public DbSet<Seat> Seats => Set<Seat>(); 7 8 protected override void OnModelCreating(ModelBuilder builder) 9 { 10 // 將 RowVersion 對應到 PostgreSQL 的 xmin 欄位 11 builder.Entity<Ticket>() 12 .Property(t => t.RowVersion) 13 .IsRowVersion() 14 .HasColumnName("xmin") 15 .HasColumnType("xid"); 16 } 17} Redis 層:庫存預扣 單純使用 DECR 可能會有 Race Condition 的風險,在「讀取 → 判斷 → 扣減」三步驟之間可能插入其他操作;使用 Lua Script 則能夠確保 Redis 的原子執行,使得整個 check-and-decrement 變得不可分割。 ...