写点什么

架构师训练营 1 期 -- 第七周笔记

用户头像
曾彪彪
关注
发布于: 2020 年 11 月 08 日

这一周准备和大家一起分享下Akka New API的入门,记了一些关于Akka的学习笔记,下边是分享内容的原文,勉强算学习笔记吧:



Akka Typed 入门介绍



大家好,我叫曾彪彪,是一名电梯物联网行业的软件架构师,今天我给大家简单介绍一下Akka Typed的入门。



写这篇文章之前,本来是想和大家介绍一下Akka入门的,讲讲为什么要用Akka,以及工作中的一些体会。去官网查资料的时候,发现好多东西都变了,官方的demo中,几乎找不到AbstractorActor这个类,都是继承了AbstractBehavior,愁死我了......



于是又多读了几篇文档,终于知道原来现在都是用New API了,并且官方也推荐使用New API。所以这个分享,大家也可以当做Akka最新版本的入门。接下来讲的这些类容,都是我这几天学习Akka 新的API的一些总结,由于时间短,如果有些理解有错误的地方,还请大家批评指正。



在开始介绍之前,先简单说一下我工作中为什么选择Akka。用一句话来说,就是吃了多线程的亏。大概6-7年前,我在上一家公司做一个电梯监控系统,这个系统主要提供一些电梯实时监控,故障处置,电梯远程控制等功能。当时服务器使用Netty,自定义的应用通信协议,客户端并发连接数大概在1W左右,长连接。为了提高性能,我们也对应用程序做了一些优化,比如降低临界区代码的范围,使用无锁容器等,但是遇到了好些问题。



举个例子,比如电梯会上报自己的运行状态,故障状态或者正常运行状态。如果在很短的时间内,电梯先后上报了故障状态,正常运行这两个状态,那么电梯的最终状态应该是正常运行状态。但是服务器的执行结果却并不保证,原因是:服务器肯定先收到故障状态,然后再收到正常状态。服务器处理消息是并发的,这两条消息被两个线程同时处理,如果正常消息先处理完成,在数据库中更新状态为正常。故障消息后处理完成,在数据库中更新状态为故障,那么电梯的最后状态是故障状态,与实际状态不一致。



这只是其中一个问题,还有一些问题,比如该同步的地方没同步,造成数据不一致的问题。



还有在使用ConcurrentHashMap的时候,我们也遇到了一些问题,这里不详细描述业务场景了,只是想和大家分享一下,ConcurrentHashMap虽然是线程安全的,但是你的使用姿势也要正确才行。比如如果多个线程同时执行put操作,那么可能造成数据的覆盖,只有使用putIfAbsence才不会出现数据覆盖。



这些问题测试环境比较难发现,有些问题是系统跑了几个月才暴露出来的。为了解决这些问题,我们尝试使用Akka,发现多线程的种种问题确实得到了改善。虽然后来在使用的过程中也遇到了一些问题,但这不是Akka框架本身的问题,应该算是我们使用姿势不对。



好吧,花这么长的篇幅介绍为什么使用Akka,是希望大家更了解Akka能解决什么样的问题(虽然Akka能解决的问题远不止我上面说的这些)。你想想,如果你不必再和恼人的多线程问题纠缠,把节省下来的时间和妹子聊天难道不好么,难道和妹子聊天的时候你不觉得开心么...(这段别给我删了啊~)



好吧,现在我们开始介绍Akka Typed吧。



考虑到有些同学可能之前没用过Akka,这里先简单介绍一下。Akka是一套开源的工具集,可以用它来构建高并发,高可用以及具有高容错率的分布式系统。如果访问Akka的官方,你会发现它包含了许多模块,我们今天要讲的只是它最基础的模块--Actor。



Actor封装了状态和行为,外界系统(也可以是其它Actor)只能通过发送消息给它来触发某些行为或改变状态。在同一时刻,一个Actor只能被一个线程处理,所以它是线程安全的。



新的版本中,引入了一个Actor Typed的概念,与之对应的原来的Actor,官方称它为Classic Actor。为什么要引入这个概念呢?



我们先来看一下传统的Actor存在什么问题,在Classic Actor中,任何消息都可以发给Actor,如果Actor处理不到,就丢给unhandled方法来处理,默认可能是打印一行日志,说这条消息没被处理。这带来了以下问题:



首先,如果消息发错了,这个actor处理不了这条消息,就丢给unhandled,系统不报错,但是你的应用程序却没有正常运行。



其次,也是最主要的问题,因为Actor可以接收任何消息,这个Actor就容易被设计成一个功能强大的Actor,职责不单一,甚至过于复杂。



针对这些问题,Akka在新的版本中使用Actor Typed,遵循protocol first(协议优先)原则。怎么理解协议优先原则呢?我们知道,actor和actor之间是可以发送消息的,如果我们对这个消息加些规范,能被哪些actor处理,不能被哪些actor处理,处理完之后应该发送给谁等等,我们认为这中被规范了的消息是一种协议。这就迫使我们在设计actor之前,要先考虑消息怎么设计,把消息的处理逻辑都想好了,那么actor的设计也就清晰了。



Actor Typed其实是通过泛型来实现的,可以理解为是类型安全的Actor。它的定义如下:



public Device extends AbstractBehavior<Device.Command> {
}



这个Device就是一个Actor了,这就规定了发给Device的消息只能是Device.Command,实际上这一般会被定为成一个接口。各种类型的消息都必须实现这个接口,才能被Device接收。



在新的API中,ActorReference也是强类型的,比如上面的Device,它的引用就是ActorRefrenece<Device.Command>,这样就只能给他发Device.Command消息了,在编译期就能识别。



Typed Actor是通过继承Behavior实现的,这是一个泛型类。Behavior就是一种类型消息的处理方式,一个Actor Typed只接受一种类型的消息,所以它是一个Behavior。



好吧,其实很多东西都是代码层面的规范。大家可以去官方看看新的API是怎么写的。如果你是一个新手,并且准备在项目中使用Akka,建议看一下官方那个IoT的demo,里边有很多值得参考的东西,比如怎么设计actor的结构,怎样设计一个有状态的actor,包括一些request-response模式的使用,都值得借鉴。



虽然新的Akka使用了New API,但是其原理还是没变的,还是智慧老师在课件中说的,发送者发送消息给mailbox,接收者从mailbox中取消息,并执行对应的消息处理逻辑,这都是由dispatcher完成的。



但是在学习的过程中发现了一个问题,没有找到新的API和Spring的整合方案,在Classic Actor中,Akka是可以和Spring整合起来的。虽然也可以在Behavior类中通过构造函数注入所需的类或者注入一个ApplicationContext,但总归觉得不优雅。如果大家有好的方案,请告诉我。



最后,为了运用所学知识,我用New API完成了本周作业,地址是使用Akka写一个简易压测工具,关于一些技术细节,代码里边有详细的注释。我的思路是用3个类来实现,一个Application类,包含main方法,负责创建Test Manager,并将测试参数传过去。第二个类是Test Manager,负责接收测试参数,通过一个router,将测试任务分发给10个TestWorker,并收集测试结果,打印统计信息。第三个类是TestWorker,负责接收测试任务并执行,返回测试结果给Test Manager。



有些有用的连接分享给大家:



Akka Type和Classic Actor的区别,里边有很多很重要的区别,比如New API的默认监管策略是,当子actor异常时,停止它,而不是重启。取消了getSender,如果要讲消息发给发送者,需要在消息中包含replyTo,等等。



为什么要使用Akka Typed,这应该是New API设计的初衷。



我的分享到此结束,谢谢班班给与这次机会和大家分享,感谢大家的聆听。如果大家有关于Akka的一些问题,我们接下来一起讨论。



用户头像

曾彪彪

关注

还未添加个人签名 2019.03.23 加入

还未添加个人简介

评论

发布
暂无评论
架构师训练营 1 期 -- 第七周笔记