AKKA中的事件流
在《企业应用集成模式》一书中,定义了许多与消息处理有关的模式,其中运用最为广泛的模式为Publisher-Subscriber模式,尤其是在异步处理场景下。
基于Publisher-Subscriber模式,还可以根据不同的场景衍生出特殊的模式,例如针对一个Publisher和多个Subscriber,演化为Broadcast模式和Message Router模式。前者会将消息同时发送给所有的Subscriber,实现分布式的并行处理。例如针对订单处理的场景,当顾客下订单后,既需要生成订单,又需要通知库存准备发货,还需要通知卖方和买方。这些任务虽然存在事务的一致性,但基于BASE原则,可以通过补偿机制实现事务的最终一致性。于是,设计时可以将这些任务交给不同的Subscriber,当接收到消息后,同时对订单进行处理。至于Message Router,则需要引入的Router对传入的消息作出智能判断,从而将消息传递给真正感兴趣的Subscriber。这就好像发布者同时发布了不同的刊物,订阅者只订阅自己喜欢的刊物。
而消息总线(message bus)则通过引入总线来彻底解除Publisher与Subscriber之间的耦合,类似设计模式中的Mediator模式。总线就是Mediator,用以协调Publisher与Subscriber之间的关系。或者,我们也可以认为是两个Publisher-Subscriber的组合。对于Publisher而言,总线就是Subscriber;对于Subscriber而言,总线则成了Publisher。
AKKA提供的事件总线(Event Bus)可以看做是一种运用于特殊场景的消息总线,此时事件即为消息。它可以看做是Message Router模式的实现,提供了向多个Actor发送消息的基础设施,内含的Classifier作为分类器,用于分发消息时选择Subscriber,扮演了Message Router的角色。
在AKKA中,Event Bus被定义为trait,定义了基本的订阅、取消订阅、发布等对应的方法,代码如下所示:
trait EventBus {
type Event
type Classifier
type Subscriber
def subscribe(subscriber: Subscriber, to: Classifier): Boolean
def unsubscribe(subscriber: Subscriber, from: Classifier): Boolean
def unsubscribe(subscriber: Subscriber): Unit
def publish(event: Event): Unit
}
根据AKKA官方文档的描述,Event为所有发布到该总线上的事件类型,Classifier是选择订阅者的分类器,Subscriber就是注册到该总线上的订阅者。它们均被定义为抽象的type,使得EventBus拥有最大的开放性。我们视情况而定去具体实现特定类型。例如针对Actor的EventBus,订阅者被指定为ActorRef类型:
trait ActorEventBus extends EventBus {
type Subscriber = ActorRef
protected def compareSubscribers(a: ActorRef, b: ActorRef) = a compareTo b
}
AKKA运用Cake Pattern,以trait类型定义了一些特化的分类器。例如:
trait ActorClassifier {
this: EventBus ⇒
type Classifier = ActorRef
}
trait PredicateClassifier {
this: EventBus ⇒
type Classifier = Event ⇒ Boolean
}
除此之外,还有诸如SubchannelClassification、ScanningClassification、ActorClassification等分类器,都采用了同样的方式定义为trait。这样就便于继承EventBus的类进行trait的混入,例如EventStream的定义:
class EventStream(private val debug: Boolean = false) extends LoggingBus with SubchannelClassification {
type Event = AnyRef
type Classifier = Class[_]
}
由于LoggingBus继承自ActorEventBus,故而EventStream中的Subscriber类型被定义为ActorRef。然后在EventStream中又重写了Event和Classfier类型,分别为AnyRef和Class,这说明任何Java引用对象都可以作为事件,而分类的依据则为Event的类型。
type Subscriber = ActorRef
type Event = AnyRef
type Classifier = Class[_]
EventStream继承了SubchannelClassification。在其中维持了订阅者列表,虽然该订阅列表类型为SubclassifiedIndex,不过我们可以将其简单地视为一个Map(实际情况更复杂,因为它实际上维护了分类的层级):
trait SubchannelClassification {
this: EventBus ⇒
private lazy val subscriptions = new SubclassifiedIndex[Classifier, Subscriber]()
}
由于此时的Classifier在EventStream中被定义为Class[_],Subscriber为ActorRef,因此subscripions相当于被定义为:
private lazy val subscriptions = new SubclassifiedIndex[Class[_], ActorRef]
当Actor出现故障,从而使得消息被转发给dead letter时,我们可能需要侦听这些死信,并对它们进行处理。则AKKA的做法就是通过EventStream来进行订阅:
class DeadLetterListener extends Actor {
def receive = {
case DeadLetter(msg, from, to) =>
println(s"${System.currentTimeMillis()}: from $from to $to with message $msg.")
}
}
val listener = system.actorOf(Props[DeadLetterListener], "listener")
system.eventStream.subscribe(listener, classOf[DeadLetter])
结合SubchannelClassification,我们来分析其执行过程。
首先,它通过subscribe方法将DeadLetterListener的actor引用对象以及事件类型DeadLetter注册到SubchannelClassification中的subscriptions。当ActorSystem的任意actor发出DeadLetter时,就会触发EventStream的publish()方法:
class EventStream(private val debug: Boolean = false) extends LoggingBus with SubchannelClassification {
protected def publish(event: AnyRef, subscriber: ActorRef) = {
if (subscriber.isTerminated) unsubscribe(subscriber)
else subscriber ! event
}
}
此时就会通过subscriber(此时为前面定义的DeadLetterListener)发送event(此时为DeadLetter对象),从而进入到DeadLetterListener的receive方法中,打印出我想要的消息。
通过EventStream还可以处理日志消息。AKKA自身也提供了默认的处理器,可以配置在application.conf文件中:
akka {
event-handlers = ["akka.event.Logging$DefaultLogger"]
}
这个默认的日志处理器会订阅高于配置级别的日志事件类,例如将日志级别配置为Debug:
system.eventStream.setLogLevel(Logging.DebugLevel)
通过这样的配置,所有低于Debug级别的日志事件发生时,都不会被EventStream分发。
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 【SpringBoot源码解析】第四章:SpringBoot是如何自动装配SpringMvc的
- 【SpringBoot源码解析】第二章:SpringBoot是如何通过内置Tomcat启动的
- 技术译文 | How Can ScaleFlux Handle MySQL Workload?
- 技术译文 | MySQL 8 需要多大的 innodb_buffer_pool_instances 值(上)
- 前端登录,这一篇就够了
- 技术译文 | MySQL 8 需要多大的 innodb_buffer_pool_instances 值(下)
- 创建线程到底有多少种方式?
- CANet|拼接注意力网络
- SQL 中判断条件的先后顺序,会引起索引失效么?
- 一个超酷的开源uHand2.0机械手掌项目
- 神经网络低比特量化——TQT
- web前端面试题:您能读懂的Promise源码实现(手写代码)
- web前端面试题对答篇:HTTP fetch发送2次请求的原因?
- MySQL 8.0之hash join
- MySQL 8.0 之原子DDL