面试的时候总会问RPC的实现原理,就连在大型互联网公司工作过几年的同学,也只停留在使用层面,这是不应该的,希望大家不仅会用,还要知道实现原理,接下来咱们就聊聊RPC的实现原理
1.为什么要RPC
相信大家都写过hello world吧,本地调用一下,便可以输出,这个服务消费方和服务提供方是本地调用关系,而当你进入公司,接触到大型项目时,一般一个项目会牵扯到服务的拆分,服务消费方和服务提供方会放到不同的机器上,这个时候之前的本地调用就不再起作用了,因为服务部署在不同的服务器上,所以就牵扯到网络通讯了,那此时的服务调用是这样的
每调用一个服务,都要写一堆繁杂都网络通信代码,那么如何解决呢,能不能像调用本地服务一样来调用远程服务呢,而让服务调用者对网络通信细节透明,这样可以大大提高效率,这种方式其实就是RPC, 业界有很多RPC的框架,如阿里的HSF、dubbo、 Facebook的thrift(开源)、Google grpc(开源)、Twitter的finagle(开源)等
2.RPC的几个关键点
前面既然提到了,RPC实现了,像调用本地服务一样来调用远程服务,让服务调用者对网络通信细节透明,那么几个比较关键的概念是什么呢
1)封装网络通信细节
答案是使用代理,针对于Java有两种 a. jdk动态代理,b.字节码生成,由于字节码生成比较难维护,目前大多数RPC框架使用的是jdk动态代理
2)数据的编码和解码
一般网络通信的第一步是要确定客户端和服务端相互通信的消息结构,客户端的请求数据结构一般需要包括以下内容
a) 接口名称 如果不传,服务端无法知道调用的哪个接口
b) 方法名 一个接口有很多方法名,不传,服务端不知道调用哪个方法
c) 参数类型&参数值
d) 超时时间
e) requestID 标识唯一请求id
服务端返回的消息结构一般包括
a) 返回值
b)状态code
c)requestID
在数据结构定了之后,就需要序列化和反序列化了,从RPC的角度上看,主要看三点:1)通用性,比如是否能支持Map等复杂的数据结构;2)性能,包括时间复杂度和空间复杂度,由于RPC框架将会被公司几乎所有服务使用,如果序列化上能节约一点时间,对整个公司的收益都将非常可观,同理如果序列化上能节约一点内存,网络带宽也能省下不少;3)可扩展性,对互联网公司而言,业务变化飞快,如果序列化协议具有良好的可扩展性,支持自动增加新的业务字段,而不影响老的服务,这将大大提供系统的灵活度。目前互联网公司广泛使用Protobuf、Thrift、Avro等成熟的序列化解决方案来搭建RPC框架
3)通信
目前有两种常用IO通信模型 a) BIO b) NIO,当然是选择NIO,那RPC的IO通信框架如何实现?a) 使用java nio方式自研,方式比较复杂,同时有隐藏的bug。b) apache mina,版本更新缓慢。c) 使用netty,现在很多RPC框架都直接基于netty这一IO通信框架,省力又省心
3.一次调用过程
综上分析,一次RPC调用如下:
以HelloService为例,首先消费端和服务端,公用同样的jar包,jar包里声明了HelloService, 然后消费端调用HelloService, 底层使用代理,生成封装了通信的代码,同时序列化请求对象,通过网络传输到服务端,服务端接受到数据,进行解码,然后反序列化出请求对象,然后通过反射invoke对应到HelloServiceImpl, 处理后的结果会序列化成响应通信对象,通过网络编解码,返回给调用端,调用端再反序列化成响应对象,这样一次调用过程结束了
4. 服务发布
其实只要消费端知道服务端的IP和端口,就可以直接调用服务了,但是关键是如何做到自动告知,人肉告知的方式:如果你发现你的服务一台机器不够,要再添加一台,这个时候就要告诉调用者我现在有两个ip了,你们要轮询调用来实现负载均衡;调用者咬咬牙改了,结果某天一台机器挂了,调用者发现服务有一半不可用,他又只能手动修改代码来删除挂掉那台机器的ip。现实生产环境当然不会使用人肉方式。
有没有一种方法能实现自动告知,即机器的增添、剔除对调用方透明,调用者不再需要写死服务提供方地址?当然可以,现如今zookeeper被广泛用于实现服务自动注册与发现功能!
简单来讲,zookeeper可以充当一个服务注册表(Service Registry),让多个服务提供者形成一个集群,让服务消费者通过服务注册表获取具体的服务访问地址(ip+端口)去访问具体的服务提供者。
网友评论