RPC的概念及运作流程
RPC协议及RPC框架的概念
Netty的基本使用
Java序列化及反序列化技术
Zookeeper的基本使用(注册中心)
自定义注解实现特殊业务逻辑
Java的动态代理
自定义Spring Boot Starter
这里只是列出了你能从RPC框架源码中能学到的东西,本文并不会每个知识点都点到,主要讲述如何手写一个RPC框架,更多细节需要读者阅读源码,文章的下方会提供源码链接哦。
二、RPC基础知识 2.1 RPC是什么?Remote Procedure Call(RPC):远程过程调用。
过程是什么?
过程就是业务处理、计算任务,更直白理解,就是程序。(像调用本地方法一样调用远程的过程。)
RPC采用Client-Server结构,通过Request-Response消息模式实现。
2.2 RPC的流程客户端处理过程中调用Client stub(就像调用本地方法一样),传递参数;
Client stub将参数编组为消息,然后通过系统调用向服务端发送消息;
客户端本地操作系统将消息从客户端机器发送到服务端机器;
服务端操作系统将接收到的数据包传递给Server stub;
Server stub解组消息为参数;
Server stub再调用服务端的过程,过程执行结果以反方向的相同步骤响应给客户端。
2.3 RPC流程中需要处理的问题Client stub、Server stub的开发;
参数如何编组为消息,以及解组消息;
消息如何发送;
过程结果如何表示、异常情况如何处理;
如何实现安全的访问控制。
2.4 RPC协议是什么?RPC调用过程中需要将参数编组为消息进行发送,接受方需要解组消息为参数,过程处理结果同样需要经编组、解组。消息由哪些部分构成及消息的表示形式就构成了消息协议。
RPC调用过程中采用的消息协议称为RPC协议
RPC协议规定请求、响应消息的格式
在TCP(网络传输控制协议)上可选用或自定义消息协议来完成RPC消息交互
我们可以选用通用的标准协议(如:http、https),也也可根据自身的需要定义自己的消息协议。
2.5 RPC框架是什么?封装好参数编组、消息解组、底层网络通信的RPC程序开发框架,带来的便捷是可以直接在其基础上只需要专注于过程代码编写。
Java领域:
传统的webservice框架:Apache CXF、Apache Axis2、Java自带的JAX-WS等。webservice框架大多基于标准的SOAP协议。
新兴的微服务框架:Dubbo、spring cloud、Apache Thrift等。
三、手写RPC 3.1 目标我们将会写一个简易的RPC框架,暂且叫它leisure-rpc-spring-boot-starter,通过在项目中引入该starter,并简单的配置一下,项目即拥有提供远程服务的能力。
编写自定义注解@Service,被它注解的类将会提供远程服务。
编写自定义注解@InjectService,使用它可注入远程服务。
3.2 项目整体结构 3.3 客户端编写 3.3.1 客户端需要做什么?客户端想要调用远程服务,必须具备服务发现的能力;在知道有哪些服务过后,还必须有服务代理来执行服务调用;客户端想要与服务端通信,必须要有相同的消息协议;客户端想要调用远程服务,那么必须具备网络请求的能力,即网络层功能。
当然,这是客户端所需的最基本的能力,其实还可以扩展的能力,例如负载均衡。
3.3.2 具体实现我们先看看客户端的代码结构:
基于面向接口编程的理念,不同角色都实现了定义了相应规范的接口。这里面我们没有发现消息协议相关内容,那是因为服务端也需要消息协议,因此抽离了出来,放在公共层。
3.3.2.1 服务发现者 /** * 服务发现抽象类,定义服务发现规范 * * @author 东方雨倾 * @since 1.0.0 */ public interface ServiceDiscoverer { List<Service> getServices(String name); } /** * Zookeeper服务发现者,定义以Zookeeper为注册中心的服务发现细则 * * @author 东方雨倾 * @since 1.0.0 */ public class ZookeeperServiceDiscoverer implements ServiceDiscoverer { private ZkClient zkClient; public ZookeeperServiceDiscoverer(String zkAddress) { zkClient = new ZkClient(zkAddress); zkClient.setZkSerializer(new ZookeeperSerializer()); } /** * 使用Zookeeper客户端,通过服务名获取服务列表 * 服务名格式:接口全路径 * * @param name 服务名 * @return 服务列表 */ @Override public List<Service> getServices(String name) { String servicePath = LeisureConstant.ZK_SERVICE_PATH + LeisureConstant.PATH_DELIMITER + name + "/service"; List<String> children = zkClient.getChildren(servicePath); return Optional.ofNullable(children).orElse(new ArrayList<>()).stream().map(str -> { String deCh = null; try { deCh = URLDecoder.decode(str, LeisureConstant.UTF_8); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return JSON.parseObject(deCh, Service.class); }).collect(Collectors.toList()); } }