dubbo入门
DUBBO入门
Zookeeper准备
本文默认使用Zookeeper作为注册中心。
1)到Zookeeper官网下载,安装到本机。我这里安装在Windows上。
2)找到conf下的zoo_sample.cfg,复制一份修改为zoo.cfg,并在里面增加:
dataDir=D:\dev\zookeeper-3.4.12\data
dataLogDir=D:\dev\zookeeper-3.4.12\log
3)进入bin目录,启动Zookeeper。windows是运行zkServer.cmd,linux是运行zkServer.sh。注意不要关闭zkServer.cmd启动弹出的控制台窗口。
一、RPC原理
1.RPC原理简介
单体应用:本地服务调用
RMI:远程方法调用(Remote Method Invocation),让客户端Java虚拟机的对象像调用本地对象一样调用服务端Java虚拟机的对象的方法。
分布式应用:远程调用(Remote Procedure Call),HTTP,RPC
>RPC:远程过程调用,调用远程机器的服务,就像调用本地服务一样。WebService:基于HTTP的RPC,跨平台;dubbo:基于TCP的RPC框架,性能比基于HTTP的高。分布式应用的基础。RPC和RMI有相识之处。
2.调用过程
如图
(1)服务消费方client艺本地调用方式调用服务;
(2) client stub接收调用后将方法、参数编码;
(3)client stub 取到远程接口的请求地址,组建协议报文并发送;
(4)server stub 收到消息,并解码;
(5)server stub反射调用本地服务;
(6)本地服务执行,结果放回给server stub;
(7)server stub将消息编码发送给client;
(8)client stub接收消息,解码;
(9)服务消费方client获得结果。
stub:存根,简单理解为代理对象;应用透明,是指2-8被封装,对client而言调用远程方法就像调用本地方法,对本地client实际调用的是代理对象。dubbo,代理是Jdk动态代理;AOP,也有动态代理。服务,简单的理解为一个接口。
3.实现原理
1).服务端
服务接口interface:service。dubbo把interface打包发布,调用方、提供方公共引用。
接口实现interfaceImpl:serviceImpl。
服务注册与暴露export:Rpc.export(ip,service,port)。
2).客户端
服务接口interface:service。与server端公用。接口interface含义:通讯协议
服务引用:localService=Rpc.reference(service,ip,port);localService.callMethod();这里的service对象在本地而言是一个代理对象。
3).RPC原型
服务端:注册并暴露服务。客户端:取得调用地址,调用服务。
实现:
服务端:
export(service,ip,port){
//1.创建Socket服务器
server=ServerSocket(ip,port)
//2.监听Socket请求
server.accept()
//3.接收请求流,解码,反序列化为请求对象
input=decodeAndDeserialize(inputStream)
//4.反射调用本地服务
method=service.getClass().getMethod(methodName,paramTypes)
localResult=method.invoke(service,args)
//5.编码处理,回传client消息
output=encodeAndSerialize(localResult)
}
客户端:reference(clazz,ip,port){
//1.生成JDK动态代理
proxy=newProxyInstanc(clazz's ClassLoader,Class[]{clazz},InvocationHandler)
//2.invoke方法,发送请求和接收响应
InvocationHandler.invoke(proxy,method,args){
//3.创建Socket Client
client=Socekt(ip,port);
//4.编码,序列化,发送请求
output=encodeAndSerialize(methodName,paramTypes,args)
//5.读取服务端响应,解码,反序列化为结果对象
localResult=decodeAndDeserialize(remoteResult)
}
}
上面RPC原型可能存在的问题
服务发布与地址管理?增加注册中心,采用发布订阅模式,如Zookeeper。
序列化选择与效率?dubbo默认使用Hessian。
通信框架?简化Socket操作,dubbo默认使用netty。
负载均衡,分布式配置,容错机制??
二、dubbo服务暴露与调用过程
1.服务暴露
对每个<dubbo:service>创建一个ServiceBean(父类是ServiceConfig)。
如图:
1)获取注册中心url列表
2)创建协议url
3)远程暴露
实现:
class DubboExport{
private final List<Exporter<?>> exporters = new ArrayList<Exporter<?>>();
<br> /**
*1.获取注册中心url列表
* registry://10.211.55.5:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&client=curator&dubbo=2.0.0&pid=2956®istry=zookeeper×tamp=1507004600231
*/
<br> List<URL> registryURLs = loadRegistries();
/**
* 2.创建协议url
* dubbo://10.10.10.10:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=2956&side=provider×tamp=1507004625957
*/
URL url = new URL(name, host, port, path, map);
/**3.远程暴露*/
for (URL registryURL : registryURLs) {
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
Exporter<?> exporter = protocol.export(invoker);
exporters.add(exporter);
}}
远程暴露过程:
将ref封装为invoker
将invoker转换为exporter
>注册服务到zookeeper
订阅
返回新的exporter实例
查看方法:RegistryProtocol.export(final Invoker
2.服务引用
引用过程:
ReferenConfig:reference.xml。
远程引用服务配置:<dubbo:reference id="service" checke="false" interfacer="com.xxx.yyy.Service">
本地使用service会得到对应的远程服务代理对象
Service service = (Service) context.getBean("service"); // 获取远程服务代理
Object result = demoService.method(args); // 执行远程方法
获取代理简化流程:查看InvokerInvocationHandler.invoke方法
proxy0.xxxMethod()
-->InvokerInvocationHandler.invoke
-->MockClusterInvoker.invoke(Invocation invocation)
-->FailoverClusterInvoker.invoke(final Invocation invocation)
-->RegistryDirectory.list(Invocation invocation) //根据RpcInvocation中的methodName获取Invoker
-->router过滤
-->loadBalancer选取一个Invoker
-->执行filter链
-->DubboInvoker.invoke(Invocation inv)
3.Dubbo协议
Dubbo协议:是一种单连接、长连接、TCP、NIO、Hessian序列化的协议。
报文格式:
具体的编码解码实现可以参看:
消息头编码:ExchangeCodec.encodeRequest(Channel channel, ChannelBuffer buffer, Request req)
消息头解码:ExchangeCodec.decode(Channel channel, ChannelBuffer buffer, int readable, byte[] header)
request编码:DubboCodec.encodeRequestData(Channel channel, ObjectOutput out, Object data)
request解码:DecodeableRpcInvocation.decode(Channel channel, InputStream input)
response编码:DubboCodec.encodeResponseData(Channel channel, ObjectOutput out, Object data)
response解码:DecodeableRpcResult.decode(Channel channel, InputStream input)
4.泛化调用
含义:以GenericService代替所有远程接口引用
使用:
GenericService service = (GenericService) context.getBean("testGenericService");
//POJO参数需要转map
Object result = service.$invoke("doService", new String[]{String.class.getName()}, new Object[]{"aaa"});
String strResult = (String) result;
三、dubbo异步调用
默认情况dubbo的client端和server端请求和响应是同步的,除非指定异步。
流程:
使用:
applicationContext.xml
<dubbo:reference id="asyncService" interface="com.zafkiel.demo.api.itf.IAsyncService"
group="demo-server" version="1.0.0" check="false" async="true" timeout="60000"/>
或者
<dubbo:reference id="demoService" interface="com.zafkiel.demo.DemoService">
<dubbo:method name="sayHello" async="true" />
</dubbo:reference>
具体可以查看下面的实例
四、Demo
这里会简单介绍一个实例。
1.首先定义消费者和提供者共同使用的api
简单的接口IAsyncService.java
/**
- @Author: Zafkiel
- @Date: 2019/02/10
*/
public interface IAsyncService {
String sayHello(String name);
}
2.在提供者(服务端)提供api的实现
1).api的实现AsyncServiceImpl.java
/**
- @Author: Zafkiel
- @Date: 2019/02/10
*/
@Service(value = "asyncService")
public class AsyncServiceImpl implements IAsyncService {
@Override
public String sayHello(String s) {
System.out.println("远程服务asyncService处理启动时间" + System.currentTimeMillis());
System.out.println("IAsyncService接口实现");
System.out.println("等待6s");
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("远程服务asyncService处理结束时间" + System.currentTimeMillis());
return "hello," + s + "!";
}
}
2).server端配置
demo-context.xml:
<context:component-scan base-package="com.zafkiel.demo.server"/>
<!--Dubbo应用配置-->
<dubbo:application name="demo-server" owner="zafkiel" organization="com.zafkiel"/>
<dubbo:protocol name="dubbo" port="20880"/>
<dubbo:registry protocol="zookeeper" address="zookeeper://127.0.0.1:2181"/>
<dubbo:annotation package="com.zafkiel.demo.server"/>
<!--服务暴露-->
<dubbo:service ref="asyncService" interface="com.zafkiel.demo.api.itf.IAsyncService"
group="demo-server" version="1.0.0" />
3).在resources下面增加dubbo.properties:
dubbo.spring.config=classpath:demo-context.xml
指定applicationContext的配置文件。
4).启动Server:写一个Main执行dubbo的Main方法。
3.在消费者(客户端)调用提供者的服务
1).写一个简单的调用方法:
public class AsyncStarter {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:demo-context.xml");
context.start();
IAsyncService asyncService = (IAsyncService) context.getBean("asyncService");
Long start = System.currentTimeMillis();
System.out.println("业务线程启动时间:" + start);
//sayHello需要消耗6s
//这个结果并没有什么卵用
String initResult = asyncService.sayHello("Nero");
//活动Future
Future<String> stringFuture = RpcContext.getContext().getFuture();
//业务线程,当前线程睡眠5s
Thread.sleep(5000);
String finalResult = stringFuture.get();
System.out.println(finalResult);
Long end = System.currentTimeMillis();
System.out.println("业务线程结束时间:" + end);
System.out.println(end - start);
context.close();
}
}
2).client配置
demo-context.xml:
<context:component-scan base-package="com.zafkiel.demo.client"/>
<!--Dubbo应用配置-->
<dubbo:application name="demo-client" owner="zafkiel" organization="com.zafkiel"/>
<dubbo:protocol name="dubbo" port="20990"/>
<dubbo:registry protocol="zookeeper" address="zookeeper://127.0.0.1:2181"/>
<dubbo:annotation package="com.zafkiel.demo.client"/>
<!--服务引用-->
<!--对方法级别使用<dubbo:method>-->
<dubbo:reference id="asyncService" interface="com.zafkiel.demo.api.itf.IAsyncService"
group="demo-server" version="1.0.0" check="false" async="true" timeout="60000"/>
3).同样增加dubbo.properties
dubbo.spring.config=classpath:demo-context.xml
4).运行AsyncStarter
完整代码可以从我的云盘分享下载:
链接: https://pan.baidu.com/s/1n8BM8QXfaibPIJiFCVec2Q 提取码: 5nnc。
引用
1.dubbo源码解析
2.dubbo官网
3.Zookeeper官网