乐趣区

关于开放源代码:蚂蚁集团网络通信框架-SOFABolt-功能介绍及协议框架解析-开源

简介: 开源网络通信框架 SOFABolt 首次线上直播文字回顾。

,乏味实用的分布式架构频道。
回顾视频以及 PPT 查看地址见文末。欢送退出直播互动钉钉群 : 30315793,不错过每场直播。

大家好,我是本期 SOFAChannel 的分享讲师丞一,来自蚂蚁团体,是 SOFABolt 的开源负责人。明天咱们来聊一下蚂蚁团体开源的网络通信框架 SOFABolt 的框架解析以及性能介绍。本期分享将从以下四个方面开展:

  • SOFABolt 简介;
  • 根底通信能力解析;
  • 协定框架解析;
  • 公有协定实现解析;

SOFABolt 是什么

SOFABolt 产生背景

置信大家都晓得 SOFAStack,SOFAStack(Scalable Open Financial Architecture Stack) 是一套用于疾速构建金融级云原生架构的中间件,也是在金融场景里锻炼进去的最佳实际。

SOFABolt 则是 SOFAStack 中的网络通信框架,是一个基于 Netty 最佳实际的轻量、易用、高性能、易扩大的通信框架,他的名字 Bolt 取自迪士尼动画《闪电狗》。他一开始是怎么在蚂蚁团体外部产生的,咱们能够类比一下 Netty 的产生起因:

  • 为了让 Java 程序员能将更多的精力放在基于网络通信的业务逻辑实现上,而不是过多的纠结于网络底层 NIO 的实现以及解决难以调试的网络问题,Netty 应运而生;
  • 为了让中间件开发者能将更多的精力放在产品性能个性实现上,而不是反复地一遍遍制作通信框架的轮子,SOFABolt 应运而生;

这些年,在微服务与消息中间件在网络通信上,蚂蚁团体解决过很多问题、积攒了很多教训并继续进行着优化和欠缺,咱们把总结的解决方案积淀到 SOFABolt 这个根底组件里并反馈到开源社区,心愿可能让更多应用网络通信的场景受害。目前该组件曾经使用在了蚂蚁团体中间件的微服务 (SOFARPC)、音讯核心、分布式事务、分布式开关、以及配置核心等泛滥产品上。

同时,已有数家企业在生产环境中应用了 SOFABolt,感激大家的必定,也心愿 SOFABolt 能够给更多的企业带来实际价值。

以上企业信息依据企业用户 Github 上反馈统计 — 截止 2020.06。

SOFABolt:https://github.com/sofastack/sofa-bolt

SOFABolt 框架组成

SOFABolt 整体能够分为三个局部:

  • 根底通信能力(基于 Netty 高效的网络 IO 与线程模型、连贯治理、超时管制);
  • 协定框架(命令与命令处理器、编解码处理器);
  • 公有协定实现(公有 RPC 通信协议的实现);

上面,咱们别离介绍一下 SOFABolt 每个局部的具体能力。

根底通信能力

根底通信模型

如上图所示,SOFABolt 有多种通信模型,别离为:oneway、sync、future、callback。上面,咱们介绍一下每个通信模型以及他们的应用场景。

  • oneway:不关注后果,即客户端发动调用后不关注服务端返回的后果,实用于发动调用的一方不须要拿到申请的处理结果,或者说申请或处理结果能够失落的场景;
  • sync:同步调用,调用线程会被阻塞,直到拿到响应后果或者超时,是最罕用的形式,实用于发动调用方须要同步期待响应的场景;
  • future:异步调用,调用线程不会被阻塞,通过 future 获取调用后果时才会被阻塞,实用于须要并发调用的场景,比方调用多个服务端并期待所有后果返回后执行特定逻辑的场景;
  • callback:异步调用,调用线程不会被阻塞,调用后果在 callback 线程中被解决,实用于高并发要求的场景;

oneway 调用的场景十分明确,当调用方不须要拿到调用后果的时候就能够应用这种模式,然而当须要解决调用后果的时候,抉择应用同步的 sync 还是应用异步的 future 和 callback?都是异步调用,又如何在 future、callback 两种模式中抉择?

显然同步能做的事件异步也能做,然而异步调用会波及到线程上下文的切换、异步线程池的设置等等,较为简单。如果你的场景比较简单,比方整个流程就一个调用并处理结果,那么倡议应用同步的形式解决;如果整个过程须要分几个步骤执行,能够拆分不同的步骤异步执行,给耗时的操作调配更多的资源来晋升零碎整体的吞吐。

在 future 和 callback 的抉择中,callback 是更彻底的异步调用,future 实用于须要协调多个异步调用的场景。比方须要调用多个服务,并且依据多个服务端响应后果执行逻辑时,能够采纳 future 的模式给多个服务发送申请,在对立对所有的 future 进行解决实现协同操作。

超时管制机制

在上一部分的通信模型中,除了 oneway 之后,其余三种(sync、future、callback)都须要进行超时管制,因为用户须要在预期的工夫内拿到后果。超时管制简略来说就是在用户发动调用后,在预期的工夫内如果没有拿到服务端响应的后果,那么这次调用就超时了,须要让用户感知到超时,防止始终阻塞调用线程或者 callback 永远得不到执行。

在通信框架中,超时管制必须要满足高效、精确的要求,因为通信框架是分布式系统的根底组件,一旦通信框架呈现性能问题,那么下层零碎的性能显然是无奈晋升的。超时管制的准确性也十分重要,比方用户预期一次调用最多只能执行 3 秒,因为超时管制不精确导致用户调用时线程被阻塞了 4 秒,这显然是不能承受的。

SOFABolt 的超时管制采纳了 Netty 中的 HashedWheelTimer,其原理如上图。假如一次 tick 示意 100 毫秒,那么下面的工夫轮 tick 一轮示意 800 毫秒,如果须要在 300 毫秒后触发超时,那么这个超时工作会被放到 ’2’ 的 bucket 中,等到 tick 到 ’2’ 时则被触发。如果一个超时工作须要在 900 毫秒后触发,那么它会被放到如 ’0’ 的 bucket 中,并标记 task 的 remainingRounds=1,当第一次 tick 到 ’0’ 时发现 remainingRounds 不等于 0,会对 remainingRounds 进行减 1 操作,当第二次 tick 到 ’0’,发现这个工作的 remainingRounds 是 0,则触发这个工作。

如果将工夫轮的一次 tick 设置为 1 秒,ticksPerWheel 设置为 60,那么就是事实时钟的秒针,走完一圈代表一分钟。如果一个工作须要再 1 分 15 秒后执行,就是标记为秒针走一轮之后指向第 15 格时触发。对于工夫轮的原理举荐浏览上面这篇论文:
《Hashed and Hierarchical Timing Wheels: data structures to efficiently implement a timer facility》。

疾速失败机制

超时管制机制能够保障客户端的调用在一个预期工夫之后肯定会拿到一个响应,无论这个响应是由服务端返回的实在响应,还是触发了超时。如果因为某些起因导致客户端的调用超时了,而服务端在超时之后理论将响应后果返回给客户端了会怎么样?

这个响应后果在客户端会被抛弃,因为对应的申请曾经因为超时被开释掉,服务端的这个响应会因为找不到对应的申请而被抛弃。既然响应在申请超时之后返回给客户端会被抛弃,那么在确定申请曾经超时的状况下服务端是否能够不解决这个申请而间接返回超时的响应给客户端?——这就是 SOFABolt 的疾速失败机制。

疾速失败机制能够加重服务端的累赘,使服务端尽快恢复服务。比方因为某些内部依赖的因素导致服务端解决一批申请产生了阻塞,而此时客户端还在将更多的申请发送到服务端沉积在 Buffer 中期待解决。当内部依赖复原时,服务端因为要解决曾经在 Buffer 中的申请(理论这些申请曾经超时,解决这些申请将没有业务意义),而导致后续失常的申请排队阻塞。退出疾速失败机制后,在这种状况下能够将 Buffer 中的申请进行抛弃而开始服务以后新增的未超时的申请,使的服务能疾速的复原。

疾速失败机制的前提条件是能判断出一个申请曾经超时,而判断超时须要依赖工夫,依赖工夫则须要对立的工夫参照。在分布式系统中是无奈依赖不同的机器上的工夫的,因为网络会有提早、机器工夫的工夫会有偏差。为了防止参照工夫的不统一(机器之间的时钟不统一),SOFABolt 的疾速失败机制只依赖于服务端机器本身的时钟(对立的工夫参照),判断申请曾经超时的条件为:

System.currentTimestamp – request.arriveTimestamp > request.timeout

request.arriveTimestamp 为申请达到服务端时的工夫,request.timeout 为申请设置的超时工夫,因为申请从客户端收回到服务端须要工夫,所以当以达到工夫来计算时,如果这个申请曾经超时,那么这个申请在客户端侧必然曾经超时,能够平安的将这个申请抛弃。

具体分布式系统中工夫和程序等相干的文件举荐浏览《Time, Clocks, and the Ordering of Events in a Distributed System》,Lamport 在此文中透彻的剖析了分布式系统中的程序问题。

协定框架

SOFABolt 中蕴含的协定命令如上图所示。在 RPC 版本的协定命令中只蕴含两类:RPC 申请 / 响应、心跳的申请 / 响应。RPC 的申请 / 响应负责携带用户的申请数据和响应数据,心跳申请用于连贯的保活,只携带大量的信息(个别只蕴含申请 ID 之类的必要信息即可)。

有了命令之后,还须要有命令的编解码器和命令处理器,以实现命令的编解码和解决。RemotingCommand 的解决模型如下:

整个申请和响应的过程设计的外围组件如上图所示,其中:

  • 客户端侧:

    • Connection 连贯对象的封装,封装对底层网络的操作;
    • CommandEncoder 负责编码 RemotingCommand,将 RemotingCommand 依照公有协定编码成 byte 数据;
    • RpcResponseProcessor 负责解决服务端的响应;
  • 服务端侧:

    • CommandDecoder 别离负责解码 byte 数据,依照公有协定将 byte 数据解析成 RemotingCommand 对象;
    • RpcHandler 依照协定码将 RemotingCommand 转发到对应的 CommandHandler 解决;
    • CommandHandler 依照 CommandCode 将 RemotingCommand 转发到对应的 RpcRequestProcessor 解决;
    • RpcRequestProcessor 依照 RemotingCommand 携带对象的 Class 将申请转发到用户的 UserProcessor 执行业务逻辑,并将后果通过 CommandDecoder 编码后返回给客户端;

公有协定实现

内置公有协定实现

SOFABolt 除了提供根底通信能力外,内置了公有协定的实现,能够做到开箱即用。内置的公有协定实现是通过实际打磨的,具备扩展性的公有协定实现。

  • proto:预留的协定码字段,当协定产生较大变更时,能够通过协定码进行辨别;
  • ver1:确定协定之后,通过协定版本来兼容将来协定的小调整,比方追加字段;
  • type:标识 Command 类型:oneway、request、response;
  • cmdcode:命令码,比方之前介绍的 RpcRequestCommand、HeartbeatCommand 就须要用不同的命令码进行辨别;
  • ver2:Command 的版本,用于标识同一个命令的不同版本;
  • requestId:申请的 ID,用于惟一标识一个申请,在异步操作中通过此 ID 来映射申请和响应;
  • codec:序列化码,用于标识应用哪种形式来进行业务数据的序列化;
  • switch:协定开关,用于标识是否开启某些协定层面的能力,比方开启 CRC 校验;
  • timeout:客户端进行申请时设置的超时工夫,疾速失败机制所依赖的超时工夫;
  • classLen:业务申请类的的类名长度;
  • headerLen:业务申请头的长度;
  • contentLen:业务申请体的长度;
  • className:业务申请类的类名;
  • header:业务申请头;
  • content:业务申请体;
  • CRC32:CRC 校验码;

实现自定义协定

在 SOFABolt 中实现公有协定的要害是实现编解码器(CommandEncoder/CommandDecoder)及命令处理器 (CommandHandler)。

下面是为了在 SOFABolt 中实现自定义公有协定锁须要编写的类。SOFABolt 将编解码器及命令处理器都绑定到 Protocol 对象上,每个 Protocol 实现都有一组本人的编解码器和命令处理器。

在编解码器中实现自定义的公有协定。在设计公有协定时肯定要思考好协定的可拓展性,以便在将来进行性能加强时不会呈现协定无奈兼容的状况。

实现编解码之后残余工作就是实现处理器。处理器分为两块:命令解决入口 CommandHandler 及具体的业务逻辑执行器 RemotingProcessor。

实现以上工作后,应用 SOFABolt 实现自定义公有协定通信的开发工作根本实现了,然而在理论编写这部分代码时会遇到种种艰难及限度,次要体现在以下一些方面:

  • 扩展性有余:比方在 RpcClient 中默认应用了内置的编解码器,且没有预留接口进行设置,当应用自定义协定时只能继承 RpcClient 进行笼罩;
  • 框架和协定耦合:比方默认提供了 CommandHandler->RemotingProcessor->UserProcessor 这样的解决模型,然而这个模型和协定耦合重大(依赖于 CommandCode 和 RequestCode),导致应用自定义协定时只能本人实现 CommandHandler,而后本人在实现申请的散发逻辑等,相当于要重写 CommandHandler->RemotingProcessor->UserProcessor 这个模型;
  • 协定限度:尽管能够通过自定义 Encoder 和 Decoder 实现自定义协定,然而框架外部组织时都依赖 ProtocolCode,导致须要将 ProtocolCode 退出到协定中,限度了用户设计公有协定的自在;

总体而言,以后 SOFABolt 提供了十分弱小的通信能力和多年积淀的协定设计。如果用户须要去适配本人以后曾经在运行的公有协定还有能够欠缺的中央,根本原因还是在于设计之初是贴合这 RPC 框架来设计的(从很多代码的命名上也能看进去),所以在协定和框架的拆散上能够做的更好。

总结

本次分享从 SOFABolt 整体框架的实现开始,介绍了 SOFABolt 的根底通信模型、超时管制以及疾速失败机制,着重剖析了公有协定实现的示例,总结而言 SOFABolt 提供了:

  • 基于 Netty 的最佳实际;
  • 根底的通信模型和高效的超时管制机制、疾速失败机制;
  • 内置的公有协定实现,开箱即用;

欢送 Star SOFABolt:https://github.com/sofastack/sofa-bolt

以上就是本期分享的次要内容。因为直播工夫无限,对于 SOFABolt 更具体的介绍,能够浏览「分析 SOFABolt 框架」系列文章,由 SOFABolt 团队以及开源社区同学独特出品:

「分析 SOFABolt 框架」解析:https://www.sofastack.tech/blog/ 点击 tag「分析 | SOFABolt 框架」

one more thing

SOFABolt 目前也存在能够晋升欠缺的中央,在尝试实现齐全自定义的公有协定时是绝对艰难的,须要对代码做一些继承革新。

针对这个现状,咱们在“阿里巴巴编程之夏”流动中提交了一个 SOFABolt 的课题:“拆分 SOFABolt 的框架和协定”,心愿先通过拆分框架和协定,之后再进行模块化的解决,使 SOFABolt 成为一个灵便的、可拓展的通信框架最佳实际!

欢送大家一起共建来解决这个问题,让 SOFABolt 变得更好:
https://github.com/sofastack/sofa-bolt/issues/224

SOFAStack 也欢送更多开源爱好者退出社区共建,成为社区 Contributor、Committer(emoji 表情)

SOFACommunity:https://www.sofastack.tech/community/

本期视频回顾以及 PPT 查看地址

https://tech.antfin.com/community/live/1265

退出移动版