学习《深入理解apache dubbo与实战》
总结:这一章,没啥内容,只是简单的介绍了一下dubbo实现注册中心,怎么使用zk,概念介绍,然后贴源码,虽然这注册中心其实没啥东西,无非就是讲服务注册到一个地方,然后好治理分配,调用这些服务,一个服务一个节点,对比Springcloud的注册中心,其实原理思想还是很多相同的
1,注册中心作用
1)动态加入,一个服务提供者通过注册中心可以动态把自己暴露给别人用,无须消费者一个个去更新配置文件。
2)动态发现,一个消费者可以动态的感知新的配置,路由规则和新的服务提供者,无须重启。
3)动态调整,注册中心支持参数的动态调整,新参数自动更新到所有相关服务节点
4)统一配置,避免了本地配置导致每个服务的配置不一致问题
dubbo主要有四种注册中心:zk,redis,simple,multicast,当然主流推荐的是zk。
2,zk
zk本身功能很强大,用于配置管理,名字服务,提供分布式同步以及集群管理。
书上其实基本没介绍过zk,网上有很多详细介绍,
zk介绍:
zk到底是干嘛的:
zk是树形结构的注册中心,每个节点的类型分为持久节点,持久顺序节点,临时节点,和临时顺序节点。
持久节点:服务注册后保证节点不会丢失,注册中心重启也会存在
持久顺序节点:在持久节点的特性的基础上增加了节点先后顺序的能力
临时节点:服务注册后连接丢失或者session超时,注册的节点会自动移除
临时顺序节点:看题意增加了顺序的能力
但是dubbo使用zk时,只有持久和临时节点,
dubbo在启动的时候注册中心会有四个目录:服务提供者目录providers,服务消费者目录coumer,路由配置目录routers,动态配置目录configurators
2.1 发布的实现
//zkClient类,如果我没找错的话,下面这两个方法就是注册与取消的,具体不清楚
//将服务注册到注册中心上去
//取消发布
@Override
public void delete(String path){
//never mind if ephemeral
persistentExistNodePath.remove(path);
deletePath(path);
}
//发布
@Override
public void create(String path, boolean ephemeral) {
if (!ephemeral) {
if(persistentExistNodePath.contains(path)){
return;
}
if (checkExists(path)) {
persistentExistNodePath.add(path);
return;
}
}
int i = path.lastIndexOf('/');
if (i > 0) {
create(path.substring(0, i), false);
}
if (ephemeral) {
createEphemeral(path);
} else {
createPersistent(path);
persistentExistNodePath.add(path);
}
}
2.2 订阅
订阅通常有pull和push两种方式,一种是客户端定时轮询注册中心拉去配置,一种是注册中心主动推送数据给客户端,目前dubbo采用的是定时轮询拉去。
在服务暴露时,服务端会订阅configurators用于监听动态配置,消费端启动时,消费端会订阅providers,routers和configurators三个目录,
zk注册中心采用的是事件通知+客户端拉取,客户端在第一次连接时,会获取对应目录下全量数据,并在订阅的节点上注册一个watcher,客户端与注册中心之间保持tcp长连接,只要有数据或者节点版本号变化,注册中心会根据watcher回调主动通知客户端,客户端再次拉取全量数据
全量订阅服务源码:
看不大懂,基本原理就是从注册中心上获取全量的服务信息,前面提到的四个目录中的东西,缓存到本地,防止一直请求注册中心,给注册中心压力,这就是下面说的缓存机制,我看的版本本地缓存只是缓存到同步map中,书上写的还有一份本地文件存在磁盘,但是我看的版本源码没有。
public void doSubscribe(final URL url, final NotifyListener listener) {
try {
if (ANY_VALUE.equals(url.getServiceInterface())) {
String root = toRootPath();
ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
//listeners为空,说明缓存里没有,则将listeners放入缓存
if (listeners == null) {
zkListeners.putIfAbsent(url, new ConcurrentHashMap<>());
listeners = zkListeners.get(url);
}
ChildListener zkListener = listeners.get(listener);
if (zkListener == null) {
listeners.putIfAbsent(listener, (parentPath, currentChilds) -> {
for (String child : currentChilds) {
child = URL.decode(child);
if (!anyServices.contains(child)) {
anyServices.add(child);
subscribe(url.setPath(child).addParameters(INTERFACE_KEY, child,
Constants.CHECK_KEY, String.valueOf(false)), listener);
}
}
});
zkListener = listeners.get(listener);
}
zkClient.create(root, false);
List<String> services = zkClient.addChildListener(root, zkListener);
if (CollectionUtils.isNotEmpty(services)) {
for (String service : services) {
service = URL.decode(service);
anyServices.add(service);
subscribe(url.setPath(service).addParameters(INTERFACE_KEY, service,
Constants.CHECK_KEY, String.valueOf(false)), listener);
}
}
} else {
List<URL> urls = new ArrayList<>();
for (String path : toCategoriesPath(url)) {
ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
if (listeners == null) {
zkListeners.putIfAbsent(url, new ConcurrentHashMap<>());
listeners = zkListeners.get(url);
}
ChildListener zkListener = listeners.get(listener);
if (zkListener == null) {
listeners.putIfAbsent(listener, (parentPath, currentChilds) -> ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds)));
zkListener = listeners.get(listener);
}
zkClient.create(path, false);
List<String> children = zkClient.addChildListener(path, zkListener);
if (children != null) {
urls.addAll(toUrlsWithEmpty(url, path, children));
}
}
notify(url, listener, urls);
}
} catch (Throwable e) {
throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
3,redis实现
略
4,缓存
消费者或者治理中心获取注册信息后会做本地缓存,防止注册中心压力过大,缓存在一个
ConcurrentMap<URL, ConcurrentMap<NotifyListener, ChildListener>>中
5,重试机制
在org.apache.dubbo.registry.support.FailbackRegistry类中定义了很多个集合,用于保存重试:
private final ConcurrentMap<URL, FailedRegisteredTask> failedRegistered = new ConcurrentHashMap<URL, FailedRegisteredTask>();
private final ConcurrentMap<URL, FailedUnregisteredTask> failedUnregistered = new ConcurrentHashMap<URL, FailedUnregisteredTask>();
private final ConcurrentMap<Holder, FailedSubscribedTask> failedSubscribed = new ConcurrentHashMap<Holder, FailedSubscribedTask>();
private final ConcurrentMap<Holder, FailedUnsubscribedTask> failedUnsubscribed = new ConcurrentHashMap<Holder, FailedUnsubscribedTask>();
private final ConcurrentMap<Holder, FailedNotifiedTask> failedNotified = new ConcurrentHashMap<Holder, FailedNotifiedTask>();
6,设计模式
整个注册中心的逻辑部分使用了模版模式
注册中心实现用的是工厂模式