简介
RPC 对应的是整个分布式应用系统,就像是“经络”一样的存在。RPC 框架能够帮助我们解决系统拆分后的通信问题,并且能让我们像调用本地一样去调用远程方法。
如果没有 RPC 框架,你要如何调用另一台服务器上的接口呢?RPC 涉及序列化、压缩算法、协议、动态代理、服务注册、加密、网络编程、连接管理、健康检测、负载均衡、优雅启停机、异常重试、业务分组以及熔断限流等方方面面的知识。如果你能把这些问题全部搞定,能力可见一斑。
做服务化,核心要解的问题有两个。
- 第一,要解决系统的水平伸缩能力的问题,因为服务化了以后,说白了你的每个应用,每个系统,要承担的责任变少了,所以伸缩性就能变得很强。
- 第二个服务化的核心问题其实是研发协作的问题。以前 100 人开发一个系统,大家都没有分工的,我接了一个需求,要改哪我就从头到尾全改,这个时候有可能会出现冲突,因为可能每个人都在改同一个地方,大家合并代码的时候就非常痛苦,效率很低。
基本通信过程
rpc调用是通过桩代码对请求进行编码后,通过网络调用的方式发送到服务器上的。服务器在收到请求后,对请求进行解码,拿到请求传过来的参数然后进行处理。处理完后对结果进行编码,再次通过网络的方式将处理结果返回给请求方。请求方拿到结果后进行解码,返回给最初发起调用的函数继续运行。
生成代码是衔接用户调用接口和框架代码的桥梁,生成的client代码中包括了:同步、半同步、异步接口。而server的接口就更简单了,框架已经把刚才提到的网络收发、解压缩、反序列化等都给做好了,然后通过生成代码调用到用户实现的派生service类的函数逻辑中。
在 RPC 框架中,最关键的就是理解“桩”的实现原理,桩是 RPC 框架在客户端的服务代理,它和远程服务具有相同的方法签名,或者说是实现了相同的接口。客户端在调用 RPC 框架提供的服务时,实际调用的就是“桩”提供的方法,在桩的实现方法中,它会发请求的服务名和参数到服务端,服务端的 RPC 框架收到请求后,解析出服务名和参数后,调用在 RPC 框架中注册的“真正的服务提供者”,然后将结果返回给客户端。
- 对象序列化 ==> 发送数据包 ==> 反序列化为对象 ==> 找到对象并执行方法。
- 如何简化?用AOP 来屏蔽底层细节。由服务提供者给出业务接口声明,在调用方的程序里面,RPC 框架根据调用的服务接口提前生成动态代理实现类,并通过依赖注入等技术注入到声明了该接口的相关业务逻辑里面。该代理实现类会拦截所有的方法调用,在提供的方法处理逻辑里面完成一整套的远程调用,并把远程调用结果返回给调用方,这样调用方在调用远程方法的时候就获得了像调用本地接口一样的体验。
一文吃透 Go 内置 RPC 原理每一次 Client 的调用都被封装为一个 Call 对象,包含了调用的方法、参数、响应、错误、是否完成。同时 Client 对象有一个 pending map,key 为请求的递增序号,当 Client 发起调用时,将序号自增,并把当前的 Call 对象放到 pending map 中,然后再向连接写入请求。写入的请求先后分别为 Request 和参数,可以理解为 header 和 body,其中 Request 就包含了 Client 的请求自增序号。Server 端响应时把这个序号带回去,Client 接收响应时读出返回数据,再去 pending map 里找到对应的请求,通知给对应的阻塞协程。PS:感觉比较习惯的思路是,先有pending map,然后有一个需要:将 一次请求的数据封装到 Call里。
rpc 都干了啥
2022.1.14云原生微服务技术趋势解读 通过上图可以学习大佬的归拢能力
如何手搓一个自定义的RPC(远程过程调用框架)要实现一个自定义的RPC框架需解决以下几个主要问题:
- 客户端调用:客户端调用本地的代理函数(stub代码,这个函数负责将调用转换为RPC请求)。这其实就是一个接口描述文件,它可以有多种形式如JSON、XML、甚至是一份word文档或是口头约定均可,只要客户端及服务端都是遵守这份接口描述文件契约即可。在我们的实际开发中一种常见的方式是服务提供者发布一个包含服务接口类的jar包到maven 中央仓库,调用方通过pom文件将之依赖到本地。
- 客户端一般通过接口代理工厂通过动态代理技术来生成一个代理实例,所有的远程调用中的细节,如参数序列化,网络传输,异常处理等都隐藏在代理实例中实现,对调用方来说调用过程是透明的,就像调用本地方法一样。
```java
//客户端通过代理工厂实现接口的一个代理实例
IShoppingCartService serviceProxy = ProxyFactory.factory(IShoppingCartService.class)
.setSerializerType(SerializerType.JDK) //客户端设置所使用的序列化工具,此处为JDK原生 .newProxyInstance(); //返回代理 实现 //像调用本地方法一样,调用此代理实例的shopping 方法 ShoppingCart result = serviceProxy.shopping(“userPin”); log.info(“result={}”, JSONObject.toJSONString(result)); // 客户端代理工厂的核心功能 public class ProxyFactory { //……省略 /**- 代理对象 *
-
@return */ public I newProxyInstance() {
//服务的元数据信息 ServiceData serviceData = new ServiceData( group, //分组 providerName, //服务名称,一般为接口的class的全限定名称 StringUtils.isNotBlank(version) ? version : “1.0.0” //版本号 );//调用器 Calller caller = newCaller().timeoutMillis(timeoutMillis); //集群策略,用于实现快速失败或失败转等功能 Strategy strategy = StrategyConfigContext.of(strategy, retries); Object handler = null; switch (invokeType) { case “syncCall”: //同步调用handler handler = new SyncCaller(serviceData, caller); break; case “asyncCall”: //异步调用handler handler = new AsyncCaller(client.appName(), serviceData, caller, strategy); break; default: throw new RuntimeException(“未知类型: “ + invokeType); }
//返回代理实例 return ProxyEnum.getDefault().newProxy(interfaceClass, handler); } //……省略 } ```
- 客户端一般通过接口代理工厂通过动态代理技术来生成一个代理实例,所有的远程调用中的细节,如参数序列化,网络传输,异常处理等都隐藏在代理实例中实现,对调用方来说调用过程是透明的,就像调用本地方法一样。
```java
//客户端通过代理工厂实现接口的一个代理实例
IShoppingCartService serviceProxy = ProxyFactory.factory(IShoppingCartService.class)
- 参数序列化:代理函数将调用参数进行序列化,并将请求发送到服务器。
- 服务端数据接收:服务器端接收到请求,并将其反序列化,恢复成原始参数。
- 执行远程过程:服务端调用实际的服务过程(函数)并获取结果。
- 返回结果:服务端将调用结果进行序列化,并通过网络传给客户端。
- 客户端接收调用结果:客户到接收到服务端传输的字节流,进行反序列化,转换为实际的结果数据格式,并返回到原始调用方。
其它
微服务拆分之道微服务设计和开发阶段为什么说是三个人分配一个服务是比较理性的?而不是 4 个,也不是 2 个呢?从团队管理来说,3 个人可以形成一个稳定的备份,即使 1 个人休假或者调配到其他系统,剩余 2 个人还可以支撑;如果是 2 个人,抽调 1 个后剩余的 1 个人压力很大;如果是 1 个人,这就是单点了。从技术提升的角度来讲,3 个人的技术小组既能够形成有效的讨论,又能够快速达成一致意见。在维护期平均 1 个人维护 1 个微服务甚至几个微服务就可以。当然考虑到人员备份问题,每个微服务最好都安排 2 个人维护
极客时间《消息队列高手课》提供了一个rpc demo实现 simple-rpc-framework
揭秘百度微服务监控:百度游戏服务监控的演进监控初探阶段的监控措施虽然可以辅助研发发现和定位一些问题,但是还是存在诸多问题:风险暴露滞后,大多报警发生时已造成影响;监控缺乏统一规划,相关监控项混乱且覆盖极不完整;监控能力弱,无法提供有效异常信息;报警混乱,研发被报警信息轰炸;