Skip to main content
  1. internet/

事件分发平台

·1717 words·4 mins·

背景 #

随着业务的增长,一个事件开始被多个子系统订阅,如用户注册事件就可能被处理用户逻辑的子系统和日志系统订阅。以往我们在处理这些逻辑的时候,要么在处理完注册逻辑后,调用多个子服务接口,要么用消息队列中间件来处理。这些都导致了较高的维护成本。

另外,整体系统使用了严格的分层,下层服务不能调用上层服务,同层之间也不能调用。如果有回调等需求,也要通过事件的方式来传递数据。

因此,我们基于消息队列的思想,创建了事件分发平台。

与MQ的区别 #

  1. mq作为中间件是不能定制功能的。比如查看某个服务订阅了哪些事件、某个事件被哪些服务订阅了、监控、报警、服务的详细信息、负责人等
  2. 定位不同,mq定位是中间件,而我们的服务定位是事件分发。可以说我们的服务是基于mq的思想来做的,但是我们要做的是事件分发而不是mq,比如我们有或者可以有一些功能(第一条)

为什么要用事件分发而不是MQ #

  1. 对接简单,直接使用http。
  2. 对业务方来讲,不需要维护mq中间件。在事件分发中mq的使用是对业务方无感的,免去版本升级等维护工作。
  3. 对于整体架构来说,使用事件分发更方便管理,我们可以一眼就能看到事件被哪些服务订阅,哪些服务发布了什么事件。
  4. 统一的事件分发工具,而不是各自实现。

什么情况下使用MQ而不是事件分发 #

  1. 海量数据,如发送事件的qps上万的服务
  2. 不能提前约定事件ID的功能

架构设计 #

整个事件分发系统大致由以下构成:

  • 事件发送方:即事件的生产者
  • 事件订阅方:即事件的消费者
  • 事件分发服务:接收事件,处理事件的发送逻辑
  • 事件管理平台:通过web页面管理事件的配置,显示错误日志等信息

我们主要关注事件分发服务:

  • 订阅者队列组:每个订阅方(事件接收方)都有自己单独的队列,因此生产者和订阅者队列是一对多的关系。订阅者队列组来管理一个事件的多个订阅队列(实际上订阅队列组在上层还有一个订阅者队列管理器,来统一管理这些订阅队列组,与业务逻辑无关,因此图中未显示)。
  • 订阅者集群组:每个订阅者队列都对应着一个订阅者集群,该集群由多个channel组成,用来加快事件的消费速度。集群具备自动扩容、缩容的功能。
  • 配置中心:配置中心是通过内存来存储着事件的订阅关系。配置中心通过读取或者监听redis的变动,来管理订阅关系。订阅者队列组和订阅者集群组都会读取配置中心并监听配置中心变动,来管理自己的队列或集群。

消息队列中间件的选择 #

事件分发平台是基于消息队列的思想来构建的,因此需要使用消息队列中间件来管理消息队列。

市面上有许多消息队列中间件,如kafka、rabbitmq等。我们考虑到所需吞吐量并不大,所以初步选择使用redis的列表来实现。使用redis的列表来实现,优势在于能够更快速的完成开发,且整体系统更轻量

一旦发展到redis的列表不能满足需求时,通过接口或者叫适配器,也能轻松的完成消息队列中间件的切换。

如何监听redis中的事件变动 #

在事件管理平台将事件订阅关系变动后,会将数据存储到redis中。配置中心如何监听这些数据的变动呢?

每隔一段时间就读取全量数据是最简单的做法,也是最粗暴的做法。因为事件数据有很多,每次读取、对比都需要不少的时间,这就导致事件分发服务对于事件配置的变动很“迟钝”。

我们通过版本号的方法来解决。通过一个hash来存储发送者信息、事件信息、订阅者信息、订阅关系信息的版本号,每次修改这些信息时,都要对其对应的版本号自增。同时,事件分发服务在内存中也会维护这样一个版本号,每隔一段时间(如200ms)读取一次,进行更新,当事件分发服务发现版本号不对的时候,就会去拉取对应的数据,来更新内存中的数据。

这样每次只读取一个很小的hash key即可知道哪些数据需要更新。

消费逻辑 #

有以下几点需要注意:

  1. 有些订阅者服务需要按照时序来接收事件
  2. 系统处于维护状态时,不能接收事件,需要将事件暂存

整体流程图如下:

其中时序功能采用最简单“先到先得”,即按照事件分发服务接收到请求的时间来排序。

可进一步优化的地方 #

在发送事件时,如果发送失败会进行重试,但是如果超过了重试次数,那么该事件就会丢失。

可考虑在发送失败后报警并每隔一段时间进行发送,直到服务恢复正常,能够正常返回数据时,再继续消费数据。