在第四节的时候,我们分析了服务的暴露,当时消费者通常都是根据注册中心,然后通过负载均衡,再找到提供者的,所以提供者除了暴露自己之外,还有到注册中心去注册,做到彻底的暴露自己。

public <T> Exporter<T> com.alibaba.dubbo.registry.integration.RegistryProtocol#export(final Invoker<T> originInvoker) throws RpcException {
    //export invoker
    //暴露,暴露逻辑我们在第四节已经分析过了
    final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
    //获取注册中心地址,这里主要是将原本的registry,替换成用户定义的真正使用的协议,假设是redis
    //话又说回来了,为啥前面要将协议进行修改成registry,现在又改回来?
    //个人理解RegistryProtocol就像个中转站,不管是哪个协议,他们都需要通过一个统一的地方分发出去
    URL registryUrl = getRegistryUrl(originInvoker);

    //registry provider
    //获取协议对应的注册器,如果是redis,那么这个实例就是RedisRegistry
    final Registry registry = getRegistry(originInvoker);
    
    。。。。。。省略部分代码
}

下面来看注册器的构建

public RedisRegistry(URL url) {
    super(url);
    //注册中心地址必须配置
    if (url.isAnyHost()) {
        throw new IllegalStateException("registry address == null");
    }
    //配置redis连接池属性
    GenericObjectPoolConfig config = new GenericObjectPoolConfig();
    //true时,获取连接时是否检查连接的有效性,如果无效移除连接
    config.setTestOnBorrow(url.getParameter("test.on.borrow", true));
    //true时,归还连接时是否检查连接的有效性,如果无效移除连接
    config.setTestOnReturn(url.getParameter("test.on.return", false));
    //true时,获取连接时是否做空闲超时检查,超时的连接会被移除
    config.setTestWhileIdle(url.getParameter("test.while.idle", false));
    //最大空闲连接数,比如JDK的线程池的maxThreads(当没有任务做的时候)
    if (url.getParameter("max.idle", 0) > 0)
        config.setMaxIdle(url.getParameter("max.idle", 0));
    //最小空闲数,比如JDK线程池的minThreads(当没有任务做的时候,max线程超时被杀掉)
    if (url.getParameter("min.idle", 0) > 0)
        config.setMinIdle(url.getParameter("min.idle", 0));
    //最大连接数,比如JDK的maxThreads,最多就这么多了,不会涨了
    if (url.getParameter("max.active", 0) > 0)
        config.setMaxTotal(url.getParameter("max.active", 0));
        //和上面的是一样的
    if (url.getParameter("max.total", 0) > 0)
        config.setMaxTotal(url.getParameter("max.total", 0));
        //最大等待时间,连接池耗尽,最大等待获取连接的时间
    if (url.getParameter("max.wait", url.getParameter("timeout", 0)) > 0)
        config.setMaxWaitMillis(url.getParameter("max.wait", url.getParameter("timeout", 0)));
        //做空闲检查,每次的采样数
    if (url.getParameter("num.tests.per.eviction.run", 0) > 0)
        config.setNumTestsPerEvictionRun(url.getParameter("num.tests.per.eviction.run", 0));
        //空闲检查的采样周期
    if (url.getParameter("time.between.eviction.runs.millis", 0) > 0)
        config.setTimeBetweenEvictionRunsMillis(url.getParameter("time.between.eviction.runs.millis", 0));
        //连接的最小空闲时间,超过时间可能会被移除
    if (url.getParameter("min.evictable.idle.time.millis", 0) > 0)
        config.setMinEvictableIdleTimeMillis(url.getParameter("min.evictable.idle.time.millis", 0));
    //redis集群类型
    String cluster = url.getParameter("cluster", "failover");
    if (!"failover".equals(cluster) && !"replicate".equals(cluster)) {
        throw new IllegalArgumentException("Unsupported redis cluster: " + cluster + ". The redis cluster only supported failover or replicate.");
    }
    //复制
    replicate = "replicate".equals(cluster);

    List<String> addresses = new ArrayList<String>();
    //添加主机地址
    addresses.add(url.getAddress());
    //获取备份机地址
    String[] backups = url.getParameter(Constants.BACKUP_KEY, new String[0]);
    if (backups != null && backups.length > 0) {
        addresses.addAll(Arrays.asList(backups));
    }

    for (String address : addresses) {
        int i = address.indexOf(':');
        String host;
        int port;
        if (i > 0) {
            host = address.substring(0, i);
            port = Integer.parseInt(address.substring(i + 1));
        } else {
            host = address;
            port = DEFAULT_REDIS_PORT;
        }
        //redis地址 -》 JedisPool
        this.jedisPools.put(address, new JedisPool(config, host, port,
                url.getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT), StringUtils.isEmpty(url.getPassword()) ? null : url.getPassword(),
                url.getParameter("db.index", 0)));
    }
    //重连周期
    this.reconnectPeriod = url.getParameter(Constants.REGISTRY_RECONNECT_PERIOD_KEY, Constants.DEFAULT_REGISTRY_RECONNECT_PERIOD);
    String group = url.getParameter(Constants.GROUP_KEY, DEFAULT_ROOT);
    if (!group.startsWith(Constants.PATH_SEPARATOR)) {
        group = Constants.PATH_SEPARATOR + group;
    }
    if (!group.endsWith(Constants.PATH_SEPARATOR)) {
        group = group + Constants.PATH_SEPARATOR;
    }
    this.root = group;
    //session过期时间
    this.expirePeriod = url.getParameter(Constants.SESSION_TIMEOUT_KEY, Constants.DEFAULT_SESSION_TIMEOUT);
    //周期延长redis缓存过期时间
    this.expireFuture = expireExecutor.scheduleWithFixedDelay(new Runnable() {
        @Override
        public void run() {
            try {
                //(*1*)
                deferExpired(); // Extend the expiration time
            } catch (Throwable t) { // Defensive fault tolerance
                logger.error("Unexpected exception occur at defer expire time, cause: " + t.getMessage(), t);
            }
        }
    }, expirePeriod / 2, expirePeriod / 2, TimeUnit.MILLISECONDS);
}
//(*1*)
private void deferExpired() {
        //循环每台redis
        for (Map.Entry<String, JedisPool> entry : jedisPools.entrySet()) {
            JedisPool jedisPool = entry.getValue();
            try {
                //从每台机器的连接池中获取连接
                Jedis jedis = jedisPool.getResource();
                try {
                    for (URL url : new HashSet<URL>(getRegistered())) {
                        if (url.getParameter(Constants.DYNAMIC_KEY, true)) {
                            //gourpName+接口名+/+/category名
                            String key = toCategoryPath(url);
                            //将时间续上expirePeriod
                            if (jedis.hset(key, url.toFullString(), String.valueOf(System.currentTimeMillis() + expirePeriod)) == 1) {
                                //发布注册事件
                                jedis.publish(key, Constants.REGISTER);
                            }
                        }
                    }
                    if (admin) {
                        clean(jedis);
                    }
                    //如果不需要备份,那么直接跳出
                    if (!replicate) {
                        break;//  If the server side has synchronized data, just write a single machine
                    }
                } finally {
                    //释放连接
                    jedis.close();
                }
            } catch (Throwable t) {
                logger.warn("Failed to write provider heartbeat to redis registry. registry: " + entry.getKey() + ", cause: " + t.getMessage(), t);
            }
        }
    }
                                                                |
                                                                V
public FailbackRegistry(URL url) {
    super(url);
    //重试周期
    this.retryPeriod = url.getParameter(Constants.REGISTRY_RETRY_PERIOD_KEY, Constants.DEFAULT_REGISTRY_RETRY_PERIOD);
    this.retryFuture = retryExecutor.scheduleWithFixedDelay(new Runnable() {
        @Override
        public void run() {
            // Check and connect to the registry
            try {
                //重试未注册的成功的
                //重试未注销成功的
                //重试未订阅成功的
                //重试未退订成功的
                retry();
            } catch (Throwable t) { // Defensive fault tolerance
                logger.error("Unexpected error occur at failed retry, cause: " + t.getMessage(), t);
            }
        }
    }, retryPeriod, retryPeriod, TimeUnit.MILLISECONDS);
}
                                                                |
                                                                V
public AbstractRegistry(URL url) {
    //设置注册中心地址
    setUrl(url);
    // Start file save timer
    syncSaveFile = url.getParameter(Constants.REGISTRY_FILESAVE_SYNC_KEY, false);
    ///home/xxx/.dubbo/dubbo-registry-[用户在配置文件配置的应用名]+"-"+注册中心地址+".cache"
    String filename = url.getParameter(Constants.FILE_KEY, System.getProperty("user.home") + "/.dubbo/dubbo-registry-" + url.getParameter(Constants.APPLICATION_KEY) + "-" + url.getAddress() + ".cache");
    //如果不存在就创建一个
    File file = null;
    if (ConfigUtils.isNotEmpty(filename)) {
        file = new File(filename);
        if (!file.exists() && file.getParentFile() != null && !file.getParentFile().exists()) {
            if (!file.getParentFile().mkdirs()) {
                throw new IllegalArgumentException("Invalid registry store file " + file + ", cause: Failed to create directory " + file.getParentFile() + "!");
            }
        }
    }
    this.file = file;
    //加载这个file
    loadProperties();
    //调用NotifyListener的notify方法
    notify(url.getBackupUrls());
}

注册

public void com.alibaba.dubbo.registry.support.FailbackRegistry#register(URL url) {
    super.register(url);
    failedRegistered.remove(url);
    failedUnregistered.remove(url);
    try {
        // Sending a registration request to the server side
        doRegister(url);
    } catch (Exception e) {
       。。。。。。省略部分代码
        // Record a failed registration request to a failed list, retry regularly
        //添加到注册失败集合中,又调度线程重试
        failedRegistered.add(url);
    }
}

public void com.alibaba.dubbo.registry.redis.RedisRegistry#doRegister(URL url) {
    //group名(默认dubbo)/接口名/category名(默认providers)
    //  /dubbo/com.dubbo.service.UserService/providers
    String key = toCategoryPath(url);
    //提供者url
    String value = url.toFullString();
    //过期时间
    String expire = String.valueOf(System.currentTimeMillis() + expirePeriod);
    boolean success = false;
    RpcException exception = null;
    //循环每台redis机器
    for (Map.Entry<String, JedisPool> entry : jedisPools.entrySet()) {
        JedisPool jedisPool = entry.getValue();
        try {
            Jedis jedis = jedisPool.getResource();
            try {
                //key -》 服务提供者的地址
                jedis.hset(key, value, expire);
                //发布注册事件
                jedis.publish(key, Constants.REGISTER);
                success = true;
                //如果不是备份,那么直接注册完就结束
                if (!replicate) {
                    break; //  If the server side has synchronized data, just write a single machine
                }
            } finally {
                jedis.close();
            }
        } catch (Throwable t) {
            exception = new RpcException("Failed to register service to redis registry. registry: " + entry.getKey() + ", service: " + url + ", cause: " + t.getMessage(), t);
        }
    }
    if (exception != null) {
        if (success) {
            logger.warn(exception.getMessage(), exception);
        } else {
            throw exception;
        }
    }
}