dubbo介绍:
dubbo: Dubbo是一款高性能、轻量级的开源 Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
目的: 实现调用远程服务像调用本地服务一样,将调用过程进行封装。在消费者端只需要一个要调用服务的接口,不需要实现,dubbo对该接口进行动态代理。并且支持多种调用协议/服务器。
框架实现:
- 动态代理接口(核心):动态代理要调用服务的接口,在这里完成了对方法的远程调用,传递一个inovocation。
public static <T> T getProxy(final Class interfaceClass){
/*动态代理,代理接口*/
return (T)Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String mock = System.getProperty("mock");
if(mock != null && mock.startsWith("return:")){
return mock.replace("return",""); // 测试用,不调用远程服务
}
Invocation invocation = new Invocation(interfaceClass.getName(), method.getName(),method.getParameterTypes(), args);
List<URL> urlList = RemoteMapRegister.get(interfaceClass.getName()); // 从注册中心获取到对应的URLs(可能有多台服务器提供服务)
/*负载均衡*/
URL url = LoadBalance.random(urlList);
Protocol protocol = ProtocolFactory.getProtocol();
return protocol.send(url,invocation);
}
});
}
- invocation类(通信的信息载体):包含要调用的接口名,方法名,参数类型,参数。
public class Invocation implements Serializable {
/*调用接口名*/
private String interfaceName;
/*调用方法名*/
private String methodName;
/*参数类型*/
private Class[] paramType;
/*参数*/
private Object[] params;
- 服务器与通信协议接口(通信的方式):包含两个方法,启动服务器与发送消息
/**
* 将多个协议抽象为一个Protocol
*/
public interface Protocol {
/*启动服务器*/
void start(URL url);
/**
* 发送数据
* @param url 发送的url
* @param invocation 发送的数据,包装为要给invocation
* @return
*/
String send(URL url, Invocation invocation);
}
- 使用工厂模式来获取需要的Protocol:
public class ProtocolFactory {
public static Protocol getProtocol() throws ProtocolNotFoundException {
/*通过命令行参数来决定通信使用dubbo还是http*/
String name = System.getProperty("protocolName");
/*默认为http请求*/
if(name == null || name.equals("")) name = "http";
switch(name) {
case "http":
return new HttpProtocol();
case "dubbo":
return new DubboProtocol();
default:
throw new ProtocolNotFoundException("此协议暂不支持");
}
}
}
-
注册中心:
记录:<接口名,对应提供者的URI>
-
使用一个本地文件模拟
提供者注册时写入文件,消费者使用时读取文件即可
-
使用zookeeper存储
-
使用curator操作zookeeper:
client = CuratorFrameworkFactory.newClient("localhost:2181", new RetryNTimes(3, 1000)); client.start();
-
注册方法:
String result = client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(String.format("/dubbo/service/%s/%s", interfaceName, JSONObject.toJSONString(url)), null); System.out.println(result);
-
获取方法:
public static List<URL> get(String interfaceName) { List<URL> urlList = new ArrayList<>(); try { List<String> result = client.getChildren().forPath(String.format("/dubbo/service/%s", interfaceName)); for (String urlstr : result) { urlList.add(JSONObject.parseObject(urlstr, URL.class)); } REGISTER.put(interfaceName, urlList); } catch (Exception e) { e.printStackTrace(); } return urlList; }
-
-
执行流程:
提供者:
-
暴露服务
URL url = new URL("localhost",8080); // 这个url是自己封装的一个对象,只包括ip和端口 /* 将<对应接口,url>注册到远程(redis/zookeeper)*/ RemoteMapRegister.regist(IHelloService.class.getName(),url); /* 服务器本地注册:<接口,对应实现类> */ LocalRegister.regist(IHelloService.class.getName(), HelloServiceImpl.class);
-
开启服务器,接受请求
// 开启服务器,使用工厂获得服务器 Protocol protocol = ProtocolFactory.getProtocol(); protocol.start(url); // 开启服务器,并且将服务器阻塞接受请求
-
处理接受到的请求( 展示使用 tomcat & Http协议 )
// java 13:获取req中内容,将InputStream转换为Invocation对象 // Invocation invocation = JSONObject.parseObject(req.getInputStream(),Invocation.class); // java8:使用URL发送请求,获得InputStream,转换为ObjectInputStream,读取对象 InputStream inStream = req.getInputStream(); ObjectInputStream objectInputStream = new ObjectInputStream(inStream); Invocation invocation = (Invocation) objectInputStream.readObject(); String interfaceName = invocation.getInterfaceName(); /*通过接口获得对应实现类*/ Class implClass = LocalRegister.get(interfaceName); /*通过方法名和参数类型获得唯一的方法*/ Method method = implClass.getMethod(invocation.getMethodName(),invocation.getParamType()); /*反射机制method.invoke进行方法调用*/ String result = (String) method.invoke(implClass.newInstance(),invocation.getParams()); // 打印结果,并且将结果写入到输出流 System.out.println("tomcat:" + result); IOUtils.write(result,resp.getOutputStream());
消费者:
-
获取代理对象,使用代理对象直接调用方法
public static void main(String[] args) { IHelloService helloService = ProxyFactory.getProxy(IHelloService.class); // 获取要调用接口的代理对象 String alan = helloService.sayHello("alan"); // 直接调用方法,底层进行方法调用 System.out.println(alan); }
-
代理对象开启服务器,并且发送请求( 展示使用 tomcat & Http 协议 ):
// 使用JDK13的Java.net发送请求 // var request = HttpRequest.newBuilder() // .uri(new URI("http", null, hostname, port, "/", null, null)) // .POST(HttpRequest.BodyPublishers.ofString(JSONObject.toJSONString(invocation))) // .build(); // var client = java.net.http.HttpClient.newHttpClient(); // // HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); // // String result = response.body(); // 使用 JDK1.8的url进行发送网络请求 /*创建一个URL对象*/ URL url = new URL("http", hostname, port, "/"); /*建立连接*/ HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection(); /*设置连接参数*/ httpURLConnection.setRequestMethod("POST"); httpURLConnection.setDoOutput(true); /*输出流*/ OutputStream outputStream = httpURLConnection.getOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(outputStream); oos.writeObject(invocation); oos.flush(); oos.close(); /*发送请求,获得结果*/ InputStream inputStream = httpURLConnection.getInputStream(); String result = IOUtils.toString(inputStream); return result;
运行验证:
首先启动服务提供者,然后启动服务消费者:
-
提供者打印:
-
消费者打印:
这里测试的只是一个消费者与一个提供者。
当使用到提供者集群时,也是可以的,本框架消费端提供了负载均衡功能(最简单的 随机负载均衡 )
一个建议的dubbo框架就这样完成了,虽然简单,但是五脏俱全!