创意电子
标题:
Dubbo源码深度分析:消费者初始化和Netty客户端应用
[打印本页]
作者:
Gemini技术窝
时间:
2021-9-30 23:14
标题:
Dubbo源码深度分析:消费者初始化和Netty客户端应用
摘要:
本文主要解说Dubbo消费者怎样注册到Zookeeper,怎样监听Dubbo提供者的Zookeeper临时目录,并及时更新提供者的注册列表,最后提到Dubbo Consumer的关键步骤,即启动Netty客户端。
消费者注册到注册中心
首先看一下消费者是怎样注册到Zookeeper的,而且生成关键的Invoker对象。
private Invoker doRefer(Cluster cluster, Registry registry, Class type, URL url) { RegistryDirectory directory = new RegistryDirectory(type, url); directory.setRegistry(registry); directory.setProtocol(protocol); // ...... // 默认必要注册到注册中心 if (directory.isShouldRegister()) { // 设置消费者的url,然后将该url注册到zk directory.setRegisteredConsumerUrl(subscribeUrl); registry.register(directory.getRegisteredConsumerUrl()); } // 创建路由规则链 directory.buildRouterChain(subscribeUrl); // 订阅服务提供者地点 // 从zk拉取服务提供者的地点,并缓存到本地 directory.subscribe(toSubscribeUrl(subscribeUrl)); // 包装机器容错策略到invoker Invoker invoker = cluster.join(directory); List listeners = findRegistryProtocolListeners(url); if (CollectionUtils.isEmpty(listeners)) { return invoker; } RegistryInvokerWrapper registryInvokerWrapper = new RegistryInvokerWrapper(directory, cluster, invoker); for (RegistryProtocolListener listener : listeners) { listener.onRefer(this, registryInvokerWrapper); } return registryInvokerWrapper;}
这里我们先关注一下registry.register(directory.getRegisteredConsumerUrl())。这部分代码就是注册到zk的地方。
public abstract class FailbackRegistry extends AbstractRegistry { @Override public void register(URL url) { //...... super.register(url); removeFailedRegistered(url); removeFailedUnregistered(url); try { // Sending a registration request to the server side // 发送注册服务请求到注册中心 doRegister(url); } //...... }} public class ZookeeperRegistry extends FailbackRegistry { @Override public void doRegister(URL url) { try { // 将服务注册到zk zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true)); } catch (Throwable e) { throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e); } }}可以看到上面的代码其实跟服务提供者注册是一样的,就是构造一下url,然后作为一个临时节点写入到zk的指定目录下,即consumer目录。这样就完成了消费者的注册,后续假如消费者下线或者网络断开了,Zookeeper都会将该临时节点删除,而且通知有监听该目录的机器节点。
下面我们继承看一下消费者是怎样订阅服务提供者的地点列表,而且注册好Zookeeper的监听,后续有变化则会收到相应的通知,然后更新本地的提供者地点列表。
订阅服务提供者和注册监听
接下去继承看directory.subscribe(),用于订阅服务提供者地点。
public class RegistryDirectory extends AbstractDirectory implements NotifyListener { public void subscribe(URL url) { setConsumerUrl(url); CONSUMER_CONFIGURATION_LISTENER.addNotifyListener(this); // 设置zk的监听器,假如服务提供者的列表出现变化,本地的缓存也会相应更新 serviceConfigurationListener = new ReferenceConfigurationListener(this, url); // 订阅提供者 registry.subscribe(url, this); }} @Overridepublic void subscribe(URL url, NotifyListener listener) { super.subscribe(url, listener); removeFailedSubscribed(url, listener); try { // Sending a subscription request to the server side // 向zk发送订阅请求 doSubscribe(url, listener); } catch (Exception e) { //...... }} @Overridepublic void doSubscribe(final URL url, final NotifyListener listener) { try { if (ANY_VALUE.equals(url.getServiceInterface())) { //...... } // 默认走这里 else { List urls = new ArrayList(); // 将消费者的url写入到zk的临时节点 for (String path : toCategoriesPath(url)) { ConcurrentMap listeners = zkListeners.computeIfAbsent(url, k -> new ConcurrentHashMap()); ChildListener zkListener = listeners.computeIfAbsent(listener, k -> (parentPath, currentChilds) -> ZookeeperRegistry.this.notify(url, k, toUrlsWithEmpty(url, parentPath, currentChilds))); zkClient.create(path, false); List children = zkClient.addChildListener(path, zkListener); if (children != null) { urls.addAll(toUrlsWithEmpty(url, path, children)); } } // 触发监听者 notify(url, listener, urls); } } //......}上面的代码其实就是注册一下zk目录的监听,然后将消费者的url注册到zk中,最后的关键就是触发了监听,然后前面设置的监听类RegistryDirectory就会实行notify。
/*** 接收zk节点变化通知*/@Overridepublic synchronized void notify(List urls) { //...... // providers // 获取zk中最新的提供者 List providerURLs = categoryUrls.getOrDefault(PROVIDERS_CATEGORY, Collections.emptyList()); //...... // 刷新invoker列表 refreshOverrideAndInvoker(providerURLs);}private void refreshOverrideAndInvoker(List urls) { // mock zookeeper://xxx?mock=return null overrideDirectoryUrl(); // 转换url为invoker refreshInvoker(urls);}private void refreshInvoker(List invokerUrls) { Assert.notNull(invokerUrls, "invokerUrls should not be null"); // 只有一个服务提供者时 //...... // 有多个提供者时 else { //...... // url转换为invoker对象 Map newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map //...... List newInvokers = Collections.unmodifiableList(new ArrayList(newUrlInvokerMap.values())); // routerChain设置newInvokers routerChain.setInvokers(newInvokers); this.invokers = multiGroup ? toMergeInvokerList(newInvokers) : newInvokers; this.urlInvokerMap = newUrlInvokerMap; // 关闭没用到的invokers try { destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker } //...... }}
其实就是从zk中获取所有的设置信息,包括降级设置、提供者、消费者信息,然后将
invokerUrls
转换为invoker对象,这一步是关键。因此我们继承深入
toInvokers()方法查看具体Dubbo怎样生成
invoker对象,也就是所谓的Dubbo消费者接口署理对象。
private Map toInvokers(List urls) { //...... for (URL providerUrl : urls) { //...... if (invoker == null) { // Not in the cache, refer again try { boolean enabled = true; if (url.hasParameter(DISABLED_KEY)) { enabled = !url.getParameter(DISABLED_KEY, false); } else { enabled = url.getParameter(ENABLED_KEY, true); } if (enabled) { // 关键代码:这里调用Dubbo协议转换服务到invoker invoker = new InvokerDelegate(protocol.refer(serviceType, url), url, providerUrl); } } //...... } keys.clear(); return newUrlInvokerMap;}从上面的这一坨代码中,关键的就是这一行:protocol.refer(serviceType, url),这里还是跟之前的一样先经过2个Wrapper类之后,就会调用DubboProtocol的refer()方法。
DubboProtocol创建连接
接下来继承深入DubboProtocol的refer()方法。
public class DubboProtocol extends AbstractProtocol { @Override public Invoker protocolBindingRefer(Class serviceType, URL url) throws RpcException { //...... // 关键代码:getClients() DubboInvoker invoker = new DubboInvoker(serviceType, url, getClients(url), invokers); invokers.add(invoker); return invoker; } private ExchangeClient[] getClients(URL url) { // 同一台机器中的差别服务是否共享连接 boolean useShareConnect = false; //...... // 假如没设置,则默认连接是共享的,否则每个服务单独有本身的连接 if (connections == 0) { useShareConnect = true; /* * xml设置优先级高于属性设置 */ String shareConnectionsStr = url.getParameter(SHARE_CONNECTIONS_KEY, (String) null); connections = Integer.parseInt(StringUtils.isBlank(shareConnectionsStr) ? ConfigUtils.getProperty(SHARE_CONNECTIONS_KEY, DEFAULT_SHARE_CONNECTIONS) : shareConnectionsStr); // 获取共享NettyClient shareClients = getSharedClient(url, connections); } // 初始化Client ExchangeClient[] clients = new ExchangeClient[connections]; for (int i = 0; i < clients.length; i++) { if (useShareConnect) { // 共享则返回已经存在的 clients
= shareClients.get(i); } else { // 否则创建新的 clients
= initClient(url); } } return clients; } /** * Create new connection * * @param url */ private ExchangeClient initClient(URL url) { //..... ExchangeClient client; try { // connection should be lazy // 创建惰性连接 if (url.getParameter(LAZY_CONNECT_KEY, false)) { client = new LazyConnectExchangeClient(url, requestHandler); } // 创建即时连接 else { client = Exchangers.connect(url, requestHandler); } } //...... return client; }}上面其实看看是否同一台机器上面的服务共享一个netty客户端,然后根据url去创建连接。所以继承跟进关键代码:Exchangers.connect(url, requestHandler)。
public static ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException { //...... url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange"); return getExchanger(url).connect(url, handler);}public class HeaderExchanger implements Exchanger { @Override public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException { return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true); }} public static Client connect(URL url, ChannelHandler... handlers) throws RemotingException { //...... return getTransporter().connect(url, handler);} public class NettyTransporter implements Transporter { public static final String NAME = "netty"; @Override public Client connect(URL url, ChannelHandler handler) throws RemotingException { return new NettyClient(url, handler); }}
看到这里就很认识了,就是层层递进去找到NettyClient,然后初始化netty客户端而已。
Netty客户端初始化和参数优化
接下来我们看一下Netty客户端怎样初始化,而且Dubbo怎样在使用Netty客户端的时间进行参数优化的。
public AbstractClient(URL url, ChannelHandler handler) throws RemotingException { //...... try { // 关键代码:创建netty客户端 doOpen(); } try { // connect. connect(); } //......} /*** 初始化netty客户端*/@Overrideprotected void doOpen() throws Throwable { // 1、创建业务handler final NettyClientHandler nettyClientHandler = new NettyClientHandler(getUrl(), this); // 2、创建启动器并设置 bootstrap = new Bootstrap(); bootstrap.group(NIO_EVENT_LOOP_GROUP) // 连接保活 .option(ChannelOption.SO_KEEPALIVE, true) // 不启用开启Nagle算法,Nagle算法会网络网络小包再一次性发送,不启用则是即时发送,进步时效性 .option(ChannelOption.TCP_NODELAY, true) // ByteBuf的分配器(重用缓冲区),也就是使用对象池重复利用内存块 .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) //.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getTimeout()) .channel(socketChannelClass()); // 设置连接超时 bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, Math.max(3000, getConnectTimeout())); // 3、添加handler到连接的pipeline bootstrap.handler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) throws Exception { int heartbeatInterval = UrlUtils.getHeartbeat(getUrl()); 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()) // 心跳查抄handler .addLast("client-idle-handler", new IdleStateHandler(heartbeatInterval, 0, 0, MILLISECONDS)) // 业务处理handler .addLast("handler", nettyClientHandler); //...... } });}上面的代码就表现了一个Netty客户端的典型用法,设置worker线程池,参数设置了KEEPALIVE、NODELAY、CONNECT_TIMEOUT、
ALLOCATOR这几个紧张参数,然后
添加编解码、心跳和业务处理的handler。
其中参数调优主要是
ALLOCATOR的设置,应用了Netty的
对象池以便充分使用内存块,提升ByteBuf的分配效率。
更多详细和完整的文章内容请订阅CSDN专栏:
Dubbo源码深度分析_Gemini的博客-CSDN博客
假如您感觉本文对您有所帮助的话,请先关注一下作者,转发一下本文,然后私信发“口试题整理资料”给作者,即可获得获得一份丰富的口试题整理资料,里面收录的口试题多达几百道,感谢您的阅读。
欢迎光临 创意电子 (https://wxcydz.cc/)
Powered by Discuz! X3.4