面试题:如何实现一个RPC框架?

发布于:2024-04-29 ⋅ 阅读:(21) ⋅ 点赞:(0)

来源

滴滴二面

原问题

如何实现一个RPC框架?
考这个问题不是让你实现一个RPC框架,而是要看你对RPC框架的理解。

我的回答

我对RPC框架不太了解。

更好的答案

RPC概念

我们平时使用RPC框架时,可以在一个服务中,像调用本地方法一样,调用其他服务中的方法。

RPC需要用到哪些技术?

以服务A调用服务B为例进行讲解。

架构图

在这里插入图片描述
ProcessOn链接:RPC调用架构

动态代理

但是这其中,框架为我们封装了很多功能。我们只是引入并调用了接口,所以,需要使用到动态代理技术,可以使用JDK动态代理,javassist,dubbo就是使用javassist来生成代理对象,由代理对象来执行一系列的请求。

序列化

我们在A中调用B时,需要让B知道A调用了哪个方法,就需要对B的接口信息进行序列化,对参数进行序列化。
可以使用现有的序列化协议进行序列化,除了下面的图中提到的序列化协议,还可以使用fastJson进行序列化,不过序列化的效率没那么高,对数据的压缩程度也没有那么好。
在这里插入图片描述

通信协议

我们可以使用现有的通信协议进行通信
在这里插入图片描述

实现自定义的协议,可以参考:
基于注解和Spring实现一个RPC框架
主要是:魔数+协议版本+消息长度+消息内容:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

服务注册与发现

可以使用Zookeeper或者Nacos这种现成的注册中心。
Zookeeper是CP系统,Nacos既可以CP,也可以AP,具体可以通过连接Nacos的配置ephemeral参数决定。可以参考我的另一篇文章:
Eureka、Nacos、Zookeeper、Redis等应用是AP还是CP?

使用Zookeeper,对于服务,一般是创建持久节点,服务和ip、端口的映射,可以在服务节点下面创建临时节点,服务调用者可以对关注的节点创建watch事件,当服务提供者列表有变动时,调用者就可以接收到,就可以更新元数据。

使用Nacos,也是需要去实现类似的接口。

负载均衡策略

服务A获取到服务提供者列表之后,需要根据某种负载均衡策略,来选择某个ip、端口进行调用,常见的负载均衡策略,有以下几种:
随机、轮询、距离最近等。
随机和轮询又可以带上权重(不知道权重是什么,可以查询以后,补充到这里)

用户可以根据配置选择需要的负载均衡策略。

熔断机制

客户端请求服务端时,当B服务出了问题之后,需要对B服务进行熔断,避免B服务的问题扩大到整个系统。
熔断策略可以实现为:当过去10秒内,请求大于20个,但是请求失败率为50%,则熔断该服务。每隔5秒,放行一定的流量对该服务进行尝试,如果失败,则保持熔断状态,如果成功,则关闭熔断状态,放开所有流量。

降级机制

客户端请求服务端时,当B服务抛出异常,或者B服务被熔断之后,需要提供降级策略,根据降级策略,直接返回固定的数据或者抛出异常。
可以使用反射,根据降级工厂或者用户配置的降级的类,创建响应的示例,并设置到我们创建的动态代理中,当发生降级时,就使用反射对降级的方法进行调用。
可以先把降级的类的相关方法存储成一个Map<方法签名,降级方法> 降级方法缓存,降级时,使用反射获取原始方法的方法签名,然后从降级方法缓存中获取到降级方法,就可以对降级方法进行调用了。
使用反射获取原始方法的方法签名:已经像GPT求证过,可以做到。

隔离机制

客户端请求服务端时,对下游的服务进行请求时,需要一定的隔离策略,防止下游服务故障导致整个系统故障。
可以实现为:基于线程池或者信号量进行隔离,对每个用户配置的key进行隔离,当key不存在时,则创建新的线程池或者信号量。
如果线程池或者信号量满了,则抛出异常。

限流机制

服务端对客户端进行响应时,也可以制定一定的限流机制,防止一个客户端请求占用太多资源,也可以防止请求者恶意攻击。
具体的限流机制可以参考Nacos的限流,可以对单个接口进行限流,可以对接口的热点key进行限流。限流算法还需要再查一下,然后补充到这里。我记得好像有什么漏桶算法之类的。

通信模块

在编写代码时,可以使用Netty框架,Netty框架对NIO和AIO都进行了封装,开发者可以很方便的编写代码。
服务端对客户端进行响应时,具体可以使用Netty的主从Reactor多线程模式来实现服务之间的通信。
客户端请求服务端时,就是使用隔离策略中的线程池来发送请求,或者使用信号量时,使用信号量来限制请求。

参考

基于注解和Spring实现一个RPC框架

Dubbo协议讲解

各种RPC协议对比