[ThingsBoard] 3. 源码解读Actor
一、前言
本文基于 ThingsBoard 4.0.2 编写,对应提交Version set to 4.0.2(01c5ba7d37006e1f8a3492afbb3c67d017ca8dd3)
。
由于个人技术能力和写作经验有限,欢迎读者指出文中的错误与不足。
二、Actor模型
参考 Actor模型是解决高并发的终极解决方案 - 知乎
本人写的一般,可以看参考文章
1. Actor
Actor 模型则将一切视为 Actor。Actor 是并发执行的基本单元,与其他 Actor 之间通过消息传递进行通信,当接收到消息时,一个 Actor 可以并行地执行三件事:向其他 Actor 发送消息、创建新的 Actor,以及决定如何处理下一条消息。
2. 消息传递
Actor之间互相并行,因此消息传递都是异步的,Mailbox负责给Actor进行消息传递。
两个Actor之间消息传递时,Actor A发送到Actor B的Mailbox。等到Actor B处理消息时。
它就会从自己的Mailbox中获取消息,进行处理。
三、ThingsBoard中的Actor
ThingsBoard
以下简称TB,代码进行一些精简,方法使用lambda方法引用表示。
1. TbActorMailBox
在TB的Actor模型中,Mailbox
对应的实现类是TbActorMailbox
,其继承关系为TbActorMailbox
→TbActorCtx
→TbActorRef
。值得注意的是,TbActorCtx
和TbActorRef
在整个系统中都只有TbActorMailbox
这一个实现类,因此在之后的代码阅读中可以将TbActorRef
和TbActorCtx
都认为是TbActorMailbox
。这点在之后的代码分析很有用。
Actor通过其::tell
和::tellWithHighPriority
传递消息其对应的Actor。每个TbActor
都只对应一个TbActorMailbox
private final TbActor actor;
public void tell(TbActorMsg actorMsg) {
enqueue(actorMsg, NORMAL_PRIORITY);
}
public void tellWithHighPriority(TbActorMsg actorMsg) {
enqueue(actorMsg, HIGH_PRIORITY);
}
private void tryProcessQueue(boolean newMsg) {
dispatcher.getExecutor().execute(this::processMailbox);
}
private void enqueue(TbActorMsg msg, boolean highPriority) {
if (highPriority) {
highPriorityMsgs.add(msg);
} else {
normalPriorityMsgs.add(msg);
}
tryProcessQueue();
}
其中::tryProcessQueue
会异步处理消息队列,实际的处理逻辑在::processMailbox
中。通过actor::process
委托给实际的Actor执行。
private void processMailbox() {
for (int i = 0; i < settings.getActorThroughput(); i++) {
TbActorMsg msg = highPriorityMsgs.poll();
if (msg == null) {
msg = normalPriorityMsgs.poll();
}
if (msg != null) {
actor.process(msg);
}
}
}
2.Actor的初始化:
初始化的逻辑只选择部分重点分析,其他部分可以让ai能进行逐行分析。
Actor可以分为两大类
- 管理其他Actor,负责创建其管理的Actor,传递消息。
- 逻辑处理,接收消息,将消息委托给processor字段类处理。
AppActor
首先TB也是一个Spring应用,因此Actor的创建也是从被Spring管理的Bean开始第一个的创建。其受到Spring管理的就是DefaultActorService
类在::initActorSystem
中创建了AppActor
。
看向TbActorSystem::createRootActor
方法,它需要一个TbActorCreator
的实例,而TbActorCreator
则是负责创建每个TbActor
的对象。会调用::createActor
。
@PostConstruct
public void initActorSystem() {
appActor = system.createRootActor(APP_DISPATCHER_NAME, new AppActor.ActorCreator(actorContext));
}
::createActor
中创建了一个TbActorMailbox
。
在TbActorSystem
内部会逐步调用到::createActor(String, TbActorCreator, TbActorId)
,它加锁进行创建。
public TbActorRef createRootActor(String dispatcherId, TbActorCreator creator) {
return createActor(dispatcherId, creator, null);
}
public TbActorRef createChildActor(String dispatcherId, TbActorCreator creator, TbActorId parent) {
return createActor(dispatcherId, creator, parent);
}
private TbActorRef createActor(String dispatcherId, TbActorCreator creator, TbActorId parent) {
Dispatcher dispatcher = dispatchers.get(dispatcherId)
TbActorId actorId = creator.createActorId();
TbActorMailbox actorMailbox = actors.get(actorId);
Lock actorCreationLock = actorCreationLocks.computeIfAbsent(actorId, id -> new ReentrantLock());
actorCreationLock.lock();
try {
if (actorMailbox == null) {
log.debug("Creating actor with id [{}]!", actorId);
TbActor actor = creator.createActor();
TbActorRef parentRef = null;
if (parent != null) {
parentRef = getActor(parent);
}
TbActorMailbox mailbox = new TbActorMailbox(this, settings, actorId, parentRef, actor, dispatcher);
actors.put(actorId, mailbox);
mailbox.initActor(); // 注意此处
actorMailbox = mailbox;
} else {
log.debug("Actor with id [{}] is already registered!", actorId);
}
} finally {
actorCreationLock.unlock();
actorCreationLocks.remove(actorId);
}
return actorMailbox;
}
private TbActorRef createActor(String dispatcherId, TbActorCreator creator, TbActorId parent) {
TbActor actor = creator.createActor();
TbActorRef parentRef = null;
if (parent != null) {
parentRef = getActor(parent);
}
TbActorMailbox mailbox = new TbActorMailbox(this, settings, actorId, parentRef, actor, dispatcher);
actors.put(actorId, mailbox);
mailbox.initActor();
}
而在TbActorMailbox::initActor
方法中,会提交::tryInit
的异步初始化,其中actor.init(this)
。就进行了Actor的初始化。
public void initActor() {
dispatcher.getExecutor().execute(() -> tryInit(1));
}
private void tryInit(int attempt) {
actor.init(this);
}
TenantActor
AppActor::doProcess
的第一个if,调用::initTenantActors
,它初始化所有TenantActor
。
AppActor自己管理着所有的TenantActors。其::getOrCreateTenantActor
则完成了获取和创建的工作。
实现了一个懒加载的功能。
protected boolean doProcess(TbActorMsg msg) {
if (!ruleChainsInitialized) {
if (MsgType.APP_INIT_MSG.equals(msg.getMsgType())) {
initTenantActors();
ruleChainsInitialized = true;
}
}
}
private void initTenantActors() {
PageDataIterable<Tenant> tenantIterator = new PageDataIterable<>(tenantService::findTenants, ENTITY_PACK_LIMIT);
for (Tenant tenant : tenantIterator) {
log.debug("[{}] Creating tenant actor", tenant.getId());
getOrCreateTenantActor(tenant.getId())
}
}
追溯::getOrCreateTenantActor
,他会使用类型为TbActorCtx的ctx字段的getOrCreateChildActor
,根据之前的分析我们可以直接到TbActorMailbox
进行分析。
private Optional<TbActorRef> getOrCreateTenantActor(TenantId tenantId) {
return Optional.ofNullable(ctx.getOrCreateChildActor(new TbEntityActorId(tenantId),
() -> DefaultActorService.TENANT_DISPATCHER_NAME,
() -> new TenantActor.ActorCreator(systemContext, tenantId),
() -> true));
}
显示从类型为TbActorSystem
的system
字段获取actor。实现上DefaultTbActorSystem
内有一个actors
的ConcurrentMap
的字段用于存储系统中所有的Actor。之后就和AppActor
创建处一致的执行流程。
public TbActorRef getOrCreateChildActor(TbActorId actorId, Supplier<String> dispatcher, Supplier<TbActorCreator> creator, Supplier<Boolean> createCondition) {
TbActorRef actorRef = system.getActor(actorId);
if (actorRef == null && createCondition.get()) {
return system.createChildActor(dispatcher.get(), creator.get(), selfId);
} else {
return actorRef;
}
}
RuleChainActor
同理RuleChainActor
也是由TenantActor
进行创建的。不过实际的逻辑在TenantActor
的父类RuleChainManagerActor
中。
一般都是在::init
方法中调用::initRuleChains
方法.initRuleChains
方法就会从数据库中获取所有RuleChain并初始化。
负责逻辑处理的Actor
RuleChainActor
到了它,明显有了不一样,一下子类一下子变得简洁了。类本身只有两个方法
分析一下它的父类。RuleChainActor
→RuleEngineComponentActor
→ComponentActor
。
其中的ComponentActor::init
方法就调用了抽象方法ComponentActor::createProcessor
。
@Override
public void init(TbActorCtx ctx) throws TbActorException {
this.processor = createProcessor(ctx);
initProcessor(ctx);
}
再向上看ComponentActor
的父类ContextAwareActor
就发现,::process
变成对::process
做了个封装。只是添加了打印日志的功能。::doProcess
其中,只是将对应的消息委托给了对应的processor
。
public boolean process(TbActorMsg msg) {
if (log.isDebugEnabled()) {
log.debug("Processing msg: {}", msg);
}
if (!doProcess(msg)) {
log.warn("Unprocessed message: {}!", msg);
}
return false;
}
RuleNodeActor
这就是规则引擎最核心的部分,可以明显的推测到每个TbNode
都对应着一个RuleNodeActor
。
不过同理,它的实际逻辑也委托给了processor
。但是明显它的创建更加复杂。
它的创建实际上是由RuleChainActor
中的processor
负责的,即RuleChainActorMessageProcessor
类。
public void init(TbActorCtx ctx) {
this.processor = createProcessor(ctx);
initProcessor(ctx);
}
protected void initProcessor(TbActorCtx ctx) {
processor.start(ctx);
}
其中的::start
方法,而::start
的方法则是在前文TbActor
的父类ComponentActor::init
中再调用::initProcessor
中,
调用的RuleChainActorMessageProcessor::start
方法。
public void start(TbActorCtx context) {
List<RuleNode> ruleNodeList = service.getRuleChainNodes(tenantId, entityId);
for (RuleNode ruleNode : ruleNodeList) {
TbActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));
}
initRoutes(ruleChain, ruleNodeList);
}
其中从数据库中读取到所有nodeActors
的信息并将其全部创建。
再调用::initRoutes
初始化ruleChain
中对应的路由。
private void initRoutes(RuleChain ruleChain, List<RuleNode> ruleNodeList) {
for (RuleNode ruleNode : ruleNodeList) {
List<EntityRelation> relations = service.getRuleNodeRelations(TenantId.SYS_TENANT_ID, ruleNode.getId());
log.trace("[{}][{}][{}] Processing rule node relations [{}]", tenantId, entityId, ruleNode.getId(), relations.size());
if (relations.isEmpty()) {
nodeRoutes.put(ruleNode.getId(), Collections.emptyList());
} else {
for (EntityRelation relation : relations) {
nodeRoutes.computeIfAbsent(ruleNode.getId(), k -> new ArrayList<>())
.add(new RuleNodeRelation(ruleNode.getId(), relation.getTo(), relation.getType()));
}
}
}
}
能看出nodeRoutes
接近一个邻接表的结构,构建了整个路由。至此将Actor创建的三种模式介绍清楚。
- 由外部创建,如
AppActor
。 - 由其父Actor创建,如
TenantActor
。 - 由对应的一个
processor
字段创建,如RuleChainActor
。
四、结尾
最开始分析的时候,我还不了解Actor模型,纯粹看着代码进行分析,惊叹于设计的巧妙。但是在QQ水群突然有人提及自己在改为了无锁的Actor模型,我搜索一番,原来这是一个很成熟的通用的设计了啊。自己写小项目还是喜欢Executor
和Future
一股脑的用。不知道下一篇要多久才更新了。
[ThingsBoard] 3. 源码解读Actor的更多相关文章
- Prometheus 源码解读(一)
Prometheus 源码解读(一) Prometheus 是云原生监控领域的事实标准,越来越来的开源项目开始支持 Prometheus 监控数据格式.从本篇开始,我将和大家一起阅读分析 Promet ...
- SDWebImage源码解读之SDWebImageDownloaderOperation
第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...
- SDWebImage源码解读 之 NSData+ImageContentType
第一篇 前言 从今天开始,我将开启一段源码解读的旅途了.在这里先暂时不透露具体解读的源码到底是哪些?因为也可能随着解读的进行会更改计划.但能够肯定的是,这一系列之中肯定会有Swift版本的代码. 说说 ...
- SDWebImage源码解读 之 UIImage+GIF
第二篇 前言 本篇是和GIF相关的一个UIImage的分类.主要提供了三个方法: + (UIImage *)sd_animatedGIFNamed:(NSString *)name ----- 根据名 ...
- SDWebImage源码解读 之 SDWebImageCompat
第三篇 前言 本篇主要解读SDWebImage的配置文件.正如compat的定义,该配置文件主要是兼容Apple的其他设备.也许我们真实的开发平台只有一个,但考虑各个平台的兼容性,对于框架有着很重要的 ...
- SDWebImage源码解读_之SDWebImageDecoder
第四篇 前言 首先,我们要弄明白一个问题? 为什么要对UIImage进行解码呢?难道不能直接使用吗? 其实不解码也是可以使用的,假如说我们通过imageNamed:来加载image,系统默认会在主线程 ...
- SDWebImage源码解读之SDWebImageCache(上)
第五篇 前言 本篇主要讲解图片缓存类的知识,虽然只涉及了图片方面的缓存的设计,但思想同样适用于别的方面的设计.在架构上来说,缓存算是存储设计的一部分.我们把各种不同的存储内容按照功能进行切割后,图片缓 ...
- SDWebImage源码解读之SDWebImageCache(下)
第六篇 前言 我们在SDWebImageCache(上)中了解了这个缓存类大概的功能是什么?那么接下来就要看看这些功能是如何实现的? 再次强调,不管是图片的缓存还是其他各种不同形式的缓存,在原理上都极 ...
- AFNetworking 3.0 源码解读 总结(干货)(下)
承接上一篇AFNetworking 3.0 源码解读 总结(干货)(上) 21.网络服务类型NSURLRequestNetworkServiceType 示例代码: typedef NS_ENUM(N ...
- AFNetworking 3.0 源码解读 总结(干货)(上)
养成记笔记的习惯,对于一个软件工程师来说,我觉得很重要.记得在知乎上看到过一个问题,说是人类最大的缺点是什么?我个人觉得记忆算是一个缺点.它就像时间一样,会自己消散. 前言 终于写完了 AFNetwo ...
随机推荐
- win mysql实现主从同步(精简版)
最近项目要弄读写分离,那首先要实现主从同步啊,网上教程很多,但大多都看得云里雾里,so,有了这个精简版: 主库my.ini添加配置: #数据库ID号, 为1时表示为Master,其中master_id ...
- php版10大设计模式,软件工程必须掌握的姿势
作为一个半路出家的php萌新,在看公司老大们的代码时无时无刻不在感叹,老大就是老大,写的代码低耦合.易扩展,我怎么就想不出这写完美的实现方式,最近看了韩大佬的视频后才明白,原来这些都是业界前辈们总结提 ...
- AI 在软件测试中的应用:2025 年趋势、工具及入门指南
引言 人工智能 (AI) 正在深刻地重塑软件开发和质量保证 (QA) 的各个方面.尤其是在软件测试领域,AI 不再仅仅是未来愿景,而是当下正在发生的变革.据世界质量报告(2023-24)指出,高达 7 ...
- Windows-exporter(node-exporter)+ Prometheus + Grafana资源监控搭建
在性能测试过程中,资源监控可以时刻掌握被测软件运行环境的各类数据,从而更加直观地反馈测试过程中潜在的问题,下面是基于Windows-exporter(node-exporter)+ Prometheu ...
- 鸿蒙Next开发实战教程—电影app
最近忙忙活活写了不少教程,但是总感觉千篇一律,没什么意思,大家如果有感兴趣的项目可以私信给幽蓝君写一写. 今天分享一个电影App. 这个项目也比较简单,主要是一些简单页面的开发和本地视频的播放以及 ...
- RPC实战与核心原理之优雅启动
优雅启动:如何避免流量打到没有启动完成的节点? 回顾 优雅停机,就是为了让服务提供方在停机应用的时候,保证所有调用方都能"安全"地切走流量,不再调用自己,从而做到对业务无损.其中实 ...
- WindowsPE文件格式入门05.PE加载器LoadPE
https://bpsend.net/thread-316-1-1.html LoadPE - pe 加载器 壳的前身 如果想访问一个程序运行起来的内存,一种方法就是跨进程读写内存,但是跨进程读写内存 ...
- odoo知识图谱
最近项目交付后,准备将系统整个知识点整理一下,下面是目录,后面针对目录编写文档--todo
- CentOS7.* 查询开机启动项
使用 systemctl list-unit-files 可以查看启动项 左边是服务名称,右边是状态,enabled是开机启动,disabled是开机不启动 过滤查询可以systemctl list- ...
- MySQL中用户及权限管理(mysql8.0版本)
概述 在MySQL中,用户与权限管理属于关键的安全机制,能让你对数据库的访问进行精准控制 MySQL用户管理 创建用户信息 语法 CREATE USER username@'host' IDENTIF ...