目录
1:启动(讲解融合spring,基于xml配置的启动),解析配置,校验
3:根据URL 创建注册中心长连接,consumer和provider的长连接以及具有集群容错,目录查找,路由功能的Invoker
4:将Invoker通过动态代理技术生成对应接口的动态代理类,后续的接口消费服务都是委托该动态代理类去调用
服务引用主流程详解:
1:启动(讲解融合spring,基于xml配置的启动),解析配置,校验
1.1 启动是spring 解析xml时,遇到duubo相关的标签时,使用dubbo 标签解析处理体系进行解析后生成对应的BeanDefination,随后交由spring容器实例化
涉及到文件和类如下:
spring.schemas //dubbo 包的META-INFO目录下
spring.handles //dubbo 包的META-INFO目录下
dubbo.xsd //dubbo 包的META-INFO目录下
DubboNamespaceHandler.java
DubboBeanDefinitionParser.java
2.2 ReferenceBean.getObject() 进行实例化(spring 的FactoryBean)
2:根据配置构建注册中心地址URL
2.1:构建注册中心URL
eg:registry://zk-ip:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-consumer-2.6&dubbo=2.0.2&pid=8480&qos.port=33333®istry=zookeeper×tamp=1611032783453
2.2:注册中心融合接口信息和监控中心信息的URL
eg:registry://zk-ip:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-consumer-2.6&dubbo=2.0.2&pid=8612&qos.port=33333&refer=application=demo-consumer-2.6&check=false&dubbo=2.0.2&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=8612&qos.port=33333®ister.ip=172.25.229.6&side=consumer×tamp=1611033392198®istry=zookeeper×tamp=1611033397239
3:根据URL 创建注册中心长连接,consumer和provider的长连接以及具有集群容错,目录查找,路由功能的Invoker
(通过地址中的protocol协议=registry,先获取到RegistryProtocol,在此类中在通过url中参数registry=zookeeper获取服务注册类ZookeeperRegistry)
大概步骤:
3.1:ZK建立长连接,并同时监听ZK状态的变化
3.2:构建consumer 的URL:registeredConsumerUrl
eg:consumer://consumer-ip/com.alibaba.dubbo.demo.DemoService?application=demo-consumer-2.6&category=consumers&check=false&dubbo=2.0.2&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=9976&qos.port=33333&side=consumer×tamp=1611033880440
3.3:创建接口对应的消费者ZK节点
eg:/dubbo/com.alibaba.dubbo.demo.DemoService/consumers (永久节点)
3.4:消费者registeredConsumerUrl添加上&category=providers,configurators,routers 创建监听永久节点(存在不进行创建);并监听节点的变更 (监听器为:目录查找服务RegistryDirectory,触发时直接从3.5->3.6);
3.4.1:在 registeredConsumerUrl 提取参数category 并转化成对应的category path
eg:/dubbo/com.alibaba.dubbo.demo.DemoService/providers;/dubbo/com.alibaba.dubbo.demo.DemoService/configurators;/dubbo/com.alibaba.dubbo.demo.DemoService/routers
3.4.2:创建对应category path 的节点(永久节点)并监听该节点下的数据变化(监听器:RegistryDirectory)
在category = providers 下时,即为监听该接口对应的providers 节点;会返回该接口的所有服务提供者
eg:dubbo://provider-ip:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider-2.6&bean.name=com.alibaba.dubbo.demo.DemoService&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=8236&side=provider×tamp=1611029027695
3.4.4:registeredConsumerUrl 将会根据根据category 转化成category 对应的URL(3个)
eg:
dubbo://provider-ip:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider-2.6&bean.name=com.alibaba.dubbo.demo.DemoService&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=8236&side=provider×tamp=1611029027695
empty://consumer-ip/com.alibaba.dubbo.demo.DemoService?application=demo-consumer-2.6&category=configurators&check=false&dubbo=2.0.2&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=9976&qos.port=33333&side=consumer×tamp=1611033880440
empty://consumer-ip/com.alibaba.dubbo.demo.DemoService?application=demo-consumer-2.6&category=routers&check=false&dubbo=2.0.2&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=9976&qos.port=33333&side=consumer×tamp=1611033880440
3.5 saveProperties(url);
//异步线程保存,当注册中心挂了,消费端可基于内存中缓存的服务提供者继续进行调用,但是如果消费端重启,则消费端可从该文件进行读取服务提供者信息,恢复到内存。
3.6 调用目录查找服务通知接口(RegistryDirectory.notify);
3.6.1:如果是configurators和routers的保存或则变更(ZK节点监听器触发),则直接更新目录查找服务的 configurators和routers
3.6.2:如果是provider的保存或则变更(ZK节点监听器触发) 则创建对应接口的Invoker
newUrlInvokerMap 具体封装过程:接口&服务提供者URL -> DubboInvoker(由DubboProtocol.refer创建,创建前会建立和服务端的长连接和心跳检测,具体分析见下文DubboProtocol.refer分析) -> ListenerInvokerWrapper -> buildInvokerChain -> InvokerDelegate
3.7 构建集群容错Invoker
Invoker invoker = cluster.join(directory); //MockClusterInvoker 包装类(集群容错Invoker,和Invokers目录)
4:将Invoker通过动态代理技术生成对应接口的动态代理类,后续的接口消费服务都是委托该动态代理类去调用
支持生成动态代理类的方式
(可通过debug获取动态代理类包名+名称;随后可通过阿里开源的arthas直接Dump该字节码下来反编译得到)
1:原生JDK动态代理技术(通过JDK一系类的校验反射生成)
如接口DemoService 生成的动态代理如下:
package com.sun.proxy;
import com.alibaba.dubbo.demo.DemoService;
import com.alibaba.dubbo.rpc.service.EchoService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy10 extends Proxy implements DemoService, EchoService //EchoService 回声测试,用于接口探活
{
private static Method m1;
private static Method m4;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy10(InvocationHandler paramInvocationHandler)
{
super(paramInvocationHandler);
//paramInvocationHandler 为 InvokerInvocationHandler 包装了MockClusterInvoker(通过上文讲解可知,其具备集群容错,目录查找,路由功能的Invoker)
}
public final Object $echo(Object paramObject)
{
try
{
return (Object)this.h.invoke(this, m4, new Object[] { paramObject });
//this.h.invoke 即调用MockClusterInvoker 的invoker
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String sayHello(String paramString)
{
try
{
return (String)this.h.invoke(this, m3, new Object[] { paramString });
//this.h.invoke 即调用MockClusterInvoker 的invoker
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
.......
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m4 = Class.forName("com.alibaba.dubbo.rpc.service.EchoService").getMethod("$echo", new Class[] { Class.forName("java.lang.Object") });
m3 = Class.forName("com.alibaba.dubbo.demo.DemoService").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
......
}
}
2:Javassit(默认方式)
//主要生成原理:通过字节码生成技术框架(ClassGenerator),直接生成动态代理类
如接口DemoService 生成的动态代理如下:
package com.alibaba.dubbo.common.bytecode;
import com.alibaba.dubbo.demo.DemoService;
import com.alibaba.dubbo.rpc.service.EchoService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class proxy0
implements ClassGenerator.DC, EchoService, DemoService
{
public static Method[] methods;
private InvocationHandler handler;
//为 InvokerInvocationHandler 包装了MockClusterInvoker(通过上文讲解可知,其具备集群容错,目录查找,路由功能的Invoker)
public String sayHello(String paramString)
{
Object[] arrayOfObject = new Object[1];
arrayOfObject[0] = paramString;
Object localObject = this.handler.invoke(this, methods[0], arrayOfObject);
return (String)localObject;
}
public Object $echo(Object paramObject)
{
Object[] arrayOfObject = new Object[1];
arrayOfObject[0] = paramObject;
Object localObject = this.handler.invoke(this, methods[1], arrayOfObject);
return (Object)localObject;
}
public proxy0() {}
public proxy0(InvocationHandler paramInvocationHandler)
{
this.handler = paramInvocationHandler;
}
}
所以无论是基于JDK还是Javassit 生成的消费接口的动态代理类,在进行方法调用的时候
都是直接执行到InvokerInvocationHandler 中的invoke方法,然后通过Invoker方法在执行MockClusterInvoker中的invoke方法
public class InvokerInvocationHandler implements InvocationHandler {
private final Invoker<?> invoker; //其具备集群容错,目录查找,路由功能的Invoker
public InvokerInvocationHandler(Invoker<?> handler) {
this.invoker = handler;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
if (method.getDeclaringClass() == Object.class) {
return method.invoke(invoker, args);
}
if ("toString".equals(methodName) && parameterTypes.length == 0) {
return invoker.toString();
}
if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
return invoker.hashCode();
}
if ("equals".equals(methodName) && parameterTypes.length == 1) {
return invoker.equals(args[0]);
}
return invoker.invoke(new RpcInvocation(method, args)).recreate();//接口代理类的方法执行,最后会执行到此处
//new RpcInvocation(method, args) 封装需要执行的接口类方法信息和方法参数,
//通过一系类的处理后,将方法调用信息和参数传递给服务提供者(后续在服务消费具体过程讲解)。
}
}
DubboProtocol.refer分析
1:创建消费侧与服务提供者长连接
默认使用netty框架,其他有mina 和 grizzly(测试阶段)
底层transport 开启连接主要过程(默认使用netty):
Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler)))
//NettyTransporter;主要设置编解码器,处理handler,开启连接长连接
步骤1:
protected void doOpen() throws Throwable { //Netty NIO 相关配置设置
final NettyClientHandler nettyClientHandler = new NettyClientHandler(getUrl(), this);
bootstrap = new Bootstrap();
bootstrap.group(nioEventLoopGroup)
//nioEventLoopGroup new NioEventLoopGroup(Constants.DEFAULT_IO_THREADS, new DefaultThreadFactory("NettyClientWorker", true));//DEFAULT_IO_THREADS=Math.min(Runtime.getRuntime().availableProcessors() + 1, 32)
.option(ChannelOption.SO_KEEPALIVE, true)//开启TCP探活机制
.option(ChannelOption.TCP_NODELAY, true)//禁用了Nagle算法;对于延时敏感型,同时数据传输量比较小的应用,开启TCP_NODELAY选项无疑是一个正确的选择
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
//.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getTimeout())
.channel(NioSocketChannel.class);
if (getConnectTimeout() < 3000) {
bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000);
} else {
bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getConnectTimeout());
}
bootstrap.handler(new ChannelInitializer() {
@Override
protected void initChannel(Channel ch) throws Exception {
NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyClient.this);
ch.pipeline()//.addLast("logging",new LoggingHandler(LogLevel.INFO))//for debug
.addLast("decoder", adapter.getDecoder()) //设置解码器
.addLast("encoder", adapter.getEncoder()) //设置编码器
.addLast("handler", nettyClientHandler); //设置对应的handler 处理
}
});
}
步骤2:
protected void connect() throws RemotingException {
connectLock.lock();
try {
if (isConnected()) { //检测连接channel是否在连接中
return;
}
initConnectStatusCheckCommand(); //channel 不在连接中,则拉起一个延时定时任务(延时时间2秒,每间隔2秒)进行重新connect()
doConnect(); //发起和服务端的长连接
..........
}
PS:
当接口不配置connections时,使用共享连接(消费IP->提供者IP 建立的一个长连接)
当接口配置connections时,连接不共享,基于具体服务的connections配置数量开启对应数量的长连接;如接口A配置connections=2,接口B配置connections=2 则建立长连接数量为4个
INFO transport.AbstractClient: [DUBBO] Successed connect to server /prover-ip:20880 from NettyClient comsumer-ip using dubbo version , channel is NettyChannel [channel=[id: 0x218c3a08 ....
INFO transport.AbstractClient: [DUBBO] Successed connect to server /prover-ip:20880 from NettyClient comsumer-ip using dubbo version , channel is NettyChannel [channel=[id: 0x8aad8fdb ....
INFO transport.AbstractClient: [DUBBO] Successed connect to server /prover-ip:20880 from NettyClient comsumer-ip using dubbo version , channel is NettyChannel [channel=[id: 0xec02a1a5 ....
INFO transport.AbstractClient: [DUBBO] Successed connect to server /prover-ip:20880 from NettyClient comsumer-ip using dubbo version , channel is NettyChannel [channel=[id: 0xaa716231 ....
2:建立消费端和服务端的心跳
在构建HeaderExchangeClient 时拉起心跳,主要作用是:检测一段时间内,如果没有业务数据传输,则会向服务端发起心跳,连续发送三次心跳后服务端都没有响应,则进行重新构建长连接
具体流程如下:
startHeartbeatTimer();//拉起心跳服务,每隔heartbeat时间(默认一分钟)执行一次心跳任务HeartBeatTask
HeartBeatTask 执行功能如下:
@Override
public void run() {
try {
long now = System.currentTimeMillis();
for (Channel channel : channelProvider.getChannels()) {
if (channel.isClosed()) {
continue;
}
try {
Long lastRead = (Long) channel.getAttribute(
HeaderExchangeHandler.KEY_READ_TIMESTAMP);
Long lastWrite = (Long) channel.getAttribute(
HeaderExchangeHandler.KEY_WRITE_TIMESTAMP);
if ((lastRead != null && now - lastRead > heartbeat)
|| (lastWrite != null && now - lastWrite > heartbeat)) { //上次读时间距离现在已超过一分钟 || 上次写距离现在已超一分钟 (heartbeat 默认1分钟)
Request req = new Request();
req.setVersion(Version.getProtocolVersion());
req.setTwoWay(true);
req.setEvent(Request.HEARTBEAT_EVENT);
channel.send(req); //进行心跳发送
if (logger.isDebugEnabled()) {
logger.debug("Send heartbeat to remote channel " + channel.getRemoteAddress()
+ ", cause: The channel has no data-transmission exceeds a heartbeat period: " + heartbeat + "ms");
}
}
if (lastRead != null && now - lastRead > heartbeatTimeout) { //上次读时间距离现在已超过3分钟 (heartbeat的三倍)
logger.warn("Close channel " + channel
+ ", because heartbeat read idle time out: " + heartbeatTimeout + "ms");
if (channel instanceof Client) { //默认使用的是channel=HeaderExchangeClient;
try {
((Client) channel).reconnect(); //NettyClient的重连接,先 disconnect();随后connect();
} catch (Exception e) {
//do nothing
}
} else {
channel.close();
}
..........
}
3:包装DubboInvoker
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);