【异步编程】实战之EventBus

事件驱动

  这篇文章记录一下我使用了两年多自研的EventBus,刚开始接触事件驱动程序的时候是结合状态机一起玩的,后来编程经验越来越足后,事件驱动就用的更多了。一方面,我们比较了解MQ,但是MQ作为中间件,在系统之间的事件传递是很有必要存在的。我所提及的事件驱动,特指进程内事件驱动。也就是进程事件总线。

  需求

  • 假设我们现在写的系统有异步需求
  • 希望利用事件进行组件之间的解耦合
  • 要保证程序具有极高的吞吐量要求

  这个时候就需要这个世界框架了,其实有经验的同学会发现,Spring其实也拥有发布事件和订阅事件的能力,而且还支持异步,支持拓展,我为何要自己写一个事件框架?

  因为:

  • 不要过分第三方,即使是Spring,因为这些代码不在你的控制范围内,例如你有中介者模式需求的时候;
  • 你可以得到比拓展Spring更高的拓展性,例如:你完全可以开发一个和Netty一样的EventLoop支持你的每一个事务;
  • 分离调度,采用Spring,你很难把过程的调度完全掌控在你手上,没有安全感;
  • 你有自己更高效的调度方式,这也就是为什么要开发一个Netty版的EventLoop

  特色:

  • 自定义事件执行的顺序一致性
  • 支持事件的发送和接收者配置化
  • 支持对事件依赖对象的异步跟踪能力

事件框架

  事件框架的主要组件有:事件总线EventBus,事件中心EventBub,事件,事件主题,事件处理者EventHandler,下面分别介绍各个组件的作用;

  事件:Event,具体事件类,通常可以用一个值对象表示,但也可以用实例表示,用实例表示的时候可以带上事件源对象,用Object存事件源,也可以把泛型payload设为Event类的一个属性

  事件总线:EventBus,事件对外的接口。具有三个发送事件的核心方法:

    • 点对点模式,send(toObj , event ,isAsync),其中toObj是处理者的实际引用
    • 点对点模式,send(toId , event  ,isAsync),其中toId是处理者的符号引用,这种是中介者模式的实现
    • 发布订阅模式,publish(event ,isAsync),发布事件出去,订阅该事件的所有EventHandler都可以收到消息处理。

  事件源:创建事件的对象,使用事件总线发送事件出去的对象;

  事件处理者:EventHandler,事件处理者,实现该接口的对象都是事件可以者,可以支持被注册到事件主题中。

  事件主题:发布订阅模式的主题,一个事件类唯一对应一个事件主题类,事件主题类主要持有订阅各种事件处理者的集合;

  事件中心:EventHub,主要作用有:

    • 用作调度,异步执行事件;
    • 创建事件主题,管理维护事件主题;
    • 注册事件处理者,用作中介者模式实现;也就是为了支持 点对点模式 send(toId , event , isAsync)而诞生的

  事件循环( 属于调度模型,其实也可以用线程池执行 :EventLoop,一个线程对应一个Loop,里面有一个事件队列等待处理,无事件的时候会阻塞。通常一个事务绑定一个Loop,而一个Loop对应多个事务。这样可以保证事务线程安全,带同步功能,具体可以参考Netty的设计,就是把channel绑定在一个EventLoop上面的。

  看一下类图,下面类图反应了部分类的关系:

设计模式

  上面提到了很多次中介者模式,下面提下这个框架主要用到的设计模式

  中介者模式  

  中介者模式的核心是一个Map,也就是所有对象之间的通讯不是直接耦合,而是通过一个中介者类通讯,达到解耦合目的,使得架构成功星型结构。

  1. 所以每一个需要被通知的对象,都要实现一个同一个抽象接口,
  2. 然后把自己注册到中介者类中,中介者类用一个Map作为容器来存储维护被通讯对象的引用,key为被通知者唯一ID,value为被通知者。
  3. 而通知者只需知道被通知者的ID就可以依赖中介者,委托中介调用被通知者的抽象方法。

  观察者模式

  这个比较简单了,EventSubject就是被观察者,EventHandler就是观察者,而Event就是他们更新和需要同步的数据。

  下面给出一段代码,表示整个事件总线接口哪个是使用哪种模式:

/**
 * 消息总线
 * @date 2019年1月9日
 */
@Component
public class EventBus  {


  public EventBus() {
  }

  /**
   * 直接发送到EventBus中
   *
   * @param eventType
   * @param toItem
   * @param object
   */
  public void push(Object toItem, int eventType, Object payload) {
    ......
  }

  /**
   * 观察者模式,也就是发布订阅模式,支持自动创建事件主题
   *
   * @param eventType
   * @param object
   */
  public void publish(int eventType, Object payload) {
    ......
  }

  /**
   * 点对点模式,中介者的使用接口
   *
   * @param eventType
   * @param toItem
   * @param object
   */
  public void push(String toItemId, int eventType, Object payload) {
    ......
  }


}

 

事件调度

  对象是过程的抽象,线程是调度的抽象,以下调度方式一个比一个复杂,所以可以按照项目需求选取

  同步:同步,最简单,直接用发送事件主线程执行

  线程池:最简单的事件调度是:线程池,池化技术虽然好用,但是并非使用所有原则,但如果你的应用比较简单,可以直接在EventHub中使用线程池,把Message封装为一个可执行任务,直接丢给线程池运行即可

  EventLoop:事件循环模型,一个线程代表一个loop,一个loop一般对应一个队列,用一个EventloopGroup管理多个EventLoop,这就是经典的Netty线程模式(适合事务消息顺序一致性),而JS的就是一个EventLoop对应多个队列,按队列自定义顺序优先级执行。

  WorkStealing:工作窃取算法结合的调度模型,效率最高,复杂度也高

结合Netty

  我们一般作的应用都会作为服务发布出去,Java搭建微服务最快的方式无疑是SpringBoot,但我们知道,如果我们的系统采用领域内事件,那么就注定了我们的设计是异步的了。那怎么办?其实这是一个异步实现同步的问题。

  方法1:用一个线程,发送异步的时候阻塞等待条件condition,等异步线程返回后notify该condition激活阻塞线程即可,这种方法编程简单,但不可取,因为线程开销很大。

  方法2:使用NIO,Netty就是我们最好的朋友了。很简单,把pipeline存起开,然后异步线程处理完后,直接把回复的任务pipeline.writeAndFlush的任务提交到对应的EventLoop中即可。

后面有具体的案例,请看我的文章

【异步编程】实战之Netty不二之选

后期会持续细化这个框架...........

posted @ 2020-03-29 01:39  饭小胖  阅读(974)  评论(0编辑  收藏  举报