55

微服务全链路异步化实践

 4 years ago
source link: https://mp.weixin.qq.com/s?__biz=Mzg4MzE2MzY1OA%3D%3D&mid=2247484945&idx=1&sn=f3bbc20122db55510e03576e96da728a&chksm=cf4ad174f83d586279eddd4d30c077b3505467f7c50eb69f530fc7ca6edbff517d94fdb5c0b3&token=543559943&lang=zh_CN%23rd
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

随着公司业务的发展,核心服务流量越来越大,使用到的资源也越来越多。在微服务架构体系中,大部分的业务是基于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互联网技术

640?wx_fmt=jpeg 我就知道你“在看”640?wx_fmt=gif


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK