Dubbo-聊聊服务端线程模型

Dubbo作为一个优秀的RPC框架,以SPI方式抽象架构,可以自由选择通信层框架,已实现的有Netty、Netty4和Mina。
Dubbo 有提供线程模型的官方文档:
http://dubbo.apache.org/zh-cn/docs/user/demos/thread-model.html
本文深入源码,尝试解析dubbo线程模型具体细节。
对于dispatcher参数,有以下几种可选项:

  • all 所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。
  • direct 所有消息都不派发到线程池,全部在 IO 线程上直接执行。
  • message 只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在 IO 线程上执行。
  • execution 只有请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在 IO 线程上执行。
  • connection 在 IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池。

dispatcher源码在 org.apache.dubbo.remoting.transport.dispatcher 包下面
由Dispatcher的定义知,all作为默认消息派发策略:

@SPI(AllDispatcher.NAME)
public interface Dispatcher {
    @Adaptive({Constants.DISPATCHER_KEY, "dispather", "channel.handler"})
    // The last two parameters are reserved for compatibility with the old configuration
    ChannelHandler dispatch(ChannelHandler handler, URL url);

}

all作为默认的派发策略

服务暴露

虽然知道了线程分发种类以及具体代码,但是是如何和dubbo 服务端接口衔接起来的呢?
这就需要看看服务端暴露过程了。
服务暴露具体细节可以看博主前面几篇关于dubbo文章:
服务暴露过程
简要介绍下暴露过程:

  1. Dubbo服务抽象配置为ServiceConfig,而服务暴露具体方法则是其内部 export 方法。
    public synchronized void export() {
		...
        if (shouldDelay()) {
            DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
        } else {
            doExport();
        }
    }
  1. 所有服务会以Exporter形式被保存在ServiceConfig中,因为服务可以暴露给多个注册中心,所以需要由url区分多份,export获取则是通过
    Protocol.export 进行。
  2. 对于一个服务,首先要跟registry交互,所以dubbo将url换为了registry,通过SPI机制,让流程先进入RegistryProtocol 的export方法
                        Exporter<?> exporter = protocol.export(wrapperInvoker);
                        exporters.add(exporter);
  1. 当向注册中心注册号服务链路之后,由SPI和Wrapper类装在,会先进入 ProtocolFilterWrapperProtocolListenerWrapperQosProtocolWrapper 的export逻辑,而后进入DubboProtocol的export,针对具体协议,做真正的服务端暴露。

上面简要的介绍了服务端暴露流程,接下来从DubboProtocol 的export方法开始,则将正式进入我们的主题。

在export中有调用一个openServer,从名字上来看是开启一个网络服务:
DubboProtocol.java

    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
		...
        openServer(url);
        optimizeSerialization(url);
        return exporter;
    }

如果根据ip+端口方式,没有获取到一个服务提供者,则会进入creatServer(url) 创建一个server。
DubboProtocol.java

    private ProtocolServer createServer(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(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);
    }

回过头来,在服务端传入的requestHandler 是什么呢?
它主要是 ExchangeHandler 在DubboProtocol的一个内部类实现类,由于Dubbo支持多个传输层,所以从设计角度,会抽出一些共性的事件,从而顶一个内部一个包装类,来统一实现逻辑。
这个抽象的类封装类其实就是:org.apache.dubbo.remoting.exchange.support.ExchangeHandlerAdapter
而 ExchangeHandler 主要看看其replay方法:
DubboProtocol.java

        @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);
			...
            RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
            Result result = invoker.invoke(inv);
            return result.thenApply(Function.identity());
        }

中间省略了回调机制的一些处理逻辑,总的来说,这个reply方法,应该就是收到调用消息后,封装成为invoker进行内部调用的过程。

Exchangers 封装了网络传输相关入口,包括服务端的bind和客户端的connect。

Dubbo通过SPI方式,获取一个Exchanger,再调用其connect方法进行绑定操作,Exchanger则进一步对底层传输框架进行封装,目前Exchanger的SPI实现类则只有一个,即HeaderExchanger,主要是在Server基础上,封装一些逻辑,例如心跳检测和其他读写事件。
Exchanger.java

    public static ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
		...
        return getExchanger(url).connect(url, handler);
    }
    public static Exchanger getExchanger(URL url) {
        String type = url.getParameter(Constants.EXCHANGER_KEY, Constants.DEFAULT_EXCHANGER);
        return getExchanger(type);
    }
    public static Exchanger getExchanger(String type) {
        return ExtensionLoader.getExtensionLoader(Exchanger.class).getExtension(type);
    }

接下来继续看Transporters的bind方法,其内部是获取对应 Transporter 的SPI实现类,从而调用其 bind方法,本文我们以org.apache.dubbo.remoting.transport.netty.NettyTransporter的 bind为例:

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

NettyServer 内部,则是Netty服务端启动相关代码了。
在Dubbo中,除了编码和解码器,只使用了一个nettyHandle进行统一服务处理,我们所关心的派发逻辑,则需要从NettyServer构造方法中去获取:
NettyServer.java

    public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
        super(ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME), ChannelHandlers.wrap(handler, url));
    }

首先是可以自定义线程名字,另一方面,对原来的handler和url,使用 ChannelHandlers 进行了进一步封装。
ChannelHandlers.java

    public static ChannelHandler wrap(ChannelHandler handler, URL url) {
        return ChannelHandlers.getInstance().wrapInternal(handler, url);
    }

    protected static ChannelHandlers getInstance() {
        return INSTANCE;
    }

    static void setTestingChannelHandlers(ChannelHandlers instance) {
        INSTANCE = instance;
    }

    protected ChannelHandler wrapInternal(ChannelHandler handler, URL url) {
        return new MultiMessageHandler(new HeartbeatHandler(ExtensionLoader.getExtensionLoader(Dispatcher.class)
                .getAdaptiveExtension().dispatch(handler, url)));
    }

通过SPI来获取Dispatcher,并且调用其dispatch方法,对handler和url进行进一步封装,我们所关心的消息派发,就是从这里进入的。

派发类别

前面,聊完了消息派发组件Dispatcher的组装过程,接下来则仔细看下几种不同派发机制,是如何实现的。

AllChannelHandler

所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。
从代码中可以看到,所有网络事件,都封装为了 ChannelEventRunnable ,交由同一个线程池统一处理,而这个线程池也是通过SPI方式创建:
WrappedChannelHandler.java

    public ExecutorService getSharedExecutorService() {
        ExecutorRepository executorRepository =
                ExtensionLoader.getExtensionLoader(ExecutorRepository.class).getDefaultExtension();
        ExecutorService executor = executorRepository.getExecutor(url);
        if (executor == null) {
            executor = executorRepository.createExecutorIfAbsent(url);
        }
        return executor;
    }

ExecutorRepository 目前只有一个SPI实现类:
DefaultExecutorRepository.java

    public synchronized ExecutorService createExecutorIfAbsent(URL url) {
    	...
        Integer portKey = url.getPort();
        ExecutorService executor = executors.computeIfAbsent(portKey, k -> createExecutor(url));
        // If executor has been shut down, create a new one
        if (executor.isShutdown() || executor.isTerminated()) {
            executors.remove(portKey);
            executor = createExecutor(url);
            executors.put(portKey, executor);
        }
        return executor;
    }
    private ExecutorService createExecutor(URL url) {
        return (ExecutorService) ExtensionLoader.getExtensionLoader(ThreadPool.class).getAdaptiveExtension().getExecutor(url);
    }

创建的业务线程池,是可以通过url参数指定的,默认为fixed,即固定大小线程池。
在这里插入图片描述

  • fixed:固定大小线程池,启动时建立线程,不关闭,一直持有。
  • coresize:200
  • maxsize:200
  • 队列:SynchronousQueue
  • 回绝策略:AbortPolicyWithReport - 打印线程信息jstack,之后抛出异常
  • cached:缓存线程池,空闲一分钟自动删除,需要时重建。
  • limited:可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题。
  • eager:当核心线程数都busy时候,继续创建线程而不是将任务放入队列。
    最终receive逻辑:
    @Override
    public void received(Channel channel, Object message) throws RemotingException {
        ExecutorService executor = getPreferredExecutorService(message);
        try {
            executor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message));
        } catch (Throwable t) {
        	if(message instanceof Request && t instanceof RejectedExecutionException){
                sendFeedback(channel, (Request) message, t);
                return;
        	}
            throw new ExecutionException(message, channel, getClass() + " error when process received event .", t);
        }
    }

再看看 DirectDispatcher,其内部对handler封装了一层 DirectChannelHandler,而内部用的线程池,如果是请求事件(client端),否则则仍然会则是从Futrue中获取io线程池,并进行执行调用。

    public ExecutorService getPreferredExecutorService(Object msg) {
        if (msg instanceof Response) {
            Response response = (Response) msg;
            DefaultFuture responseFuture = DefaultFuture.getFuture(response.getId());
            // a typical scenario is the response returned after timeout, the timeout response may has completed the future
            if (responseFuture == null) {
                return getSharedExecutorService();
            } else {
                ExecutorService executor = responseFuture.getExecutor();
                if (executor == null || executor.isShutdown()) {
                    executor = getSharedExecutorService();
                }
                return executor;
            }
        } else {
            return getSharedExecutorService();
        }
    }

其他几种派发策略类似,博主就不一一读书了。

关注博主公众号: 六点A君。
哈哈哈,Dubbo小吃街不迷路:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Dubbo-Admin 是 Dubbo 提供的一个可视化管理平台,用于监控和管理 Dubbo 服务。安装和部署 Dubbo-Admin 需要以下步骤: 1. 下载 Dubbo-Admin 的安装包,可以从 Dubbo 的官方 GitHub 仓库中获取。你可以访问 https://github.com/apache/dubbo-admin/releases 下载最新版本的安装包。 2. 解压下载的安装包到你选择的目录。你可以使用以下命令解压: ```shell unzip dubbo-admin-x.x.x.zip ``` 3. 进入解压后的目录,找到 `dubbo-admin-server` 目录。 4. 打开 `dubbo-admin-server` 目录下的 `src/main/resources/application.properties` 文件,修改其中的配置信息。 - 配置 Dubbo 注册中心的地址:`dubbo.registry.address=zookeeper://localhost:2181`,根据你实际的注册中心地址进行修改。 - 配置 Dubbo Admin 的访问端口:`server.port=8080`,根据你的需求进行修改。 5. 保存并关闭 `application.properties` 文件。 6. 在 `dubbo-admin-server` 目录下执行以下命令来构建 Dubbo-Admin: ```shell mvn clean package ``` 7. 构建完成后,进入 `target` 目录,执行以下命令来启动 Dubbo-Admin: ```shell java -jar dubbo-admin-x.x.x.jar ``` 8. 等待启动完成,访问 http://localhost:8080 即可进入 Dubbo-Admin 的管理界面。 请注意,安装和部署 Dubbo-Admin 需要满足以下依赖条件: - JDK 1.8 或以上版本 - Maven 3.x - ZooKeeper 3.x 或以上版本 希望以上步骤对你有帮助!如果还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值