基於donetcore/CAP實現分佈式事務一致性

官網:https://cap.dotnetcore.xyz

相關介紹

CAP 是一個EventBus,同時也是一個在微服務或者SOA系統中解決分佈式事務問題的一個框架。它有助於創建可擴展,可靠並且易於更改的微服務系統。

在微軟的 eShop 微服務示例項目中,推薦使用 CAP 作爲生產環境可用的 EventBus。

什麼是 EventBus?

事件總線是一種機制,它允許不同的組件彼此通信而不彼此瞭解。 組件可以將事件發送到Eventbus,而無需知道是誰來接聽或有多少其他人來接聽。 組件也可以偵聽Eventbus上的事件,而無需知道誰發送了事件。 這樣,組件可以相互通信而無需相互依賴。 同樣,很容易替換一個組件。 只要新組件瞭解正在發送和接收的事件,其他組件就永遠不會知道.

相對於其他的 Service Bus 或者 Event Bus, CAP 擁有自己的特色,它不要求使用者發送消息或者處理消息的時候實現或者繼承任何接口,擁有非常高的靈活性。我們一直堅信約定大於配置,所以CAP使用起來非常簡單,對於新手非常友好,並且擁有輕量級。

CAP 採用模塊化設計,具有高度的可擴展性。你有許多選項可以選擇,包括消息隊列,存儲,序列化方式等,系統的許多元素內容可以替換爲自定義實現。

簡單實戰

使用CAP需要依賴的第三方庫,這裏我們持久化選用了SqlServer數據庫,消息隊列使用了Rabbitmq

 

相關配置(上游系統、下游系統配置相同)

// 注入EF上下文對象 
var CapConnectionString = builder.Configuration.GetSection("CAPConnectionStrings").GetValue<string>("DefaultConnection");
builder.Services.AddDbContext<CAPDbContext>(options =>
    options.UseSqlServer(CapConnectionString));
//注入CAP
builder.Services.AddCap(x =>
{
    // 使用RabbitMQ作爲消息隊列  
    x.UseRabbitMQ(opt =>
    {
        opt.HostName = "192.168.3.128";
        opt.Port = 5674;
        opt.UserName = "guest";
        opt.Password = "guest";
        opt.VirtualHost = "/";
    });
    // 使用SqlServer作爲CAP的存儲  
    x.UseSqlServer(opt =>
    {
        opt.ConnectionString = CapConnectionString;
    });
    // 設置CAP的其他選項  
    x.FailedRetryCount = 5; // 失敗重試次數  
    x.FailedRetryInterval = 60; // 失敗重試間隔(秒)
    x.UseDashboard(dashoptions =>
    {
        dashoptions.PathMatch = "/cap";  //面板地址
    });
});

上游系統發佈

 /// <summary>
 /// CAP:基於消息隊列通過事件驅動+回調方法補償的機制實現分佈式事務的最終一致性
 /// 1、消息被消費者至少接收到一次,需要通過接口冪等性/redis唯一ID實現消息不會重複消費
 /// </summary>
 [Route("api/[controller]")]
 [ApiController]
 public class CAPController : ControllerBase
 {
     private readonly ICapPublisher capPublisher;
     private readonly CAPDbContext cAPDbContext;
     public CAPController(ICapPublisher capPublisher, CAPDbContext cAPDbContext)
     {
         this.capPublisher = capPublisher;
         this.cAPDbContext = cAPDbContext;
     }

     [HttpGet("CreateOrder")]
     public IActionResult CreateOrder()
     {
         using (var trans = cAPDbContext.Database.BeginTransaction(capPublisher, autoCommit: true))
         {
             //業務代碼
             try
             {
                 var order = new OrderInfo { OrderId = Guid.NewGuid().ToString(), ProId= "Pro_13327530-e706-4ef3-aa5b-02aac9227499", Status = 0 };
                 cAPDbContext.orderInfos.Add(order);
                 capPublisher.Publish("test_createorder_v1", order, "test_createorder_callback_v1");
                 cAPDbContext.SaveChanges();
             }
             catch (Exception ex)
             {
                 Console.WriteLine(ex);
             }
         }
         return Ok();
     }
     [CapSubscribe("test_createorder_callback_v1")]
     private void CreateOrderCallback(JsonElement param)
     {
         var isSuccess = param.GetProperty("IsSuccess").GetBoolean();
         var OrderId = param.GetProperty("OrderId").GetString();
         if (isSuccess)
         {
             var order = cAPDbContext.orderInfos.FirstOrDefault(c => c.OrderId == OrderId);
             order.Status = 1;
             cAPDbContext.Update(order);
             cAPDbContext.SaveChanges();
             Console.WriteLine("修改訂單狀態成功");
         }
         else
         {
             var order = cAPDbContext.orderInfos.FirstOrDefault(c => c.OrderId == OrderId);
             order.Status = -1;
             cAPDbContext.Update(order);
             cAPDbContext.SaveChanges();
             Console.WriteLine("下單失敗,執行補償");
         }
     }
 }

下游訂閱處理

[Route("api/[controller]")]
[ApiController]
public class CapConsumerController : ControllerBase
{
    private readonly ICapPublisher capPublisher;
    private readonly CAPDbContext cAPDbContext;
    public CapConsumerController(ICapPublisher capPublisher, CAPDbContext cAPDbContext)
    {
        this.capPublisher = capPublisher;
        this.cAPDbContext = cAPDbContext;
    }

    [HttpGet("CreatePro")]
    public IActionResult CreatePro()
    {
        cAPDbContext.proInfos.Add(new ProInfo { ProId = "Pro_" + Guid.NewGuid().ToString(), Count = 10, ProName = "商品1" });
        cAPDbContext.proInfos.Add(new ProInfo { ProId = "Pro_" + Guid.NewGuid().ToString(), Count = 10, ProName = "商品2" });
        cAPDbContext.proInfos.Add(new ProInfo { ProId = "Pro_" + Guid.NewGuid().ToString(), Count = 10, ProName = "商品3" });
        cAPDbContext.proInfos.Add(new ProInfo { ProId = "Pro_" + Guid.NewGuid().ToString(), Count = 10, ProName = "商品4" });
        cAPDbContext.SaveChanges();
        return Ok();
    }

    [CapSubscribe("test_createorder_v1")]
    private object KjProCount(JsonElement param)
    {
        var obj = param.Deserialize<OrderInfo>();
        try
        {
            if (obj != null)
            {
                using (var trans = cAPDbContext.Database.BeginTransaction(capPublisher, autoCommit: true))
                {
                    var pro = cAPDbContext.proInfos.FirstOrDefault(c => c.ProId == obj.ProId);
                    if (pro != null && pro.Count > 0)
                    {
                        pro.Count--;
                        cAPDbContext.Update(pro);
                        cAPDbContext.proOrderRecords.Add(new ProOrderRecord { RecordId = Guid.NewGuid().ToString(), OrderId = obj.OrderId, ProId = pro.ProId });
                        cAPDbContext.SaveChanges();
                        throw new Exception("Error");
                        Console.WriteLine($"下單成功{JsonConvert.SerializeObject(obj)}");
                        return new { IsSuccess = true, OrderId = obj.OrderId };
                    }
                    else
                    {
                        Console.WriteLine($"庫存不足{JsonConvert.SerializeObject(obj)}");
                    }
                }
            }
            return new { IsSuccess = false, OrderId = obj.OrderId };
        }
        catch (Exception ex)
        {
            Console.WriteLine("發生異常回滾");
            return new { IsSuccess = false, OrderId = obj.OrderId };
        }
    }
}

在上游和下游系統中會自動創建2個表(發佈、接收的消息)

監控界面

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章