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的更多相关文章

  1. [年薪60W分水岭]基于Netty手写Apache Dubbo(带注册中心和注解)

    阅读这篇文章之前,建议先阅读和这篇文章关联的内容. 1. 详细剖析分布式微服务架构下网络通信的底层实现原理(图解) 2. (年薪60W的技巧)工作了5年,你真的理解Netty以及为什么要用吗?(深度干 ...

  2. 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 手写文字识别   ...

  3. 框架源码系列四:手写Spring-配置(为什么要提供配置的方法、选择什么样的配置方式、配置方式的工作过程是怎样的、分步骤一个一个的去分析和设计)

    一.为什么要提供配置的方法 经过前面的手写Spring IOC.手写Spring DI.手写Spring AOP,我们知道要创建一个bean对象,需要用户先定义好bean,然后注册到bean工厂才能创 ...

  4. 透彻理解Spring事务设计思想之手写实现(山东数漫江湖)

    前言 事务,是描述一组操作的抽象,比如对数据库的一组操作,要么全部成功,要么全部失败.事务具有4个特性:Atomicity(原子性),Consistency(一致性),Isolation(隔离性),D ...

  5. 带你手写基于 Spring 的可插拔式 RPC 框架(一)介绍

    概述 首先这篇文章是要带大家来实现一个框架,听到框架大家可能会觉得非常高大上,其实这和我们平时写业务员代码没什么区别,但是框架是要给别人使用的,所以我们要换位思考,怎么才能让别人用着舒服,怎么样才能让 ...

  6. 手写RPC框架指北另送贴心注释代码一套

    Angular8正式发布了,Java13再过几个月也要发布了,技术迭代这么快,框架的复杂度越来越大,但是原理是基本不变的.所以沉下心看清代码本质很重要,这次给大家带来的是手写RPC框架. 完整代码以及 ...

  7. 利用SpringBoot+Logback手写一个简单的链路追踪

    目录 一.实现原理 二.代码实战 三.测试 最近线上排查问题时候,发现请求太多导致日志错综复杂,没办法把用户在一次或多次请求的日志关联在一起,所以就利用SpringBoot+Logback手写了一个简 ...

  8. Zookeeper——基本使用以及应用场景(手写实现分布式锁和rpc框架)

    文章目录 Zookeeper的基本使用 Zookeeper单机部署 Zookeeper集群搭建 JavaAPI的使用 Zookeeper的应用场景 分布式锁的实现 独享锁 可重入锁 实现RPC框架 基 ...

  9. 看了这篇你就会手写RPC框架了

    一.学习本文你能学到什么? RPC的概念及运作流程 RPC协议及RPC框架的概念 Netty的基本使用 Java序列化及反序列化技术 Zookeeper的基本使用(注册中心) 自定义注解实现特殊业务逻 ...

随机推荐

  1. Python3 Selenium自动化web测试 ==> 第十一节 WebDriver高级应用 -- 显示等待 + 二次封装

    学习目的: 掌握显示等待 掌握二次封装 正式步骤: step1:显示等待的代码示例 # -*- coding:utf-8 -*- from selenium import webdriver from ...

  2. Spark + sbt + IDEA + HelloWorld + MacOS

    构建项目步骤 首先要安装好scala.sbt.spark,并且要知道对应的版本 sbt版本可以在sbt命令行中使用sbtVersion查看 spark-shell可以知晓机器上spark以及对应的sc ...

  3. Class WriteGroupAttribute

    [WriteGroup]可以排除在创建已声明写入同一组件的查询时未知的组件. 这允许安全地扩展组件系统而无需编辑先前存在的系统. 目标是为期望将数据从一组组件(输入)转换为另一组(输出[s])的系统能 ...

  4. 关于macOS上常用操作命令(持续更新)

    1.mac上显示/隐藏Finder中的隐藏文件: 显示隐藏文件:在终端中输代码“defaults write com.apple.finder AppleShowAllFiles -boolean t ...

  5. Windows下的3389端口渗透

    1.Win7.Win2003.XP系统 在CMD命令行开启3389端口:REG ADD HKLM\SYSTEM\CurrentControlSet\Control\Terminal" &qu ...

  6. tensorflow搭建神经网络

    最简单的神经网络 import tensorflow as tf import numpy as np import matplotlib.pyplot as plt date = np.linspa ...

  7. Hogan.js的使用

    一个比较简单的前端模板引擎,参考一下两篇文件即可学会:https://www.cnblogs.com/zhangruiqi/p/8547268.htmlhttps://www.imooc.com/ar ...

  8. bootstrap基础学习【表单含按钮】(二)

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  9. poj1915(双向bfs)

    题目链接:https://vjudge.net/problem/POJ-1915 题意:求棋盘上起点到终点最少的步数. 思路:双向广搜模板题,但玄学的是我的代码G++会wa,C++过了,没找到原因QA ...

  10. [转帖]Linux日期和时间的那些事儿

    Linux日期和时间的那些事儿 http://embeddedlinux.org.cn/emb-linux/entry-level/201311/09-2672.html 自己还是稚嫩啊.. 除了年龄 ...