如何解决应用的扩展性问题
当一个应用需要处理海量并发请求时,传统的开发模式往往显得力不从心,为什么应用需要扩展性?
- 需求增长: 用户量激增,数据量爆炸式增长。
- 资源限制: 服务器、带宽、存储等资源有限。
- 复杂性增加: 代码逻辑、系统架构日益复杂。
- 成本考量: 资源投入与收益不成正比。
面对这些挑战,我们通常会怎么做?传统方法的困境
- 线程与锁,低效并发,易死锁,管理复杂
- RPC,同步调用阻塞,异步回调地狱
- 数据库,共享状态,事务管理,性能瓶颈
- 混合编程模型,多种技术混杂,难以统一管理和优化
有没有一种更优雅、更统一的方式来应对这些挑战呢?答案是肯定的,这就是Akka。
The Reactive Manifesto (www.reactivemanifesto.org) is an initiative to push for the design of systems that are more robust, more resilient, more flexible, and better positioned to meet modern demands.
Blocking I/O limits opportunities for parallelism, so nonblocking I/O is preferred.Synchronous interaction limits opportunities for parallelism, so asynchronous interaction is preferred.
Polling reduces the opportunity to use fewer resources, so an event-driven style is preferred.
If one node can bring down all other nodes, that’s a waste of resources. So you need isolation of errors (resilience) to avoid losing all your work.
Systems need to be elastic: if there’s less demand, you want to use fewer resources. If there’s more demand, use more resources, but never more than required.
Complexity is a big part of cost. So if you can’t easily test your system, change it, or add new features, you’ve got a big problem.
核心理念是一次编写,随处扩展。以下是传统方法和akka的对比
举个例子
一个传统业务聊天应用程序的发展历程,如何变得越来越复杂?以及actor如何解决扩展性问题。
- team对象是user构成的,许多user可以成为conversation的一部分,message对象是消息的集合。所有对象保存在内存中,只能在单机部署,重启应用会丢失状态。
- 在生产环境中,将app扩容到两个副本,增加负载均衡器,状态从内存迁移到数据库中管理。状态变化从内存操作变成了网络通信,错误概率增加。测试变得复杂。性能开销增加。还要完全重构代码。线程锁和表锁结合使用会变得复杂。
- 用户量进一步增加,大量资源用于seri和deseri。计划增加新功能Mentions,解析消息将提及的user添加到notification表中,应用检索notification表并通知对应的user。增加了单独的Mentions(包含通知表和用户表的一个副本)和消息队列,app和Mentions使用消息队列通信。而且app需要轮询数据库获得提及的用户。
- 增加新功能,通过TeamFinder对象增加自动补全user名称能力,TeamFinder会调用外部的接口。为了避免TeamFinder导致的级联失败,创建新的TeamFinder服务。现在有4个逻辑,用于内存中的线程,用于数据库操作,用于 Mentions 消息队列,用于联系人 Web 服务。
- 如果请求量进一步增加,将机器扩容到100个又会出现哪些问题?
接下来是akka如何解决此问题
- conversion持久化问题。Conversation actor向database log发送 MessageAdded 事件,以记录内存中添加的每个消息。所有变化都作为事件序列进行保存,可以通过重放内存中的 Conversation 事件来重建Conversation 的当前状态。
- 分片以扩展数据。将conversation以可预测的方式分割到服务器上,或者跟踪每个对话的位置。
- 推送消息而非轮询。直接发送消息来通知事件。还可以通过发送事件消息作为内部信号来执行特定任务。
- 异步解耦。Mentions和TeamFinder组件的失败此时不会影响到正常的chat功能,使用消息事件驱动而非直接调用方法进行对象通信,消息必须是不可变的。
- 监控和守护。Supervisor 监视组件并在组件崩溃时采取行动。当组件失败时,它可以向 Supervisor 发送消息,而Supervisor 可以向组件发送消息以停止或尝试重启
Actor模型
Akka的核心是Actor模型,它提供了一种单一的抽象来处理并发和分布式问题,akka设计思想如下:
- Actor 模型,统一的并发和分布式编程模型。
- 消息传递,异步通信,解耦组件,提高灵活性。
- 弹性伸缩,自动适应负载变化,高效利用资源。
- 简化复杂性,降低并发和分布式编程的复杂度。
Actor模型到底是什么?
- Actor,轻量级进程,封装状态和行为。封装了自己的状态和行为,外界无法直接访问其内部状态,只能通过发送消息来与其交互
- 消息,不可变数据结构,Actor 间通信的媒介。一旦创建就不能被修改,这大大简化了并发处理的复杂性
- 信箱 (Mailbox),Actor 接收消息的队列,保证消息顺序。每个Actor都有一个信箱,也就是Mailbox,所有发给它的消息都会先进入这个队列,然后由调度器Dispatcher按顺序取出并交给Actor处理。
- 调度器 (Dispatcher),负责将消息分发给 Actor。
Actor能做什么呢?主要就是四大核心操作
- 创建 (reate),Actor 可以创建其他 Actor。例如,Supervisor Actor可以创建多个子Actor来管理不同的业务模块
- 发送 (Send),Actor 通过消息与其他 Actor 通信。这也是Actor之间唯一的交互方式。
- 指定行为 (Designate Next Behavior),Actor 可以根据消息动态改变自身行为。
- 监督 (Supervise),Actor 可以监督其创建的子 Actor,并处理异常。如果子Actor发生故障,父Actor可以决定是重启它还是终止它,从而实现容错和恢复
Akka实现Actor模型
- ActorSystem,Actor 的容器,管理 Actor 的生命周期,包括创建、停止和监控Actor。其他的功能例如remote和persistent等,大多数功能都是以Akka扩展的形式提供的,可以针对所讨论的ActorSystem进行特定配置的模块
- ActorRef,Actor 的引用,用于发送消息。创建actor时会收到ActorRef,可以把它理解为Actor的地址或引用
- ActorPath,每个Actor都有一个唯一的ActorPath,类似于URL路径,用来精确定位它。
- Mailbox,Actor 的信箱,存储待处理的消息。
- Dispatcher,消息分发器,负责将消息分发给actor,Akka提供了多种Dispatcher类型,可以根据需要进行配置和优化。
Akka相比传统方法,有哪些显著的优势呢?
- 简化并发,无需手动管理线程和锁,降低并发编程难度。
- 提高灵活性,消息传递机制解耦组件,易于扩展和修改。
- 提升性能,异步非阻塞 I/O,高效利用资源,弹性伸缩。
- 增强可靠性,监督机制和容错能力,保障系统稳定运行。
akka入门实践
- remote Actor: Akka将远程Actor的消息传递到目标机器,并返回结果。
- 配置驱动: 仅需配置远程Actor引用的查找方式,代码无需改动。
- 无缝扩展: 从垂直扩展(scale up)到水平扩展(scale out),代码保持不变。
- 核心优势: Actors提供更高的灵活性和可扩展性,简化应用架构。
akka的核心理念,以Actor为中心,简化并发编程,提升系统可扩展性