在第四节的时候,我们分析了服务的暴露,当时消费者通常都是根据注册中心,然后通过负载均衡,再找到提供者的,所以提供者除了暴露自己之外,还有到注册中心去注册,做到彻底的暴露自己。
public Exporter com.alibaba.dubbo.registry.integration.RegistryProtocol#export(final Invoker originInvoker) throws RpcException {
//export invoker
//暴露,暴露逻辑我们在第四节已经分析过了
final ExporterChangeableWrapper 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 addresses = new ArrayList();
//添加主机地址
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 entry : jedisPools.entrySet()) {
JedisPool jedisPool = entry.getValue();
try {
//从每台机器的连接池中获取连接
Jedis jedis = jedisPool.getResource();
try {
for (URL url : new HashSet(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 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;
}
}
}