Dubbo源码分析 (二)之服务引用

目录

服务引用主流程详解:

1:启动(讲解融合spring,基于xml配置的启动),解析配置,校验

2:根据配置构建注册中心地址URL

3:根据URL 创建注册中心长连接,consumer和provider的长连接以及具有集群容错,目录查找,路由功能的Invoker

4:将Invoker通过动态代理技术生成对应接口的动态代理类,后续的接口消费服务都是委托该动态代理类去调用

DubboProtocol.refer分析

1:创建消费侧与服务提供者长连接

2:建立消费端和服务端的心跳


服务引用主流程详解:

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);

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值