事件驱动架构及实现

什么是事件驱动架构

事件驱动架构(Event Driven Architecture,EDA)一个事件驱动框架(EDA)定义了一个设计和实现一个应用系统的方法学,在这个系统里事件可传输于松散耦合的组件和服务之间。一个事件驱动系统典型地由事件消费者和事件产生者组成。事件消费者向事件管理器订阅事件,事件产生者向事件管理器发布事件。当事件管理器从事件产生者那接收到一个事件时,事件管理把这个事件转送给相应的事件消费者。如果这个事件消费者是不可用的,事件管理者将保留这个事件,一段间隔之后再次转送该事件消费者。这种事件传送方法在基于消息的系统里就是:储存(store)和转送(forward)。

什么是事件

事件表示含有业务语义的消息,对消息体大小,消息格式有严格的限制。

什么是生产者

生产者表示事件的发布方,将事件投递到事件中心。

生产者需要明确事件的定义,格式。

什么是消费者

消费者表示事件的消费方,接收事件并完成相应的业务动作响应。

传统的请求/响应式微服务架构

传统的请求/响应式架构,服务间需要通过API二方包来完成服务的发起和调用,比如我们常见的dubbo,或者springcloud中的feign。二方包的依赖导致服务间强耦合。

事件驱动架构及实现_第1张图片

举一个常见场景:

交易成功后发货

事件驱动架构及实现_第2张图片

随着业务发展业务人员提个需求,希望增加积分系统,交易成功后给用户增加积分

事件驱动架构及实现_第3张图片

存在问题:

  1. 每次下游新增业务,都会导致上游系统变动。
  2. 由于是下游系统提供的接口,上游系统必须感知下游的数据协议。比如积分系统提供的API,里面可能涉及到其他系统字段,这时交易系统由于缺少这部分字段,必须再去其他系统中获取然后组装成积分系统的数据协议。

事件驱动架构及实现_第4张图片

  1. 依赖倒置,上游系统依赖了下游系统的API。

基于消息的架构

事件驱动架构及实现_第5张图片

因为不确定下游需要的数据协议,所以一般会在消息中带id信息,然后下游根据需求反查。

解决问题:

  1. 部分解耦上下游系统,但是由于消息协议不明确,下游系统还是要依赖上游的查询接口才能获得数据。
  2. 解决依赖倒置,上游系统不再依赖下游系统的API。
  3. 由上游统一数据协议。

存在问题:

  1. 当业务量/并发量大时,极大增加上游系统压力
  2. 通常为了保证最终一致,消费方会将消息保存下来再处理业务逻辑,通过补偿机制处理失败的消息,重复设计导致业务系统中侵入大量非业务代码。

事件驱动架构

事件驱动架构及实现_第6张图片

解决问题:

  1. 解决统一处理消费异常等问题
  2. 完全解耦上下游
  3. 定义标准事件协议,系统内按照协议契约执行,避免扯皮

存在问题:

  1. 我们说的事件,本质上还是基于消息中间件(MQ),所以在严格事务要求的场景下,不能完美的解决,当然有一些事务消息的解决方案,但并不是通用方式。
  2. 虽然大部分的MQ支持ACK,有精准一次,最多一次,至少一次功能,但是从性能和功能综合考虑,还是建议消费者端增加幂等逻辑处理。

为什么传统企业数字化更需要事件驱动

传统企业数字化业务流程更复杂

通过事件驱动能更好的解耦各服务间关系。

拿出差这一个简单场景来说,出差审核通过后就需要订机票,订酒店;然后自动财务结账核算;然后OA考勤自动打卡“出差”等等。当有新的下游需要接入时,只需要接对应的事件即可,且事件是约定标准的,也不需要上游的任何改动。

传统企业数字化未知场景更多

相较于消费互联网的已经被“玩烂”,产业互联网还是一块未开垦的处女地。我们说的一些系统(OA,SAP等)都是传统信息化遗留下来的产物,但真正深入每个行业,每个领域,未知的变化层出不穷。笔者就曾经碰到过需求一天一小改,3天一大改的场景。但是需求在变,其业务的本质或者说核心流程并没有变。事件驱动能很好的解耦变与不变,让开发人员将精力更多投入到变化中。

如何设计事件驱动

定义

事件模版

只某一类/种事件的描述及约束条件。

常用的信息包括:事件编号,事件名称,事件发布者,事件协议。

事件订阅

表示需要消费某个事件模版的关联关系,只有配置订阅了的消费者才能消费指定事件模版

事件

具体发送的事件实例,一般包含:事件头,事件体。事件头中包含事件编号,事件时间,标签,事件大小等。

事件体一般为标准的事件协议内容。

实现目标

  1. 事件发布需要有权限控制,即指定的事件只能由指定的服务发布。
  2. 每个事件模版有标准的事件协议,当发送的协议不符合时自动拦截。
  3. 事件需要有标签(tag),某一个消费者并不一定要消费某个事件模版的所有事件
  4. 消费者能自动接入事件中心,订阅相关事件,并通过tag能过滤相应事件
  5. 事件投递失败可以自动重试,并有相关报警机制
  6. 事件消费失败可以自动重试,并可以指定消费者主动重发,并有相关报警机制
  7. 事件日志存储方式可以支持关系型数据库(Mysql)或其他Nosql(Mongo,hive)等

领域模型

事件驱动架构及实现_第7张图片

状态机

事件驱动架构及实现_第8张图片

设计方案

相关模块及功能

1,事件中心

  1. 事件中心用于维护事件/订阅模版,并保存事件记录
  2. 事件中心维护相关MQ的Topic和事件关系
  3. 事件中心会根据模版拦截/过滤相关事件,并投递到MQ
  4. 事件中心的MQ不能建过多topic,不能一个事件一个topic

2,事件生产者SDK

  1. SDK封装投递事件API,将事件发送给事件中心,该API为同步接口,并在事件中心保存相关记录后返回
  2. SDK支持重投功能,当返回失败时能自动重投(该功能非必要,可以通过异常机制有生产者服务自己判断是否重投)

3,事件消费者

  1. 消费者能自动主动(无配置)连接消息中间件,并注册相关事件
  2. 消费者能通过事件编号/Tag将事件发送到指定执行器执行
  3. 完成消费后能通知事件中心消费成功/失败

技术选型

基于上述需要实现的模块及功能,采用如下技术选型

Spring-Cloud

选用Spring-cloud的最大原因是大多数分布式架构都是用了spring全家桶,减少学习成本。

RocketMQ

  1. RocketMQ在性能上虽然不及Kafka,但是已经足够应用与企业应用。 并且横向扩容非常优秀。
  2. RocketMQ支持基于tags的消息过滤,并且是在broker端,符合我们基于事件编号过滤的诉求
  3. RocketMQ支持一个topic对应 多个consumerGroup,正好符合一个consumerGroup是一个服务的关系
  4. RocketMQ支持事务消息,可以更好的支持后续扩展。
  5. RocketMQ支持广播模式,符合当订阅变更时通知consumerGroup下所有服务变更的诉求
  6. RocketMQ在消费失败后,会将消息放入一个以consumerGroup命名的死信队列,符合后期对于消息重试的场景
  7. RocketMQ的tag支持动态变更,支持订阅变更时的基于新tag过滤需求

其他

数据库,缓存,Nosql等使用常用的方案即可,不做过多赘述。

详细方案

服务启动

服务启动流程

  1. 消费者从配置中心获取MQ配置,并初始化相关consumer
  2. 消费者通过rpc接口(feigin)连接事件中心,获取本服务订阅的事件列表
  3. 处理事件列表,设置相应consumer的tags

订阅变更

当消费者需要订阅新事件时

  1. 在事件中心表中增加订阅信息
  2. 推送新订阅信息给到指定服务
  3. 指定消费者服务根据新订阅设置tags

事件驱动架构及实现_第9张图片

核心链路

事件驱动架构及实现_第10张图片

代码实现

事件驱动架构及实现_第11张图片

event-consumer

事件消费者SDK

事件驱动架构及实现_第12张图片

核心代码

  • EventHandleRocketMQConsumeAdapter

用于实际事件处理的消费者组

  • ConfigInfoRocketMQConsumeAdapter

用于接收订阅配置信息的消费者组,该消费者组为广播模式,当接到订阅变更消息时,会自动将新订阅信息设置到事件处理consumer中

  • StandardEventReceiver

标准事件处理器,在将事件分派到各业务处理前进行统一处理,比如根据事件头设置租户,操作信息等

  • EventDispatcher

将事件分派到各业务处理器

  • EventBusHandlerBeanPostProcessor

事件处理器缓存,服务启动时将处理器按事件编号+tag分组

event-consumer-api

消费者RPC接口,返回事件消费成功/失败,主动拉取事件订阅列表

event-domain

事件驱动架构及实现_第13张图片

  • EventSender

事件发送机,将事件投递到MQ

  • ConfigSender

配置发送机,将订阅配置通过MQ发送到指定消费者服务

event-producer

事件生产者SDK,核心功能通过RPC将事件发送给事件中心,由事件中心去投递

你可能感兴趣的:(传统企业数字化转型及落地,架构,java,开发语言)