RPC 使用中的一些注意点

最近线上碰到一点小问题,分析其原因发现是出在对 RPC 使用上的一些细节掌握不够清晰导致。很多时候我们做业务开发会把 RPC 当作黑盒机制来使用,但若不对黑盒的工作原理有个基本掌握,也容易犯一些误用的微妙错误。
虽然曾经已经写过一篇《RPC 的概念模型与实现解析》 从概念模型和实现细节上讲述了 RPC 的原理,这一篇就从使用上的一些注意点来捋一捋吧。
同步
RPC 的调用通常为了方便使用,会被伪装成普通方法调用的形式。但实际二者之间存在巨大的差异,进程内的方法调用的时间量级是 ns(纳秒),而进程间的 RPC 方法调用时间量级通常是 ms(毫秒),它们之间差着 10 的六次方呢。RPC 的冰山底部透视图如下:

但在目前流行的微服务架构模式下,跨服务的同步调用隐藏着巨大的风险。一般微服务化架构下,通常一个业务的调用会跨 N(N 一般大于 2) 个服务进程,整个调用链路上的同步调用等待的瓶颈会由最慢(或脆弱)的服务决定,A-B-C 像这样一个链路,A 同步调用 B 并等待返回,B 同步调用 C 并等待返回,以此类推,就像一组齿轮链,级级传动,这很容易产生雪崩效应。若 C 服务挂住了,会导致前面的服务全部都因为等待超时而占用大量不必要的线程资源。
因此,微服务架构下,内部主服务链之间的 RPC 调用需要异步化,服务之间的调用请求和等待结果相互之间解耦,如下是一个服务链路调用的示意图:

外部用户通过服务网关(API Gateway)发起调用并等待结果,随后网关派发调用请求给后续服务,其主调用链路为 A-B-C,其内部为异步调用,链路上不等待,最后由 C 返回结果给服务网关。其中 B 又依赖两个子服务,S1 和 S2,B 需要 S1 和 S2 的返回结果才能发起 C 调用,因此在支线上 B 针对 S1 和 S2 调用就需要是同步的。
异步
RPC 的同步调用确保请求送达对方并收到对方响应,若没有收到响应,框架则抛出 Timeout 异常。这种情况下调用方是无法确定调用是成功还是失败的,需要根据业务场景(是否可重入,幂等)选择重试和补偿策略。
而 RPC 的异步调用意味着 RPC 框架不阻塞调用方线程,调用方不需要立刻拿到返回结果,甚至调用方根本就不关心返回结果。RPC 的异步交互场景示意图如下:

在上面的示意图中,对于是否需要返回值的异步请求,其中的细微差异在于是否返回一个 Future 对象给调用方,以便未来(Future)调用方可以再通过它来获取返回值。正是因为这种 Future 机制的存在,所以针对前面(图2)中 S1 和 S2 的调用就可以采用一种异步并行的调用机制来提升并行性和性能,如下图所示:

这样调用 S1 和 S2 的总时间就由最慢的一个服务响应时间来决定了。(上图中其实调用 S1 和 S2 不可能做到同时,有细微的时间差异,但相对跨进程的调用本身来说这种差异基本忽略不计。)
线程
RPC 的线程模型一般如下所示:

其中,RPC 的网络层通常采用非阻塞型 I/O 模型,放在 Java 的实现语境下就是 NIO 了。而 RPC 框架通常共享一个 I/O 线程池,处理所有连接上的 I/O 事件派发。通常业务事件会派发到内部的一个固定大小(可配置)的业务执行线程池,再由业务执行线程调用应用实现层的代码。
但有些 RPC 框架在实现客户端的 I/O 线程模型时,也采用了针对每个不同的服务端一个独立的 I/O 线程池,这样就变成了下面这个图所示:

这带来了一个潜在的问题,在一个客户端需要连接大量服务端时(这在基于 RPC 实现的服务框架中很常见),客户端的 I/O 线程池数就和需连接的服务数相等。在现在的微服务部署模式下,一般一个服务部署在一个 Docker 容器中,同一个服务会有很多个(几十上百个)进程共同组成集群提供服务,这样就导致客户端 I/O 线程数可能会很多。
而在 Docker 环境下 Java 的 Runtime.availableProcessors() 获取的 CPU 数量实际是物理机的,而不是 Docker 隔离的核数。另外,像 Netty 这样的网络框架经常默认是基于 CPU 核数来启动默认的 I/O 线程数的,所以导致针对每个服务的客户端会启动 CPU 核数个 I/O 线程再乘上服务实例数,这个线程数量也是颇为客观,出现单进程好几千固化的线程,线程调度和切换的成本颇高,另外服务的水平扩展性也有一定的受限。这也是需要注意的另一点。
...
在曾经那篇《RPC 的概念模型与实现解析》 的的结尾,我曾写到:
无论 RPC 的概念是如何优雅,但是“草丛中依然有几条蛇隐藏着”,只有深刻理解了 RPC 的本质,才能更好地应用。
所以这一篇大概就是抓出了几条隐藏着的蛇吧。
写点文字,画点画儿,记录成长瞬间。
微信公众号「瞬息之间」,既然遇见,不如一起成长。

RPC 使用中的一些注意点的更多相关文章
- 微博RPC框架motan入门笔记
Motan 是一套高性能.易于使用的分布式远程服务调用(RPC)框架. 功能 支持通过spring配置方式集成,无需额外编写代码即可为服务提供分布式调用能力. 支持集成consul.zookeeper ...
- FastRPC 3.2 发布,高性能 C++ 协程 RPC 框架
用过go erlang gevent的亲们应该都会知道协程在应用中带来的方便. 如果对协程不理解的同学,通过阅读下面例子可以快速了解我们框架的协程的意义,已了解的可以跳过这部分. 协程例子:假设我们要 ...
- 【.NET Core项目实战-统一认证平台】第十六章 网关篇-Ocelot集成RPC服务
[.NET Core项目实战-统一认证平台]开篇及目录索引 一.什么是RPC RPC是"远程调用(Remote Procedure Call)"的一个名称的缩写,并不是任何规范化的 ...
- 远程过程调用(RPC)
在第二篇教程中我们介绍了如何使用工作队列(work queue)在多个工作者(woker)中间分发耗时的任务. 可是如果我们需要将一个函数运行在远程计算机上并且等待从那儿获取结果时,该怎么办呢?这就是 ...
- Spring Cloud+Dubbo对Feign进行RPC改造
因为Spring Cloud Feign是基于Http Restful的调用,在高并发下的性能不够理想(虽然他是基于Ribbon以及带有熔断机制,可以防止雪崩),成为性能瓶颈,所以我们今天对Feign ...
- rabbitmq系列五 之远程过程调用(RPC)
1.远程过程调用(RPC) 在第二篇教程中我们介绍了如何使用工作队列(work queue)在多个工作者(woker)中间分发耗时的任务. 可是如果我们需要将一个函数运行在远程计算机上并且等待从那儿获 ...
- NFS及RPC讲解
导读 NFS(Network File System)即网络文件系统,由Sun公司开发,于1984年向外公布.功能是通过网络让不同的机器.不同的操作系统能够彼此分享个别的数据,让应用程序在客户端通过网 ...
- .NET 跨平台RPC框架DotNettyRPC Web后台快速开发框架(.NET Core) EasyWcf------无需配置,无需引用,动态绑定,轻松使用 C# .NET 0配置使用Wcf(半成品) C# .NET Socket 简单实用框架 C# .NET 0命令行安装Windows服务程序
.NET 跨平台RPC框架DotNettyRPC DotNettyRPC 1.简介 DotNettyRPC是一个基于DotNetty的跨平台RPC框架,支持.NET45以及.NET Standar ...
- 高并发架构系列:如何从0到1设计一个类Dubbo的RPC框架
在过去持续分享的几十期阿里Java面试题中,几乎每次都会问到Dubbo相关问题,比如:“如何从0到1设计一个Dubbo的RPC框架”,这个问题主要考察以下几个方面: 你对RPC框架的底层原理掌握程度. ...
随机推荐
- TDD在Unity3D游戏项目开发中的实践
0x00 前言 关于TDD测试驱动开发的文章已经有很多了,但是在游戏开发尤其是使用Unity3D开发游戏时,却听不到特别多关于TDD的声音.那么本文就来简单聊一聊TDD如何在U3D项目中使用以及如何使 ...
- TODO:GitHub创建组织的步骤
TODO:GitHub创建组织的步骤 使用GitHub进行团队合作,写这个步骤主要作用是为了OneTODO作为一个团队组织进行代码的分享,让更多人来参与. 使用帐号.密码登录GitHub 2.右上角加 ...
- hash表长度优化证明
hash表冲突的解决方法一般有两个方向: 一个是倾向于空间换时间,使用向量加链表可以最大程度的在节省空间的前提下解决冲突. 另外一个倾向于时间换空间,下面是关于这种思路的一种合适表长度的证明过程: 这 ...
- 手动添加kdump
背景: Linux嵌入式设备内核挂死后,无法自动重启,需要手动重启.而且如果当时没有连串口的话,就无法记录内核挂死时的堆栈,所以需要添加一种方式来记录内核挂死信息方便以后调试使用.设备中增加k ...
- 【走过巨坑】android studio对于jni调用及运行闪退无法加载库的问题解决方案
相信很多小伙伴都在android开发中遇到调用jni的各种巨坑,因为我们不得不在很多地方用到第三方库so文件,然而第三方官方通常都只会给出ADT环境下的集成方式,而谷歌亲儿子android studi ...
- [原] KVM 虚拟化原理探究(2)— QEMU启动过程
KVM 虚拟化原理探究- QEMU启动过程 标签(空格分隔): KVM [TOC] 虚拟机启动过程 第一步,获取到kvm句柄 kvmfd = open("/dev/kvm", O_ ...
- Win10 UWP开发系列——开源控件库:UWPCommunityToolkit
在开发应用的过程中,不可避免的会使用第三方类库.之前用过一个WinRTXamlToolkit.UWP,现在微软官方发布了一个新的开源控件库—— UWPCommunityToolkit 项目代码托管在G ...
- Lind.DDD.LindMQ~关于持久化到Redis的消息格式
回到目录 关于持久化到Redis的消息格式,主要是说在Broker上把消息持久化的过程中,需要存储哪些类型的消息,因为我们的消息是分topic的,而每个topic又有若干个queue组成,而我们的to ...
- iOS从零开始学习直播之1.播放
对于直播来说,客户端主要做两件事情,推流和播放.今天先讲播放. 播放流程 1.拉流:服务器已有直播内容,从指定地址进行拉取的过程.其实就是向服务器请求数据. 2.解码:对视屏数据进行解压缩. 3. ...
- 简历生成平台项目开发-STEP1问卷设计
周五课程结束完后,小组建立QQ群和微信群,着手讨论项目问题.一开始的大概想法:就业信息平台,收集企业招聘信息和就业生资料,提供给学生和企业的校企对接平台.后来发现群里谭卓同学也有个相关的思路,经过商量 ...