功能架构

首先通过一张图来了解整个功能架构

dubbo 内存泄漏_Boo

下面是rpc的核心流程

dubbo 内存泄漏_Boo_02

服务暴露

首先分析服务暴露的过程,通过源码来理解

通过协议的方式来暴露服务:

String urlText="dubbo://127.0.0.1:20880/cxylk.dubbo.UserService?timeout=6000";
    // 暴露服务测试
    @Test
    public void exportTest() throws IOException, IOException {
        DubboProtocol protocol=new DubboProtocol();
        ProxyFactory proxyFactory=new JdkProxyFactory();// 反射调用
        UserServiceImpl refImpl = new UserServiceImpl();// 实现类
        Invoker<UserService> invoker =
                proxyFactory.getInvoker(refImpl, UserService.class, URL.valueOf(urlText));
        Exporter<UserService> export = protocol.export(invoker);//
        ApplicationModel.getServiceRepository().registerService(UserService.class);

        System.out.println("服务暴露成功");
        System.in.read();
    }
export暴露服务

进入DubboProtocol类的export方法,也就是暴露服务。参数是invoker,这里必须要说明invoker和invocation的区别:

invoker是一个调用器,它通过方法invoke和传入的参数invocation来发起调用,而invocation是具体的一次调用,它包含了接口名,方法名,参数等等。可以把invoker理解成一个大炮,而invocation就是炮弹,大炮发射需要炮弹,而炮弹中包含了这个大炮的信息,这样它才知道需要被哪个大炮发射,invocation也是通过内部的invoker来找到具体的调用者。

下面是export的源码:

@Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        //url就是测试代码中的Url
        URL url = invoker.getUrl();

        // export service.
        //key就是接口名+端口
        String key = serviceKey(url);
        //初始化dubboExporter中的invoker,key,exporterMap
        //exporterMap是一个concurrentHashMap,
        DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
        exporterMap.put(key, exporter);

        //export an stub service for dispatching event
        Boolean isStubSupportEvent = url.getParameter(STUB_EVENT_KEY, DEFAULT_STUB_EVENT);
        Boolean isCallbackservice = url.getParameter(IS_CALLBACK_SERVICE, false);
        if (isStubSupportEvent && !isCallbackservice) {
            String stubServiceMethods = url.getParameter(STUB_EVENT_METHODS_KEY);
            if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
                if (logger.isWarnEnabled()) {
                    logger.warn(new IllegalStateException("consumer [" + url.getParameter(INTERFACE_KEY) +
                            "], has set stubproxy support event ,but no stub methods founded."));
                }

            }
        }

        //开启服务
        openServer(url);
        optimizeSerialization(url);

        return exporter;
    }
openServer开启服务

进入该类的openServer开启服务:

private void openServer(URL url) {
        // find server.
    	//key=127.0.0.1:20880
        String key = url.getAddress();
        //client can export a service which's only for server to invoke
        boolean isServer = url.getParameter(IS_SERVER_KEY, true);
    	//判断是否是服务
        if (isServer) {
            ProtocolServer server = serverMap.get(key);
            if (server == null) {
                synchronized (this) {
                    server = serverMap.get(key);
                    if (server == null) {
                        //没有就创建服务,并且放入serverMap中
                        serverMap.put(key, createServer(url));
                    }
                }
            } else {
                // server supports reset, use together with override
                //如果服务已经被创建,那么重置
                server.reset(url);
            }
        }
    }

上面代码就是使用一个双重检测实现的,也就是单例模式。

创建服务
private ProtocolServer createServer(URL url) {
    url = URLBuilder.from(url)
            // send readonly event when server closes, it's enabled by default
            .addParameterIfAbsent(CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString())
            // enable heartbeat by default
            .addParameterIfAbsent(HEARTBEAT_KEY, String.valueOf(DEFAULT_HEARTBEAT))
            .addParameter(CODEC_KEY, DubboCodec.NAME)
            .build();
    //获取运行服务,str=netty
    String str = url.getParameter(SERVER_KEY, 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 {
    	//最重要的地方,特别是requestHandler这个参数
        server = Exchangers.bind(url, requestHandler);
    } catch (RemotingException e) {
        throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
    }

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

    return new DubboProtocolServer(server);
}
开启NettyServer服务

下面的这行代码是最重要的,特别是其中的参数requestHandler,这个参数是当接收到请求后进行处理的。

server = Exchangers.bind(url, requestHandler);

这个bind方法最后会开启netty服务,但是中间会经过很多层封装,我们直接跳到最终实现。

在Transportes中的bind方法会跳转到Transporter接口中的bind方法

public static RemotingServer bind(URL url, ChannelHandler... handlers) throws RemotingException {
        if (url == null) {
            throw new IllegalArgumentException("url == null");
        }
        if (handlers == null || handlers.length == 0) {
            throw new IllegalArgumentException("handlers == null");
        }
        ChannelHandler handler;
        if (handlers.length == 1) {
            handler = handlers[0];
        } else {
            handler = new ChannelHandlerDispatcher(handlers);
        }
        return getTransporter().bind(url, handler);
    }

bind方法其中的一个实现就是nettyTransporter(基于netty4)

@Override
    public RemotingServer bind(URL url, ChannelHandler handler) throws RemotingException {
        return new NettyServer(url, handler);
    }

    @Override
    public Client connect(URL url, ChannelHandler handler) throws RemotingException {
        return new NettyClient(url, handler);
    }

bind方法用于服务端调用的,而connect方法用于客户端进行连接。当我们在bind方法中打个断点时,最终就会调用到这里,再往下就是关于netty的具体操作了,这里不再深入。

接下来看requestHandler这个参数:

private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {

    @Override
    public CompletableFuture<Object> reply(ExchangeChannel channel, Object message) throws RemotingException {

        if (!(message instanceof Invocation)) {
            throw new RemotingException(channel, "Unsupported request: "
                    + (message == null ? null : (message.getClass().getName() + ": " + message))
                    + ", channel: consumer: " + channel.getRemoteAddress() + " --> provider: " + channel.getLocalAddress());
        }

        Invocation inv = (Invocation) message;
        Invoker<?> invoker = getInvoker(channel, inv);
        // need to consider backward-compatibility if it's a callback
        if (Boolean.TRUE.toString().equals(inv.getObjectAttachments().get(IS_CALLBACK_SERVICE_INVOKE))) {
            String methodsStr = invoker.getUrl().getParameters().get("methods");
            boolean hasMethod = false;
            if (methodsStr == null || !methodsStr.contains(",")) {
                hasMethod = inv.getMethodName().equals(methodsStr);
            } else {
                String[] methods = methodsStr.split(",");
                for (String method : methods) {
                    if (inv.getMethodName().equals(method)) {
                        hasMethod = true;
                        break;
                    }
                }
            }
            if (!hasMethod) {
                logger.warn(new IllegalStateException("The methodName " + inv.getMethodName()
                        + " not found in callback service interface ,invoke will be ignored."
                        + " please update the api interface. url is:"
                        + invoker.getUrl()) + " ,invocation is :" + inv);
                return null;
            }
        }
        RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
        //发起调用,调用reflmpl
        Result result = invoker.invoke(inv);
        return result.thenApply(Function.identity());
    }

    .....
};

requestHandler就是一个回调,什么时候会回调呢?就是当nettyServer接收到客户端发送的请求时。当执行到requestHandler时,编解码,IO这些操作已经实现了,所以我们不用去关心。当消息过来后,会传入一个invocation,有了这个invocation,就可以进行调用了

当我们发起一个请求调用:

@Test
    public void invokeTest() {
        ReferenceConfig<UserService> referenceConfig=new ReferenceConfig();
        //  应用名称
        ApplicationConfig app=new ApplicationConfig("lk-client");
        referenceConfig.setApplication(app);
        // URL地址
        referenceConfig.setUrl(urlText);
        // 指定接口
        referenceConfig.setInterface(UserService.class);
        // 获取接口服务 (动态代理)
        UserService userService = referenceConfig.get();
        System.out.println(userService.getUser(1111));
    }

就是走到reply方法。

总结:

dubbo 内存泄漏_dubbo 内存泄漏_03

上面应该是创建并添加至serverMap

服务引用

服务引用需要做两件事情:

1.创建连接;2.创建代理对象

下面通过代码+源码分析这个过程

/**
     * 服务引用
     */
    @Test
    public void referTest(){
        DubboProtocol protocol=new DubboProtocol();
        ProxyFactory proxyFactory=new JdkProxyFactory();// 反射调用
        //对服务进行引用
        Invoker<UserService> invoker = protocol.refer(UserService.class, URL.valueOf(urlText));
        UserService proxy = proxyFactory.getProxy(invoker);
        System.out.println(proxy.getUser(111));
    }
引用服务

通过protocol.refer来引用服务

@Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        return new AsyncToSyncInvoker<>(protocolBindingRefer(type, url));
    }

在该方法中会创建两个invoker:

1.AsyncToSyncInvoker:异步转同步

2.dubboInvoker,通过protocolBindingRefer得到

创建invoker

在DubboProtocol类中的如下方法创建dubboInvoker

@Override
    public <T> Invoker<T> protocolBindingRefer(Class<T> serviceType, URL url) throws RpcException {
        optimizeSerialization(url);

        // create rpc invoker.
        DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
        invokers.add(invoker);

        return invoker;
    }
获取连接

通过getClients来获取连接,还是在该类中:

private ExchangeClient[] getClients(URL url) {
        // whether to share connection

        boolean useShareConnect = false;

    	//连接数,这里默认是0
        int connections = url.getParameter(CONNECTIONS_KEY, 0);
        List<ReferenceCountExchangeClient> shareClients = null;
        // if not configured, connection is shared, otherwise, one connection for one service
        if (connections == 0) {
            //使用共享连接
            useShareConnect = true;

            /*
             * The xml configuration should have a higher priority than properties.
             */
            String shareConnectionsStr = url.getParameter(SHARE_CONNECTIONS_KEY, (String) null);
            //此时这里变为1,默认值
            connections = Integer.parseInt(StringUtils.isBlank(shareConnectionsStr) ? ConfigUtils.getProperty(SHARE_CONNECTIONS_KEY,
                    DEFAULT_SHARE_CONNECTIONS) : shareConnectionsStr);
            //获取共享连接
            shareClients = getSharedClient(url, connections);
        }

    	//遍历连接
        ExchangeClient[] clients = new ExchangeClient[connections];
        for (int i = 0; i < clients.length; i++) {
            //如果是共享连接,那么上面的if语句会执行,useSharConnect=true,所以
            //此时这里就直接获取
            if (useShareConnect) {
                clients[i] = shareClients.get(i);

            } else {//否则初始连接
                clients[i] = initClient(url);
            }
        }

        return clients;
    }

上面提到了共享连接,这里解释下是什么意思:当有多个服务的时候,dubbo默认是让这多个服务使用一个连接的,除非我们进行了配置,比如让connections=2。这也是为什么dubbo协议不推荐传输大文件的原因,就是快进快出。如果一个服务传输的数据很大的时候,那么它就会长时间占用这个连接。

获取共享连接

通过getSharedClient获取共享连接,方法很长,贴出最重要的部分:

List<ReferenceCountExchangeClient> clients = referenceClientMap.get(key);
	 ...
			if (CollectionUtils.isEmpty(clients)) {
                clients = buildReferenceCountExchangeClientList(url, connectNum);
                referenceClientMap.put(key, clients);

            } else {
                for (int i = 0; i < clients.size(); i++) {
                    ReferenceCountExchangeClient referenceCountExchangeClient = clients.get(i);
                    // If there is a client in the list that is no longer available, create a new one to replace him.
                    if (referenceCountExchangeClient == null || referenceCountExchangeClient.isClosed()) {
                        clients.set(i, buildReferenceCountExchangeClient(url));
                        continue;
                    }

                    referenceCountExchangeClient.incrementAndGetCount();
                }
            }
	...

上面代码就是判断是否存在共享连接,如果没存在,那么通过

clients = buildReferenceCountExchangeClientList(url, connectNum);

来初始连接。然后放入referenceClientMap中。

初始连接

无论是使用共享连接,还是没有使用,都需要初始化连接。

通过buildReferenceCountExchangeClientList方法,然后调用buildReferenceCountExchangeClient方法:

private ReferenceCountExchangeClient buildReferenceCountExchangeClient(URL url) {
        ExchangeClient exchangeClient = initClient(url);

        return new ReferenceCountExchangeClient(exchangeClient);
    }

其中initClient就是用来初始连接。

在这个方法中,会通过Exchangers.connect来开启NettyClient,我们看下堆栈信息

dubbo 内存泄漏_Boo_04

可以看到,从initClient到开启NettyClient,这期间经过了几层封装。

最后看下NettyTransporter中的方法:

@Override
    public RemotingServer bind(URL url, ChannelHandler handler) throws RemotingException {
        return new NettyServer(url, handler);
    }

    @Override
    public Client connect(URL url, ChannelHandler handler) throws RemotingException {
        return new NettyClient(url, handler);
    }

bind方法我们在服务暴露的时候分析过来,而connect就是进行服务引用。一样的,再往下就是具体的netty操作,这里不再分析。

经过上面一系列的操作后,最终在getClients方法中就得到了一个client,并且返回出去被DubboInvoker所接收,DubboInvoker就根据这个clients来进行调用

//dubboInvoker类中存放了clinets
private final ExchangeClient[] clients;

总结:将上面的步骤总结成如下图:

dubbo 内存泄漏_dubbo_05

总结

分析了服务暴露和服务引用后,通过下面这张图来加深理解:

dubbo 内存泄漏_ide_06