目錄
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
如果有什麼不清楚的地方,歡迎大家留言,咱們可以一起交流討論。