工作流程
服务容器负责启动,加载,运行服务提供者。
- 服务提供者在启动时,向注册中心注册自己提供的服务。
- 服务消费者在启动时,向注册中心订阅自己所需的服务。
- 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
- 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
整体架构
注册中心实现类都是FailbackRegistery子类,
各类作用
Node接口
可视为客户端视角的注册中心节点
public interface Node {
/**
* get url.
*
* @return url.
*/
URL getUrl();
/**
* is available.
*
* @return available.
*/
boolean isAvailable();
/**
* destroy.
*/
void destroy();
}
RegistryService接口
抽象出注册方法,注册中心具体实现类需要重写该接口中方法
public interface RegistryService {
/*
注册数据,如:提供者服务、使用者地址、路由规则、覆盖规则等数据。
为支持合同,需要进行注册:
1. 当URL设置check=false参数时。当注册失败时,异常不会抛出,并在后台重试。否则,将抛出异常。
2. URL设置dynamic=false参数时,需要持久化存储,否则,当注册人异常退出时,需要自动删除。
3.当URL设置category=路由器时,表示分类存储,默认类别是提供商,数据可以通过分类部分通知。
4. 注册表重启时,网络抖动,数据不能丢失,包括自动删除虚线中的数据。
5. 允许URL相同但参数不同的URL共存,它们不能相互覆盖。
Params: url -注册信息,不允许为空,例如:dubbo://10.20.153.10/org.apache.dubbo.foo.BarService?version=1.0.0&application=kylin
*/
void register(URL url);
/*
为支持本合同,需要注销:
1. 如果它是dynamic=false的持久存储数据,则找不到注册数据,则抛出IllegalStateException,否则将忽略它。
2. Unregister according to the full url match.
Params: url -注册信息,不允许为空,例如:dubbo://10.20.153.10/org.apache.dubbo.foo.BarService?version=1.0.0&application=kylin
*/
void unregister(URL url);
/*
订阅符合条件的注册数据,并在注册数据更改时自动推送。
订阅需要支持契约:
1. 当URL设置check=false参数时。当注册失败时,异常不会抛出,并在后台重试
2. 当URL设置category=路由器时,只通知指定的分类数据。多个分类由逗号分隔,并允许星号匹配,这表示订阅了所有分类数据。
3.允许接口、组、版本和分类器作为条件查询,例如:interface=org.apache.dubbo.foo.BarService&version=1.0.0
4. 查询条件允许匹配星号,订阅所有接口的所有报文的所有版本,例如:interface=*&group=*&version=*&classifier=*
5。当注册表重新启动并且网络抖动时,有必要自动恢复订阅请求。
6. 允许URL相同但参数不同的URL共存,它们不能相互覆盖。
7. 当第一个通知完成并返回时,必须阻塞订阅过程。
Params: url -订阅条件,不允许为空,例如consumer://10.20.153.10/org.apache.dubbo.foo.BarService?version=1.0.0&application=kylin
listener -更改事件的监听器,不允许为空
*/
void subscribe(URL url, NotifyListener listener);
/*
为了支持合同,需要:
1.如果不订阅,直接忽略它。
2. 按完全URL匹配取消订阅。
Params:
url -订阅条件,不允许为空,例如consumer://10.20.153.10/org.apache.dubbo.foo.BarService?version=1.0.0&application=kylin
listener -更改事件的监听器,不允许为空
*/
void unsubscribe(URL url, NotifyListener listener);
/*
查询符合条件的注册数据。对应于订阅的推送模式,这是拉取模式,只返回一个结果
Params:
url—查询条件,不允许为空,例如consumer://10.20.153.10/org.apache.dubbo.foo.BarService?version=1.0.0&application=kylin
返回:
注册信息列表,可以为空,含义与NotifyListener.notify(list)参数相同。
*/
List<URL> lookup(URL url);
}
Registry 接口
dubbo2.7中没有定义任何方法,dubbo3.2中定义了四个抽象方法,不是重要的核心方法
AbstractRegistry抽象类
注册类的抽象实现,对上面三个接口中部分方法提供了简单实现,并在内部缓存注册信息,加载存储在磁盘的缓存数据
public abstract class AbstractRegistry implements Registry {
// URL地址分隔符,用于文件缓存、服务提供者URL分隔
private static final char URL_SEPARATOR = ' ';
// URL地址分离的正则表达式,用于解析文件缓存中的服务提供者URL列表
private static final String URL_SPLIT = "\s+";
// 重试将属性保存到本地缓存文件的最大次数
private static final int MAX_RETRY_TIMES_SAVE_PROPERTIES = 3;
// 缓存信息
private final Properties properties = new Properties();
// 是否同步保存缓存信息到本地磁盘
private final boolean syncSaveFile;
//当前缓存版本号
private final AtomicLong lastCacheChanged = new AtomicLong();
private final AtomicInteger savePropertiesRetryTimes = new AtomicInteger();
//已经注册的服务
private final Set<URL> registered = new ConcurrentHashSet<>();
//订阅服务的监听器列表
private final ConcurrentMap<URL, Set<NotifyListener>> subscribed = new ConcurrentHashMap<>();
//订阅服务的提供者列表
private final ConcurrentMap<URL, Map<String, List<URL>>> notified = new ConcurrentHashMap<>();
//注册中心地址
private URL registryUrl;
//本地磁盘缓存文件
private File file;
}
notified
内存中的服务缓存对象,notified的结构是CurrentHashMap内嵌一个Map,外层Map的Key为消费者的URL(包含组、方法、版本等),内层key是分类,包含providers、consumers、routes、configurations四种,value对应服务列表,对于没有服务提供者提供服务的URL,他会以特殊的empty://前缀开头
properties
用于本地磁盘缓存,HashTable子类,Properties保存了所有服务提供者的URL,使用{group}/{interfaceName}:{version}作为key,URL列表作为value。当value包含多个值时,使用空格隔开。
还有一个特殊的key.registies保存所有的注册中心的地址。
如果应用中心无法连接,则使用properties中的值
FailbackRegistry抽象类
public abstract class FailbackRegistry extends AbstractRegistry {
/* retry task map */
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>();
// ==== Template method ====
public abstract void doRegister(URL url);
public abstract void doUnregister(URL url);
public abstract void doSubscribe(URL url, NotifyListener listener);
public abstract void doUnsubscribe(URL url, NotifyListener listener);
}
注册类的抽象实现,对上面三个接口中部分方法提供了简单实现
定义了自动重试能力的注册中心服务的模板实现,提供给四个抽象方法用于定义具体实现
RegistryFactory接口
工厂模式
@SPI("dubbo")
public interface RegistryFactory {
/*
连接到注册表
连接注册表需要支持契约:
1. 当设置check=false时,不检查连接,否则在断开连接
2时抛出异常。支持URL上的用户名和密码权限认证。
3.支持备份=10.20.153.10候选注册表集群地址。
4. 支持文件=注册表。缓存本地磁盘文件缓存。
5. 支持timeout=1000请求超时设置。
6. 支持session=60000会话超时或过期设置。
参数:
url -注册表地址,不允许为空
返回:
注册表引用,从不返回空值
*/
@Adaptive({"protocol"})
Registry getRegistry(URL url);
}
NotifyListener接口
监听注册中心变更,用于推送模式,设定上与拉模式RegistryService.lookup(URL)对应
public interface NotifyListener {
/*
当收到服务变更通知时触发。
Notify需要支持契约:
1. 始终在服务接口和数据类型的维度上通知。也就是说,不会通知属于一个服务的同类型数据的一部分。用户不需要比较前一个通知的结果。
2. 订阅时的第一个通知必须是服务所有类型数据的完整通知。
3.在更改时,允许分别通知不同类型的数据,例如:提供者、消费者、路由器、覆盖。它只允许通知这些类型中的一种,但这种类型的数据必须是完整的,而不是增量的。
4. 如果一个数据类型
Params:
urls –列表中注册的信息,始终不是空的。其含义与org.apache.dubbo.registry.RegistryService.lookup(URL)的返回值相同。
*/
void notify(List<URL> urls);
}
服务
注册
流程概括
- 将当前服务的URL添加到已注册集合中,将当前URL的历史注册、取消注册失败重试任务删除
- 调用子类中实现的实际注册方法
- 如果开启了失败检测,取消注册失败则报错
否则则为当前URL添加失败重试任务,定期重试
取消注册
流程概括
- 将当前URL从已注册集合中移除,将当前URL的历史注册、取消注册失败充实任务删除
- 调用子类中实现的实际取消注册方法
- 如果开启了失败检测,注册失败则报错
否则则为当前URL添加失败重试任务,定期重试
服务订阅(推送模式)
订阅通常有pull和push两种方式,一种是客户端定时轮询注册中心拉取配置,另一种是注册中心主动推送数据给客户端。目前dubbo采用的是第一次启动拉取方式,后续接收事件重新拉取数据。
上述两种方式需要在子类中实现,dubbo中提供的抽象方法只负责记录和重试。
流程概括
- 在
ConcurrentMap<URL, Set<NotifyListener>> subscribed为当前服务添加一个监听器 - 将当前服务从订阅失败、取消订阅失败、notify失败集合中移除
- 调用子类中实现的实际订阅方法,拉取相关url列表,调用notify方法,缓存结果,结束
- 如果失败,使用 url中构造{group}/{interfaceName}:{version}格式的key,从磁盘缓存中拿到提供者URL列表
- 如果从磁盘中拿到的URL列表为空,且开启了检测,报错
- 如果不为空,对URL进行分类,分类notify,缓存结果
取消订阅
流程概括
- 将URL对应的Listener集合清空
- 将当前服务从订阅失败、取消订阅失败、notify失败集合中移除
- 调用子类实现的方法
- 调用失败,如果开启了检测,报错
将失败的注册请求记录到失败列表中,定期重试