建议:小规模分布式就用DTC,大规模分布式就用NetCore.CAP

建议:小规模分布式就用DTC,大规模分布式就用NetCore.CAP

小规模分布式就用DTC强一致性模式,大规模分布式就用DotNetCore.CAP框架

dotnetCore.CAP框架

[CAP]是一个用来解决微服务或者分布式系统中分布式事务问题的一个开源项目解决方案,同样可以用来作为EventBus使用。

1 github地址:https://github.com/dotnetcore/CAP

2 官网地址: https://cap.dotnetcore.xyz/

3 官网文档:https://cap.dotnetcore.xyz/user-guide/zh/cap/idempotence/

分布式的代价:

分布式环境下,服务器之间的通信,可能是不靠谱,这种情况无法避免分区容错,一定存在。CAP是不能同时满足的!

ConsistencyAvailability怎么选?一致性和可用性,不能同时满足,要什么?

CP重要,一致性最重要了,数据不能错,银行—交易数据

AP重要,可用性最重要了,系统的可用性,尤其是分布式----微服务,可用性尤为重要,没有可用性是跑不起来

CAP

1、Consistency(一致性):意思是写操作之后的读操作,必须返回该值,不能出现一个不正确的结果。

a)强一致性:任意时刻数据都是一致性的。

b)弱一致性性:允许某一时刻不一致,承诺在一定时间内变成一致。

c)最终一致性:允许数据不一致,但是最终最终,数据还是的一致的。

2、Availability(可用性):意思是只要收到用户的请求,服务器就必须给出回应。(分布式下面可用性是最重要的)

3、Partition tolerance(分区容错):大多数分布式系统都分布在多个子网络。每个子网络就叫做一个区(partition)。分区容错的意思是,区间通信可能失败。比如,一台服务器放在中国,另一台服务器放在美国,这就是两个区,它们之间可能无法通信。

多种一致性:

1、强一致性—任意时刻数据都是一致的

a)、2PC(预备-》就绪-》提交):阻塞导致性能问题、 单点故障(事务管理器挂了)、消息丢失问题。=》DTC是基于数据库层面的事务 .NET Framework 下MSDTC实现,Distributed Transaction Coordinator服务必须开启(演示的是单机—局域网需要配置)。 .NET Core不支持—Linux

在这里插入图片描述

b)、3PC(预备-》就绪-》预提交-》提交):阻塞导致性能问题、 单点故障(事务管理器挂了)、消息丢失问题。只是增加了数据库自动提交。

2、弱一致性—允许某一时刻不一致,承诺在一定时间内变成一致的–Try-Confirm-Cancel。=》主要用在银行、阿里—钱必须保障-不能阻塞—设计负责,开发工作重ByteTCC、Himly、TCC-transaction事务管理器—这些都是Java的 .NET没有—所以解决方案也没有—除非自己写

a)、通过TCC实现弱一致性。

3、最终一致性—允许数据不一致,但是最终最终,数据还是得一致的 业务层面

a)、基于本地消息表实现最终一致性。.net领域首选采用DotNetCore.CAP框架来实现基于本地消息表的分布式事务。

幂等性----重放攻击

对同一个系统,使用同样的条件,一次请求和重复的多次请求对系统资源的影响是一致的。(网页提交按钮,点1次 跟10次结果一样)。

场景:支付—页面修改信息—订单减库存

1 MVCC多版本并发控制—乐观锁—数据库更新时带上版本号—更新+1 条件必带version-----id + version

2 去重表—请求带个guid—操作前校验下guid—点赞—100赞-不能重复—文章id+用户id+唯一索引

3 Token机制—每次操作都带个唯一id,请求来了先检测再执行

4 form表单每次都带一个guid或雪花算法id,然后将这个id存入redis并设置过期时间,每次提交表单的时候判断redis里面是否存在,如果存在就从redis移除,进行相应业务。如果不存在就不能进行相应业务操作。

本地消息表分布式事务

MQ分布式事务–本地消息表–基于消息的一致性

基于DotNetCore.CAP框架搭建分布式集群事务过程详解—微服务

项目说明:

a)、此项目NuGet相关依赖包:DotNetCore.CAP、DotNetCore.CAP.RabbitMQ、DotNetCore.CAP.SqlServer、DotNetCore.CAP.MongoDB(需要 MongoDB 4.0+ 集群)、DotNetCore.CAP.Dashboard

b)、此项目分布式事务参与微服务节点:用户服务(Published)-》订单服务(Received/Published)-》仓储服务(Received/Published)-》物流服务(Received/Published)-》支付服务(Received)。基于本地消息表模式将任务扭转到下一个服务节点,从而组成一个完整的业务流程。

c)、服务启动命令:

--consul.exe启动
首先cmd到consul.exe所在路径-》consul.exe agent --dev

--用户服务(Published)
dotnet run --urls=http://*:11111

--订单服务(Received/Published)
dotnet run --urls=http://*:11112

--仓储服务(Received/Published)
dotnet run --urls=http://*:11113

--物流服务(Received/Published)
dotnet run --urls=http://*:11114

--支付服务(Received)
dotnet run --urls=http://*:11115

image
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

image
image
image
在这里插入图片描述


CAP 使用阿里云 AMQP 服务

安装依赖

dotnet add package DotNetCore.CAP
dotnet add package DotNetCore.CAP.RabbitMQ
dotnet add package Tfc.CAP.AliyunAMQP

修改配置

  • appsettings.json 配置
"Cap": {
    "RabbitMQ": {
      "HostName": "", // amqp 访问地址
      "UserName": "", // 阿里云 AccessKey
      "Password": "" // 阿里云访问 AccessSecret
    },
    "Aliyun": {
      "VirtualHost": "" // 阿里云AMQP创建
    }
  }
  • StartupConfigureServices 添加如下配置
context.AddCap(capOptions =>
            {
                ......
                capOptions.UseRabbitMQ(_ =>
                {
                    _.HostName = configuration["Cap:RabbitMQ:HostName"];
                    _.UserName = configuration["Cap:RabbitMQ:UserName"];
                    _.Password = configuration["Cap:RabbitMQ:Password"];
                    _.ConnectionFactoryOptions = options =>
                    {
                        options.VirtualHost = configuration["Cap:Aliyun:VirtualHost"];
                        options.AuthMechanisms = new List<IAuthMechanismFactory>() {new AliyunMechanismFactory()};
                    };
                });
            });
  • 支持 Abp.EventBus.CAP, 配置和上面保持一致

DotNetCore.CAP.Provider

DotNetCore.CAP 为其增加FreeSql中的统一事务提交

Getting Started

NuGet

你可以运行以下下命令在你的项目中安装 CAP。

PM> Install-Package DotNetCore.CAP.MySql.Provider

安装FreeSql

PM> Install-Package FreeSql
PM> Install-Package FreeSql.Provider.MySqlConnector

Configuration

首先配置CAP到 Startup.cs 文件中,如下:

    public IFreeSql Fsql { get; }
    public IConfiguration Configuration { get; }
    private string connectionString = @"Data Source=localhost;Port=3306;User ID=root;Password=123456;Initial Catalog=captest;Charset=utf8mb4;SslMode=none;Max pool size=10";
    
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
        Fsql = new FreeSqlBuilder()
            .UseConnectionString(DataType.MySql, connectionString)
            .UseAutoSyncStructure(true)
            .Build();
    }
    
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<IFreeSql>(Fsql);
        services.AddCap(x =>
        {
            x.UseMySql(connectionString);
            x.UseRabbitMQ("localhost");
            x.UseDashboard();
            x.FailedRetryCount = 5;
            x.FailedThresholdCallback = (type, msg) =>
            {
                Console.WriteLine(
                    $@"A message of type {type} failed after executing {x.FailedRetryCount} several times, requiring manual troubleshooting. Message name: {msg.GetName()}");
            };
        });
        services.AddControllers();
    }

在控制器中得到

private readonly IFreeSql _freeSql;
private readonly ICapPublisher _capBus;
public TestController(IFreeSql freeSql, ICapPublisher capBus)
{
    _freeSql = freeSql;
    _capBus = capBus;
}
[HttpGet("~/freesql/transaction")]
public DateTime GetTime3()
{
    DateTime now = DateTime.Now;
    using (var uow = _freeSql.CreateUnitOfWork())
    {
        using ICapTransaction trans = uow.BeginTransaction(_capBus, false);
        var repo = uow.GetRepository<WeatherForecast>();
        repo.Insert(new WeatherForecast()
        {
            Date = now,
            Summary = "summary",
            TemperatureC = 100
        });
        repo.Insert(new WeatherForecast()
        {
            Date = now,
            Summary = "11222",
            TemperatureC = 200
        });
        _capBus.Publish("freesql.time", now);
        trans.Commit();
    }
    return now;
}
[CapSubscribe("freesql.time")]
public void GetTime(DateTime time)
{
    Console.WriteLine($"time:{time}");
}

DotNetCore.CAP.Provider

DotNetCore.CAP 为其增加FreeSql中的统一事务提交,没有减少代码,如对EFCore的依赖,如果需要去掉EFCore的依赖。

该项目分为二种方式实现CAP配合FreeSql的分布式事务一致性。

1.改源码,为其增加一些扩展,并在switch的地方,加IUnitOfWork的判断。

Getting Started

NuGet

你可以运行以下下命令在你的项目中安装 CAP。

PM> Install-Package DotNetCore.CAP.MySql.Provider

安装FreeSql

PM> Install-Package FreeSql
PM> Install-Package FreeSql.Provider.MySqlConnector
PM> Install-Package FreeSql.DbContext

Configuration

首先配置CAP到 Startup.cs 文件中,如下:

    public IFreeSql Fsql { get; }
    public IConfiguration Configuration { get; }
    private string connectionString = @"Data Source=localhost;Port=3306;User ID=root;Password=123456;Initial Catalog=captest;Charset=utf8mb4;SslMode=none;Max pool size=10";
    
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
        Fsql = new FreeSqlBuilder()
            .UseConnectionString(DataType.MySql, connectionString)
            .UseAutoSyncStructure(true)
            .Build();
    }
    
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<IFreeSql>(Fsql);
        services.AddCap(x =>
        {
            x.UseMySql(connectionString);
            x.UseRabbitMQ("localhost");
            x.UseDashboard();
            x.FailedRetryCount = 5;
            x.FailedThresholdCallback = (type, msg) =>
            {
                Console.WriteLine(
                    $@"A message of type {type} failed after executing {x.FailedRetryCount} several times, requiring manual troubleshooting. Message name: {msg.GetName()}");
            };
        });
        services.AddControllers();
    }

在控制器中得到

private readonly IFreeSql _freeSql;
private readonly ICapPublisher _capBus;
public TestController(IFreeSql freeSql, ICapPublisher capBus)
{
    _freeSql = freeSql;
    _capBus = capBus;
}
[HttpGet("~/freesql/transaction")]
public DateTime GetTime3()
{
    DateTime now = DateTime.Now;
    using (var uow = _freeSql.CreateUnitOfWork())
    {
        using ICapTransaction trans = uow.BeginTransaction(_capBus, false);
        var repo = uow.GetRepository<WeatherForecast>();
        repo.Insert(new WeatherForecast()
        {
            Date = now,
            Summary = "summary",
            TemperatureC = 100
        });
        repo.Insert(new WeatherForecast()
        {
            Date = now,
            Summary = "11222",
            TemperatureC = 200
        });
        _capBus.Publish("freesql.time", now);
        trans.Commit();
    }
    return now;
}
[CapSubscribe("freesql.time")]
public void GetTime(DateTime time)
{
    Console.WriteLine($"time:{time}");
}

2. 不修改CAP源码怎么办呢?

另外,如果你不想修改CAP的源码,FreeSql作者叶老板,为我们指一个思路。

按照大佬的思路,可以不改变CAP的代码基础上,通过写一个扩展方法。这样我们就可以仅安装官方提供的包。

> Install-Package DotNetCore.CAP.Dashboard
> Install-Package DotNetCore.CAP.MySql
> Install-Package DotNetCore.CAP.RabbitMQ
> Install-Package FreeSql
> Install-Package FreeSql.DbContext
> Install-Package FreeSql.Provider.MySqlConnector
  public static class CapUnitOfWorkExtensions
    {
        public static ICapTransaction  BeginTransaction(this IUnitOfWork unitOfWork, ICapPublisher publisher, bool autoCommit = false)
        {
            publisher.Transaction.Value = publisher.ServiceProvider.GetService<ICapTransaction>();
            return publisher.Transaction.Value.Begin(unitOfWork.GetOrBeginTransaction(), autoCommit);
        }
        
        public static void Flush(this ICapTransaction capTransaction)
        {
            capTransaction?.GetType().GetMethod("Flush", BindingFlags.Instance | BindingFlags.NonPublic)
                ?.Invoke(capTransaction, null);
        }
        
        public static void Commit(this IUnitOfWork unitOfWork,ICapTransaction capTransaction)
        {
            unitOfWork.Commit();
            capTransaction.Flush();
        }
    }

使用demo,完整项目可查看 https://github.com/luoyunchong/DotNetCore.CAP.Provider/tree/master/samples/Sample.RabbitMQ.MySql.FreeSql

[HttpGet("~/freesql/flush/{id}")]
public DateTime Flush(int id = 0)
{
    DateTime now = DateTime.Now;
    using (var uow = _freeSql.CreateUnitOfWork())
    {
        //这个不能使用using,因为这个using掉,uow.Dispose()时就会导致FreeSql,提示cannot access dispose transaction
        ICapTransaction trans = uow.BeginTransaction(_capBus, false);
        var repo = uow.GetRepository<WeatherForecast>();
        repo.Insert(new WeatherForecast()
        {
            Date = now,
            Summary = "summary" + (id == 1
                ? "summarysummarysummarysummarysummarysummarysummarysummarysummarysummarysummary"
                : ""),
            TemperatureC = 100
        });
        if (id == 0)
        {
            throw new Exception("异常,事务不正常!!");
        }
        repo.Insert(new WeatherForecast()
        {
            Date = now,
            Summary = "11222",
            TemperatureC = 200
        });
        _capBus.Publish("FreeSqlController.time", now);
        uow.Commit(trans);
    }
    return now;
}

前言

CAP用来处理分布式事务以及提供EventBus的功能,具有轻量级,高性能,易使用等特点。

安装包

Install-package DotNetCore.CAP   
Install-package DotNetCore.CAP.RabbitMQ  
Install-package DotNetCore.CAP.SqlServer   

使用方法

配置

Startup->ConfigureServices中添加服务:

//add CAP  
services.AddCap(x =>  
{  
    x.UseEntityFramework<OrderContext>(); // 使用EF  

    x.UseSqlServer(connecttext); // 使用SQL Server  

    x.UseRabbitMQ(cfg =>  
    {  
        cfg.HostName = "127.0.0.1";  
        cfg.Port = 5672;  
        cfg.UserName = "test1";  
        cfg.Password = "test1";  
    }); // 使用RabbitMQ  

    x.UseDashboard(); // Dashboard  

    // Below settings is just for demo  
    x.FailedRetryCount = 2;  
    x.FailedRetryInterval = 5;  
}); 

案例

模仿订单系统下单成功后,推送后续消息到各个子系统,保证分布式系统间通信的一致性。

代码

订单系统的消息发布者:

public interface IOrderRepository
{  
    bool CreateOrder(Order order);  
}  
public class OrderRepository : IOrderRepository  
{  
    public readonly OrderContext _context; //ef 订单上下文  
    public readonly ICapPublisher _capPublisher;//cap 消息发布者  

    public OrderRepository(OrderContext context, ICapPublisher capPublisher)  
    {  
        this._context = context;  
        this._capPublisher = capPublisher;  
    }  

    public bool CreateOrder(Order order)  
    {  
        using (var trans = _context.Database.BeginTransaction())  
        {  
            var orderEntity = new Order()  
            {  
                OrderTime = order.OrderTime  
            };  

            _context.Orders.Add(orderEntity);  
            _context.SaveChanges();  

            _capPublisher.Publish("czhsoft.services.order.create", orderEntity);  

            trans.Commit();  
        }  

        return true;  
    }  
} 

其它子系统的消息订阅者:

	public interface IOrderSubscriberService  
    {  
        void ConsumeOrderMessage(Order message);  
    }  

	public class OrderSubscriberService : IOrderSubscriberService, ICapSubscribe  
    {  
        public OrderSubscriberService()  
        {  
        }  

        [CapSubscribe("czhsoft.services.order.create")]  
        public void ConsumeOrderMessage(Order message)  
        {  
            Console.Out.WriteLine($"[MsgApi] Received message : order id {message.OrderId}");  
        }  
    }  
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值