一、dubbo 的衍生
随着服务技术架构的发展,大部分公司都从单体架构发展到了分布式架构,而单体架构与分布式架构最大的区别就是分布式架构需要进行服务的远程通讯;然而随着服务的复杂度增大,我们对分布式架构的要求就不仅仅局限于远程通讯,传统的RPC框架已经无法满足现在的需求;我们需要对集群中每个节点的服务进行监控;并且需要对大规模的服务集群进行服务的注册和发现;还需要对大规模的客服端的访问进行请求的分发(负载均衡);当服务之间产生了网络异常,为了避免大规模的系统故障,还需要增加容错机制。现在很多的公司都有了自己研发的RPC框架,例如:阿里的dubbo\HSF,京东的JSF,当当的dubbox,蚂蚁金服的sofa,新浪的motan等等;
二、dubbo 的基本使用
可以参考官方文档http://dubbo.apache.org/zh/
dubbo架构中将服务分为 服务的提供者provider和消费者consumer,消费者消费提供者注册的服务,我们需要将调用的接口放到一个单独提取出来作为公共接口层API,使用zookeeper作为注册中心,创建一个provider模块,和一个consumer模块
1、API接口层:
package com.tealala.dubbo.api;
public interface DemoService {
String sayHello(String name);
}
2、启动zookeeper服务(我使用的是docker容器服务,我简单说下启动步骤)
1、pull zookeeper 镜像,docker pull zookeeper 就可以将最新的zookeeper容器镜像导入到本机
2、启动zookeeper镜像,生成对应的容器,将对应的端口映射出来,docker run -d --name zookeeper -p 2181:2181 --network mynet zookeeper
就简单的两个步骤就可以启动好zookeeper服务了;
3、provider模块
首先引入需要的jar包:
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>com.tealala.dubbo</groupId>
<artifactId>service-api</artifactId>
</dependency>
在resource/META-INF/spring目录下创建application.xml文件配置参数
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- provider's application name, used for tracing dependency relationship -->
<dubbo:application name="demo-provider"/>
<!-- use multicast registry center to export service -->
<dubbo:registry address="zookeeper://localhost:2181"/>
<!-- use dubbo protocol to export service on port 20880 -->
<dubbo:protocol name="dubbo" port="20880"/>
<!-- service implementation, as same as regular local bean -->
<bean id="demoService" class="com.tealala.dubbo.service.DemoServiceImpl"/>
<!-- declare the service interface to be exported -->
<dubbo:service interface="com.tealala.dubbo.api.DemoService" ref="demoService" protocol="dubbo"/>
</beans>
实现接口
package com.tealala.dubbo.service;
import com.tealala.dubbo.api.DemoService;
public class DemoServiceImpl implements DemoService {
@Override
public String sayHello(String name) {
System.out.println(name);
return "hello";
}
}
启动类
package com.tealala.dubbo;
import org.apache.dubbo.container.Main;
import java.io.IOException;
public class Provider {
public static void main(String[] args) throws IOException {
Main.main(new String[]{"spring","log4j"});
}
}
查看服务是否已经注册成功,这里我使用的是ZooInspector 图形工具来查看的 在build包下面启动 zookeeper-dev-ZooInspector.jar,连接到zookeeper服务器,localhost:2181
4、consumer模块
同样引入jar包依赖
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
</dependency>
<dependency>
<groupId>com.tealala.dubbo</groupId>
<artifactId>service-api</artifactId>
</dependency>
同样在resource/META-INF/spring下创建application.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- consumer's application name, used for tracing dependency relationship (not a matching criterion),
don't set it same as provider -->
<dubbo:application name="demo-consumer"/>
<!-- use multicast registry center to discover service -->
<dubbo:registry address="zookeeper://localhost:2181"/>
<dubbo:protocol name="dubbo" port="20880"/>
<!-- generate proxy for the remote service, then demoService can be used in the same way as the
local regular interface -->
<dubbo:reference id="demoService" check="false" interface="com.tealala.dubbo.api.DemoService"/>
</beans>
启动服务
package com.tealala.dubbo.consumer;
import com.tealala.dubbo.api.DemoService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Consumer {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"META-INF/spring/consumer.xml"});
context.start();
// Obtaining a remote service proxy
DemoService demoService = (DemoService)context.getBean("demoService");
// Executing remote methods
String hello = demoService.sayHello("world");
// Display the call result
System.out.println(hello);
}
}
通过上面的示例,进行了dubbo服务注册,发现,调用;然而服务又是如何发布,注册到zookeeper中的,消费端又是如何调用注册到zookeeper中的服务,就需要自行查看源码进行分析;
以下是我进行源码分析时画的简易时序图,(有误请指正)
cachedAdaptiveInstance:用来存储ExtensionFactory的扩展类:AdaptiveExtensionFactory
cachedClasses ::需要查看在/META-INF/目录下配置文件中配置的,加载对应的type类型的类路径,并保存到cachedClasses这个Holder中
问题1:在loadExtensionClasses()进行类扫描的时候,是如何,在哪里加载class org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper的
回答:在ExtensionLoader的方法loadExtensionClasses()中,分解析传入进来的接口类,例如:org.apache.dubbo.rpc.Protocol类是需要加载的类,并且扫描配置文件/Library/repository/org/apache/dubbo/dubbo/2.7.2/dubbo-2.7.2.jar!/META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol 在该配置文件中配置类Protocol的包装类和实现类,并根据类别的不同存放到不同的内存中,例如: Adaptive标注的类就存放到Class<?> cachedAdaptiveClass中,包装器类型的就存放到Set<Class<?>> cachedWrapperClasses中。
问题2:/Library/repository/org/apache/dubbo/dubbo/2.7.2/dubbo-2.7.2.jar!/META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol文件下配置类很多,但是默认还是8个: 猜想:没有装载类路径存到extensionClasses中是因为它的实现类是:AbstractProxyProtocol,而将类路径存放到extensionClasses中的是实现类AbstractProtocol或者是Protocol;
回答:在进行类扫描的时候loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL)中使用方法Class.forName(line, true, classLoader),无法通过类加载器Launcher#AppClassLoader来进行类加载,抛出异常,无法再将部分数据存放到内存中。
EXTENSION_INSTANCES:用来存放每一个初始化之后到extension对象例如:SpringExtensionFactory/SpiExtensionFactory,或者是自定义的类
AdaptiveExtensionFactory:加载ExtensionFactory的实现类:SpringExtensionFactory/SpiExtensionFactory
问题3:为什么自定义实现的Protocol使用三种装饰器类(ProtocolListenerWrapper, ProtocolFilterWrapper, QosProtocolWrapper)来进行包装, 而默认的DubboProtocol使用的是Protocol$Adaptive来进行代理,Compiler接口使用的是AdaptiveCompiler来实现;
回答:像问题1中我们已经知道类在进行类扫描的时候,已经将该接口的装饰器类型存放到类内存中,在调用getExtension(“实现类名称”)方法的时候,在方法createExtension(String name)的最后
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for(Iterator var5 = wrapperClasses.iterator(); var5.hasNext(); instance = this.injectExtension(wrapperClass.getConstructor(this.type).newInstance(instance))) {
wrapperClass = (Class)var5.next();
}
}
将保存到内存中的装饰器类层层将需要初始化的类进行类包装;
如果我们不自己实现接口,系统会默认返回一个扩展类实现,该扩展类在接口中就已经指定了:
private Class<?> createAdaptiveExtensionClass() {
String code = (new AdaptiveClassCodeGenerator(this.type, this.cachedDefaultName)).generate();
ClassLoader classLoader = findClassLoader();
Compiler compiler = (Compiler)getExtensionLoader(Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}
将code拷贝出来,系统默认生成的扩展类如下:(package的路径自己修改了一下)
package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
org.apache.dubbo.common.URL url = arg0.getUrl();
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
if (arg1 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg1;
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
public void destroy() {
throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
public int getDefaultPort() {
throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
}
public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws java.lang.reflect.InvocationTargetException {
com.tealala.springboot.dubbo.api.IHelloService w;
try {
w = ((com.tealala.springboot.dubbo.api.IHelloService) $1);
} catch (Throwable e) {
throw new IllegalArgumentException(e);
}
try {
if ("sayHello".equals($2) && $3.length == 1) {
return ($w) w.sayHello((java.lang.String) $4[0]);
}
} catch (Throwable e) {
throw new java.lang.reflect.InvocationTargetException(e);
}
throw new org.apache.dubbo.common.bytecode.NoSuchMethodException("Not found method \"" + $2 + "\" in class com.tealala.springboot.dubbo.api.IHelloService.");
}
public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws java.lang.reflect.InvocationTargetException {
com.tealala.springboot.dubbo.provider.HelloServiceImpl w;
try {
w = ((com.tealala.springboot.dubbo.provider.HelloServiceImpl) $1);
} catch (Throwable e) {
throw new IllegalArgumentException(e);
}
try {
if ("sayHello".equals($2) && $3.length == 1) {
return ($w) w.sayHello((java.lang.String) $4[0]);
}
} catch (Throwable e) {
throw new java.lang.reflect.InvocationTargetException(e);
}
throw new org.apache.dubbo.common.bytecode.NoSuchMethodException("Not found method \"" + $2 + "\" in class com.tealala.springboot.dubbo.provider.HelloServiceImpl.");
}