.net core ocelot+consul+jwt 身份验证,服务治理与发现,网关配置(三)增加 cap 分布式事务
随着服务的增多 访问量增加 数据库 压力增加 可能拆分多个
拆分 分 水平 拆分 水平拆分 库 水平拆分表 ,只是 表结构不变 拆分多个 相同结构的表 或者库
垂直分 表 就结构 改变了 拆分出 多张 有主外键关系的表 这种拆分 多是数据库设计的问题
在分布式 集群 环境下 怎么做到 分布式事务呢 那就是net.cap
先装个rabbitmq 吧 因为 要依赖消息队列
docker run -d --name rabbit -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -p 15672:15672 -p 5672:5672 -p 25672:25672 -p 61613:61613 -p 1883:1883 rabbitmq:management
http://192.168.0.100:15672/
u admin p admin

需要Nuget 下载
DotNetCore.CAP
DotNetCore.CAP.Dashboard
DotNetCore.CAP.MySql
DotNetCore.CAP.RabbitMQ
我这里是依赖的mysql
注意 cap数据库不能单独一个 会报错文章下面 有解释
接下来先搞个简单点的 项目结构

JwtOrder service注入
1 using JwtEFCore; 2 using Microsoft.Extensions.Options; 3 4 var builder = WebApplication.CreateBuilder(args); 5 // Add services to the container. 6 builder.Services.AddControllers(); 7 // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle 8 builder.Services.AddEndpointsApiExplorer(); 9 builder.Services.AddSwaggerGen(); 10 builder.Services.Configure<DBOptions>(builder.Configuration.GetSection("DBOptions")); 11 builder.Services.AddTransient<JwtContext>(); 12 13 string capDbStr = builder.Configuration["CAPDB:con"]; 14 builder.Services.AddCap(options => 15 { 16 options.FailedRetryCount = 10; //失败重试的次数 不一定会正确的到消息队列 所以这里添加是失败重试 17 //如果 重试 次数超过10次 但是后期 人修好了消息队列 可以修改数据库中得retry 次数 改小一点 继续重试 18 options.FailedRetryInterval = 60;//重试间隔60s 19 options.FailedThresholdCallback = failed => 20 { 21 var logger = failed.ServiceProvider.GetService<ILogger<Program>>(); 22 logger.LogError($@"MessageType {failed.MessageType} 失败了, 重试了 {options.FailedRetryCount} 次, 23 消息名称: {failed.Message.Value}");//do anything 24 }; 25 options.UseRabbitMQ(config => 26 { 27 config.HostName = "192.168.0.100"; 28 config.Port = 5672; 29 config.VirtualHost = "cap"; 30 config.UserName = "admin"; 31 config.Password = "admin"; 32 33 }); 34 options.UseMySql(capDbStr); 35 options.UseDashboard(); 36 }); 37 var app = builder.Build(); 38 39 // Configure the HTTP request pipeline. 40 if (app.Environment.IsDevelopment()) 41 { 42 app.UseSwagger(); 43 app.UseSwaggerUI(); 44 } 45 46 app.UseHttpsRedirection(); 47 48 app.UseAuthorization(); 49 50 app.MapControllers(); 51 52 app.Run();
1 { 2 "Logging": { 3 "LogLevel": { 4 "Default": "Information", 5 "Microsoft.AspNetCore": "Warning" 6 } 7 }, 8 "AllowedHosts": "*", 9 "DBOptions": { "ConStr": "server=192.168.0.100;port=3306;database=jwtocelot;uid=root;pwd=123456;" }, 10 "CAPDB": { "con": "server=192.168.0.100;port=3306;database=jwtocelotcap;uid=root;pwd=123456;" } 11 }
1 using DotNetCore.CAP; 2 using JwtEFCore.Models; 3 using Microsoft.AspNetCore.Http; 4 using Microsoft.AspNetCore.Mvc; 5 using JwtEFCore.Enums; 6 namespace JwtOrder.Controllers 7 { 8 [Route("api/[controller]")] 9 [ApiController] 10 public class OrderCapController : ControllerBase 11 { 12 private readonly string publishName = "User-Order-Pay"; 13 private readonly ICapPublisher _publisher; 14 public OrderCapController(ICapPublisher publisher) 15 { 16 _publisher = publisher; 17 } 18 19 /// <summary> 20 /// 发送未支付订单到支付 21 /// </summary> 22 /// <param name="Id"></param> 23 /// <returns></returns> 24 [HttpGet("OrderToPay/{Id}")] 25 public IActionResult OrderToPay([FromRoute] long Id) 26 { 27 Order order = new Order { Id = Id, OrderStatus = OrderStatus.Unpaid }; 28 _publisher.Publish(publishName, order); 29 return Ok(order); 30 } 31 } 32 }
这里 如果 只运行 Order项目 是在rabbitmq是没有消息队列的 ,消息队列 是在 消费者 创建的
还有 第一次运行的时候 会创建两个表 cap.published cap.received
published 有retries 列 对应 配置重试次数 statusName 对应状态 发送 成功或者失败

received 表有 group 列 就是对应 消息队列的 队列的名称 name列 对应的 生成者主题名称

对应的是队列名称

主题名称

下面来一个 消费者
消费者 service注入与 生成者是一样的
1 using JwtEFCore; 2 using Microsoft.Extensions.Options; 3 4 var builder = WebApplication.CreateBuilder(args); 5 // Add services to the container. 6 builder.Services.AddControllers(); 7 // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle 8 builder.Services.AddEndpointsApiExplorer(); 9 builder.Services.AddSwaggerGen(); 10 builder.Services.Configure<DBOptions>(builder.Configuration.GetSection("DBOptions")); 11 builder.Services.AddTransient<JwtContext>(); 12 string capDbStr = builder.Configuration["CAPDB:con"]; 13 builder.Services.AddCap(options => 14 { 15 options.FailedRetryCount = 10; //失败重试的次数 不一定会正确的到消息队列 所以这里添加是失败重试 16 //如果 重试 次数超过10次 但是后期 人修好了消息队列 可以修改数据库中得retry 次数 改小一点 继续重试 17 options.FailedRetryInterval = 60;//重试间隔60s 18 options.FailedThresholdCallback = failed => 19 { 20 var logger = failed.ServiceProvider.GetService<ILogger<Program>>(); 21 logger.LogError($@"MessageType {failed.MessageType} 失败了, 重试了 {options.FailedRetryCount} 次, 22 消息名称: {failed.Message.Value}");//do anything 23 }; 24 options.UseRabbitMQ(config => 25 { 26 config.HostName = "192.168.0.100"; 27 config.Port = 5672; 28 config.VirtualHost = "cap"; 29 config.UserName = "admin"; 30 config.Password = "admin"; 31 32 }); 33 options.UseMySql(capDbStr); 34 options.UseDashboard(); 35 }); 36 var app = builder.Build(); 37 38 // Configure the HTTP request pipeline. 39 if (app.Environment.IsDevelopment()) 40 { 41 app.UseSwagger(); 42 app.UseSwaggerUI(); 43 } 44 45 app.UseHttpsRedirection(); 46 47 app.UseAuthorization(); 48 49 app.MapControllers(); 50 51 app.Run();
消费者具体代码
特性 [NonAction] 是必须的
[CapSubscribe("User-Order-Pay",Group =("User-Order-Pay-Grop1"))] 订阅主题 是 User-Order-Pay rabbitmq 队列是 User-Order-Pay-Grop1 生成者 与消费者是 一对多 可以有多个 消费者
也就是 可以多次订阅同一个主题 下面就是典型 运用
如果 还有 下一级 业务 可以 继续 写 _publisher.Publish(publishName, order); 发送到下一级 业务中
!!!Cap 源码 Name 是 Topic 或者是 交换机的路由key Group 是队列名称
1 using DotNetCore.CAP; 2 using JwtEFCore.Models; 3 using Microsoft.AspNetCore.Http; 4 using Microsoft.AspNetCore.Mvc; 5 6 namespace JwtPay.Controllers 7 { 8 [Route("api/[controller]")] 9 [ApiController] 10 public class PayCapController : ControllerBase 11 { 12 private readonly ICapPublisher _capPublisher; 13 private readonly ILogger<PayCapController> _logger; 14 public PayCapController(ICapPublisher capPublisher, ILogger<PayCapController> logger) 15 { 16 _capPublisher = capPublisher; 17 _logger = logger; 18 } 19 20 [NonAction] 21 [CapSubscribe("User-Order-Pay",Group =("User-Order-Pay-Grop1"))] 22 public void OrderPay1(Order order) 23 { 24 order.OrderStatus = JwtEFCore.Enums.OrderStatus.Payment; 25 _logger.LogError($"订单状态{order.OrderStatus}"); 26 } 27 [NonAction] 28 [CapSubscribe("User-Order-Pay")] 29 public void OrderPay2(Order order) 30 { 31 order.OrderStatus = JwtEFCore.Enums.OrderStatus.Payment; 32 _logger.LogError($"订单状态{order.OrderStatus}"); 33 } 34 35 [NonAction] 36 [CapSubscribe("User-Order-Pay",Group =("User-Order-Pay-Grop3"))] 37 public void OrderPay3(Order order) 38 { 39 order.OrderStatus = JwtEFCore.Enums.OrderStatus.Payment; 40 _logger.LogError($"订单状态{order.OrderStatus}"); 41 } 42 } 43 }
两个项目 一起运行 才能有 效果 如果 只运行 生产者 那么消息只是发送到 交换机 因为 此时并没有 生成消息队列 消息队列是 消费者创建的 所以 看不出 效果 而且 发送到交换机的消息不会持久化

以上 并没有 加入 事物 ,以上的演示 可以用做 领域事件
接下来 演示 分布式 事物的实现 分布式事物 没有 回滚 只能 是commit 因为 没法回滚 服务 都不在一个 服务器 上 很难回滚
Table 'jwtocelot.cap.published' doesn't exist

分布式事物 数据库与在Order表是一个库要不就会报上面的错
所以代码有改动
order服务代码
using JwtEFCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.Configure<DBOptions>(builder.Configuration.GetSection("DBOptions")); builder.Services.AddTransient<JwtContext>(); //必须是在一个库 不能 单独一个库 //string capDbStr = builder.Configuration["CAPDB:con"]; string capDbStr = builder.Configuration["DBOptions:ConStr"]; builder.Services.AddCap(options => { options.FailedRetryCount = 10; options.FailedRetryInterval = 60; options.FailedThresholdCallback = failed => { var logger = failed.ServiceProvider.GetService<ILogger<Program>>(); logger.LogError($@"MessageType {failed.MessageType} 失败了, 重试了 {options.FailedRetryCount} 次, 消息名称: {failed.Message.Value}");//do anything }; options.UseRabbitMQ(config => { config.HostName = "192.168.0.100"; config.Port = 5672; config.VirtualHost = "cap"; config.UserName = "admin"; config.Password = "admin"; }); options.UseMySql(capDbStr); options.UseDashboard(); }); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run();
1 using DotNetCore.CAP; 2 using JwtEFCore.Models; 3 using Microsoft.AspNetCore.Http; 4 using Microsoft.AspNetCore.Mvc; 5 using JwtEFCore.Enums; 6 using JwtEFCore; 7 8 namespace JwtOrder.Controllers 9 { 10 [Route("api/[controller]")] 11 [ApiController] 12 public class OrderCapController : ControllerBase 13 { 14 private readonly string publishName = "User-Order-Pay"; 15 private readonly string publishTransName = "UserTrans-Order-Pay"; 16 private readonly ICapPublisher _publisher; 17 private readonly JwtContext _jwtContext; 18 public OrderCapController(ICapPublisher publisher, JwtContext jwtContext) 19 { 20 _publisher = publisher; 21 _jwtContext = jwtContext; 22 } 23 24 /// <summary> 25 /// 发送未支付订单到支付 26 /// </summary> 27 /// <param name="Id"></param> 28 /// <returns></returns> 29 [HttpGet("OrderToPay/{Id}")] 30 public IActionResult OrderToPay([FromRoute] long Id) 31 { 32 Order order = new Order { Id = Id, OrderStatus = OrderStatus.Unpaid }; 33 _publisher.Publish(publishName, order); 34 return Ok(order); 35 } 36 37 /// <summary> 38 /// 分布式事物 39 /// </summary> 40 /// <param name="Id"></param> 41 /// <returns></returns> 42 43 [HttpGet("OrderToPayTran/{Id}")] 44 public async Task<IActionResult> OrderToPayTranAsync([FromRoute] long Id) 45 { 46 Order order = _jwtContext.Orders.Find(Id); 47 if (order == null) 48 { 49 User user = new User { Id = 1, Name = "ceshi", Password = "1234", Phone = "1" }; 50 order = new Order { Id = Id, OrderStatus = OrderStatus.Unpaid }; 51 order.User = user; 52 53 } 54 55 //BeginTransaction 这个方法是 cap的扩展方法 autoCommit: false 设置 不是自动commit 56 using (var trans = _jwtContext.Database.BeginTransaction(this._publisher, autoCommit: false)) 57 { 58 //保存 59 _jwtContext.Orders.Add(order); 60 //保存到数据库 61 await _jwtContext.SaveChangesAsync(); 62 //发送消息 63 this._publisher.Publish(publishTransName, order); 64 //提交 65 trans.Commit(); 66 } 67 68 return Ok("保存成功"); 69 } 70 71 72 } 73 }
pay项目
1 using JwtEFCore; 2 using Microsoft.Extensions.Options; 3 4 var builder = WebApplication.CreateBuilder(args); 5 // Add services to the container. 6 builder.Services.AddControllers(); 7 // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle 8 builder.Services.AddEndpointsApiExplorer(); 9 builder.Services.AddSwaggerGen(); 10 builder.Services.Configure<DBOptions>(builder.Configuration.GetSection("DBOptions")); 11 builder.Services.AddTransient<JwtContext>(); 12 //string capDbStr = builder.Configuration["CAPDB:con"]; 13 string capDbStr = builder.Configuration["DBOptions:ConStr"]; 14 builder.Services.AddCap(options => 15 { 16 options.FailedRetryCount = 10; //失败重试的次数 不一定会正确的到消息队列 所以这里添加是失败重试 17 //如果 重试 次数超过10次 但是后期 人修好了消息队列 可以修改数据库中得retry 次数 改小一点 继续重试 18 options.FailedRetryInterval = 60;//重试间隔60s 19 options.FailedThresholdCallback = failed => 20 { 21 var logger = failed.ServiceProvider.GetService<ILogger<Program>>(); 22 logger.LogError($@"MessageType {failed.MessageType} 失败了, 重试了 {options.FailedRetryCount} 次, 23 消息名称: {failed.Message.Value}");//do anything 24 }; 25 options.UseRabbitMQ(config => 26 { 27 config.HostName = "192.168.0.100"; 28 config.Port = 5672; 29 config.VirtualHost = "cap"; 30 config.UserName = "admin"; 31 config.Password = "admin"; 32 33 }); 34 options.UseMySql(capDbStr); 35 options.UseDashboard(); 36 }); 37 var app = builder.Build(); 38 39 // Configure the HTTP request pipeline. 40 if (app.Environment.IsDevelopment()) 41 { 42 app.UseSwagger(); 43 app.UseSwaggerUI(); 44 } 45 46 app.UseHttpsRedirection(); 47 48 app.UseAuthorization(); 49 50 app.MapControllers(); 51 52 app.Run();
如果 还有下一级 还可以 写事物 和 消息发布
1 using DotNetCore.CAP; 2 using JwtEFCore.Models; 3 using Microsoft.AspNetCore.Http; 4 using Microsoft.AspNetCore.Mvc; 5 6 namespace JwtPay.Controllers 7 { 8 [Route("api/[controller]")] 9 [ApiController] 10 public class PayCapController : ControllerBase 11 { 12 private readonly ICapPublisher _capPublisher; 13 private readonly ILogger<PayCapController> _logger; 14 public PayCapController(ICapPublisher capPublisher, ILogger<PayCapController> logger) 15 { 16 _capPublisher = capPublisher; 17 _logger = logger; 18 } 19 20 [NonAction] 21 [CapSubscribe("User-Order-Pay",Group =("User-Order-Pay-Grop1"))] 22 public void OrderPay1(Order order) 23 { 24 order.OrderStatus = JwtEFCore.Enums.OrderStatus.Payment; 25 _logger.LogError($"订单状态{order.OrderStatus}"); 26 } 27 [NonAction] 28 [CapSubscribe("User-Order-Pay")] 29 public void OrderPay2(Order order) 30 { 31 order.OrderStatus = JwtEFCore.Enums.OrderStatus.Payment; 32 _logger.LogError($"订单状态{order.OrderStatus}"); 33 } 34 35 [NonAction] 36 [CapSubscribe("User-Order-Pay",Group =("User-Order-Pay-Grop3"))] 37 public void OrderPay3(Order order) 38 { 39 order.OrderStatus = JwtEFCore.Enums.OrderStatus.Payment; 40 _logger.LogError($"订单状态{order.OrderStatus}"); 41 } 42 43 44 [NonAction] 45 [CapSubscribe("UserTrans-Order-Pay", Group = ("UserTrans-Order-Pay-Group"))] 46 public void OrderPayForTrans(Order order) 47 { 48 order.OrderStatus = JwtEFCore.Enums.OrderStatus.Payment; 49 _logger.LogError($"订单状态{order.OrderStatus}"); 50 } 51 } 52 }
测试


还可以进行 header 消息头传递
生产者代码
1 /// <summary> 2 /// 发送未支付订单到支付 3 /// </summary> 4 /// <param name="Id"></param> 5 /// <returns></returns> 6 [HttpGet("OrderToPay/{Id}")] 7 public IActionResult OrderToPay([FromRoute] long Id) 8 { 9 Order order = new Order { Id = Id, OrderStatus = OrderStatus.Unpaid }; 10 ConcurrentDictionary<string, string> dic = new ConcurrentDictionary<string, string>(); 11 dic.TryAdd("username", "测试"); 12 dic.TryAdd("age","11"); 13 _publisher.Publish(publishName, order,dic); 14 return Ok(order); 15 }
消费者
1 [NonAction] 2 [CapSubscribe("User-Order-Pay")] 3 public void OrderPay2(Order order, [FromCap] CapHeader header) 4 { 5 order.OrderStatus = JwtEFCore.Enums.OrderStatus.Payment; 6 _logger.LogError($"订单状态{order.OrderStatus}"); 7 _logger.LogError($"用户名称={header["username"]},年龄={header["age"]} "); 8 }
测试

https://localhost:6001/cap/index.html

浙公网安备 33010602011771号