Dubbo源码解读

ExtensionLoader源码解析

https://www.jianshu.com/p/2f4eeb8ef93a

ExtensionLoader

服务暴露过程

1、spring会先解析xml生成BeanDefeination,DubboBeanDefinitionParser的parse方法中完成dubbo 命名空间的解析。

2、在applicationContext启动的refresh阶段会广播ApplicationEvent事件,触发ApplicatinListener.onApplicationEvent()方法的执行,ServiceBean实现了ApplicatuinListener接口,所以onApplicationEvent()方法开始执行。

3、ProxyFactory$Adaptive是dubbo通过javassist字节码动态产生的一个类,主要功能是利益dubbo的接口扩展机制适配指定的实现

4、JavassistProxyFactory.getInvoker()实现就是反射调用指定的方法。封装成了Wrapper类

5、Protocol$Adaptive的原理同3

6、RegistryProtocol.export使用同样的原理,通过Protocol$Adaptive适配器,将真正的export工作代理给了DubboProtocol

7、DubboProtocol.export 核心代码参考openServer方法,创建dubbo 服务的监听socket等。

8、ProtocolFilterWrapper.export将filter加入到Invoker中,最核心的代码在buildInvokerChain方法中,为每个filter产生一个Invoker,然后绑定到Invoker链中。

9、RegistryProtocol.getRegistry获取注册中心的实现,根据 元素配置获取对应的RegistryFactory实现,获取Registry对象。

10、ZookeeperRegistry.registry调用具体的注册中心完成服务的注册。至此,服务暴露完成。

ProxyFactory(代理)

代理分为两种,静态代理和动态代理。

  1. 静态代理:如果代理类在程序运行前就已经存在,那么这种代理就是静态代理。

  2. 动态代理:代理类在程序运行时创建的代理方式。动态代理关系由两组静态代理关系组成,这就是动态代理的原理。

动态代理的底层原理就是字节码技术,dubbo提供了两种方式来实现代理:

dubbo对于动态代理有两种实现方式

1、javassist

javassist是一款java字节码引擎工具,能够在运行时编译生成class。该方法也是代理的默认方法

2、jdk

它内置在JDK中,因此不依赖第三方jar包,但是功能相对较弱,当调用Proxy的静态方法创建动态代理类时,类名格式是"$ProxyN",N代表第N次生成的动态代理类,如果重复创建动态代理类会直接返回原先创建的代理类。但是以这种格式命名的类是继承Proxy类的,并且实现了其所代理的一组接口,这里就出现了它的一个局限性,由于java的类只能单继承,所以JDK动态代理仅支持接口代理。

dubbo的服务端和消费端启动都默认采用javassist代理

dubbo不能对非接口类进行代理的

ProxyFactory

public class JavassistProxyFactory extends AbstractProxyFactory {

    // 获取代理
    public  T getProxy(Invoker invoker, Class[] interfaces) {
        return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
    }

    public  Invoker getInvoker(T proxy, Class type, URL url) {
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
        return new AbstractProxyInvoker(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName, 
                                      Class[] parameterTypes, 
                                      Object[] arguments) throws Throwable {
                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };
    }

}

getProxy:引用服务时调用,将Invoker对象转化为proxy代理对象。

getInvoker:暴露服务时调用,将ref(真正的服务实现类)转化为invoker。

Protocol

https://my.oschina.net/u/2377110/blog/1857642

protocol的接口方法

/**
 * Protocol. (API/SPI, Singleton, ThreadSafe)
 */
@SPI("dubbo")
public interface Protocol {

    /**
     * Get default port when user doesn't config the port.
     *
     * @return default port
     */
    int getDefaultPort();

    /**
     * Export service for remote invocation: 
* 1. Protocol should record request source address after receive a request: * RpcContext.getContext().setRemoteAddress();
* 2. export() must be idempotent, that is, there's no difference between invoking once and invoking twice when * export the same URL
* 3. Invoker instance is passed in by the framework, protocol needs not to care
* * @param Service type * @param invoker Service invoker * @return exporter reference for exported service, useful for unexport the service later * @throws RpcException thrown when error occurs during export the service, for example: port is occupied */ @Adaptive Exporter export(Invoker invoker) throws RpcException; /** * Refer a remote service:
* 1. When user calls `invoke()` method of `Invoker` object which's returned from `refer()` call, the protocol * needs to correspondingly execute `invoke()` method of `Invoker` object
* 2. It's protocol's responsibility to implement `Invoker` which's returned from `refer()`. Generally speaking, * protocol sends remote request in the `Invoker` implementation.
* 3. When there's check=false set in URL, the implementation must not throw exception but try to recover when * connection fails. * * @param Service type * @param type Service class * @param url URL address for the remote service * @return invoker service's local proxy * @throws RpcException when there's any error while connecting to the service provider */ @Adaptive Invoker refer(Class type, URL url) throws RpcException; /** * Destroy protocol:
* 1. Cancel all services this protocol exports and refers
* 2. Release all occupied resources, for example: connection, port, etc.
* 3. Protocol can continue to export and refer new service even after it's destroyed. */ void destroy(); }

dubboProtocol是dubbo的核心类,具体实现了暴露服务和引用服务,即export方法

首先根据url生成servicekey,也就是这个invoker的唯一标识,然后放到exportMap中缓存起来。

openServer会启动一个socket服务(默认监听20880端口),里面最终也会调用createServer方法。

public  Exporter export(Invoker invoker) throws RpcException {
        URL url = invoker.getUrl();
        
        // export service.
        String key = serviceKey(url);
        DubboExporter exporter = new DubboExporter(invoker, key, exporterMap);
        exporterMap.put(key, exporter);
        // .....
        openServer(url);
        
        return exporter;
    }

createServer方法:在 server = Exchangers.bind(url, requestHandler) 中,放置了一个requestHandler

来处理所有的请求,其实这个handler也是一个dispatcher

private ExchangeServer createServer(URL url) {
        url = URLBuilder.from(url)
                // send readonly event when server closes, it's enabled by default
                .addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString())
                // enable heartbeat by default
                .addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT))
                .addParameter(Constants.CODEC_KEY, DubboCodec.NAME)
                .build();
        String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);

        if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
            throw new RpcException("Unsupported server type: " + str + ", url: " + url);
        }

        ExchangeServer server;
        try {
            server = Exchangers.bind(url, requestHandler);
        } catch (RemotingException e) {
            throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
        }

        str = url.getParameter(Constants.CLIENT_KEY);
        if (str != null && str.length() > 0) {
            Set supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
            if (!supportedTypes.contains(str)) {
                throw new RpcException("Unsupported client type: " + str);
            }
        }

        return server;
    }

reply方法就是requestHandler核心方法,getInvoker会计算inv的serviceKey,然后从exporter缓存中找到exporter,进而拿到invoker,调用返回结果。

public Object reply(ExchangeChannel channel, Object message) throws RemotingException {
            if (message instanceof Invocation) {
                Invocation inv = (Invocation) message;
                Invoker invoker = getInvoker(channel, inv);
                // ....
                RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
                return invoker.invoke(inv);
            }
            throw new RemotingException(channel, "Unsupported request: " + message == null ? null : (message.getClass().getName() + ": " + message) + ", channel: consumer: " + channel.getRemoteAddress() + " --> provider: " + channel.getLocalAddress());
        }

export方法执行完成后就标志服务端可以接受客户端的请求了,但是还需要向注册中心注册.

下面的就是RegistryProtocol的export方法,可以看到调用getRegistry后,其实Registry是一个代理,调用了原创服务的Registry的register方法完成注册的。

public  Exporter export(final Invoker originInvoker) throws RpcException {
        //export invoker
        final ExporterChangeableWrapper exporter = doLocalExport(originInvoker);
        //registry provider
        final Registry registry = getRegistry(originInvoker);
        final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
        registry.register(registedProviderUrl);
        // .....
    }

以上就是服务端暴露服务的过程。

客户端引用的过程:客户端在通过getBean方法最终会调用createProxy,然后是refer方法:

private  Invoker doRefer(Cluster cluster, Registry registry, Class type, URL url) {
        RegistryDirectory directory = new RegistryDirectory(type, url);
        directory.setRegistry(registry);
        directory.setProtocol(protocol);
        URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, NetUtils.getLocalHost(), 0, type.getName(), directory.getUrl().getParameters());
        if (! Constants.ANY_VALUE.equals(url.getServiceInterface())
                && url.getParameter(Constants.REGISTER_KEY, true)) {
            registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
                    Constants.CHECK_KEY, String.valueOf(false)));
        }
        directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY, 
                Constants.PROVIDERS_CATEGORY 
                + "," + Constants.CONFIGURATORS_CATEGORY 
                + "," + Constants.ROUTERS_CATEGORY));
        return cluster.join(directory);
}

这是RegistryProtocol的doRefer方法,可以看到是向Registry请求感兴趣的服务地址,Registry返回的每一个url都会被dubboProtocol使用refer封装成一个invoker,最后RegistryProtocol使用cluster把所有invoker封装成一个invoker,也就是代理。

Asm技术

Java字节码操控框架,能被用来动态生成类或者增强既有类的功能。ASM可以直接产生二进制class文件,也可以在类被加载入Java虚拟机之前改变类行为。Java class被存储在严格格式定义的.class文件里面。这些类文件拥有足够的元数据来解析类中的所有速算。ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

ThreadLocal

ThreadLocal是如何实现为线程提供变量副本的:

首先我们要知道每一个线程下都有一个私有变量map,当我们使用ThreadLocal进行set(val)变量时,会向当前线程下的map中put一个键为当前ThreadLocal对象(虚引用),值为val的键值对,这样当使用ThreadLocal的get方法时,会直接向当前线程下的map获得键为此ThreadLocal的值。由于此操作只在当前线程下,所以完美的避免了并发。

threadpool的线程池类型

FixedThreadPool :固定大小线程池,启动时创建线程,不关闭,一直特有(缺省)

 public Executor getExecutor(URL url) {
        // 获取线程名称前缀,默认Dubbo
        String name = url.getParameter(Constants.THREAD_NAME_KEY, Constants.DEFAULT_THREAD_NAME);
        // 获取核心线程和最大线程数,fixed是相等的,默认200
        int threads = url.getParameter(Constants.THREADS_KEY, Constants.DEFAULT_THREADS);
        // 获取队列大小,默认为0 ,SynchronousQueue,否则为LinkedBlockingQueue
        int queues = url.getParameter(Constants.QUEUES_KEY, Constants.DEFAULT_QUEUES);
        return new ThreadPoolExecutor(threads, threads, 0, TimeUnit.MILLISECONDS,
                queues == 0 ? new SynchronousQueue() :
                        (queues < 0 ? new LinkedBlockingQueue()
                                : new LinkedBlockingQueue(queues)),
                new NamedThreadFactory(name, true), new AbortPolicyWithReport(name, url));
    }

注意:如果使用这个线程池,默认的线程池数量为200,默认阻塞队列大小为0,默认使用的阻塞队列为SynchronousQueue。如果业务时间并发较高或者处理时间较长,请适当的调整阻塞队列的大小,即queues变量,否则会导致大量的请求被丢弃。该线程池也是 Dubbo 默认使用的线程池,估计出事的挺多。

CachedThreadPool:缓存线程池,空闲一分钟自动删除,需要时重建。

 public Executor getExecutor(URL url) {
        String name = url.getParameter(Constants.THREAD_NAME_KEY, Constants.DEFAULT_THREAD_NAME);
        // 获取核心线程数 corethreads,默认0
        int cores = url.getParameter(Constants.CORE_THREADS_KEY, Constants.DEFAULT_CORE_THREADS);
        // 获取最大线程数 默认Integer.MAX_VALUE 2147483647
        int threads = url.getParameter(Constants.THREADS_KEY, Integer.MAX_VALUE);
        // 获取线程池队列 默认0
        int queues = url.getParameter(Constants.QUEUES_KEY, Constants.DEFAULT_QUEUES);
        // 获取线程存活时间 默认1分钟
        int alive = url.getParameter(Constants.ALIVE_KEY, Constants.DEFAULT_ALIVE);
        return new ThreadPoolExecutor(cores, threads, alive, TimeUnit.MILLISECONDS,
                queues == 0 ? new SynchronousQueue() :
                        (queues < 0 ? new LinkedBlockingQueue()
                                : new LinkedBlockingQueue(queues)),
                new NamedThreadFactory(name, true), new AbortPolicyWithReport(name, url));
}

注意:它并不是正经的cache,该线程池的阻塞队列虽然默认是SynchronousQueue,但是如果用户配置了queues变量,且其值较大,使用的阻塞队列就是LinkBolckingQueue,此时一旦corethreads再使用默认值0,就会导致处理时间阻塞。

LimitedThreadPool:可伸缩线程池,但池中的线程数只会增长不会收缩(为避免收缩时突然来了大流量引起的性能问题)

public Executor getExecutor(URL url) {
        String name = url.getParameter(Constants.THREAD_NAME_KEY, Constants.DEFAULT_THREAD_NAME);
        int cores = url.getParameter(Constants.CORE_THREADS_KEY, Constants.DEFAULT_CORE_THREADS);
        int threads = url.getParameter(Constants.THREADS_KEY, Constants.DEFAULT_THREADS);
        int queues = url.getParameter(Constants.QUEUES_KEY, Constants.DEFAULT_QUEUES);
        // 存活无限长时间
        return new ThreadPoolExecutor(cores, threads, Long.MAX_VALUE, TimeUnit.MILLISECONDS,
                queues == 0 ? new SynchronousQueue() :
                        (queues < 0 ? new LinkedBlockingQueue()
                                : new LinkedBlockingQueue(queues)),
                new NamedThreadFactory(name, true), new AbortPolicyWithReport(name, url));
}

注意:这个线程池中的线程数可以一直增长到上限,永不回收,所以threads变量即线程最大限制的值不能太大,使用默认200即可,避免00M。

EagerThreadPool

AbortPolicyWithReport

dubbo自定义的线程池拒绝策略,任务被拒绝后输出堆栈信息dumpJStack()

dubbo架构介绍以及各个模块的关系

https://www.cnblogs.com/wangzhuxing/p/9725096.html

dubbo-common 公共逻辑模块:包括Util类和通用模型

dubbo-remoting 远程通讯模块:相当于Dubbo协议的实现,如果RPC用RMI协议,则不需要使用此包

dubbo-rpc 远程调用模块:抽象各种协议,以及动态代理,只包含一对一的调用,不关心集群的管理。

dubbo-cluster 集群模块:将多个服务提供方伪装为一个提供方,包括:负载均衡,容错,路由等,集群的地址列表可以是静态配置的,也可以是由注册中心下发。

dubbo-registry 注册中心模块:基于注册中心下发地址的集群方式,以及对各种注册中心的抽象。

dubbo-monitor监控模块:统计服务调用次数,调用时间的,调用链跟踪的服务。

dubbo-config 配置模块:是Dubbo对外的API,用户通过config使用dubbo,因此dubbo的所有细节。

dubbo-container容器模块:是一个Standlone的容器,以简单的Main加载Spring启动,因为服务通常不需要Tomcat/JBoss等WEB容器的特性,没必要用WEB容器去加载服务。

logger模块

第三方日志框架的优先级

Log4J 最高 (默认就用这个)

SLF4J 次高 (上面没有采用这个)

Common Logging(jcl就是common logging) 次低(Log4j和SLF4J在项目中均么有用这个)

JDK log 最低(最好的选择)

有和没有指的是项目classpath下面有没有对应的jar包,如果有则表示支持对应的日志实现。

dubbo选择日志提供方的代码

// 查找常用的日志框架
     static {
        String logger = System.getProperty("dubbo.application.logger", "");
        switch (logger) {
            case "slf4j":
                setLoggerAdapter(new Slf4jLoggerAdapter());
                break;
            case "jcl":
                setLoggerAdapter(new JclLoggerAdapter());
                break;
            case "log4j":
                setLoggerAdapter(new Log4jLoggerAdapter());
                break;
            case "jdk":
                setLoggerAdapter(new JdkLoggerAdapter());
                break;
            case "log4j2":
                setLoggerAdapter(new Log4j2LoggerAdapter());
                break;
            default:
                List> candidates = Arrays.asList(
                        Log4jLoggerAdapter.class,
                        Slf4jLoggerAdapter.class,
                        Log4j2LoggerAdapter.class,
                        JclLoggerAdapter.class,
                        JdkLoggerAdapter.class
                );
                for (Class clazz : candidates) {
                    try {
                        setLoggerAdapter(clazz.newInstance());
                        break;
                    } catch (Throwable ignored) {
                    }
                }
        }
    }

热部署和热替换

你可能感兴趣的:(Dubbo源码解读)