24丨如何在线上环境里兼容多种 RPC 协议?

简介: 本文探讨了在复杂线上环境中如何兼容多种RPC协议。由于历史原因,企业常存在多套RPC框架并行的问题,导致维护成本高、升级困难。为实现平滑演进,可通过支持多协议共存的方式,在不中断服务的前提下逐步迁移。关键在于利用协议的magic number识别类型,并统一转换为与协议无关的内部对象,使核心逻辑解耦。最终不仅降低升级风险,也为未来扩展奠定基础,提升系统可维护性与灵活性。

上一讲我们学习了如何在没有接口的情况下完成 RPC 调用,其关键在于你要理解接口定义在 RPC 里面的作用。除了我们前面说的,动态代理生成的过程中需要用到接口定义,剩余的其它过程中接口的定义只是被当作元数据来使用,而动态代理在 RPC 中并不是一个必须的环节,所以在没有接口定义的情况下我们同样也是可以完成 RPC 调用的。今天一起看看如何在线上环境里兼容多种 RPC 协议。

看到这个问题后,可能你的第一反应就是,在真实环境中为什么会存在多个协议呢?我们说过,RPC 是能够帮助我们屏蔽网络编程细节,实现调用远程方法就跟调用本地一样的体验。大白话说就是,RPC 是能够帮助我们在开发过程中完成应用之间的通信,而又不需要我们关心具体通信细节的工具。

为什么要支持多协议?

既然应用之间的通信都是通过 RPC 来完成的,而能够完成 RPC 通信的工具有很多,比如像 Web Service、Hessian、gRPC 等都可以用来充当 RPC 使用。这些不同的 RPC 框架都是随着互联网技术的发展而慢慢涌现出来的,而这些 RPC 框架可能在不同时期会被我们引入到不同的项目中解决当时应用之间的通信问题,这样就导致我们线上的生成环境中存在各种各样的 RPC 框架。

很显然,这种混乱使用 RPC 框架的方式肯定不利于公司技术栈的管理,最明显的一个特点就是我们维护 RPC 框架的成本越来越高,因为每种 RPC 框架都需要有专人去负责升级维护。

为了解决早期遗留的一些技术负债,我们通常会去选择更高级的、更好用的工具来解决,治理 RPC 框架混乱的问题也是一样。为了解决同时维护多个 RPC 框架的困难,我们肯定希望能够用统一用一种 RPC 框架来替代线上所有的 RPC 框架,这样不仅能降低我们的维护成本,而且还可以让我们在一种 RPC 上面去精进。

既然目标明确后,我们该如何实施呢?

可能你会说这很简单啊,我们只要把所有的应用都改造成新 RPC 的使用方式,然后同时上线所有改造后的应用就可以了。如果在团队比较小的情况下,这种断崖式的更新可能确实是最快的方法,但如果是在团队比较大的情况下,要想做到同时上线所有改造后的应用,暂且不讨论这种方式是否存在风险,光从多个团队同一时间上线所有应用来看,这也几乎是一件不可能做到的事儿。

那对于多人团队来说,有什么办法可以让其把多个 RPC 框架统一到一个工具上呢?我们先看下多人团队在升级过程中所要面临的困难,人数多就意味着要维护的应用会比较多,应用多了之后线上应用之间的调用关系就会相对比较复杂。那这时候如果单纯地把任意一个应用目前使用的 RPC 框架换成新的 RPC 框架的话,就需要让所有调用这个应用的调用方去改成新的调用方式。

通过这种自下而上的滚动升级方式,最终是可以让所有的应用都切换到统一的 RPC 框架上,但是这种升级方式存在一定的局限性,首先要求我们能够清楚地梳理出各个应用之间的调用关系,只有这样,我们才能按部就班地把所有应用都升级到新的 RPC 框架上;其次要求应用之间的关系不能存在互相调用的情况,最好的情况就是应用之间的调用关系像一颗树,有一定的层次关系。但实际上我们应用的调用关系可能已经变成了网状结构,这时候想再按照这种方式去推进升级的话,就可能寸步难行了。

为了解决上面升级过程中遇到的问题,你可能还会想到另外一个方案,那就是在应用升级的过程中,先不移除原有的 RPC 框架,但同时接入新的 RPC 框架,让两种 RPC 同时提供服务,然后等所有的应用都接入完新的 RPC 以后,再让所有的应用逐步接入到新的 RPC 上。这样既解决了上面存在的问题,同时也可以让所有的应用都能无序地升级到统一的 RPC 框架上。

在保持原有 RPC 使用方式不变的情况下,同时引入新的 RPC 框架的思路,是可以让所有的应用最终都能升级到我们想要升级的 RPC 上,但对于开发人员来说,这样切换成本还是有点儿高,整个过程最少需要两次上线才能彻底地把应用里面的旧 RPC 都切换成新 RPC。

那有没有更好的方式可以让应用上线一次就可以完成新老 RPC 的切换呢?关键就在于要让新的 RPC 能同时支持多种 RPC 调用,当一个调用方切换到新的 RPC 之后,调用方和服务提供方之间就可以用新的协议完成调用;当调用方还是用老的 RPC 进行调用的话,调用方和服务提供方之间就继续沿用老的协议完成调用。对于服务提供方来说,所要处理的请求关系如下图所示:

怎么优雅处理多协议?

要让新的 RPC 同时支持多种 RPC 调用,关键就在于要让新的 RPC 能够原地支持多种协议的请求。怎么才能做到?在  第 02 讲 我们说过,协议的作用就是用于分割二进制数据流。每种协议约定的数据包格式是不一样的,而且每种协议开头都有一个协议编码,我们一般叫做 magic number。

当 RPC 收到了数据包后,我们可以先解析出 magic number 来。获取到 magic number 后,我们就很容易地找到对应协议的数据格式,然后用对应协议的数据格式去解析收到的二进制数据包。

协议解析过程就是把一连串的二进制数据变成一个 RPC 内部对象,但这个对象一般是跟协议相关的,所以为了能让 RPC 内部处理起来更加方便,我们一般都会把这个协议相关的对象转成一个跟协议无关的 RPC 对象。这是因为在 RPC 流程中,当服务提供方收到反序列化后的请求的时候,我们需要根据当前请求的参数找到对应接口的实现类去完成真正的方法调用。如果这个请求参数是跟协议相关的话,那后续 RPC 的整个处理逻辑就会变得很复杂。

当完成了真正的方法调用以后,RPC 返回的也是一个跟协议无关的通用对象,所以在真正往调用方写回数据的时候,我们同样需要完成一个对象转换的逻辑,只不过这时候是把通用对象转成协议相关的对象。

在收发数据包的时候,我们通过两次转换实现 RPC 内部的处理逻辑跟协议无关,同时保证调用方收到的数据格式跟调用请求过来的数据格式是一样的。整个流程如下图所示:

总结

在我们日常开发的过程中,最难的环节不是从 0 到 1 完成一个新应用的开发,而是把一个老应用通过架构升级完成从 70 分到 80 分的跳跃。因为在老应用升级的过程中,我们不仅需要考虑既有的功能逻辑,也需要考虑切换到新架构上的成本,这就要求我们在设计新架构的时候要考虑如何让老应用能够平滑地升级,就像在 RPC 里面支持多协议一样。

在 RPC 里面支持多协议,不仅能让我们更从容地推进应用 RPC 的升级,还能为未来在 RPC 里面扩展新协议奠定一个良好的基础。所以我们平时在设计应用架构的时候,不仅要考虑应用自身功能的完整性,还需要考虑应用的可运维性,以及是否能平滑升级等一些软性能力。

课后思考

在 RPC 里面支持多协议的时候,有一个关键点就是能够识别出不同的协议,并且根据不同的 magic number 找到不同协议的解析逻辑。如果线上协议存在很多种的话,就需要我们事先在 RPC 里面内置各种协议,但通过枚举的方式可能会遗漏,不知道针对这种问题你有什么好的办法吗?

笔者认为:服务端注册到注册中心时,可以将自己支持的功能,注册上去,那么客户调用时,就能提前判定服务端是否支持该方式

相关文章
|
4月前
|
存储 缓存 负载均衡
CP(强制一致性),AP(最终一致)
本文探讨RPC框架中的服务发现机制,对比DNS、ZooKeeper等方案,指出其在超大规模集群下的局限性。重点提出基于消息总线的最终一致性注册中心,通过AP模型替代CP,提升系统性能与稳定性,适用于高并发、大规模服务节点场景。
CP(强制一致性),AP(最终一致)
|
4月前
|
缓存 Ubuntu Linux
Docker安装
本文介绍Docker在CentOS和Ubuntu系统中的安装与配置方法,涵盖卸载旧版本、配置yum源、在线/离线安装、启动服务、设置开机自启、运行HelloWorld测试及daemon.json配置详解,并提供阿里云镜像加速、日志管理、命令补全等实用操作步骤。
|
4月前
|
Java Shell Maven
06-nexus私仓环境搭建
本文介绍Nexus私有仓库环境搭建全过程,包括JDK安装、Nexus OSS版下载与解压、配置文件修改、创建nexus用户并启动服务。详细说明了如何通过Web界面登录、修改默认密码、配置匿名访问,并创建Maven私仓。同时提供上传本地jar包的两种方式,重点演示使用脚本批量导入本地仓库依赖的方法,包含清理无效文件、重命名元数据及执行上传命令等步骤,适用于企业内网构建Maven私服场景。
|
4月前
|
网络协议 算法 前端开发
07 | 架构设计:设计一个灵活的 RPC 框架
本文深入讲解如何设计一个灵活的 RPC 框架,从基础通信原理出发,剖析传输、协议、服务发现、连接管理等核心模块,并提出分层架构与插件化设计思想,提升系统可扩展性与维护性,助力构建高性能、易演进的分布式服务架构。
|
4月前
|
Java Maven 数据安全/隐私保护
Nexus仓库
Nexus仓库是Sonatype推出的开源制品管理工具,支持Maven、Npm、Docker等格式。本文介绍其在Linux和Docker环境下的安装配置,包括JDK部署、OSS版下载、用户权限、匿名访问设置,以及仓库创建与上传下载操作,涵盖密码重置、数据持久化及脚本批量导入等内容,助力搭建高效私有仓库。
|
4月前
|
存储 负载均衡 Dubbo
10 | 路由策略:怎么让请求按照设定的规则发到不同的节点上?
本文介绍RPC中的路由策略,通过规则控制请求分发至不同服务节点,实现灰度发布、流量隔离。结合IP路由与参数路由,可精准控制调用路径,降低上线风险,提升系统稳定性,是服务治理的重要手段。
|
4月前
|
存储 NoSQL 算法
10-Docker安装Redis
本文介绍如何使用Docker安装Redis 6.0.8,涵盖单机版与集群部署。重点讲解Redis集群的哈希槽分区机制、一致性哈希算法原理及3主3从集群搭建步骤,并演示主从扩容缩容操作,帮助实现高可用分布式缓存架构。
|
4月前
|
存储 JSON 编解码
06 | RPC 实战:剖析 gRPC 源码,动手实现一个完整的 RPC
本文通过剖析 gRPC 源码,深入讲解 RPC 框架的实现原理。从 Protocol Buffer 接口定义到代码生成,结合 Netty 实现网络通信,详细解析请求的序列化、HTTP/2 帧传输、服务端解码与调用流程,帮助读者将序列化、协议设计等理论知识落地为实战代码,掌握构建高性能 RPC 的核心技术细节。
|
4月前
|
消息中间件 网络协议 Java
04 | 网络通信:RPC 框架在网络通信上更倾向于哪种网络 IO 模型?
本讲深入解析RPC框架的网络通信机制,重点探讨常用网络IO模型。由于RPC调用本质是服务消费者与提供者间的网络数据交换,其性能依赖高效的IO处理。常见的IO模型中,同步阻塞IO(BIO)简单直观但并发能力弱;IO多路复用则通过单线程管理多个连接,适合高并发场景,成为RPC框架首选。结合系统与语言支持,如Java Netty基于Reactor模式,广泛应用IO多路复用,并融合零拷贝技术提升性能。Netty的零拷贝不仅利用堆外内存减少数据拷贝,还通过CompositeByteBuf、slice等机制优化用户空间内存操作,有效解决拆包粘包问题。
|
4月前
|
网络协议
02 | 协议:怎么设计可扩展且向后兼容的协议?
本文深入探讨如何设计可扩展且向后兼容的RPC协议。通过对比HTTP协议,解析协议在应用层通信中的核心作用——明确消息边界、保障语义一致。文章详解私有协议的设计要点:包含长度、序列化方式、消息ID等字段的协议头与可变长协议体的结构,并提出支持协议头扩展的三段式设计(固定长度+动态头+主体),以实现平滑升级与高性能。强调协议设计需兼顾兼容性与低开销,避免“鸡同鸭讲”,支撑分布式系统高效通信。(238字)

热门文章

最新文章

下一篇
开通oss服务