第四篇 - 手写RPC框架
Github源码下载地址:https://github.com/chenxingxing6/myrpc
一、前言
RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外的为这个交互作用编程,如果涉及的软件采用面向对象编程(java),那么远程过程调用亦可称作远程调用或远程方法调用。只要支持网络传输的协议就是RPC协议,RPC是一种框架。简单的说就是远程调用,以API的方式调用远程的服务器上的方法,像调本地方法一样!
RPC要解决的两个问题
1.解决分布式系统中,服务之间的调用问题。
2.远程调用时,要能够像本地调用一样方便,让调用者感知不到远程调用的逻辑。
完整RPC调用过程:
以左边的Client端为例,Application就是rpc的调用方,Client Stub就是我们上面说到的代理对象,其实内部是通过rpc方
式来进行远程调用的代理对象,至于Client Run-time Library,则是实现远程调用的工具包,比如jdk的Socket,最后通过底
层网络实现实现数据的传输。
这个过程中最重要的就是序列化和反序列化了,因为数据传输的数据包必须是二进制的,你直接丢一个Java对象过去,人家可不
认识,你必须把Java对象序列化为二进制格式,传给Server端,Server端接收到之后,再反序列化为Java对象。
二、项目结构
要实现一个RPC不算难,难的是实现一个高性能高可靠的RPC框架。
实现功能
1.支持多种协议传输socket,http,dubbo
2.支持多种注册方式file,redis,zookeepre
3.可以动态切换协议和注册方式,只需要更改配置文件
4.Netty实现 (更新2019.10.1)
5.注解方式发布服务@Rpc (更新2019.10.1)
服务端:提供API,启动的时候要注册服务
消费端:从注册中心获取服务,调用子服务
注册中心:保存服务配置
RPC协议:基于Tomcat的HttpProtocol,基于Netty的DubboProtocol,Socket
注册中心:本地文件,Redis,Zookeeper方式进行注册
三、核心代码
如果要补充协议,只需要实现IProtocolClient,IProtocolService接口
package com.mydubbo.rpc.protocol;import com.mydubbo.rpc.framework.Invocation;
import com.mydubbo.rpc.framework.URL;/*** User: lanxinghua* Date: 2019/9/30 17:24* Desc:*/
public interface IProtocolClient {/*** 客户端发送请求* @param url* @param invocation* @return*/public Object send(URL url, Invocation invocation);
}
package com.mydubbo.rpc.protocol;import com.mydubbo.registry.AbstractRegistryDiscovery;
import com.mydubbo.rpc.framework.URL;/*** User: lanxinghua* Date: 2019/9/30 17:24* Desc:*/
public interface IProtocolServer {/*** 启动服务* @param url* @param charset* @param registryDiscovery*/public void start(URL url, String charset, AbstractRegistryDiscovery registryDiscovery);
}
如果要实现多种注册中心,只需继承AbstractRegistryDiscovery
package com.mydubbo.registry;import com.mydubbo.rpc.framework.URL;import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;/*** User: lanxinghua* Date: 2019/9/30 16:09* Desc: 服务注册,发现接口*/
public abstract class AbstractRegistryDiscovery {protected static Map<String/*接口名*/, Map<URL, Class>> REGISTER = new HashMap<String, Map<URL, Class>>();/*** 服务注册* @param url* @param interfaceName* @param implClass*/public void register(URL url, String interfaceName, Class implClass){Map<URL, Class> map = new HashMap<URL, Class>();map.put(url, implClass);REGISTER.put(interfaceName, map);save();}/*** 服务发现* @param url* @param interfaceName* @return*/public Class discovery(final URL url, String interfaceName){REGISTER = get();return Optional.ofNullable(REGISTER.get(interfaceName)).map(r -> r.get(url)).orElseThrow(() -> new RuntimeException("service not found!"));}/*** 负载均衡,获取可用服务地址* @param interfaceName* @return*/public URL randomServer(String interfaceName){REGISTER = get();Set<URL> urls = Optional.ofNullable(REGISTER.get(interfaceName)).map(r -> r.keySet()).orElseThrow(() -> new RuntimeException("service not found!"));if (urls == null || urls.isEmpty()){throw new RuntimeException("service not found!");}// 这里就返回第一个return urls.iterator().next();}/*** 保存到注册中心*/public abstract void save();/*** 从注册中心获取*/public abstract Map<String, Map<URL, Class>> get();
}
四、内置tomca
<Server port="8005" shutdown="SHUTDOWN"><Service name="Catalina"><Connector port="8080" protocol="HTTP/1.1"connectionTimeout="20000"redirectPort="8443" URIEncoding="UTF-8"/><Engine name="Catalina" defaultHost="localhost"><Host name="localhost" appBase="webapps"unpackWARs="true" autoDeploy="true"><Context path="" doBase="WORKDIR" reloadable="true"/></Host></Engine></Service>
</Server>
package com.mydubbo.rpc.protocol.http;import com.mydubbo.registry.AbstractRegistryDiscovery;
import com.mydubbo.rpc.framework.URL;
import com.mydubbo.rpc.protocol.IProtocolServer;
import org.apache.catalina.*;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.startup.Tomcat;/*** User: lanxinghua* Date: 2019/9/30 11:59* Desc:*/
public class HttpServer implements IProtocolServer {private static final String TOMCAT = "Tomcat";@Overridepublic void start(URL url, String charset, AbstractRegistryDiscovery registryDiscovery){// 实例一个TomcatTomcat tomcat = new Tomcat();// 构建ServerServer server = tomcat.getServer();// 获取ServiceService service = server.findService(TOMCAT);// 构建ConnectorConnector connector = new Connector();connector.setPort(url.getPort());connector.setURIEncoding(charset);// 构建引擎Engine engine = new StandardEngine();engine.setDefaultHost(url.getHostName());// 构建HostHost host = new StandardHost();host.setName(url.getHostName());// 构建ContextString contextPath = "";Context context = new StandardContext();context.setPath(contextPath);context.addLifecycleListener(new Tomcat.FixContextListener());// 按照server.xml进行配置host.addChild(context);engine.addChild(host);service.setContainer(engine);service.addConnector(connector);// tomcat是一个servlet,设置路径与映射String servletName = "dispatcher";tomcat.addServlet(contextPath, servletName, new DispatcherServlet(url, registryDiscovery));context.addServletMappingDecoded("/client/*", servletName);// 启动服务,接受请求try {System.out.println("Tomcat..服务启动成功.....");tomcat.start();tomcat.getServer().await();}catch (Exception e){e.printStackTrace();}}
}
五、注册中心
1.本地文件
2.zk
3.redis
六、测试
## 协议配置信息
## 支持socket,http,dubbo
config.protocol.name=socket
config.protocol.host=localhost
config.protocol.port=8080
config.protocol.charset=UTF-8## 服务注册 支持localfile,redis,zookeeper
service.registry.type=redis
#service.registry.type=localfile
#service.registry.type=zookeeper
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><context:property-placeholder location="classpath:spring.properties"/><!-- 协议配置信息 --><bean id="protocolConfig" class="com.mydubbo.config.ProtocolConfig"><constructor-arg name="name" value="${config.protocol.name}"/><constructor-arg name="host" value="${config.protocol.host}"/><constructor-arg name="port" value="${config.protocol.port}"/><constructor-arg name="charset" value="${config.protocol.charset}"/></bean><!-- 服务注册方式 --><bean id="registryDiscoveryFactory" class="com.mydubbo.registry.RegistryDiscoveryFactory"><constructor-arg name="registryType" value="${service.registry.type}"/></bean><bean id="rpcClient" class="com.mydubbo.rpc.RpcClient"><constructor-arg name="config" ref="protocolConfig"/><constructor-arg name="registryDiscoveryFactory" ref="registryDiscoveryFactory"/></bean><bean id="rpcServer" class="com.mydubbo.rpc.RpcServer"><constructor-arg name="config" ref="protocolConfig"/><constructor-arg name="registryDiscoveryFactory" ref="registryDiscoveryFactory"/></bean>
</beans>
package com.demo.consumer;import com.demo.provider.api.IHelloService;
import com.mydubbo.rpc.RpcClient;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;/*** User: lanxinghua* Date: 2019/9/30 11:56* Desc: 服务启动*/
public class ConsumerStart {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");RpcClient rpcClient = (RpcClient) context.getBean("rpcClient");IHelloService helloService = rpcClient.getProxy(IHelloService.class);System.out.println(helloService.sayHello("lanxinghua"));System.out.println(helloService.getUser());System.out.println(helloService.saveUser(helloService.getUser()));}
}
package com.demo.provider;import com.demo.provider.api.IHelloService;
import com.demo.provider.impl.HelloService;
import com.mydubbo.rpc.RpcServer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;/*** User: lanxinghua* Date: 2019/9/30 11:55* Desc: 服务提供*/
public class ProviderStart {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");RpcServer rpcServer = (RpcServer) context.getBean("rpcServer");// 服务注册rpcServer.register(IHelloService.class.getName(), HelloService.class);// 启动服务rpcServer.start();}
}
注解方式发布服务
package com.demo.provider.impl;import com.demo.provider.api.ILogService;
import com.mydubbo.config.Rpc;/*** @Author: cxx* @Date: 2019/10/1 20:03* 通过注解方式发布dubbo服务*/
@Rpc
public class LogService implements ILogService {@Overridepublic void log(String msg) {System.out.println("【日志】" + msg);}
}
总结
希望路过的童鞋们,自己手写RPC后,可以更加清楚知道服务远程间是怎么调用的,后期还要许多东西需要完善,比如:负载均衡,熔断机制,Mock数据等等…
第四篇 - 手写RPC框架相关推荐
- 手写篇:如何手写RPC框架?
手写篇:如何手写RPC框架? 首先我们讲下什么是RPC? RPC(Remote Procedure Call)远程过程调用协议,他是一种通过网络从远程计算机程序请求服务.简单的来说,就是通过网络进行远 ...
- MyRPCDemo netty+jdk动态代理+反射+序列化,反序列化手写rpc框架
RPC RPC(remote procedure call)远程过程调用 RPC是为了在分布式应用中,两台主机的Java进程进行通信,当A主机调用B主机的方法时,过程简洁,就像是调用自己进程里的方法一 ...
- 第一篇 - 手写SpringMvc框架
Github源码下载地址:https://github.com/chenxingxing6/springmvc CSDN源码下载地址:https://download.csdn.net/downloa ...
- 第02篇:手写JavaRPC框架之设计思路
作者: 西魏陶渊明 博客: https://blog.springlearn.cn/ 天下代码一大抄, 抄来抄去有提高, 看你会抄不会抄! 一.前言 隔壁老李又在喷我了: "完犊子了,小编这 ...
- 手写RPC框架(六)
v1.4 小更新 更新事项 暂定目标对启动类进行修改 直接集合 这个就直接看代码吧 不是特别难 难的地方我会点出来 启动引导类直接进行修改 可以传参 可以这样 当然 我想到了可以注解传参 注解构造 注 ...
- 手写RPC框架(十六)
v2.7 更新:实现CGLIB动态代理 实现CGLIB动态代理 实现一下统一调用代理类,创建总调用类,和对应模板接口,调用注解,同时在每个consumerbootstrap进行修改 对应模板接口 pa ...
- 手写RPC框架(五)
v1.3 (启动器依旧使用1.2 1.3版本在启动服务版本上尚未做出大变动 主要是增加了方便学习的功能) 更新事项 以下更新均在非阻塞模块进行更新,阻塞模块可供读者自己尝试 使用注解方式 改造一下启动 ...
- 手写RPC框架(八)
v1.6 热补丁,nio目前来看最后的完善,使用Curator简化zookeeper的操作,优化调用体验 使用Curator创建服务注册和服务发现类(是看快速开始速成的) 服务注册类实现代码 pack ...
- Marco's Java【Dubbo 之手写Dubbo框架实现远程调用】
前言 关于Dubbo入门的网上教程也特别多,因此我没有专门出关于Dubbo的系列博文(主要呢- 也是在忙些工作上的事儿),用Dubbo特别简单,但是想要把Dubbo学好,学精还得花费不少时间的,特别是 ...
最新文章
- Python时间转换函数:时间转化为时间戳、时间戳转化为时间、当前日期、当前时间、星期几、前面或者后面多少天、年、月、日等
- OS- -调度(一)
- Linux 下禅道和 SVN、GIT 集成插件发布
- 干货 | 双目摄像头实现手势识别,完美还原人体运动手势
- 垃圾代码还能出圈?手把手教你写垃圾代码,从入门到精通!
- java多线程中的死锁、活锁、饥饿、无锁都是什么鬼?
- 教你开发Jquery插件-Jquery插件开发教程
- wamp无法访问php,wamp无法访问phpmyadmin怎么办
- POJ NOI MATH-7647 余数相同问题
- 本地离线语音识别芯片厂家盘点,哪一家实力更强
- matlab傅里叶光学仿真,关于微透镜阵列的傅里叶光学分析
- 如何合理的拆分微服务
- 暖风熏的游人醉 直把杭州作汴州 题临安邸--林升
- php 2038,php处理大于2038年以后日期的一种方法
- Ubuntu下安装 rust和urdf-viz
- halcon改变图像大小
- 3D建模一个月的真实收入,当作副业在家就能月入过万?
- 阿姆斯特朗数python
- 回归和分类模型性能评估指标MSE,MAE,PR,ROC,AUC
- 头皮发麻之win10宽带拨号错误797