总体介绍
RegistryFactory
//得到注册中心对象
@SPI("dubbo")
public interface RegistryFactory {
/**
* 1. 当设置check=false时表示不检查连接,否则在连接不上时抛出异常。
* 2. 支持URL上的username:password权限认证。
* 3. 支持backup=10.20.153.10备选注册中心集群地址。
* 4. 支持file=registry.cache本地磁盘文件缓存。
* 5. 支持timeout=1000请求超时设置。
* 6. 支持session=60000会话超时或过期设置
*/
@Adaptive({"protocol"}) //根据 url.protocol 获得对应的 RegistryFactory 实现类
Registry getRegistry(URL url);//例如url.protocol = zookeeper 时,获得 ZookeeperRegistryFactory 实现类。
}
复制代码
AbstractRegistryFactory
RegistryFactory的抽象实现类。实现 Registry 的容器管理。
// 锁[static] 用于 #destroyAll() 和 #getRegistry(url) 方法,对 REGISTRIES 访问的竞争。
private static final ReentrantLock LOCK = new ReentrantLock();
//Registry[static] 集合
private static final Map<String, Registry> REGISTRIES = new ConcurrentHashMap<String, Registry>();
//子类实现该方法,根据url创建其对应的 Registry 实现类。
//例如,ZookeeperRegistryFactory 的该方法,创建 ZookeeperRegistry 对象。
protected abstract Registry createRegistry(URL url);
@Override//获得注册中心 Registry 对象。优先从缓存中获取,否则进行创建。
public Registry getRegistry(URL url) {
url = url.setPath(RegistryService.class.getName())
.addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName())
.removeParameters(Constants.EXPORT_KEY, Constants.REFER_KEY);
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("Can not create registry " + url);
}
REGISTRIES.put(key, registry);
return registry;
} finally {
LOCK.unlock();
}
}
复制代码
RegistryService
//注册中心服务接口,定义了注册、订阅、查询三种操作方法
public interface RegistryService {
//注册数据,比如:提供者地址,消费者地址,路由规则,覆盖规则,等数据。
void register(URL url);
//订阅符合条件的已注册数据,当有注册数据变更时自动推送。
void subscribe(URL url, NotifyListener listener);
//查询符合条件的已注册数据,与订阅的推模式相对应,这里为拉模式,只返回一次结果。
List<URL> lookup(URL url);
}
复制代码
Registry
Registry ,注册中心接口。Registry 继承了:
RegistryService 接口,拥有拥有注册、订阅、查询三种操作方法。
Node 接口,拥有节点相关的方法。
复制代码
AbstractRegistry
public abstract class AbstractRegistry implements Registry {
// URL地址分隔符,用于文件缓存中,服务提供者URL分隔
private static final char URL_SEPARATOR = ' ';
// URL地址分隔正则表达式,用于解析文件缓存中服务提供者URL列表
private static final String URL_SPLIT = "\\s+";
// Log output
protected final Logger logger = LoggerFactory.getLogger(getClass());
//本地磁盘缓存。
//1. 其中特殊的 key 值 .registies 记录注册中心列表
// 2. 其它均为 {@link #notified} 服务提供者列表
private final Properties properties = new Properties();
// 注册中心缓存写入执行器。线程数=1
private final ExecutorService registryCacheExecutor = Executors.newFixedThreadPool(1, new NamedThreadFactory("DubboSaveRegistryCache", true));
// 是否同步保存文件
private final boolean syncSaveFile;
//数据版本号
private final AtomicLong lastCacheChanged = new AtomicLong();
//已注册 URL 集合。注意,注册的 URL 不仅仅可以是服务提供者的,也可以是服务消费者的
private final Set<URL> registered = new ConcurrentHashSet<URL>();
// 订阅 URL 的监听器集合
//key:消费者的 URL
private final ConcurrentMap<URL, Set<NotifyListener>> subscribed = new ConcurrentHashMap<URL, Set<NotifyListener>>();
//被通知的 URL 集合
//key1:消费者的 URL
// key2:分类,例如:providers、consumers、routes、configurators。【实际无 consumers ,因为消费者不会去订阅另外的消费者的列表】
private final ConcurrentMap<URL, Map<String, List<URL>>> notified = new ConcurrentHashMap<URL, Map<String, List<URL>>>();
private URL registryUrl;//注册中心 URL
// 本地磁盘缓存文件,缓存注册中心的数据
private File file;
public AbstractRegistry(URL url) {
setUrl(url);
// Start file save timer
syncSaveFile = url.getParameter(Constants.REGISTRY_FILESAVE_SYNC_KEY, false);
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;
loadProperties();// 加载本地磁盘缓存文件到内存缓存
notify(url.getBackupUrls());// 通知监听器,URL 变化结果
}
复制代码
FailbackRegistry
/**
* 支持失败重试的 Registry 抽象类。
* AbstractRegistry 进行的注册、订阅等操作,更多的是修改状态,而无和注册中心实际的操作。
* FailbackRegistry 在 AbstractRegistry 的基础上,实现了和注册中心实际的操作,并且支持失败重试的特性。
*
*/
public abstract class FailbackRegistry extends AbstractRegistry {
// failedXXX 属性 每种操作都有一个记录失败的集合。
// 定时任务执行器
private final ScheduledExecutorService retryExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("DubboRegistryFailedRetryTimer", true));
// 失败重试定时器,定时检查是否有请求失败,如有,无限次重试
private final ScheduledFuture<?> retryFuture;
//发起注册失败的 URL 集合
private final Set<URL> failedRegistered = new ConcurrentHashSet<URL>();
//取消注册失败的 URL 集合
private final Set<URL> failedUnregistered = new ConcurrentHashSet<URL>();
//发起订阅失败的监听器集合
private final ConcurrentMap<URL, Set<NotifyListener>> failedSubscribed = new ConcurrentHashMap<URL, Set<NotifyListener>>();
//取消订阅失败的监听器集合
private final ConcurrentMap<URL, Set<NotifyListener>> failedUnsubscribed = new ConcurrentHashMap<URL, Set<NotifyListener>>();
//失败通知的 URL 集合
private final ConcurrentMap<URL, Map<NotifyListener, List<URL>>> failedNotified = new ConcurrentHashMap<URL, Map<NotifyListener, List<URL>>>();
/**
* The time in milliseconds the retryExecutor will wait
*/
private final int retryPeriod;
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);
}
复制代码
NotifyListener
public interface NotifyListener {
/**
* 当收到服务变更通知时触发
* 1. 总是以服务接口和数据类型为维度全量通知,即不会通知一个服务的同类型的部分数据,用户不需要对比上一次通知结果。
* 2. 订阅时的第一次通知,必须是一个服务的所有类型数据的全量通知。
* 3. 中途变更时,允许不同类型的数据分开通知,比如:providers, consumers, routers, overrides,允许只通知其中一种类型,但该类型的数据必须是全量的,不是增量的。
* 4. 如果一种类型的数据为空,需通知一个empty协议并带category参数的标识性URL数据
* 5. 通知者(即注册中心实现)需保证通知的顺序,比如:单线程推送,队列串行化,带版本对比
*/
void notify(List<URL> urls);//urls 已注册信息列表,总不为空
}
复制代码