在许多情况下,尤其是在搞一致性算法的时候,如何对事件(Event)集中进行处理就变成了一件很重要的事情,下面,我们来介绍如何写一个简单的事件处理引擎,并且向上层提供一个简单可用(无脑)的API供其他程序员使用。
首先我们需要明白我们要对各种各样的事件进行处理,例如,在PBFT中,我们的消息类型有 Preprepare
, Prepare
, Commit
, ViewChange
等等,具体的消息类型的定义我们可以使用protobuf来,而无论是什么事件,我们在Golang中我们都可以把它们抽象成一个 interface{}
。
1 | type Event interface{} |
这里我们定义的 Event
是我们以后围绕着处理的中心,我们继续看。定义完了 Event
,我们还要定义一个 Event
的接受者(Receiver),也就是事物处理的核心。
1 | type Receiver interface { |
在这里我们注意到这是一个 interface
,只要你定义的对象实现了 ProcessEvent
这个方法,那么它就是一个 Receiver
,当然,你自己定义的 ProcessEvent
里面才是真正的业务逻辑,这里仅仅提供一个框架而已。
定义完了事件的接受者,Receiver
,我们来看下更高层次的管理者 Manager
,如果把 Manager
想象成公司里的领导,那么他负责的事情就是调度,并且指派任务给专门的个体也就是我们的 Receiver
去处理。 Receiver
是具体干活的,而 Manager
则是分配任务的。
1 | type Manager interface { |
一个Manager的职能具体有两大点,一就是管理事件的队列(events这个通道),二就是取出队列中的事件,指派接受者(SetReceiver(Receiver))进行处理(Start)。
1 | func (em *managerImpl) Queue() { |
1 | func (em *managerImpl) Start() { |
Manager
的启动就是就是开了一个goroutine对消息进行循环处理,下面来看一下 eventLoop
的实现。
1 | func (em *managerImpl) eventLoop() { |
eventLoop
里面是咋一看是一个死循环,但它还是有出口的,它通过 for select
机制一直去轮询 em.events
和em.exit
这两个通道,如果 em.exit
这个通道有值,则说明该事件处理循环要退出了,这就是程序的一个出口,但如果em.events
有消息返回,我们则将它插入到我们 manager
的事件队列里等待被处理,也就是调用 em.Inject(e)
。
1 | func (em *managerImpl) Inject(event Event) { |
Inject
行为的主体是 manager
, manager
把任务(event)分配给我们的 receiver
。
1 | func SendEvent(receiver Receiver, event Event) { |
SendEvent
函数才是真正干活的主力,它主要就是调用 Receiver
实现定义好的 ProcessEvent
方法,因为处理一个事件后可能会返回另一件事件,所以循环处理它直到为空时候返回。看到这里,你可能会不理解处理意见事件会什么会返回另一件事件呢,其实现实中处理完A事件返回B事件的例子有很多,例如在PBFT中,处理完 Preprepare
事件,我们赶忙就要开始处理 Prepare
事件了。这种例子还有很多,以后有机会我们再详细介绍。
上面就是我们整个事件处理的引擎了,不难,但是设计的非常精妙,下面我们来看如何使用它。
1 | type aEvent Event |
这里我们定义了三种事件类型,并且定义了一个包含manager的结构体,这个结构体实现了 ProcessEvent
方法,也就是说它实现了 Receiver
这个接口, ProcessEvent
方法里通过判断接收到的event的类型,来进行相应的处理,注意到处理完aEvent
会返回bEvent
,这就说我们说的处理完一件事件再返回一件事件的例子了。
在 main
函数中,我们新起一个 Instance
实例,并初始化里面的 manager
,把 manager
的 receiver
设置为自己,因为是 Instance
实现了 ProcessEvent
方法,所以说谁实现了 ProcessEvent
方法, SetReceiver
就设置为谁,最后再用 Start
起一个goroutine不断地处理接收进行的事件。
在我们向manager管理的事件队列中插入一个 aEvent
事件后, eventLoop
就会开始处理事件,命令行中应该会依次输出:
1 | Received A Event, return B Event |
这个事件处理引擎的实现非常精妙,还是希望读者可以反复阅读几次。