蚂蚁金服通信框架SOFABolt解析|超时控制机制及心跳机制

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: SOFAScalable Open Financial Architecture 是蚂蚁金服自主研发的金融级分布式中间件,包含了构建金融级云原生架构所需的各个组件,是在金融场景里锤炼出来的最佳实践。

SOFA
Scalable Open Financial Architecture

是蚂蚁金服自主研发的金融级分布式中间件,包含了构建金融级云原生架构所需的各个组件,是在金融场景里锤炼出来的最佳实践。

本文为《蚂蚁金服通信框架 SOFABolt 解析》系列第五篇,作者胡萝卜、丞一。

《蚂蚁金服通信框架 SOFABolt 解析》系列由 SOFA 团队和源码爱好者们出品。

SOFARPC: https://github.com/alipay/sofa-rpc

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

前言

SOFABolt是一个基于 Netty 最佳实践的轻量、易用、高性能、易扩展的通信框架。目前已经运用在了蚂蚁中间件的微服务,消息中心,分布式事务,分布式开关,配置中心等众多产品上。

本文将分析SOFABolt的超时控制和心跳机制。

超时

在程序中,超时一般指的是程序在特定的等待时间内没有得到响应,网络通信问题、程序BUG等等都会引起超时。系统引入超时机制往往是为了解决资源的问题,比如一个同步RPC请求,在网络不稳定的情况下可能一直无法得到响应,那么请求线程将一直等待结果而无法执行其它任务,最终导致所有线程资源耗尽。超时机制正是为了解决这样的问题,在特定的等待时间之后触发一个“超时事件”来释放资源。

在一个网络通信框架中,超时问题无处不在,连接的建立、数据的读写都可能遇到超时问题。并且网络通信框架作为分布式系统的底层组件,需要管理大量的连接,如何建立一个高效的超时处理机制就成为了一个问题。

时间轮(TimeWheel)

在网络通信框架中动辄管理上万的连接,每个连接上都有很多的超时任务,如果每个超时任务都启动一个java.util.Timer,不仅低效而且会占用大量的资源。George Varghese 和 Tony Lauck在1996年发表了一篇论文:《Hashed and Hierarchical Timing Wheels: EfficientData Structures for Implementing a Timer Facility》来高效的管理和维护大量的定时任务。

timewheel.jpg | left | 412x227

时间轮其实就是一种环形的数据结构,可以理解为时钟,每个格子代表一段时间,每次指针跳动一格就表示一段时间的流逝(就像时钟分为60格,秒针没跳动一格代表一秒钟)。时间轮每一格上都是一个链表,表示对应时间对应的超时任务,每次指针跳动到对应的格子上则执行链表中的超时任务。时间轮只需要一个线程执行指针的“跳动”来触发超时任务,且超时任务的插入和取消都是O(1)的操作,显然比java.util.Timer的方式要高效的多。

SOFABolt的超时控制机制

image.png | left | 631x583

如上图所示,SOFABolt中支持四中调用方式:

  • oneway:不关心调用结果,所以不需要等待响应,那么就没有超时
  • sync:同步调用,在调用线程中等待响应
  • future:异步调用,返回future,由用户从future中获取结果
  • callback:异步调用,异步执行用户的callback
    在oneway调用中,因为并不关心响应结果,所以没有超时的概念。下面具体介绍SOFABolt中同步调用(sync)和异步调用(futurecallback)的超时机制实现。

同步调用的超时控制实现

同步调用中,每一次调用都会阻塞调用线程等待服务端的响应,这种场景下同一时刻产生最大的超时任务取决于调用线程的数量。线程资源是非常昂贵的,用户的线程数是相对可控的,所以这种场景下,SOFABolt使用简单的java.util.concurrent.CountDownLatch来实现超时任务的触发。

image.png | left | 738x722

SOFABolt同步调用的代码如上,核心逻辑是:

  1. 创建InvokeFuture
  2. 在Netty的ChannelFuture中添加Listener,在写入操作失败的情况下通过future.putResponse方法修改Future状态(正常服务端响应也是通过future.putResponse来改变InvokeFuture的状态的,这个流程不展开说明)
  3. 写入出现异常的情况下也是通过future.putResponse方法修改Future状态
  4. 通过future.waitResponse来执行等待响应
    其中和超时相关的是future.waitResponse的调用,InvokeFuture内部通过java.util.concurrent.CountDownLatch来实现超时触发。

image.png | left | 666x178

java.util.concurrent.CountDownLatch#await(timeout, timeoutUnit)方法实现了等待一段时间的逻辑,并且通过countDown方法来提前中断等待,SOFABolt中InvokeFuture通过构建new CountDownLatch(1)的实例,并将await和countDown方法包装为awaitResponse和putResponse来实现同步调用的超时控制。

异步调用的超时控制实现

相对于同步调用,异步调用并不会阻塞调用线程,那么超时任务的数量并不受限于线程对的数量,用户可能通过一个线程来触发量大的请求,从而产生大量的定时任务。那么我们需要一个机制来管理大量的定时任务,并且作为系统底层的通信框架,需要保证这个机制尽量少的占用资源。上文已经提到TimeWheel是一个非常适合于这种场景的数据结构。
Netty中实现了TimeWheel数据结构:io.netty.util.HashedWheelTimer,SOFABolt异步调用的超时控制直接依赖于Netty的io.netty.util.HashedWheelTimer实现。
Future模式和Callback模式在超时控制机制上一致的,下面以Callback为例分析异步调用的超时控制机制。

image.png | left | 747x901

SOFABolt异步调用的代码如上,核心逻辑是:

  1. 创建InvokeFuture
  2. 创建Timeout实例,Timeout实例的run方法中通过future.putResponse来修改InvokeFuture的状态
  3. 在Netty的ChannelFuture中添加Listener,在写入操作失败的情况下通过future.cancelTimeout来取消超时任务,通过future.putResponse来修改InvokeFuture的状态
  4. 在写入异常的情况下同样通过future.cancelTimeout来取消超时任务,通过future.putResponse来修改InvokeFuture的状态
    在异步调用的实现中,通过Timeout来触发超时任务,相当于同步调用中的java.util.concurrent.CountDownLatch#await(timeout, timeoutUnit)。Future#cancelTimeout()方法则是调用了Timeout的cancel来取消超时任务,相当于同步调用中通过java.util.concurrent.CountDownLatch#countDown()来提前结束超时任务。具体超时任务的管理则全部委托给了Netty的Timer实现。

另外值得注意的一点是SOFABolt在使用Netty的Timer时采用了单例的模式,因为一般情况下使用一个Timer管理所有的超时任务即可,这样可以节省系统的开销。

Fail-Fast机制

以上关于SOFABolt的超时机制介绍都是关于SOFABolt客户端如何完成高效的超时任务管理的,其实在SOFABolt的服务端同样针对超时的场景做了优化。
客户端为了应对没有响应的情况,增加了超时机制,那么就可能存在服务端返回一个响应但是客户端在收到这个响应之前已经认为请求超时了,移除了相关的请求上下文,那么这个响应对客户端来说就没有意义了。既然这个响应对客户端来说是没有意义的,那么服务端其实可以进一步优化:在确认请求已经超时的情况下,服务端可以直接丢弃请求来减轻服务端的处理负担,SOFABolt把这个机制称为Fail-Fast。

image.png | left | 747x282

如上图所示,请求可能在服务端积压了一段时间,此时这些请求在客户端看来已经超时了,如果服务端继续处理这些超时的请求,第一请求的响应最终会被客户端丢弃;第二可能加剧服务端的压力导致后续更多请求超时。通过Fail-Fast机制直接丢弃掉这批请求能减轻服务端的负担使服务端尽快恢复并提供正常的服务能力。
Fail-Fast机制是一个明显的优化手段,唯一面临的问题是如何确定一个请求已经超时。注意,一定不要依赖跨系统的时钟,因为时钟可能不一致,从而导致未超时的请求被误认为超时而被服务端丢弃。
SOFABolt采用了请求被处理时的时间和请求到达服务端的时间来判定请求是否已经超时,如下图所示:

image.png | left | 747x242

这样会有一小部分客户端认为已经超时的请求服务端还会处理(因为网络传输是需要时间的),但是不会出现误判的情况。

SOFABolt的心跳机制

除了上文提供的超时机制外,在通信框架中往往还有另一类超时,那就是连接的超时。
我们知道,一次 tcp 请求大致分为三个步骤:建立连接、通信、关闭连接。每次建立新连接都会经历三次握手,中间包含三次网络传输,对于高并发的系统,这是一笔不小的负担。所以在通信框架中我们都会维护一定数量的连接,其中一个手段就是通过心跳来维持连接,避免连接因为空闲而被回收。
Netty提供了IdleStateHandler,如果连接空闲时间过长,则会触发IdleStateEvent。SOFABolt基于IdleStateHandler的IdleStateEvent来触发心跳,一来这样可以通过心跳维护连接,二来基于IdleStateEvent可以减少不必要的心跳。
SOFABolt心跳相关的处理有两部分:客户端发送心跳,服务端接收心跳处理并返回响应。

image.png | left | 747x1612

上面是客户端触发心跳后的代码,当客户端接收到IdleStateEvent时会调用上面的heartbeatTriggered方法。
在Connection对象上会维护心跳失败的次数,当心跳失败的次数超过系统的最大次时,主动关闭Connection。如果心跳成功则清除心跳失败的计数。同样的,在心跳的超时处理中同样使用Netty的Timer实现来管理超时任务(和请求的超时管理使用的是同一个Timer实例)。

image.png | left | 747x974

RpcHeartbeatProcessor是SOFABolt对心跳处理的实现,包含对心跳请求的处理和心跳响应的处理(服务端和客户端复用这个类,通过请求的数据类型来判断是心跳请求还是心跳响应)。
如果接收到的是一个心跳请求,则直接写回一个HeartbeatAckCommand(心跳响应)。如果接收到的是来自服务端的心跳响应,则从Connection取出InvokeFuture对象并做对应的状态变更和其他逻辑的处理:取消超时任务、执行Callback。如果无法从Connection获取InvokeFuture对象,则说明客户端已经判定心跳请求超时。
另外值得注意的一点是,SOFABolt中心跳请求和心跳响应对象都只包含RequestCommand和ResponseCommand的必要字段,没有额外增加任何属性,这也是为了减少不必要的网络带宽的开销。

总结

本文简单的介绍了TimeWheel的原理,SOFABolt的超时控制机制和心跳机制的实现。SOFABolt基于高效的TimeWheel实现了自己的超时控制机制,同时增加Fail-Fast策略优化服务端对超时请求的处理。另外SOFABolt默认实现了连接的心跳机制,以保持系统空闲时连接的可用性,这些都为SOFABolt的高性能打下了坚实的基础。
image | left | 216x216

长按关注,获取分布式架构干货

欢迎大家共同打造 SOFAStack https://github.com/alipay

相关文章
|
1月前
|
机器学习/深度学习 自然语言处理 搜索推荐
自注意力机制全解析:从原理到计算细节,一文尽览!
自注意力机制(Self-Attention)最早可追溯至20世纪70年代的神经网络研究,但直到2017年Google Brain团队提出Transformer架构后才广泛应用于深度学习。它通过计算序列内部元素间的相关性,捕捉复杂依赖关系,并支持并行化训练,显著提升了处理长文本和序列数据的能力。相比传统的RNN、LSTM和GRU,自注意力机制在自然语言处理(NLP)、计算机视觉、语音识别及推荐系统等领域展现出卓越性能。其核心步骤包括生成查询(Q)、键(K)和值(V)向量,计算缩放点积注意力得分,应用Softmax归一化,以及加权求和生成输出。自注意力机制提高了模型的表达能力,带来了更精准的服务。
|
24天前
|
小程序 前端开发 关系型数据库
uniapp跨平台框架,陪玩系统并发性能测试,小程序源码搭建开发解析
多功能一体游戏陪练、语音陪玩系统的开发涉及前期准备、技术选型、系统设计与开发及测试优化。首先,通过目标用户分析和竞品分析明确功能需求,如注册登录、预约匹配、实时语音等。技术选型上,前端采用Uni-app支持多端开发,后端选用PHP框架确保稳定性能,数据库使用MySQL保证数据一致性。系统设计阶段注重UI/UX设计和前后端开发,集成WebSocket实现语音聊天。最后,通过功能、性能和用户体验测试,确保系统的稳定性和用户满意度。
|
2月前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
3月前
|
存储 缓存 监控
后端开发中的缓存机制:深度解析与最佳实践####
本文深入探讨了后端开发中不可或缺的一环——缓存机制,旨在为读者提供一份详尽的指南,涵盖缓存的基本原理、常见类型(如内存缓存、磁盘缓存、分布式缓存等)、主流技术选型(Redis、Memcached、Ehcache等),以及在实际项目中如何根据业务需求设计并实施高效的缓存策略。不同于常规摘要的概述性质,本摘要直接点明文章将围绕“深度解析”与“最佳实践”两大核心展开,既适合初学者构建基础认知框架,也为有经验的开发者提供优化建议与实战技巧。 ####
|
2月前
|
PHP 开发者 UED
PHP中的异常处理机制解析####
本文深入探讨了PHP中的异常处理机制,通过实例解析try-catch语句的用法,并对比传统错误处理方式,揭示其在提升代码健壮性与可维护性方面的优势。文章还简要介绍了自定义异常类的创建及其应用场景,为开发者提供实用的技术参考。 ####
|
3月前
|
缓存 NoSQL Java
千万级电商线上无阻塞双buffer缓冲优化ID生成机制深度解析
【11月更文挑战第30天】在千万级电商系统中,ID生成机制是核心基础设施之一。一个高效、可靠的ID生成系统对于保障系统的稳定性和性能至关重要。本文将深入探讨一种在千万级电商线上广泛应用的ID生成机制——无阻塞双buffer缓冲优化方案。本文从概述、功能点、背景、业务点、底层原理等多个维度进行解析,并通过Java语言实现多个示例,指出各自实践的优缺点。希望给需要的同学提供一些参考。
69 8
|
2月前
|
Java 数据库连接 开发者
Java中的异常处理机制:深入解析与最佳实践####
本文旨在为Java开发者提供一份关于异常处理机制的全面指南,从基础概念到高级技巧,涵盖try-catch结构、自定义异常、异常链分析以及最佳实践策略。不同于传统的摘要概述,本文将以一个实际项目案例为线索,逐步揭示如何高效地管理运行时错误,提升代码的健壮性和可维护性。通过对比常见误区与优化方案,读者将获得编写更加健壮Java应用程序的实用知识。 --- ####
|
3月前
|
Java 开发者 Spring
深入解析:Spring AOP的底层实现机制
在现代软件开发中,Spring框架的AOP(面向切面编程)功能因其能够有效分离横切关注点(如日志记录、事务管理等)而备受青睐。本文将深入探讨Spring AOP的底层原理,揭示其如何通过动态代理技术实现方法的增强。
103 8
|
3月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
130 2
|
2月前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析

热门文章

最新文章

推荐镜像

更多