我是靠谱客的博主 魔幻花卷,最近开发中收集的这篇文章主要介绍dubbo学习笔记(8):注册中心源码剖析,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

核心流程

前面在服务暴露与引用分析的时候,我们是基于直连的方式,没有使用注册中心,这就需要将url写死,这显然不符合我们的实际开发。

实际开发中,rpc的核心流程应该是这样的:

在这里插入图片描述

代理协议

有了注册中心后,就引出了RegistryProtocol这个概念,它继承了Protocol,但是它并不具备远程通信的能力,所以在它内部代理了一个Protocol,所以它是一个代理协议。通过下图来看看它和注册中心,DubboProtocol的关系

在这里插入图片描述

此时的url就类似于下面这种

"zookeeper://192.168.63.128:2181/org.apache.dubbo.registry.RegistryService?application=bootserver&dubbo=2.0.2&export=dubbo://127.0.0.1:20880/cxylk.dubbo.UserService&timeout=6000";

服务暴露

测试代码:

public class RegistryProtocolTest {
    final String urlTest="zookeeper://192.168.63.128:2181/org.apache.dubbo.registry.RegistryService?n" +
            "application=boot-server&dubbo=2.0.2&n" +
            "export=dubbo://127.0.0.1:20880/cxylk.dubbo.UserService&timeout=6000";
    @Before
    public void init() {
        ApplicationModel.getConfigManager().setApplication(new ApplicationConfig("test"));
    }

    @Test
    public void exportTest() throws IOException {
        //环境准备
        UserServiceImpl impl = new UserServiceImpl();
        impl.name = "注册中心测试";
        ServiceRepository repository = ApplicationModel.getServiceRepository();
        ServiceDescriptor serviceRepository = repository.registerService(UserService.class);
        repository.registerProvider(
                "cxylk.dubbo.UserService", impl, serviceRepository,
                new ServiceConfig<>(), null
        );
        //初始化代理协议
        //一个是目标协议,一个是注册工厂
        RegistryProtocol protocol = new RegistryProtocol();
        //代理的目标协议,这里是dubbo协议
        protocol.setProtocol(new DubboProtocol());
        ZookeeperRegistryFactory registryFactory = new ZookeeperRegistryFactory();
        registryFactory.setZookeeperTransporter(new CuratorZookeeperTransporter());
        protocol.setRegistryFactory(registryFactory);

        //构建Invoker 使用ProxyFactory的方式
        ProxyFactory proxyFactory=new JavassistProxyFactory();//动态生成代理类
        Invoker<UserService> invoker=proxyFactory.getInvoker(impl,UserService.class, URL.valueOf(urlTest));

        //dubbo协议 netty-server远程服务
        Exporter<UserService> export = protocol.export(invoker);
        //暴露服务 注册提供者
        System.in.read();
    }
}

上面有一个需要注意的地方,构建invoker有两种方式:

1、基于反射

ProxyFactory proxy=new JdkProxyFactory()

在JdkProxyFactory中通过getInvoker方法

    @Override
    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        return new AbstractProxyInvoker<T>(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName,
                                      Class<?>[] parameterTypes,
                                      Object[] arguments) throws Throwable {
                Method method = proxy.getClass().getMethod(methodName, parameterTypes);
                return method.invoke(proxy, arguments);
            }
        };
    }

可以看到,就是基于一个反射实现的。

2、基于动态代理

ProxyFactory proxy=new JavassistProxyFactory()

在JavassistProxyFactory中:

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

关键就是这个wrapper.invokeMethod方法,最终是在Wrapper.class中实现代理的,关键代码在makeWrapper方法:

private static Wrapper makeWrapper(Class<?> c) {
    ...
    try {
        		//拿到类对象
                Class<?> wc = cc.toClass();
                wc.getField("pts").set((Object)null, pts);
                wc.getField("pns").set((Object)null, pts.keySet().toArray(new String[0]));
                wc.getField("mns").set((Object)null, mns.toArray(new String[0]));
                wc.getField("dmns").set((Object)null, dmns.toArray(new String[0]));
                len = 0;
                Iterator var48 = ms.values().iterator();

                while(var48.hasNext()) {
                    Method m = (Method)var48.next();
                    wc.getField("mts" + len++).set((Object)null, m.getParameterTypes());
                }
				
        		//得到实例
                Wrapper var50 = (Wrapper)wc.newInstance();
                return var50;
            } 
    ...

下面通过代码来测试下生成的代理类:

@Test
    public void proxyFactoryTest() throws IOException {
        ProxyFactory proxyFactory=new JavassistProxyFactory();//动态生成代理类
        String urlTest="dubbo://127.0.0.1:20880/cxylk.dubbo.UserService";

        Invoker<UserService> invoker=proxyFactory.getInvoker(new UserServiceImpl(),UserService.class,URL.valueOf(urlTest));
        System.out.println(invoker);
        System.in.read();
    }

启动后,我们使用阿里的arthas工具,通过命令sc.*Wrapper找到warpper0(不是wrapper),然后再通过jad类名获取反编译代码,如下:

/*
 * Decompiled with CFR.
   */
   package org.apache.dubbo.common.bytecode;

import com.cxylk.UserServiceImpl;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import org.apache.dubbo.common.bytecode.ClassGenerator;
import org.apache.dubbo.common.bytecode.NoSuchMethodException;
import org.apache.dubbo.common.bytecode.NoSuchPropertyException;
import org.apache.dubbo.common.bytecode.Wrapper;

public class Wrapper0
extends Wrapper
implements ClassGenerator.DC {
    public static String[] pns;
    public static Map pts;
    public static String[] mns;
    public static String[] dmns;
    public static Class[] mts0;
    public static Class[] mts1;
@Override
public String[] getMethodNames() {
    return mns;
}

public Object invokeMethod(Object object, String string, Class[] classArray, Object[] objectArray) throws InvocationTargetException {
    UserServiceImpl userServiceImpl;
    try {
        userServiceImpl = (UserServiceImpl)object;
    }
    catch (Throwable throwable) {
        throw new IllegalArgumentException(throwable);
    }
    try {
        if ("getUser".equals(string) && classArray.length == 1) {
            return userServiceImpl.getUser((Integer)objectArray[0]);
        }
        if ("findUsersByLabel".equals(string) && classArray.length == 2) {
            return userServiceImpl.findUsersByLabel((String)objectArray[0], (Integer)objectArray[1]);
        }
    }
    catch (Throwable throwable) {
        throw new InvocationTargetException(throwable);
    }
    throw new NoSuchMethodException(new StringBuffer().append("Not found method "").append(string).append("" in class com.cxylk.UserServiceImpl.").toString());
}

@Override
public String[] getDeclaredMethodNames() {
    return dmns;
}

@Override
public String[] getPropertyNames() {
    return pns;
}

public Class getPropertyType(String string) {
    return (Class)pts.get(string);
}

@Override
public boolean hasProperty(String string) {
    return pts.containsKey(string);
}

@Override
public Object getPropertyValue(Object object, String string) {
    try {
        UserServiceImpl userServiceImpl = (UserServiceImpl)object;
    }
    catch (Throwable throwable) {
        throw new IllegalArgumentException(throwable);
    }
    throw new NoSuchPropertyException(new StringBuffer().append("Not found property "").append(string).append("" field or setter method in class com.cxylk.UserServiceImpl.").toString());
}

@Override
public void setPropertyValue(Object object, String string, Object object2) {
    try {
        UserServiceImpl userServiceImpl = (UserServiceImpl)object;
    }
    catch (Throwable throwable) {
        throw new IllegalArgumentException(throwable);
    }
    throw new NoSuchPropertyException(new StringBuffer().append("Not found property "").append(string).append("" field or setter method in class com.cxylk.UserServiceImpl.").toString());
}
}

可以看到,它是通过调用的方法名来返回具体的方法。

暴露流程:

在这里插入图片描述

服务引用

    @Test
    public void referTest(){
        //初始化代理协议
        RegistryProtocol protocol=new RegistryProtocol();
        protocol.setProtocol(new DubboProtocol());
        ZookeeperRegistryFactory registryFactory=new ZookeeperRegistryFactory();
        registryFactory.setZookeeperTransporter(new CuratorZookeeperTransporter());
        protocol.setRegistryFactory(registryFactory);

        //服务引用
        Invoker<UserService> invoker = protocol.refer(UserService.class, URL.valueOf(refUrlText));
        ProxyFactory proxyFactory=new JavassistProxyFactory();
        UserService userService = proxyFactory.getProxy(invoker);
        System.out.println(userService.getUser(111));
    }

注意服务暴露和服务引用中invoker的含义

在服务暴露中:

Invoker<UserService> invoker=proxyFactory.getInvoker(impl,UserService.class, URL.valueOf(urlTest));

在服务引用中:

Invoker<UserService> invoker = protocol.refer(UserService.class, URL.valueOf(refUrlText));

注意这两者实现的区别:

假设现在有一个用户发起调用,那么会先走UserService&proxy代理,然后到达服务引用中的invoker,然后经过远程通信,到达服务暴露中的invoker,这个invoker就会去调用UserServiceImpl,然后将结果按上面路线返回给客户

然后通过debug可以看看当前服务引用中的invoker:

在这里插入图片描述

directory也就是RegistryDirectory是注册表,看下它里面包含了什么:

在这里插入图片描述

invokers的数量取决于有几个服务的提供者,当前只开启一个服务,所以只有一个

而clients取决当前有几个连接,默认是1个,也就是我们前面提到过的共享连接,client底层就是nettyClient

现在我们开启三个服务,然后在客户端设置连接数为3

dubbo.consumer.connections=3

这个时候去客户端的controller层调用方法,看看当前userService包含了什么

在这里插入图片描述

可以很清楚的看到当前invokers有3个,因为我们开启了三个服务,clients有3个,因为我们设置连接数为3。

当发起调用时,在AbstractClusterInvoker类中的invoke方法就会获取注册表中的invokers

    @Override
    public Result invoke(final Invocation invocation) throws RpcException {
        checkWhetherDestroyed();

        // binding attachments into invocation.
        Map<String, Object> contextAttachments = RpcContext.getContext().getObjectAttachments();
        if (contextAttachments != null && contextAttachments.size() != 0) {
            ((RpcInvocation) invocation).addObjectAttachments(contextAttachments);
        }
		//获取注册表中的invokers
        List<Invoker<T>> invokers = list(invocation);
        LoadBalance loadbalance = initLoadBalance(invokers, invocation);
        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
        return doInvoke(invocation, invokers, loadbalance);
    }

list方法:

    protected List<Invoker<T>> list(Invocation invocation) throws RpcException {
        return directory.list(invocation);
    }

然后在invoke中的具体实现会交由子类的doInvoke方法完成,而子类的doInvoke方法又会调用select方法来根据从注册表中获取的invokers使用负载均衡算法进行选择具体的invoker,这个select方法在AbstractClusterInvoker类中实现,最终是由select方法中的doSelect完成

    private Invoker<T> doSelect(LoadBalance loadbalance, Invocation invocation,
                                List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {

        if (CollectionUtils.isEmpty(invokers)) {
            return null;
        }
        if (invokers.size() == 1) {
            return invokers.get(0);
        }
        //负载均衡
        Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation);

        //If the `invoker` is in the  `selected` or invoker is unavailable && availablecheck is true, reselect.
        if ((selected != null && selected.contains(invoker))
                || (!invoker.isAvailable() && getUrl() != null && availablecheck)) {
            try {
                Invoker<T> rInvoker = reselect(loadbalance, invocation, invokers, selected, availablecheck);
                if (rInvoker != null) {
                    invoker = rInvoker;
                } else {
                    //Check the index of current selected invoker, if it's not the last one, choose the one at index+1.
                    int index = invokers.indexOf(invoker);
                    try {
                        //Avoid collision
                        invoker = invokers.get((index + 1) % invokers.size());
                    } catch (Exception e) {
                        logger.warn(e.getMessage() + " may because invokers list dynamic change, ignore.", e);
                    }
                }
            } catch (Throwable t) {
                logger.error("cluster reselect fail reason is :" + t.getMessage() + " if can not solve, you can set cluster.availablecheck=false in url", t);
            }
        }
        return invoker;
    }

流程:

在这里插入图片描述

下面通过一张时序图来深入到源码中:

在这里插入图片描述

1.首先通过refer方法进入到doRefer方法中:

    private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
        RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
        //既然是注册表,那就需要设置注册中心,
        //然后从注册中心拿到url后要进行连接,所以还需要设置协议
        directory.setRegistry(registry);
        directory.setProtocol(protocol);
        // all attributes of REFER_KEY
        Map<String, String> parameters = new HashMap<String, String>(directory.getConsumerUrl().getParameters());
        URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
        if (directory.isShouldRegister()) {
            directory.setRegisteredConsumerUrl(subscribeUrl);
            //注册
            registry.register(directory.getRegisteredConsumerUrl());
        }
        directory.buildRouterChain(subscribeUrl);
        directory.subscribe(toSubscribeUrl(subscribeUrl));

        //通过注册表连接成一个invoker,这个invoker就是一个clusterInvoker
        //而在 clusterInvoker中有一个属性就是registrtDirectory   
        Invoker<T> invoker = cluster.join(directory);
        List<RegistryProtocolListener> listeners = findRegistryProtocolListeners(url);
        if (CollectionUtils.isEmpty(listeners)) {
            return invoker;
        }

        RegistryInvokerWrapper<T> registryInvokerWrapper = new RegistryInvokerWrapper<>(directory, cluster, invoker);
        for (RegistryProtocolListener listener : listeners) {
            listener.onRefer(this, registryInvokerWrapper);
        }
        return registryInvokerWrapper;
    }

上面方法的参数cluster就是默认使用的FailoverCluster,不过它使用了一个MockClusterWrapper进行了封装。

2.register最后是由doRegister来实现的(ZookeeperRegistry类中)

    @Override
    public void doRegister(URL url) {
        try {
            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);
        }
    }

可以看到,它会通过zkClient来创建一个临时节点,因为参数为true。

3.create后就要进行subscibe了,回到第一步的代码:

directory.subscribe(toSubscribeUrl(subscribeUrl));

首先是在RegistryProtocol中对RegistryDirectory进行订阅,然后RegistryDirectory又反过来对ZookeeperRegistry进行订阅,因为只有注册中心才提供订阅的能力

    //registryDirectory类中
	public void subscribe(URL url) {
        setConsumerUrl(url);
        CONSUMER_CONFIGURATION_LISTENER.addNotifyListener(this);
        serviceConfigurationListener = new ReferenceConfigurationListener(this, url);
        //对zookeeperRegistry进行订阅
        registry.subscribe(url, this);
    }

默认会进入FailbackRegistry中的subscribe方法:

@Override
    public void subscribe(URL url, NotifyListener listener) {
        super.subscribe(url, listener);
        //删除订阅失败的节点
        removeFailedSubscribed(url, listener);
        try {
            // Sending a subscription request to the server side
            doSubscribe(url, listener);
        } catch (Exception e) {
            Throwable t = e;

            List<URL> urls = getCacheUrls(url);
            if (CollectionUtils.isNotEmpty(urls)) {
                notify(url, listener, urls);
                logger.error("Failed to subscribe " + url + ", Using cached list: " + urls + " from cache file: " + getUrl().getParameter(FILE_KEY, System.getProperty("user.home") + "/dubbo-registry-" + url.getHost() + ".cache") + ", cause: " + t.getMessage(), t);
            } else {
                // If the startup detection is opened, the Exception is thrown directly.
                boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
                        && url.getParameter(Constants.CHECK_KEY, true);
                boolean skipFailback = t instanceof SkipFailbackWrapperException;
                if (check || skipFailback) {
                    if (skipFailback) {
                        t = t.getCause();
                    }
                    throw new IllegalStateException("Failed to subscribe " + url + ", cause: " + t.getMessage(), t);
                } else {
                    logger.error("Failed to subscribe " + url + ", waiting for retry, cause: " + t.getMessage(), t);
                }
            }

            // Record a failed registration request to a failed list, retry regularly
            addFailedSubscribed(url, listener);
        }
    }

上面方法传入了一个NotifyListener参数,这个NotifyListener表示当订阅事件触发之后执行监听操作。而当前类RegistryDirectory已经实现了NotifyListener这个类并且重写了notify方法

public class RegistryDirectory<T> extends AbstractDirectory<T> implements NotifyListener{...}

NotifyListener接口中的notify方法:

void notify(List<URL> urls);

所以这个listener就是RegistryDirectory本身。

4.进入ZookeeperRegistry中的doSubscribe方法,主要看这段实现:

List<URL> urls = new ArrayList<>();
                for (String path : toCategoriesPath(url)) {
                    //判断是否重复进行监听
                    ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.computeIfAbsent(url, k -> new ConcurrentHashMap<>());
                    //ChildListener,zookeeper本身还有个监听
                    ChildListener zkListener = listeners.computeIfAbsent(listener, k -> (parentPath, currentChilds) -> ZookeeperRegistry.this.notify(url, k, toUrlsWithEmpty(url, parentPath, currentChilds)));
                    zkClient.create(path, false);
                    List<String> children = zkClient.addChildListener(path, zkListener);
                    if (children != null) {
                        urls.addAll(toUrlsWithEmpty(url, path, children));
                    }
                }
                notify(url, listener, urls);

ChildListener接口

public interface ChildListener {

    void childChanged(String path, List<String> children);

}

就是去监听当前路径下变更的子节点。

5.notify方法

    @Override
    protected void notify(URL url, NotifyListener listener, List<URL> urls) {
        if (url == null) {
            throw new IllegalArgumentException("notify url == null");
        }
        if (listener == null) {
            throw new IllegalArgumentException("notify listener == null");
        }
        try {
            doNotify(url, listener, urls);
        } catch (Exception t) {
            // Record a failed registration request to a failed list, retry regularly
            //失败进行重新刷新
            addFailedNotified(url, listener, urls);
            logger.error("Failed to notify for subscribe " + url + ", waiting for retry, cause: " + t.getMessage(), t);
        }
    }

urls里面存放的都是提供者的一些信息,包括协议,地址,path等。

notify后就会进行第一次刷新,此时的NotifyListener就是RegistryDirectory。

进入doNotify方法

    protected void doNotify(URL url, NotifyListener listener, List<URL> urls) {
        super.notify(url, listener, urls);
    }

注意:这个时候会发现他调用了父类的notify方法,而父类的notify方法不是被子类重写了吗?也就是上面的notify方法,那不就是进入死循环了吗?

其实不会的,它会走父类AbstractRegistry的notify方法:

 protected void notify(URL url, NotifyListener listener, List<URL> urls) {
        ...
        Map<String, List<URL>> categoryNotified = notified.computeIfAbsent(url, u -> new ConcurrentHashMap<>());
        for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
            String category = entry.getKey();
            List<URL> categoryList = entry.getValue();
            categoryNotified.put(category, categoryList);
            listener.notify(categoryList);
            // We will update our cache file after each notification.
            // When our Registry has a subscribe failure due to network jitter, we can return at least the existing cache URL.
            saveProperties(url);
        }
    }

最终会进入RegistryDirectory的notify方法

    @Override
    public synchronized void notify(List<URL> urls) {
        //这里是对url进行验证
        Map<String, List<URL>> categoryUrls = urls.stream()
                .filter(Objects::nonNull)
                .filter(this::isValidCategory)
                .filter(this::isNotCompatibleFor26x)
                .collect(Collectors.groupingBy(this::judgeCategory));

        List<URL> configuratorURLs = categoryUrls.getOrDefault(CONFIGURATORS_CATEGORY, Collections.emptyList());
        this.configurators = Configurator.toConfigurators(configuratorURLs).orElse(this.configurators);

        List<URL> routerURLs = categoryUrls.getOrDefault(ROUTERS_CATEGORY, Collections.emptyList());
        toRouters(routerURLs).ifPresent(this::addRouters);

        // providers
        List<URL> providerURLs = categoryUrls.getOrDefault(PROVIDERS_CATEGORY, Collections.emptyList());
        /**
         * 3.x added for extend URL address
         */
        ExtensionLoader<AddressListener> addressListenerExtensionLoader = ExtensionLoader.getExtensionLoader(AddressListener.class);
        List<AddressListener> supportedListeners = addressListenerExtensionLoader.getActivateExtension(getUrl(), (String[]) null);
        if (supportedListeners != null && !supportedListeners.isEmpty()) {
            for (AddressListener addressListener : supportedListeners) {
                providerURLs = addressListener.notify(providerURLs, getConsumerUrl(),this);
            }
        }
        refreshOverrideAndInvoker(providerURLs);
    }

上面代码会对url进行分组,分别分成configurators,routers,providers

6.refreshOverrideAndInvoker方法实现刷新

    private void refreshOverrideAndInvoker(List<URL> urls) {
        // mock zookeeper://xxx?mock=return null
        overrideDirectoryUrl();
        refreshInvoker(urls);
    }

交给refreshInvoker方法实现

里面代码很长,关注最重要的实现:

Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map

...
    
    try {
        destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker
    } catch (Exception e) {
        logger.warn("destroyUnusedInvokers error. ", e);
    }

代码很长,首先是拿到url变成invoker,然后进行一些列的验证,最主要的是下面这行代码:

                    if (enabled) {
                        invoker = new InvokerDelegate<>(protocol.refer(serviceType, url), url, providerUrl);
                    }

上面这行代码才是真正去引用服务,建立连接,以及封装一个dubboInvoker

然后回到上一步的destroyUnusedInvokers方法,会根据参数来判断是否销毁作废的提供者。

只有在注册中心删除了提供者provider,那么才会去真正去销毁invoker,否则会进行尝试重连。

通过上面的分析,来看下整个服务集群的结构:
在这里插入图片描述
完整代码:github,码云

最后

以上就是魔幻花卷为你收集整理的dubbo学习笔记(8):注册中心源码剖析的全部内容,希望文章能够帮你解决dubbo学习笔记(8):注册中心源码剖析所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(55)

评论列表共有 0 条评论

立即
投稿
返回
顶部