经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » 编程经验 » 查看文章
如何通过本地化事件正确实现微服务内部强一致性,事件总线跨微服务间最终一致性 - Ahoo-Wang
来源:cnblogs  作者:Ahoo-Wang  时间:2018/10/16 9:30:36  对本文有异议

目录

  1. 设计重点
  2. 流程图
  3. 伪代码
    2.1. PublishEvent
    2.2. SubscribeEvent
    2.3. Publisher
    2.4. Subscriber
  4. 微服务 强一致性
    3.1 Publisher
    3.2 Subscriber
  5. 事件总线 - 跨服务 最终一致性
    4.1 Publisher & Subscriber 都开启了本地事务,保证了强一致性
    4.2 问题场景一:当 ③ 发布失败怎么办?
    4.3 问题场景二:当 ③ 发布成功,但 ④ 更新事件状态失败怎么办?
    4.4 问题场景三:Publisher 端Ok,Subscriber 消费出错

0. 设计重点

  1. Publisher 本地化 PublishEvent 保证事件发布可靠性
  2. Subscriber 本地化 SubscribeEvent 保证事件订阅可靠性
  3. SubscribeEvent 通过 EventId & HandlerType 组合约束 保证不重复消费事件
  4. 事件中央控制台 处理 Publisher & Subscriber 事件重试

1. 执行流程图

执行流程图


2. 伪代码

2.1 PublishEvent

  1. public abstract class Event
  2. {
  3. public Event()
  4. {
  5. Id = Guid.NewGuid();
  6. CreationTime = DateTime.UtcNow;
  7. }
  8. public Guid Id { get; set; }
  9. public DateTime CreationTime { get; set; }
  10. }
  11. public class PublishEvent : Event
  12. {
  13. public PublishEvent(Event @event)
  14. {
  15. Id = @event.Id;
  16. CreationTime = @event.CreationTime;
  17. Type = @event.GetType().FullName;
  18. Data = JsonConvert.SerializeObject(@event);
  19. Status = PublishEventStatus.NotPublished;
  20. }
  21. public String Type { get; set; }
  22. public String Data { get; set; }
  23. public PublishEventStatus Status { get; set; }
  24. }
  25. public enum PublishEventStatus
  26. {
  27. NotPublished = 0,
  28. Published = 1,
  29. PublishedFailed = 2
  30. }

2.2 SubscribeEvent

  1. public class SubscribeEvent
  2. {
  3. public SubscribeEvent(Event @event, IEventHandler handler)
  4. {
  5. EventId = @event.Id;
  6. EventCreationTime = @event.CreationTime;
  7. EventType = @event.GetType().FullName;
  8. EventData = JsonConvert.SerializeObject(@event);
  9. HandlerType = handler.GetType().FullName;
  10. HandlingStatus = HandlingStatus.HandleSucceeded;
  11. HandlingTime = DateTime.Now;
  12. }
  13. public Guid EventId { get; set; }
  14. public String EventType { get; set; }
  15. public String EventData { get; set; }
  16. public DateTime EventCreationTime { get; set; }
  17. public String HandlerType { get; set; }
  18. public DateTime HandlingTime { get; set; }
  19. public HandlingStatus HandlingStatus { get; set; }
  20. }
  21. public enum HandlingStatus
  22. {
  23. HandleSucceeded = 0,
  24. HandleFailed = 1
  25. }

2.3 Publisher

  1. try
  2. {
  3. BeginTransaction(); // ①
  4. //Biz Flow
  5. EventRepository.PubilshEvent(@event);// ②
  6. CommitTransaction();
  7. }
  8. catch(Exception ex){
  9. RollbackTransaction();
  10. throw ex;
  11. }
  12. EventBus.Publish(@event); // ③
  13. EventResitory.EventPublished(@event.ToString()); // ④

2.4 Subscriber

  1. try
  2. {
  3. BeginTransaction();
  4. //Biz Flow
  5. EventRepository.SubscribeEvent(@event , eventHandler); // ⑤
  6. CommitTransaction();
  7. }
  8. catch(Exception ex){
  9. RollbackTransaction();
  10. throw ex;
  11. }

3. 微服务 强一致性

3.1 Publisher

  1. 开启本地事务达到强一致性
  2. 执行本地业务代码
  3. 本地事务内部保存事件 预发布 状态
  4. 发布事件到事件总线
  5. 修改事件发布状态为已发布

3.2 Subscriber

  1. 开启本地事务达到强一致性
  2. 执行本地业务代码
  3. 保存订阅事件到本地仓库

4 事件总线 - 跨服务 最终一致性

4.1 Publisher & Subscriber 都开启了本地事务,保证了强一致性


4.2 问题场景一:当 ③ 发布失败怎么办?

  1. 发布失败,意味着抛出异常,则 不执行,那么事件状态依然保持 预发布状态
  2. 后续 事件重试 重新发布该事件,并更新事件状态为 已发布

4.3 问题场景二:当 ③ 发布成功,但 ④ 更新事件状态失败怎么办?

4.3.1 场景二·一 Subscriber 订阅成功

  1. 发布成功,但 更新事件状态失败,事件状态依然是 预发布状态
  2. Subscriber 订阅到该事件后成功执行完业务代码
  3. Subscriber 将订阅事件保存到本地订阅事件仓库
    该场景存在的问题: Publisher 会通过 事件重试 再次发布 预发布 状态的事件,那么此时Subscriber 将重复消费该事件
    方案:该问题我们可以通过将 SubscribeEvent EventId & HandlerType 组合唯一约束,来避免重复消费

4.3.2 场景二·二 Subscriber 订阅失败

  1. 发布成功,但 更新事件状态失败,事件状态依然是 预发布状态
  2. Subscriber 执行消费失败
  3. Subscriber 回滚本地事务
    该场景不存在任何问题,因为 Publisher 会通过 事件重试 再次发布 预发布 状态的事件 。

4.4 问题场景三:Publisher 端Ok,Subscriber 消费出错

  1. Publisher 端处理顺利
  2. Subscriber 消费失败,回滚本地事务,此时 SubscribeEvent 未存储到本地仓库
    该场景存在的问题:
    Publisher 发送成功,并且本地 PublishEvent 事件为已发布,那么意味着从Publisher端是无法知道Subscriber消费失败需要重新消费
    解决方案:
  3. 通过检测 PublishEvent & SubscribeEvent 获得需要 事件重试PublishEvent
  4. PublishEvent 重新发布Subscriber

5. 通过Nuget安装组件支持以上编程模型

  1. Install-Package SmartEventBus.RabbitMQImpl
  2. Install-Package SmartEventBus.Repository

6. ORM:SmartSql 广而告之

SmartSql = Dapper + MyBatis + Cache(Memory | Redis) + ZooKeeper + R/W Splitting + ......

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号