1. 背景
随着公司业务的发展,核心服务流量越来越大,使用到的资源也越来越多。在微服务架构体系中,大部分的业务是基于Java 语言实现的,受限于Java 的线程实现,一个Java 线程映射到一个kernel 线程,造成了高并发场景下线程资源的极大浪费,线程成为提高系统并发和吞吐量的瓶颈。
在微服务架构下,使用同步编程模式时不仅造成了资源的极大浪费,并且在流量发生激增波动的时候,受制于系统资源而无法快速的扩容。本文将探索服务异步化在并发、吞吐量方面对系统带来的提升。
2. 如何快速提高服务吞吐量
首先,以微服务架构中的RPC 服务调用举例,测试和探索在微服务架构中,异步架构如何提高服务的吞吐量和并发。ESA Stack 是OPPO 自研的基础框架技术栈,ESA RPC 是自研的RPC 框架。本节测试服务我们使用ESA RPC 搭建。
关于ESA RPC的详情,可以参考我们之前发布的文章《Dubbo协议解析及ESA RPC实践》。
2.1 服务架构
下图所示为测试环境架构。其中Service A 既是服务端也是客户端,它模拟了生产环境中大部分服务的角色。我们对Service A 分别采用同步模型和纯异步模型进行压测,其中纯异步模型包含了客户端、服务端逻辑的异步处理。Service B 模拟一个耗时为N ms 的下游服务,为Service A 的调用提供固定的延时响应。
测试服务器的配置为8 核16G,千兆网卡。
2.2 同步异步模型对比
测试场景1:
并发压测客户端200~8000,服务耗时50ms,分别对同步和异步架构进行压测,对比TPS、服务耗时、CPU 上下文切换;同步模式下,线程数和并发客户端相同;异步模式下,使用框架默认的200 线程。测试数据如下。
测试场景2:
并发压测客户端8000,服务耗时50~500ms,分别对同步和异步模式进行压测,对比TPS、服务耗时、CPU 上下文切换;同步模式下服务端8000 线程;异步模式下,使用框架默认的200 线程。
2.3 服务扩展性对比
并发指服务瞬时同时处理的任务数(包含处于IO 等待状态的任务)。服务端设置业务处理线程200,那么同步模式下能提供的并发为200;纯异步模式下服务并发不受线程限制,IO密集型服务尤其收益。在系统流量突增的情景下,异步模式具有更强的可扩展性(Scalability)。
2.4 结论
根据上面的测试数据可以做出以下对比:
同步模式,线程数与并发成正比,并发越高对线程的消耗越多
异步模式,提高并发不需要线程增加
同步模式,系统Context Switch 次数随并发提高而快速增加
异步模式,系统Context Switch 次数明显小于同步模式
同步模式,并发超过某个临界点后,服务耗时快速上升,系统吞吐量急剧下降
异步模式,吞吐量随着并发增加,服务耗时上升速度明显低于同步模式
从而得出以下结论:
可以通过异步化微服务架构,提高相同资源配置下的服务吞吐量
随着下游平均耗时的增加,异步化带来的吞吐和耗时的提升作用减小
线程资源有限(内核、内存),不能无限增加来提高并发能力,异步化能极大提高系统瞬时并发能力(Scalability)
结论分析:
高并发下同步模型大量线程在内核态度/用户态、不同CPU 核之间进行切换,Context Switch 增加,系统性能下降
下游平均耗时增加时,系统CPU 繁忙程度降低,Context Switch 对性能系统影响下降
3. 异步模型探索
3.1 阻塞与非阻塞
在操作系统中,线程是CPU 调度的基本单位;阻塞调用是指发起调用后,线程进入阻塞状态(让出CPU),直到获得结果或异常返回;非阻塞调用是指不等待结果,调用不阻塞线程直接返回。
3.2 同步与异步
同步和异步关注的是消息通信机制;同步就是在发起调用后就得到返回结果(未必是完整结果),也就是由调用者主动等待结果;异步则是调用在发出之后直接返回,通过信号通知、回调函数处理来通知结果。
3.3 四种IO 模型
非IO 系统调用层面, 阻塞/非阻塞和同步/异步基本是同义词;在IO 系统调用层面,同步/异步和阻塞/非阻塞有以下组合:
同步阻塞调用,线程同步等待阻塞调用结果
同步非阻塞调用,线程通过轮训获取非阻塞调用结果
异步阻塞调用,IO 事件阻塞,IO 操作不阻塞
异步非阻塞调用,调用立即返回,信号/回调处理结果
我们通过一个简单的客户端来介绍四种IO 模型的代码写法:
同步阻塞IO
非同步阻塞IO
多路复用IO
Asynchnorous IO
对四中IO 模型,有以下的对比:
同步阻塞式IO 模型,编程简单但线程阻塞,资源利用率低;
同步非阻塞式IO 模型,需要轮训CPU,浪费资源;
异步非阻塞AIO 模型,不阻塞线程,使用回调方式处理数据,但是编程难度高;
多路复用IO 模型,能够实现异步非阻塞IO,且编程简单,方便实现同步和异步调用,因此成为RPC 框架的首选。
4. 全链路异步编程指南
4.1 全链路组成及现状
微服务架构下的全链路包含了网关层、WEB 服务、RPC 服务、数据层等。目前公司的网关层已经实现了纯异步架构,Web 框架和RPC 框架支持纯异步编程,数据存储层目前异步方案还不成熟。
4.2 网关异步化
网关层由于其特殊性,不需要访问业务数据库只做协议转换和流量转发,目前已经使用了纯异步的架构;其IO 密集型的特点,特别适合纯异步的架构,可以极大的节省资源。
4.3 Web 服务异步化
Web 服务作为微服务体系内的重要组成,服务节点众多,传统的Web 服务框架SpringMVC 不支持纯异步化编程,OPPO 自研Web 框架Restlight 支持纯异步编程,且性能远超SpringMVC。下面是性能对比及Restlight 异步实践。
Restlight 框架异步编程实践:通过Controller 方法返回值区分同步和异步调用,且支持三种异步调用方式,CompletableFuture、ListenableFuture(Guava)、Future(Netty)。
4.4 RPC 调用异步化
RPC 调用等待下游response 返回时,线程不应处于block 状态;作为微服务架构中数据流量最大的一部分,RPC 调用异步化的收益巨大;目前ESA RPC 已经具备了纯异步化的能力,提供RPC 调用的服务一般既是客户端也是服务端,因此包含了客户端异步调用能力和服务端异步处理能力;为了兼容存量接口,ESA RPC 既支持CompletableFuture 也支持普通返回值的接口。
客户端异步化实践:底层使用异步非阻塞IO 收发网路数据包,使用CompletableFUture传递IO 事件以实现响应式编程,客户端不被RPC 调用阻塞,可继续调用其他服务。
接口返回CompletableFuture 来实现异步调用:
普通接口使用ESARpcContext::asyncCall 实现异步调用:
服务端异步化实践:通过服务端异步功能返回CompletableFuture 给框架以释放Biz 线程,自定义线程池或者IO 线程池收到下游response 后,完成返回给框架的Future。
接口定义返回CompletableFuture 来实现异步调用:
普通接口通过ESARpcContext::startAsync 开启服务端异步:
4.5 存储层异步化
数据操作是每个请求调用链的终点,纯异步的架构必须使用异步存储层客户端,目前OPPO 没有自研的存储层异步客户端,但业界开源方案欣欣向荣:
数据库:Vert.x JDBC 客户端
Redis:Redisson、Lettuce
Queue:基本都支持异步调用
4.6 纯异步与伪异步
异步调用目的在于防止当前业务线程被阻塞。伪异步将任务包装为Runnable 放入另一个线程执行并等待,当前Biz 线程不阻塞;纯异步为响应式编程模型,通过IO 实践驱动任务完成。他们的区别不在于是否将请求放入另一个线程池执行,而在于是否有线程阻塞等待Response。
5. 异步化未来发展
5.1 异步化带来的问题
相比于同步模型,异步模型存在以下问题:
代码可读性和可维护性较差,可能出现Callback Hell
框架SDK 变得复杂,使用门槛增加
业务可能不清楚代码逻辑执行线程
大量的ThreadLocal 需要手动export/import
简单来说,异步编程就是以编程的简单性(simplity)来交换性能(performance)。
5.2 使用协程实现异步非阻塞
目前在其他语言中,Erlang、Go、Kotlin 等都支持了协程,使用协程的好处是在语言层面支持了异步调用,业务代码可以使用同步的写法达到异步的效果,线程不被阻塞,避免大量的CPU 上下文切换,提升系统的性能。
目前Java 对协程的支持也在进行中, Project Loom 就是Java 的协程项目:http://openjdk.java.net/projects/loom/。
主要有以下几个概念:
Fiber,轻量级线程(用户态线程),基于Continuation 实现
Continuation,指令执行单元, 阻塞时调用Continuation::yield , 恢复时调用Continuation::run
Scheduler,用户态Fiber 调度器(ForkJoinPool),使用有限Workers 线程执行任意数量Fibers
开发者可以使用 Fiber 来执行业务代码块,当遇到LockSupport::park、socket io 等阻塞调用时,Fiber 中的代码单元执行会被阻塞,但是底层的线程并不会被阻塞。由此达到了开发同步模式代码,运行时达到异步执行的目的。
未来,ESAStack服务框架会支持协程。目前 Restlight框架已经支持协程并在内部开始试用,ESARPC也有支持协程的计划。框架提供的服务线程使用 Fiber 执行业务逻辑,业务实现中数据库请求、下游服务调用均在 Fiber 之中执行, 其包含的 IO 等阻塞调用只挂起 Fiber 而不阻塞所在线程,从而避免了过多的上下文切换提升 了吞吐量,达到了和异步模式一样的效果。
☆ END ☆
招聘信息OPPO互联网基础技术团队招聘一大波岗位,涵盖C++、Go、OpenJDK、Java、DevOps、Android、ElasticSearch等多个方向,请点击这里查看详细信息及JD。
你可能还喜欢OPPO自研ESA DataFlow架构与实践
Dubbo协议解析与ESA RPC实践
自研代码审查系统火眼Code Review实践
OPPO异地多活实践——缓存篇
更多技术干货
扫码关注
OPPO互联网技术
我就知道你“在看”