学习笔记----分布式事务CAP简单使用

前言

本文简单介绍DotNetCore.CAP的简单使用,关于分布式事务中的“CAP原则”和“BASE理论”以及分布式事务的其他解决方案不做过多的介绍

 

CAP介绍


CAP是一个在分布式系统(SOA)/微服务系统(MicroService)中实现事件总线及最终一致性(分布式事务)的一个开源的C#库,具有轻量级,高性能,易使用等特点

CAP 具有Event Bus的所有功能,简化EventBus中de发布/订阅

CAP 具有消息持久化的功能,服务进行重启或者宕机不比担心消息丢失保证可靠性

 

CAP安装配置

 NuGet安装:DotNetCore.CAP(基础支持),DotNetCore.CAP.RabbitMQ(底层消息队列),DotNetCore.CAP.SqlServer(数据库)

 

 底层消息队列支持:支持使用 RabbitMQ,Kafka,Azure Service Bus等

 数据库支持: Sql Server,MySql,PostgreSql,MongoDB

Startup:

    public void ConfigureServices(IServiceCollection services)
        {
      
            services.AddCap(x =>
            {
                var OrderDatabaseString = Configuration.GetConnectionString("OrderDatabaseString");
                var rabbitMQ = Configuration.GetConnectionString("rabbitMQ");
                x.UseSqlServer(OrderDatabaseString);
                x.UseRabbitMQ(rabbitMQ);
                x.FailedRetryCount = 10;//重试最高次数
                x.FailedRetryInterval = 10; //重试间隔
                x.FailedThresholdCallback = Failed =>
                {
                    /*重试次数达到上限*/
                };

            });
         
        }

当项目正确配置完成启动后,系统会自动创建[cap].[Published] 和[cap].[Received]本地消息业务表

 

CAP的简单使用

用一个简单的项目举例,一个订单服务,一个产品服务(产品服务为集群);用户下单调用订单服务下单接口,下单接口需要调用产品服务的减库存接口

 

 

 

 

1,创建订单发送消息

   public async Task<string> CreateOrder()
        {

            var orderId = DateTime.Now.ToString("yyyyMMdd") + new Random().Next(0, 100000000);
            var order = new Orders()
            {
                orderId = orderId,
                skunanme = "测试商品",
                skuId = 40004456666244,
                num = 50
            };
            var headers = new Dictionary<string, string>();
            headers.Add("Buy", "111");

            /*消息接收方名称*/
            this.PublishName = Enum.ProductCheckHouseNum.ToString();
            /*推送内容*/
            this.PublishContent = order;
            /*推送Header*/
            this.PublishHeader = headers;

            using (var connection = Db.OpenConnection(1))
            {

                IDbTransaction transaction = null;
                try
                {
                    transaction = connection.BeginTransaction(_capBus, false);
                    var result = await connection.InsertAsync(order, transaction) > 0;
                    await _capBus.PublishAsync(this.PublishName, this.PublishContent);
                    if (result) transaction.Commit();
                    else transaction.Rollback();
                    orderId = result ? orderId : "";
                }
                catch (Exception ex)
                {

                    transaction?.Rollback();
                }

            }
            return orderId;
        }
View Code

2,消息接收

 /// <summary>
        /// 接收下单库存验证消息
        /// </summary>
        [NonAction]
        [CapSubscribe("ProductCheckHouseNum")]
        public void ProductCheckHouseNum(DB.Model.Orders Order, [FromCap] CapHeader header)
        {

            if (Order != null)
            {
               
            }          
        }

注意接收方的CapSubscribe("ProductCheckHouseNum")和推送方设置的名称必须一直,

3,补偿事务

某些情况下消费者需要返回值以告诉发布者执行结果,以便于发布者实施一些动作,通常情况下这属于补偿范围,以上面的项目为例,商品在接收到消息验证之后向订单服务返回执行结果
现在修改一下订单服务的推送

 public async Task<string> CreateOrder()
        {

            var orderId = DateTime.Now.ToString("yyyyMMdd") + new Random().Next(0, 100000000);
            var order = new Orders()
            {
                orderId = orderId,
                skunanme = "测试商品",
                skuId = 40004456666244,
                num = 50
            };
            var headers = new Dictionary<string, string>();
            headers.Add("Buy", "111");

            /*消息接收方名称*/
            this.PublishName = Enum.ProductCheckHouseNum.ToString();
            /*推送内容*/
            this.PublishContent = order;
            /*推送Header*/
            this.PublishHeader = headers;

            using (var connection = Db.OpenConnection(1))
            {

                IDbTransaction transaction = null;
                try
                {
                    transaction = connection.BeginTransaction(_capBus, false);
                    var result = await connection.InsertAsync(order, transaction) > 0;
                    await _capBus.PublishAsync(this.PublishName, this.PublishContent, "callbackFunction");
                    if (result) transaction.Commit();
                    else transaction.Rollback();
                    orderId = result ? orderId : "";
                }
                catch (Exception ex)
                {

                    transaction?.Rollback();
                }

            }
            return orderId;
        }




   /// <summary>
        /// 回调函数
        /// </summary>
        /// <param name="order"></param>
        [NonAction]
        [CapSubscribe("callbackFunction")]
        public void MarkOrderStatus(DB.Model.Orders order)
        {
            if (order != null)
            {

            }

        }
对应的修改商品服务:
 /// <summary>
        /// 接收下单库存验证消息
        /// </summary>
        [NonAction]
        [CapSubscribe("ProductCheckHouseNum")]
        public async Task<Orders> ProductCheckHouseNums(DB.Model.Orders Order, [FromCap] CapHeader header)
        {

            if (Order != null)
            {
              //业务处理
            }
            return Order;
        }

在CAP推送方法PublishAsync的调用时指定 callbackName 来得到消费者的执行结果,注意下游服务一定要return


4,幂等性验证

    幂等性指的是多次操作,结果是一致的  ,但是CAP无法保证消息不重复(即使是能保证该有的验证还是要自己弄),实际使用中需要自己考虑一下消息的重复过滤和幂等性   

   在上面的例子中接收方是模拟的集群,相同的消息会接收两次,相应的推送方订单服务也会接收到两次  

  所以为保证幂等性在商品库中建立了一个流水表,用商品Id和订单号作为联合主键保证业务处理的唯一,订单服务同理,, 当然这只是一种处理方式,也可以使用Redis等进行处理 

 

总结

 这只是CAP的简单使用,更多介绍请访问:https://cap.dotnetcore.xyz/user-guide/zh/getting-started/quick-start/

   

 

  
   
   

 

 

 

 

 

 

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