是什么优化一波 RPC 框架了。。

简介: 近来总是会有服务遇到 OOM 的情况,简单定位后发现 rpc 框架内存占用较多,看来是时候需要优化一波了。占用内存膨胀首先我们需要简单了解一下目前 rpc 框架的层次结构。

近来总是会有服务遇到 OOM 的情况,简单定位后发现 rpc 框架内存占用较多,看来是时候需要优化一波了。


占用内存膨胀

首先我们需要简单了解一下目前 rpc 框架的层次结构。


image.png


先从服务注册中心 zookeeper 的数据结构看,一个命名空间对应数个服务,而每个服务对应数个实例信息,我们的 api 信息则是与实例配置一同放在实例信息的 body 里面的。


当我们根据用户配置的订阅列表拉取服务信息的时候,会将所有的 api 信息一同拉下来,目前在不改变存储粒度的情况下这点是无法优化的。


image.png


根据在 serviceContainer 对象中配置的订阅列表从 zk 上拉取数据后,为每一个服务实例分别创建了 serviceProvider 对象,同时由于我们拉取了这个服务实例的所有 api 信息,所以还为每一个 api 都创建了一个 apiInvoker 对象。


serviceProvider 对象是根据配置的订阅列表中的 vip 而创建的,所以是”按需创建的“。但 apiInvoker 对象确是拉取 zk 上这个服务下所有的 api 信息一股脑创建起来的,而大部分场景下也许我们仅仅只需要依赖其中少数几个 api 接口而已。


不顾真实的依赖需求,而一直都创建所有的 apiInvoker 对象,在微服务应用变多、需求多样化、调用关系复杂化、api 接口持续增长的同时,占用内存也会持续膨胀,甚至出现 oom 情况。


寻址缓慢

寻址缓慢的问题对于普通微服务客户端和 api 网关都存在,而 api 网关尤为严重。


我们需要先了解一个真实的请求是怎么寻址,找到对应的内存中已创建好的 apiInvoker 的。


对于普通微服务客户端来说,我们往往使用动态代理创建 api 接口代理对象,而代理 api 上往往会带注解使用类似服务唯一标识码来标识出这个 api 是属于哪个依赖服务的。有了这个服务 id 信息后,我们可以直接从内存中精确的找到对应的 serviceProvider 对象。


有了 serviceProvider 对象后我们还需要寻找 apiInvoker 对象,这里就没有这么方便了。初始化时在构建好每个 apiInvoker 对象后会依次加入到前缀树 prefixTree 里去,而在寻址时候通过代理 api 上的 url 信息来对 prefixTree 里的所有节点进行匹配从而找到对应的 apiInvoker。抛开前缀树的一些实现细节来说,我们相当于是需要遍历这个 serviceProvider 下的所有 apiInvoker 来进行匹配。


image.png


至于 api 网关就更惨了,由于请求是直接来自于外部的,不可能会带有服务标识码,我们不知道这个请求是属于哪个服务的,大多数情况下我们需要遍历所有的 serviceProvider 对象,然后再遍历里面的所有 apiInvoker 对象去依次匹配,直到匹配到为止。当然,这边如果接口设计规范,同一个服务都是统一前缀的话就好办很多,这就是另一个问题了。


image.png


从上面得知,不管怎么样,我们都需要遍历一个实例里的所有 api 信息去寻址。但实际场景上,也许我们只需要依赖其中的一两个接口而已。


服务引用优化

了解上述原理之后,服务引用方面的优化是顺其自然的。之前在声明服务引用的时候,只是到服务级别,即配置了服务的订阅列表。但是其实更合理的是需要到 api 接口级别的,即描述我们到底需要依赖哪些接口。


那么 api 网关在配置订阅列表时,在配置服务订阅同时声明这个服务下面的接口依赖。网关在管理平台的界面上添加服务信息及服务下面的接口信息后存储至数据库,初始化时直接从数据库抓取依赖信息并创建改造过的的 serviceContainer 对象即可。

rpc:
  reference:
    com.qh.middleware.rpctest:
      - /test/testJson:POST
      - /test/testKryo:POST

对于普通微服务客户端,是不太可能去直接配置 api 订阅的。因为使用者使用的代理对象都是基于 class 粒度的,所以这里在配置服务订阅的同时还需要配置服务下订阅的 api 接口类的全限定类名,与 dubbo reference 类似。这样,在取到需要订阅的 class 后会去扫描里面的所有 api method 信息,并转换成相应的 api 订阅。

rpc:
  reference:
    com.qh.middleware.rpctest:
      - com.qh.mdw.api.testApi
      - com.qh.mdw.api.testApi2

这样,每个 serviceProvider 对象中只会按需创建描述过的 apiInvoker 对象,而不是 zk 上拉取的所有 api。这样 apiInvoker 对象的数量就会按实际情况减少,服务内存占用减少,网关和微服务客户端的寻址也都会快很多。

相关文章
|
负载均衡 Dubbo Java
Dubbo 3.x:探索阿里巴巴的开源RPC框架新技术
随着微服务架构的兴起,远程过程调用(RPC)框架成为了关键组件。Dubbo,作为阿里巴巴的开源RPC框架,已经演进到了3.x版本,带来了许多新特性和技术改进。本文将探讨Dubbo 3.x中的一些最新技术,包括服务注册与发现、负载均衡、服务治理等,并通过代码示例展示其使用方式。
813 9
|
JSON 负载均衡 网络协议
Rpc编程系列文章第二篇:RPC框架设计目标
Rpc编程系列文章第二篇:RPC框架设计目标
|
设计模式 负载均衡 网络协议
【分布式技术专题】「分布式技术架构」实践见真知,手把手教你如何实现一个属于自己的RPC框架(架构技术引导篇)
【分布式技术专题】「分布式技术架构」实践见真知,手把手教你如何实现一个属于自己的RPC框架(架构技术引导篇)
810 0
|
Dubbo Java 应用服务中间件
Rpc编程系列文章第三篇:Hessian RPC一个老的RPC框架
Rpc编程系列文章第三篇:Hessian RPC一个老的RPC框架
|
存储 缓存 Linux
【实战指南】嵌入式RPC框架设计实践:六大核心类构建高效RPC框架
在先前的文章基础上,本文讨论如何通过分层封装提升一个针对嵌入式Linux的RPC框架的易用性。设计包括自动服务注册、高性能通信、泛型序列化和简洁API。框架分为6个关键类:BindingHub、SharedRingBuffer、Parcel、Binder、IBinder和BindInterface。BindingHub负责服务注册,SharedRingBuffer实现高效数据传输,Parcel处理序列化,而Binder和IBinder分别用于服务端和客户端交互。BindInterface提供简单的初始化接口,简化应用集成。测试案例展示了客户端和服务端的交互,验证了RPC功能的有效性。
760 79
|
自然语言处理 负载均衡 API
gRPC 一种现代、开源、高性能的远程过程调用 (RPC) 可以在任何地方运行的框架
gRPC 是一种现代开源高性能远程过程调用(RPC)框架,支持多种编程语言,可在任何环境中运行。它通过高效的连接方式,支持负载平衡、跟踪、健康检查和身份验证,适用于微服务架构、移动设备和浏览器客户端连接后端服务等场景。gRPC 使用 Protocol Buffers 作为接口定义语言,支持四种服务方法:一元 RPC、服务器流式处理、客户端流式处理和双向流式处理。
|
Dubbo 网络协议 Java
RPC框架:一文带你搞懂RPC
这篇文章全面介绍了RPC(远程过程调用)的概念、原理和应用场景,解释了RPC如何工作以及为什么在分布式系统中广泛使用,并探讨了几种常用的RPC框架如Thrift、gRPC、Dubbo和Spring Cloud,同时详细阐述了RPC调用流程和实现透明化远程服务调用的关键技术,包括动态代理和消息的编码解码过程。
RPC框架:一文带你搞懂RPC
|
XML 负载均衡 监控
分布式-dubbo-简易版的RPC框架
分布式-dubbo-简易版的RPC框架
|
XML 存储 JSON
(十二)探索高性能通信与RPC框架基石:Json、ProtoBuf、Hessian序列化详解
如今这个分布式风靡的时代,网络通信技术,是每位技术人员必须掌握的技能,因为无论是哪种分布式技术,都离不开心跳、选举、节点感知、数据同步……等机制,而究其根本,这些技术的本质都是网络间的数据交互。正因如此,想要构建一个高性能的分布式组件/系统,不得不思考一个问题:怎么才能让数据传输的速度更快?
852 1
|
分布式计算 负载均衡 数据安全/隐私保护
什么是RPC?有哪些RPC框架?
RPC(Remote Procedure Call,远程过程调用)是一种允许运行在一台计算机上的程序调用另一台计算机上子程序的技术。这种技术屏蔽了底层的网络通信细节,使得程序间的远程通信如同本地调用一样简单。RPC机制使得开发者能够构建分布式计算系统,其中不同的组件可以分布在不同的计算机上,但它们之间可以像在同一台机器上一样相互调用。
575 8