自己手写实现Dubbo
dubbo 简单介绍
dubbo 是阿里巴巴开源的一款分布式rpc框架。
为什么手写实现一下bubbo?
很简单,最近从公司离职了,为了复习一下dubbo原理相关的知识,决定自己手写实现一个tony的dubbo,然后再结合dubbo的源码已达到复习的目的。
什么是RPC?
rpc 简单的说就是远程调用,以API的方式调用远程的服务器上的方法,像调本地方法一样!
创建一个api的包模块,供服务端和消费者端共同使用。
接口抽象
package com.nnk.rpc.api;
public interface HelloService {
/**
* 接口服务
* @param name
* @return
*/
String sayHello(String name);
}
服务端实现
服务端server端要实现这个接口。同时要发布这个接口,何谓发布这个接口?其实就是要像注册中心注册一下这个服务。这样,消费者在远程调用的时候可以通过注册中心注册的信息能够感知到服务。
服务的实现:
package com.nnk.rpc.server.provide;
import com.nnk.rpc.api.HelloService;
public class HelloServiceImpl implements HelloService {
public String sayHello(String name) {
System.out.println("hello," + name);
return "hello " + name;
}
}
服务端抽象:
package com.nnk.rpc.server.protocl;
/**
* 服务端server
*/
public interface RpcServer {
/**
* 开启服务 监听hostName:port
* @param hostName
* @param port
*/
public void start(String hostName,int port);
}
http协议的RPCServer实现
package com.nnk.rpc.server.protocl.http;
import com.nnk.rpc.server.protocl.RpcServer;
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;
public class HttpServer implements RpcServer {
public void start(String hostName,int port){
Tomcat tomcat = new Tomcat();
Server server = tomcat.getServer();
Service service = server.findService("Tomcat");
Connector connector = new Connector();
connector.setPort(port);
Engine engine = new StandardEngine();
engine.setDefaultHost(hostName);
Host host = new StandardHost();
host.setName(hostName);
//设置上下文
String contextPath="";
Context context = new StandardContext();
context.setPath(contextPath);
context.addLifecycleListener(new Tomcat.FixContextListener());
host.addChild(context);
engine.addChild(host);
service.setContainer(engine);
service.addConnector(connector);
//设置拦截servlet
tomcat.addServlet(contextPath,"dispather",new DispatcherServlet());
context.addServletMappingDecoded("/*","dispather");
try {
//启动tomcat
tomcat.start();
tomcat.getServer().await();
} catch (LifecycleException e) {
e.printStackTrace();
}
}
}
启动了tomcat并用到DispatcherServlet来拦截我们的请求。
package com.nnk.rpc.server.protocl.http;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 这个代码大家应该很熟悉吧,这个是sevlet的基本知识。
* 任何请求被进来都会被这个sevlet处理
*/
public class DispatcherServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//把所有的请求交给HttpHandler接口处理
new HttpHandler().handler(req,resp);
}
}
再看一下HttpHandler类:
package com.nnk.rpc.server.protocl.http;
import com.nnk.rpc.api.entity.Invocation;
import com.nnk.rpc.register.RegisterType;
import com.nnk.rpc.register.factory.LocalRegisterFactory;
import org.apache.commons.io.IOUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class HttpHandler {
public void handler(HttpServletRequest req, HttpServletResponse resp){
// 获取对象
try {
//从流里面获取数据
InputStream is = req.getInputStream();
ObjectInputStream objectInputStream = new ObjectInputStream(is);
//从流中读取数据反序列话成实体类。
Invocation invocation = (Invocation) objectInputStream.readObject();
//拿到服务的名字
String interfaceName = invocation.getInterfaceName();
//从注册中心里面拿到接口的实现类
Class interfaceImplClass = LocalRegisterFactory.getLocalRegister(RegisterType.LOCAL).get(interfaceName);
//获取类的方法
Method method = interfaceImplClass.getMethod(invocation.getMethodName(),invocation.getParamtypes());
//反射调用方法
String result = (String) method.invoke(interfaceImplClass.newInstance(),invocation.getObjects());
//把结果返回给调用者
IOUtils.write(result,resp.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
我们看看Invocation的实现:
package com.nnk.rpc.api.entity;
import java.io.Serializable;
public class Invocation implements Serializable {
private String interfaceName;
private String methodName;
private Class[] paramtypes;
private Object[] objects;
/**
*
* @param interfaceName 接口名字
* @param methodName 方法名字
* @param paramtypes 参数类型列表
* @param objects 参数列表
*/
public Invocation(String interfaceName, String methodName, Class[] paramtypes, Object[] objects) {
this.interfaceName = interfaceName;
this.methodName = methodName;
this.paramtypes = paramtypes;
this.objects = objects;
}
.... get set 方法省略掉
}
到这里服务端先告一段落下面实现一下注册中心
注册中心
接口抽象:
package com.nnk.rpc.register;
public interface LocalRegister {
/**
*
* @param interfaceName 接口名称
* @param interfaceImplClass 接口实现类
*/
void register(String interfaceName,Class interfaceImplClass);
/**
* 获取实现类
* @param interfaceName
* @return
*/
Class get(String interfaceName);
}
LocalRegister 这个主要是供服务端自己在反射调用的时候根据服务名称找到对应的实现。
package com.nnk.rpc.register;
import com.nnk.rpc.api.entity.URL;
public interface RemoteRegister {
/**
* 注册到远程注册中心
* @param interfaceName
* @param host
*/
void register(String interfaceName, URL host);
/**
* 根据服务名称获取调用者的地址信息
* @param interfaceName
* @return
*/
URL getRadomURL(String interfaceName);
}
这个主要是供消费者端根据服务名字找对应的地址发起远程调用用的。
我们分别来看看这两个接口的实现:
package com.nnk.rpc.register.local;
import com.nnk.rpc.register.LocalRegister;
import java.util.HashMap;
import java.util.Map;
public class LocalMapRegister implements LocalRegister {
private Map<String, Class> registerMap = new HashMap<String,Class>(1024);
public void register(String interfaceName, Class interfaceImplClass) {
registerMap.put(interfaceName,interfaceImplClass);
}
public Class get(String interfaceName) {
return registerMap.get(interfaceName);
}
}
很简单就是写在缓存里,map存储。
package com.nnk.rpc.register.local;
import com.nnk.rpc.api.entity.URL;
import com.nnk.rpc.register.RemoteRegister;
import java.io.*;
import java.util.*;
public class RemoterMapRegister implements RemoteRegister {
private Map<String, List<URL>> registerMap = new HashMap<String,List<URL>>(1024);
public static final String path = "/data/register";
public void register(String interfaceName, URL host) {
if(registerMap.containsKey(interfaceName)){
List<URL> list = registerMap.get(interfaceName);
list.add(host);
}else {
List<URL> list = new LinkedList<URL>();
list.add(host);
registerMap.put(interfaceName,list);
}
try {
saveFile(path,registerMap);
} catch (IOException e) {
e.printStackTrace();
}
}
public URL getRadomURL(String interfaceName) {
try {
registerMap = (Map<String, List<URL>>) readFile(path);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
List<URL> list = registerMap.get(interfaceName);
Random random = new Random();
int i = random.nextInt(list.size());
return list.get(i);
}
/**
* 写入文件
* @param path
* @param object
* @throws IOException
*/
private void saveFile(String path,Object object) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream(new File(path));
ObjectOutputStream objectOutputStream =new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(object);
}
/**
* 从文件中读取
* @param path
* @return
* @throws IOException
* @throws ClassNotFoundException
*/
private Object readFile(String path) throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream(new File(path));
ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
return inputStream.readObject();
}
}
这里为什么要写入文件呢?这是因为如果只存在内存中话,消费者和服务者不是同一个程序,消费者不额能感知到服务者程序内存的变化的。所以只能服务端写入文件,消费者从文件里取才能取得到。
dubbo注册中心怎么干的呢,dubbo只是把这些信息写到了zookeeper,或者redis.或者其他地方。
这里我就不再实现zookeeper的注册中心了。
接下来我们开启服务
package com.nnk.rpc.server.provide;
import com.nnk.rpc.api.HelloService;
import com.nnk.rpc.api.entity.URL;
import com.nnk.rpc.register.LocalRegister;
import com.nnk.rpc.register.RegisterType;
import com.nnk.rpc.register.RemoteRegister;
import com.nnk.rpc.register.factory.LocalRegisterFactory;
import com.nnk.rpc.register.factory.RemoteRegisterFactory;
import com.nnk.rpc.server.protocl.Protocl;
import com.nnk.rpc.server.protocl.ProtoclFactory;
import com.nnk.rpc.server.protocl.ProtoclType;
public class Provider {
public static void main(String[] args) {
URL url = new URL("localhost",8021);
//远程服务注册地址
RemoteRegister register = RemoteRegisterFactory.getRemoteRegister(RegisterType.ZOOKEEPER);
register.register(HelloService.class.getName(),url);
//本地注册服务的实现类
LocalRegister localRegister = LocalRegisterFactory.getLocalRegister(RegisterType.LOCAL);
localRegister.register(HelloService.class.getName(),HelloServiceImpl.class);
//这里我又封装了一层协议层,我们都知道dubbo有基于netty的dubbo协议,有基于http的http协议,还有基于redis的redis协议等等。
Protocl protocl = ProtoclFactory.getProtocl(ProtoclType.HTTP);
protocl.start(url);
}
}
消费者端:
消费者端其实很简单,就是根据注册中心里的信息远程调用对应服务器上的方法。
package com.nnk.rpc.client.comsummer;
import com.nnk.rpc.api.HelloService;
import com.nnk.rpc.client.proxy.ProxyFactory;
import com.nnk.rpc.register.RegisterType;
import com.nnk.rpc.server.protocl.ProtoclType;
public class Consumer {
public static void main(String[] args) {
HelloService helloService = ProxyFactory.getProxy(ProtoclType.HTTP, RegisterType.ZOOKEEPER,HelloService.class);
String result = helloService.sayHello("liuy");
System.out.println(result);
}
}
package com.nnk.rpc.client.proxy;
import com.nnk.rpc.api.entity.Invocation;
import com.nnk.rpc.api.entity.URL;
import com.nnk.rpc.register.RegisterType;
import com.nnk.rpc.register.RemoteRegister;
import com.nnk.rpc.register.factory.RemoteRegisterFactory;
import com.nnk.rpc.server.protocl.Protocl;
import com.nnk.rpc.server.protocl.ProtoclFactory;
import com.nnk.rpc.server.protocl.ProtoclType;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactory {
public static <T> T getProxy(final ProtoclType protoclType ,final RegisterType registerType, final Class interfaceClass){
return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Protocl protocl = ProtoclFactory.getProtocl(protoclType);
Invocation invocation = new Invocation(interfaceClass.getName(),method.getName(),method.getParameterTypes(),args);
RemoteRegister remoteRegister = RemoteRegisterFactory.getRemoteRegister(registerType);
URL radomURL = remoteRegister.getRadomURL(interfaceClass.getName());
System.out.println("调用地址host:"+ radomURL.getHost()+ ",port:"+radomURL.getPort());
return protocl.invokeProtocl(radomURL,invocation);
}
});
}
}
至此Dubbo的RPC调用核心框架就已经基本实现了。
涉及到的东西其实挺多的,有tomcat的知识(http协议实现),协议的序列化和反序列化(远程调用消息的传递),netty的知识(dubbo协议的实现),动态代理的知识(消费者端实现)。反射(远程调用的核心)。再深入点就是负载均衡算法(在远程获取服务者的地址时可以抽象)。
更完整的代码请去我的github上下载 Dubbo-tony
如果有什么不清楚的地方,欢迎大家留言,咱们可以一起交流讨论。
自己手写实现Dubbo的更多相关文章
- [年薪60W分水岭]基于Netty手写Apache Dubbo(带注册中心和注解)
阅读这篇文章之前,建议先阅读和这篇文章关联的内容. 1. 详细剖析分布式微服务架构下网络通信的底层实现原理(图解) 2. (年薪60W的技巧)工作了5年,你真的理解Netty以及为什么要用吗?(深度干 ...
- Atitit s2018.2 s2 doc list on home ntpc.docx \Atiitt uke制度体系 法律 法规 规章 条例 国王诏书.docx \Atiitt 手写文字识别 讯飞科大 语音云.docx \Atitit 代码托管与虚拟主机.docx \Atitit 企业文化 每日心灵 鸡汤 值班 发布.docx \Atitit 几大研发体系对比 Stage-Gat
Atitit s2018.2 s2 doc list on home ntpc.docx \Atiitt uke制度体系 法律 法规 规章 条例 国王诏书.docx \Atiitt 手写文字识别 ...
- 框架源码系列四:手写Spring-配置(为什么要提供配置的方法、选择什么样的配置方式、配置方式的工作过程是怎样的、分步骤一个一个的去分析和设计)
一.为什么要提供配置的方法 经过前面的手写Spring IOC.手写Spring DI.手写Spring AOP,我们知道要创建一个bean对象,需要用户先定义好bean,然后注册到bean工厂才能创 ...
- 透彻理解Spring事务设计思想之手写实现(山东数漫江湖)
前言 事务,是描述一组操作的抽象,比如对数据库的一组操作,要么全部成功,要么全部失败.事务具有4个特性:Atomicity(原子性),Consistency(一致性),Isolation(隔离性),D ...
- 带你手写基于 Spring 的可插拔式 RPC 框架(一)介绍
概述 首先这篇文章是要带大家来实现一个框架,听到框架大家可能会觉得非常高大上,其实这和我们平时写业务员代码没什么区别,但是框架是要给别人使用的,所以我们要换位思考,怎么才能让别人用着舒服,怎么样才能让 ...
- 手写RPC框架指北另送贴心注释代码一套
Angular8正式发布了,Java13再过几个月也要发布了,技术迭代这么快,框架的复杂度越来越大,但是原理是基本不变的.所以沉下心看清代码本质很重要,这次给大家带来的是手写RPC框架. 完整代码以及 ...
- 利用SpringBoot+Logback手写一个简单的链路追踪
目录 一.实现原理 二.代码实战 三.测试 最近线上排查问题时候,发现请求太多导致日志错综复杂,没办法把用户在一次或多次请求的日志关联在一起,所以就利用SpringBoot+Logback手写了一个简 ...
- Zookeeper——基本使用以及应用场景(手写实现分布式锁和rpc框架)
文章目录 Zookeeper的基本使用 Zookeeper单机部署 Zookeeper集群搭建 JavaAPI的使用 Zookeeper的应用场景 分布式锁的实现 独享锁 可重入锁 实现RPC框架 基 ...
- 看了这篇你就会手写RPC框架了
一.学习本文你能学到什么? RPC的概念及运作流程 RPC协议及RPC框架的概念 Netty的基本使用 Java序列化及反序列化技术 Zookeeper的基本使用(注册中心) 自定义注解实现特殊业务逻 ...
随机推荐
- Hadoop 3.1.3伪分布式环境安装Hive 3.1.2的异常总结
背景:hadoop版本为3.1.3, 且以伪分布式形式安装,hive版本为3.1.2,hive为hadoop的一个客户端. 1. 安装简要步骤 (1) 官网下载apache-hive-3.1.2-bi ...
- centos(linux)-jdk配置
1.清理系统默认自带的jdk 在安装centos时,可能系统会默认安装了例如openjdk等,需要先手动卸载 先执行:rpm -qa | grep jdk (查看已经自带的jdk): 卸载命名:sud ...
- Java编程思想(第一章 对象入门)总结
面向对象编程(oop) 1.1抽象的进步 所有编程语言的最终目的都是提供一种“抽象”方法. 难点是 在机器模型(位于“方案空间”)和实际解决问题模型(位于“问题空间”)之间,程序员必须建立起一种联 ...
- 《MIT 6.828 Lab 1 Exercise 10》实验报告
本实验的网站链接:MIT 6.828 Lab 1 Exercise 10. 题目 Exercise 10. To become familiar with the C calling conventi ...
- Junit测试类中如何调用Http通信
在使用Junit做测试的时候,有时候需要调用Http通信,无论是request还是response或者是session会话,那么在测试类里该如何调用呢,其实很简单,spring给我们提供了三个类 or ...
- VMWare虚拟机15.X局域网网络配置(修改网卡)
最近在搞几台虚拟机来学习分布式和大数据的相关技术,首先先要把虚拟机搞起来,搞起虚拟机第一步先安装系统,接着配置网络 vmware为我们提供了三种网络工作模式,它们分别是:Bridged(桥接模式).N ...
- Neo4j
Neo4j是一个高性能的,NOSQL图形数据库,它将结构化数据存储在网络上而不是表中.它是一个嵌入式的.基于磁盘的.具备完全的事务特性的Java持久化引擎,但是它将结构化数据存储在网络(从数学角度叫做 ...
- PHP学习之PHP编码习惯
命名的注意事项: 命名要有实际含义 命名风格保持一致 不用拼音命名 不用语言关键字 适当的使用注释 好的代码应该是自描述的 难以理解的地方加上注释 函数的功能加上注释说明 类的功能和使用方法加注释 多 ...
- LeetCode 2——两数相加(JAVA)
给出两个 非空 的链表用来表示两个非负的整数.其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字. 如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和 ...
- S02_CH13_ AXI_PWM 实验
S02_CH13_ AXI_PWM 实验 当学习了上一章的协议介绍内容后,开发基于这些协议的方案已经不是什么难事了,关键的一点就是从零到有的突破了.本章就以AXI-Lite总线实现8路LED自定义IP ...