本文是笔者学习原动力的手撸Rpc课程的毕竟,并且加上个人的一些理解。推荐一下该课程属实不错。
RPC(Remote Procedure Call)远程过程调用协议,一种通过网络从远程计算机上请求服务,而不需要了解底层网络技术的协议。RPC它假定某些协议的存在,例如TPC/UDP等,为通信程序之间携带信息数据。在OSI网络七层模型中,RPC跨越了传输层和应用层,RPC使得开发,包括网络分布式多程序在内的应用程序更加容易。
过程是什么? 过程就是业务处理、计算任务,更直白的说,就是程序,就是想调用本地方法一样调用远程的过程。
RPC的概念与技术早在1981年由Nelson提出。1984年,Birrell和Nelson把其用于支持异构型分布式系统间的通讯。Birrell的RPC 模型引入存根进程( stub) 作为远程的本地代理,调用RPC运行时库来传输网络中的调用。Stub和RPC runtime屏蔽了网络调用所涉及的许多细节,特别是,参数的编码/译码及网络通讯是由stub和RPC runtime完成的,因此这一模式被各类RPC所采用。由于分布式系统的异构性及分布式计算模式与计算任务的多样性,RPC作为网络通讯与委托计算的实现机制,在方法、协议、语义、实现上不断发展,种类繁多,其中SUN公司和开放软件基金会在其分布式产品中所建立和实用的RPC较为典型。
在SUN公司的网络文件系统NFS及开放网络计算环境ONC中,RPC是基本实现技术。OSF酝酿和发展的另一个重要的分布式计算软件环境DCE也是基于RPC的。在这两个系统中,RPC既是其自身的实现机制,又是提供给用户设计分布式应用程序的高级工具。由于对分布式计算的广泛需求,ONC和DCE成为Client/Server模式分布式计算环境的主流产品,而RPC也成为实现分布式计算的事实标准之一。
摘抄自:百度文库https://baike.baidu.com/item/%E8%BF%9C%E7%A8%8B%E8%BF%87%E7%A8%8B%E8%B0%83%E7%94%A8/7854346?fromtitle=RPC&fromid=609861&fr=aladdin
同时微服务的出现也进一步促进了RPC的发展,我们知道在微服务当道的今天。众多个微服务之间需要合作才能完成业务,例如“订单服务”需要调用“用户服务”的某个接口,这个场景就非常适合RPC(当然了用Http请求也是可以的)
上面我们已经介绍了RPC是什么,接下来我们讨论要实现一个RPC框架我们需要做什么。首先在RPC中有3个重要的角色分别是注册中心(Registry)、服务提供方(Provider)、服务消费方(Consumer),这里我们借用Dubbo官方的图来展示一下。(当然注册中心不一定是Zookeeper,也可以是Nacos、甚至是数据库)
从上图中我们可以看出,服务消费方从注册中心中获取到服务提供方的地址信息然后发送网络请求到调用服务提供方的方法。从整体来看过程还是比较简单的。
接下来我们分析一下实现一个PRC需要解决的问题:
答:可以将服务提供方的信息添加到“注册中心”,由消费者定期去取最新的信息
答:站在消费者的角度,一次PRC调用和本地调用看似并没有什么区别,那是因为框架在底层做了很多工作。消费者要调用服务提供方的方法必然要通过网络请求,提供方在收到请求之后执行本地方法并返回结果。所以,我们可以得出结论,既然要发送请求那么我们就需要知道服务提供方的地址信息(IP+端口),以及服务的提供方提供了多少个接口。
通常我们要调用一个接口会采用HTTP的形式,但是在RPC调用中,我们不会定义一个HTTP接口而是一个简单的本地方法,那么我们要如何发送请求呢?众所周知Http协议底层是基于TCP协议,那我们能不能模仿Http协议,拟定一套自己的协议呢?协议这个词看似非常高大上,其实说白了就是一种约定,双方以约定的格式进行交互。所以我们需要自己拟定一个协议
在Java中说到网络请求,八九不离十就是Netty了,一个高性能的基于事件驱动的NIO框架。
写代码切忌闭门造车,目前市面上已经有非常成熟的RPC框架,我们手写自己RPC框架的目的也是为了更好的学习思路,所以我们可以站在巨人的肩膀上,参看“巨人”是怎么做的。这里我们参考的是Dubbo。首先看下我们的项目结构
zrpc-demo:放一些demo案例,展示整个框架是如何使用的
zrpc-framewor:框架的核心代码
zrpc-manager:管理页面
(完整代码放在文章末尾)
1 - 零基础快速部署一个微服务应用
// 定义所有的服务
ServiceConfig<GreetingsService> service = new ServiceConfig<>();
service.setInterface(GreetingsService.class);
service.setRef(new GreetingsServiceImpl());
// 启动 Dubbo
DubboBootstrap.getInstance()
.application("first-dubbo-provider")
.registry(new RegistryConfig(ZOOKEEPER_ADDRESS))
.protocol(new ProtocolConfig("dubbo", -1))
.service(service)
.start();
那我们是不是可以模仿着写一个呢?
public class ProviderTest {
private static final String ZOOKEEPER_ADDRESS = "zk://127.0.0.1:2181";
public static void main(String[] args) {
// 定义所有的服务
ServiceConfig<GreetingsService> service = new ServiceConfig<>();
service.setInterface(GreetingsService.class);
service.setRef(new GreetingsServiceImpl());
// 启动 rpc
ZrpcBootstrap.getInstance().application("provider-demo")
.registry(new RegistryConfig("ZOOKEEPER_ADDRESS"))
.service(service)
.start();
}
}
引导类用于开启框架
public class ZrpcBootstrap {
private ZrpcBootstrap() {
}
//单例:饿汉式
private static ZrpcBootstrap zrpcBootstrap = new ZrpcBootstrap();
public static ZrpcBootstrap getInstance() {
return zrpcBootstrap;
}
/**
* 应用名称
*/
private String application;
/**
* 提供的服务
*/
private ServiceConfig<?> service;
/**
* 注册中心相关信息
*/
private RegistryConfig registryConfig;
/**
* 设置应用名称
*
* @param application 应用名称
* @return this
*/
public ZrpcBootstrap application(String application) {
this.application = application;
return this;
}
/**
* 添加要提供的服务
*
* @param service 服务
* @return this
*/
public ZrpcBootstrap service(ServiceConfig<?> service) {
this.service = service;
return this;
}
/**
* 添加注册中心信息
*
* @param registryConfig 注册中心相关信息
* @return this
*/
public ZrpcBootstrap registry(RegistryConfig registryConfig) {
this.registryConfig = registryConfig;
return this;
}
/**
* 开启框架:暂时这么写
*/
public void start() {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
该配置类用于定义要对外同的接口和实现类
public class ServiceConfig<T> {
private Class<T> interfaceClass;
private T reference;
public void setInterface(Class<T> interfaceClass) {
this.interfaceClass = interfaceClass;
}
public Class<T> getInterface() {
return this.interfaceClass;
}
public void setRef(T reference) {
this.reference = reference;
}
public T getRef() {
return this.reference;
}
}
该类用于配置注册中心,当然这里用class不太适合,因为注册中心有许多选择,所以之后把这里改成接口
public class RegistryConfig {
private String address;
public RegistryConfig(String address){
this.address = address;
}
}
至此我们已经有了一个框架的雏形,接下来要做的就是填充框架的代码,完善各种功能。
今天我们模仿着Dubbo初步搭建一个RPC框架,虽然功能都没还没实现,但是已经初见雏形,下一步我们会继续完善框架,希望对你有所帮助。