注册中心可以是zookeeper、redis和dubbo
zookeeper的路径如上图所示,root下面有接口,接口下面有providers和consumers。
首先会注册节点.
消费者会订阅接口下的providers的所有子节点。一旦providers下的子节点发生改变,就会通知消息给消费者。
而监控中心订阅的是接口。
其中接口下会有四个子节点providers, consumers, routers, configurators
其中dubbo、接口、providers都是持久化节点,只有url是临时节点。当会话消失(服务器断开与zookeeper的连接),对应的临时节点会被删除。(利用zookeeper中的临时节点特性以及watch)
AbstractRegistryFactory 这个主要的目的是在获取注册中心时
public abstract class AbstractRegistryFactory implements RegistryFactory {
// 注册中心获取过程锁
private static final ReentrantLock LOCK = new ReentrantLock();
// 注册中心集合 注册中心地址-- Registry
private static final Map REGISTRIES = new
ConcurrentHashMap();
/**
* 获取所有注册中心
*
* @return 所有注册中心
*/
public static Collection getRegistries() {
return Collections.unmodifiableCollection(REGISTRIES.values());
}
public Registry getRegistry(URL url) {
/**
zookeeper://10.118.22.25:2181/com.alibaba.dubbo.registry.RegistryService?
application=testservice&dubbo=2.8.4&interface=
com.alibaba.dubbo.registry.RegistryService&logger=slf4j&pid=6668
×tamp=1524045346142
**/
url = url.setPath(RegistryService.class.getName())
.addParameter(Constants.INTERFACE_KEY,
RegistryService.class.getName())
.removeParameters(Constants.EXPORT_KEY,
Constants.REFER_KEY);
/**zookeeper://10.118.22.25:2181/com.alibaba.dubbo.registry.
RegistryService**/
String key = url.toServiceString();
// 锁定注册中心获取过程,保证注册中心单一实例
LOCK.lock();
try {
Registry registry = REGISTRIES.get(key);
if (registry != null) {
return registry;
}
registry = createRegistry(url);
if (registry == null) {
throw new IllegalStateException("...");
}
REGISTRIES.put(key, registry);
return registry;
} finally {
// 释放锁
LOCK.unlock();
}
}
/**
* 关闭所有已创建注册中心
*/
public static void destroyAll() {
// 锁定注册中心关闭过程
LOCK.lock();
try {
for (Registry registry : getRegistries()) {
try {
registry.destroy();
} catch (Throwable e) {
LOGGER.error(e.getMessage(), e);
}
}
REGISTRIES.clear();
} finally {
// 释放锁
LOCK.unlock();
}
}
//创建注册中心
protected abstract Registry createRegistry(URL url);
}
ZookeeperRegistryFactory提供生成ZookeeperRegistry对象.
属于多继承
ZookeeperRegistry – >FailbackRegistry --> AbstractRegistry
初始化:
1) 创建本地缓存文件,从缓存中读取到property中
2) 启动定时器,去重新注册、订阅等操作
3) 初始化zookeeper的客户端(zkClient)
AbstractRegistry构造函数
AbstractRegistry:主要是缓存注册中心里的地址到本地文件中
public abstract class AbstractRegistry implements Registry {
//本地磁盘缓
private final Properties properties = new Properties();
// 本地磁盘缓存文件
private File file;
public AbstractRegistry(URL url) {
this.registryUrl = url;
// 启动文件保存定时器
syncSaveFile = url.getParameter("save.file", false);
/**C:\Users\pc/.dubbo/dubbo-registry-10.118.22.25.cache
主机名+.dubbo/dubbo-registry-注册中心ip+.cache
**/
String filename = url.getParameter("file",
System.getProperty("user.home") + "/.dubbo/dubbo-registry-" +
url.getHost() + ".cache");
File file = null;
if (ConfigUtils.isNotEmpty(filename)) {
file = new File(filename);
//如果目录不存在,就创建目录 ..
}
this.file = file;
}
//将缓存文件载入到properties(注册中心zookeeper中的提供者接口)
}
FailbackRegistry
定时对失败的进行重试,主要针对于以下几种:
1) 注册失败
2)取消注册失败
3)订阅失败
4)取消订阅失败
5)通知失败
public abstract class FailbackRegistry extends AbstractRegistry {
// 失败重试定时器,定时检查是否有请求失败,如有,无限次重试
private final ScheduledFuture> retryFuture;
//注册失败
private final Set failedRegistered = new ConcurrentHashSet();
//取消注册失败
private final Set failedUnregistered = new ConcurrentHashSet();
//订阅失败
private final ConcurrentMap> failedSubscribed
= new ConcurrentHashMap>();
//取消订阅失败
private final ConcurrentMap> failedUnsubscribed
= new ConcurrentHashMap>();
//通知失败
private final ConcurrentMap>>
failedNotified = new ConcurrentHashMap>>();
public FailbackRegistry(URL url) {
super(url);
int retryPeriod = url.getParameter("retry.period",5 * 1000);
//启动定时器进行重连注册中心
this.retryFuture=retryExecutor.scheduleWithFixedDelay(new
Runnable() {
public void run() {
// 检测并连接注册中心
retry();
}
}, retryPeriod, retryPeriod, TimeUnit.MILLISECONDS);
}
//重试失败的动作 (如果中间出现异常,忽略等待重试)
protected void retry() {
//注册
if (! failedRegistered.isEmpty()) {
//遍历failedRegistered并且从中删除
doRegister(url);
}
//取消注册
if(! failedUnregistered.isEmpty()) {
doUnregister(url);
}
//订阅
if (! failedSubscribed.isEmpty()) {
doSubscribe(url, listener);
}
//取消订阅
if (! failedUnsubscribed.isEmpty()) {
doUnsubscribe(url, listener);
}
//通知
if (! failedNotified.isEmpty()) {
NotifyListener listener = entry.getKey();
List urls = entry.getValue();
listener.notify(urls);
}
}
}
**ZookeeperRegistry **
主要处理Zookeeper,客户端有两种,Curator和ZkClient,默认是ZkClient
public class ZookeeperRegistry extends FailbackRegistry {
private final String root;
private final Set anyServices = new ConcurrentHashSet();
private final ConcurrentMap> zkListeners = new
ConcurrentHashMap>();
//zoookeeper客户端(默认是zkclient)
private final ZookeeperClient zkClient;
public ZookeeperRegistry(URL url, ZookeeperTransporter
zookeeperTransporter) {
super(url);
//根节点,默认是/dubbo
this.root = group;
//zookeeperTransporter客户端有Curator和ZkClient,默认是ZkClient
zkClient = zookeeperTransporter.connect(url);
//监听如果重连了,那么
zkClient.addStateListener(new StateListener() {
public void stateChanged(int state) {
if (state == RECONNECTED) {
try {
//将所有注册的地址放入到注册失败的集合中
//将所有订阅的地址放入到订阅失败的集合中
recover();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}
});
}
}
消费者或者提供者在暴露服务或者引用服务时,会往zookeeper上注册节点。
主要做了以下几步:
1)记录注册注册地址
2) 注册节点到zookeeper上
3) 捕捉错误信息,出错则记录下来,等待定期器去重新执行
private final Set registered = new ConcurrentHashSet();
public void register(URL url) {
registered.add(url);
}
public abstract class FailbackRegistry extends AbstractRegistry {
public void register(URL url) {
super.register(url);
//从注册失败集合中删除
failedRegistered.remove(url);
failedUnregistered.remove(url);
try {
// 向服务器端发送注册请求
doRegister(url);
} catch (Exception e) {
Throwable t = e;
// 如果开启了启动时检测,则直接抛出异常....
// 将失败的注册请求记录到失败列表,定时重试
failedRegistered.add(url);
}
}
}
public class ZookeeperRegistry extends FailbackRegistry {
protected void doRegister(URL url) {
/**
/consumer://10.118.14.204/com.test.ITest?application=ec-service-impl..
向zookeeper中注册临时文件节点/dubbo/com.test.ITest/consumers,
值就是consumer://10.118.14.204/com.test.ITest?application=..
/dubbo/com.test.ITest/consumers
*/
zkClient.create(toUrlPath(url), url.getParameter("dynamic", true));
}
}
消费者在引用服务时,会订阅接口下的providers的节点。一旦providers下的子节点发生改变(提供者的服务器增加或者删除),会通知到消费者。消费者会把提供者的集群地址缓存到本地。
主要做了以下几步操作(以具体接口为例)
如果消费者的接口没有创建过子节点监听器,那么就创建子节点监听器
创建路径节点,并将子节点监听器放入到节点上。(一旦子节点发生改变,就通知)
获取到当前路径节点下的所有子节点(提供者),将这些子节点组装成集合,如果没有节点,那么就将消费者的地址的协议变成empty
empty://10.118.14.204/com.test.ITestService?application=testservice&category=configurators
通知
//订阅
private final ConcurrentMap> subscribed =
new ConcurrentHashMap>();
public void subscribe(URL url, NotifyListener listener) {
Set listeners = subscribed.get(url);
if (listeners == null) {
subscribed.putIfAbsent(url,
new ConcurrentHashSet());
listeners = subscribed.get(url);
}
listeners.add(listener);
}
public void subscribe(URL url, NotifyListener listener) {
super.subscribe(url, listener);
removeFailedSubscribed(url, listener);
try {
// 向服务器端发送订阅请求
doSubscribe(url, listener);
} catch (Exception e) {
Throwable t = e;
List urls = getCacheUrls(url);
if (urls != null && urls.size() > 0) {
notify(url, listener, urls);
}
// 将失败的订阅请求记录到失败列表,定时重试
addFailedSubscribed(url, listener);
}
}
ZookeeperRegistry
接口是 (对所有的接口进行订阅,有点类似于递归订阅)*
接口是具体 (以providers为例)
1)将接口名称转变成/dubbo/com.test.ITestService/providers,集合中没有没有providers的子节点监听器,就创建子节点监听器。一旦子节点发生改变,那么就通知
总结
当消费者要订阅接口中的提供者时
会监听/dubbo/xxService/providers下的所有提供者。一旦提供者的节点删除或增加时,都会通知到消费者的url(consumer://10.118.14.204/com.test.ITestService…)
它会监听以下三个节点的子节点
1) /dubbo/xxService/providers
2)/dubbo/xxService/configurators
3)/dubbo/xxService/routers
组装的url集合(即提供者的子节点providers,configurators,routers下的子节点)。如果没有子节点(没有提供者),那么就将消费者的协议变成empty作为url。
//存放子节点的监听器
private final ConcurrentMap> zkListeners = new ConcurrentHashMap>();
protected void doSubscribe(final URL url, final NotifyListener listener) {
//接口名称(*代表需要监听root下面的所有节点)
if ("*".equals(url.getServiceInterface())) {
ConcurrentMap listeners = zkListeners.
get(url);
//如果listeners为空创建并放入到map中...
ChildListener zkListener = listeners.get(listener);
/**
root下的子节点是service接口
创建子节点监听器,对root下的子节点做监听,一旦有子节点发生改变,
那么就对这个节点进行订阅.
**/
if (zkListener == null) {
listeners.putIfAbsent(listener, new ChildListener() {
public void childChanged(String parentPath, List
currentChilds) {
for (String child : currentChilds) {
//如果不存在,才订阅
if (! anyServices.contains(child)) {
anyServices.add(child);
//订阅
subscribe(url.setPath(child).addParameters(
"interface", child,"check", "false"), listener);
}
}
}
});
zkListener = listeners.get(listener);
}
//创建root节点
zkClient.create(root, false);
//添加root节点的子节点监听器,并返回当前的services
List services = zkClient.addChildListener(root, zkListener);
if (services != null && services.size() > 0) {
//对root下的所有service节点进行订阅
for (String service : services) {
service = URL.decode(service);
anyServices.add(service);
subscribe(url.setPath(service).addParameters("interface",
service, "check", "false"), listener);
}
}
} else {
List urls = new ArrayList();
/**将url转变成
/dubbo/com.test.ITestService/providers
/dubbo/com.test.ITestService/configurators
/dubbo/com.test.ITestService/routers
**/
for (String path : toCategoriesPath(url)) {
ConcurrentMap listeners =
zkListeners.get(url);
//如果listeners为空就创建并放入盗map中
ChildListener zkListener = listeners.get(listener);
/**
对接口下的providers的子节点进行监听,一旦发生改变,就通知
**/
if (zkListener == null) {
listeners.putIfAbsent(listener, new ChildListener() {
public void childChanged(String parentPath, List
currentChilds) {
//通知
ZookeeperRegistry.this.notify(url, listener,
toUrlsWithEmpty(url, parentPath, currentChilds));
}
});
zkListener = listeners.get(listener);
}
//创建/dubbo/com.test.ITestService/providers
zkClient.create(path, false);
//获取到providers的所有子节点(提供者)
List children = zkClient.addChildListener(path,
zkListener);
//获取到所有的提供者,组装起来
if (children != null) {
//有子节点组装,没有那么就将消费者的协议变成empty作为url。
urls.addAll(toUrlsWithEmpty(url, path, children));
}
}
//通知/dubbo/com.test.ITestService/providers的所有子节点
notify(url, listener, urls);
}
}
/**
根据url获取到哪些类型
consumer://10.118.14.204/com.test.ITestService?application=testservice
&category=providers,configurators,routers&...
这里的category是重点
**/
private String[] toCategoriesPath(URL url) {
String[] categroies;
//如果是*
if ("*".equals(url.getParameter(Constants.CATEGORY_KEY)))
categroies = new String[] {"providers", "consumers",
"routers", "configurators"};
else
//从url获取到category的值,没有的话就默认providers
categroies = url.getParameter("category",
new String[] {"providers"});
String[] paths = new String[categroies.length];
//将格式转变成/dubbo/xxService/类型
for (int i = 0; i < categroies.length; i ++) {
paths[i] = toServicePath(url) + "/" + categroies[i];
}
return paths;
}
/**
组装providers、routers、configurators下的url。
如果有提供者那么就组装;没有的话,就将消费者的协议变成empty
**/
private List toUrlsWithEmpty(URL consumer, String path, List providers) {
List urls = toUrlsWithoutEmpty(consumer, providers);
if (urls.isEmpty()) {
int i = path.lastIndexOf('/');
String category = i < 0 ? path : path.substring(i + 1);
URL empty = consumer.setProtocol("empty").addParameter(
"category", category);
urls.add(empty);
}
return urls;
}
有三个参数
url: 消费者的地址 consumer://10.118.14.204/com…
listener: 监听器
urls: providers,configurators和routers
1)写入到本地缓存文件中
文件名称:
2) 监听器通知
protected void notify(URL url, NotifyListener listener, List urls) {
try {
doNotify(url, listener, urls);
} catch (Exception t) {
// 将失败的通知请求记录到失败列表,定时重试..
Map> listeners = failedNotified.get(url);
}
}
AbstractRegistry
urls三种
1) providers(providers下的子节点)
dubbo://10.118.22.29:20710/com.test.ITestService?anyhost=true&application=testservice&default.cluster=failfast…
configurators(configurators下的子节点为空,将消费者的url变成empty )
empty://10.118.14.204/com.test.ITestService?application=testservice&category=configurators&default.check=false…
routers(routers下的子节点为空,将消费者的url变成empty)
empty://10.118.14.204/com.test.ITestService?application=testservice&category=routers&default.check=false…
protected void notify(URL url, NotifyListener listener, List urls) {
Map> result = new HashMap>();
//根据url中的category分割
Map> categoryNotified = notified.get(url);
//空的话就创建并且放入到map
for (Map.Entry> entry : result.entrySet()) {
String category = entry.getKey();
List categoryList = entry.getValue();
//将url写入到本地缓存中
saveProperties(url);
//监听器通知url
listener.notify(categoryList);
}
}
组装url保存到properties中,如果是同步,直接保存到本地缓存文件中,否则文件缓存定时写入
private void saveProperties(URL url) {
try {
//根据url取出所有的地址
StringBuilder buf = new StringBuilder();
Map> categoryNotified = notified.get(url);
if (categoryNotified != null) {
for (List us : categoryNotified.values()) {
for (URL u : us) {
if (buf.length() > 0) {
buf.append(URL_SEPARATOR);
}
buf.append(u.toFullString());
}
}
}
//放入到properties中
properties.setProperty(url.getServiceKey(), buf.toString());
long version = lastCacheChanged.incrementAndGet();
if (syncSaveFile) {
//直接保存文件缓存
doSaveProperties(version);
} else {
//文件缓存定时写入
registryCacheExecutor.execute(new SaveProperties(version));
}
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
通过 AtomicLong 来控制锁
首先会有个dubbo-registry-10.118.22.25.cache.lock,会获取这个文件的锁,然后保存dubbo-registry-10.118.22.25.cache文件,再释放锁。
public void doSaveProperties(long version) {
if(version < lastCacheChanged.get()){
return;
}
Properties newProperties = new Properties();
// 保存之前先读取一遍file,防止多个注册中心之间冲突...
// 保存
try {
newProperties.putAll(properties);
//首先会有个空文件dubbo-registry-10.118.22.25.cache.lock
File lockfile = new File(file.getAbsolutePath() + ".lock");
if (!lockfile.exists()) {
lockfile.createNewFile();
}
RandomAccessFile raf = new RandomAccessFile(lockfile, "rw");
try {
FileChannel channel = raf.getChannel();
try {
//获取到lock文件的锁
FileLock lock = channel.tryLock();
try {
// 将Properties保存到file中
} finally {
lock.release();
}
} finally {
channel.close();
}
} finally {
raf.close();
}
} catch (Throwable e) {
//出错了再重新保存
if (version < lastCacheChanged.get()) {
return;
} else {
registryCacheExecutor.execute(new SaveProperties
(lastCacheChanged.incrementAndGet()));
}
}
}
当收到提供者的地址发生改变时,这时刷新缓存中的invoker,如果url不存在,那么重新refer(根据dubbo协议)
通知需处理契约:
1. 总是以服务接口和数据类型为维度全量通知,即不会通知一个服务的同类型的部分数据,用户不需要对比上一次通知结果。
2. 订阅时的第一次通知,必须是一个服务的所有类型数据的全量通知。
3. 中途变更时,允许不同类型的数据分开通知,比如:providers, consumers, routers, overrides,允许只通知其中一种类型,但该类型的数据必须是全量的,不是增量的。
4. 如果一种类型的数据为空,需通知一个empty协议并带category参数的标识性URL数据。
5. 通知者(即注册中心实现)需保证通知的顺序,比如:单线程推送,队列串行化,带版本对比。
public synchronized void notify(List urls) {
//providers
List invokerUrls = new ArrayList();
//router
List routerUrls = new ArrayList();
//configurator
List configuratorUrls = new ArrayList();
//循环url,根据category放入到对应的集合中...
// 处理configurators,去掉empty
if (configuratorUrls != null && configuratorUrls.size() >0 ){
this.configurators = toConfigurators(configuratorUrls);
}
// 处理routers,去掉empty
if (routerUrls != null && routerUrls.size() >0 ){
List routers = toRouters(routerUrls);
if(routers != null){ // null - do nothing
setRouters(routers);
}
}
List localConfigurators = this.configurators;
// 合并override参数
this.overrideDirectoryUrl = directoryUrl;
if (localConfigurators != null && localConfigurators.size() > 0) {
for (Configurator configurator : localConfigurators) {
this.overrideDirectoryUrl = configurator.configure(
overrideDirectoryUrl);
}
}
// providers
refreshInvoker(invokerUrls);
}
private void refreshInvoker(List invokerUrls){
//如果只有一个empty,那么就禁止访问
if (invokerUrls != null && invokerUrls.size() == 1
&& invokerUrls.get(0) != null
&& "empty".equals(invokerUrls.get(0).getProtocol())) {
this.forbidden = true; // 禁止访问
this.methodInvokerMap = null; // 置空列表
destroyAllInvokers(); // 关闭所有Invoker
} else {
this.forbidden = false; // 允许访问
//...
this.cachedInvokerUrls.addAll(invokerUrls);
if (invokerUrls.size() ==0 ){
return;
}
Map> oldUrlInvokerMap = this.urlInvokerMap;
// 将URL列表转成Invoker列表
Map> newUrlInvokerMap = toInvokers(invokerUrls) ;
// 换方法名映射Invoker列表
Map>> newMethodInvokerMap =
toMethodInvokers(newUrlInvokerMap);
// state change
//如果计算错误,则不进行处理.
if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0 ){
return ;
}
this.methodInvokerMap = multiGroup ?
toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;
this.urlInvokerMap = newUrlInvokerMap;
// 关闭未使用的Invoker
destroyUnusedInvokers(oldUrlInvokerMap,newUrlInvokerMap);
}
}
/**
* 将urls转成invokers,如果url已经被refer过,不再重新引用。
* 如果没有那么需要refer
* protocol.refer(serviceType, url)
根据协议dubbo,所以他是DubboProtocol
DubboInvoker invoker = new DubboInvoker
(serviceType, url, getClients(url), invokers);
*/
private Map> toInvokers(List urls) {
Map> newUrlInvokerMap = new HashMap>();
if(urls == null || urls.size() == 0){
return newUrlInvokerMap;
}
Set keys = new HashSet();
String queryProtocols = this.queryMap.get(Constants.PROTOCOL_KEY);
for (URL providerUrl : urls) {
//如果reference端配置了protocol,则只选择匹配的protocol
if ("empty".equals(providerUrl.getProtocol())) {
continue;
}
//没有这个指定的协议,那么报错
if (! ExtensionLoader.getExtensionLoader(Protocol.class).
hasExtension(providerUrl.getProtocol())) {
continue;
}
//合并url参数 顺序为override > -D >Consumer > Provider
URL url = mergeUrl(providerUrl);
// URL参数是排序的
String key = url.toFullString();
// 重复URL,过滤
Map> localUrlInvokerMap = this.urlInvokerMap;
Invoker invoker = localUrlInvokerMap == null ? null :
localUrlInvokerMap.get(key);
// 缓存中没有,重新refer
if (invoker == null) {
boolean enabled = true;
//根据url获取参数disabled或enabled
if (enabled) {
//重新refer
invoker = new InvokerDelegete(
protocol.refer(serviceType, url), url, providerUrl);
}
if (invoker != null) { // 将新的引用放入缓存
newUrlInvokerMap.put(key, invoker);
}
}else {
newUrlInvokerMap.put(key, invoker);
}
}
keys.clear();
return newUrlInvokerMap;
}