redis支持很多的数据结构类型,key_value模式,内存存储数据,因此速度很快,效率很高;
dubbo利用JRedis来连接到Redis分布式哈希键-值数据库,JedisPool是一个线程安全的网络连接池。可以用JedisPool创建一些可靠Jedis实例,可以从池中获取Jedis实例,使用完后再把Jedis实例还回JedisPool。这种方式可以避免创建大量socket连接并且会实现高效的性能。
继承FailbackRegistry,基于redis实现注册,取消注册,订阅,取消订阅,时间通知等行为;
类基本属性:
public class RedisRegistry extends FailbackRegistry {
// 日志输出
private static final Logger logger = LoggerFactory.getLogger(RedisRegistry.class);
// redis默认port 6379
private static final int DEFAULT_REDIS_PORT = 6379;
private final static String DEFAULT_ROOT = "dubbo";
// 过其调度器
private final ScheduledExecutorService expireExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("DubboRegistryExpireTimer", true));
// 过期执行器
private final ScheduledFuture> expireFuture;
// port
private final String root;
// jedisPolol ip:port集合
private final Map jedisPools = new ConcurrentHashMap<>();
// 通知器 key root+service
private final ConcurrentMap notifiers = new ConcurrentHashMap<>();
private final int reconnectPeriod;
private final int expirePeriod;
private volatile boolean admin = false;
private boolean replicate;
初始化方法:
public RedisRegistry(URL url) {
// 父类方法
super(url);
if (url.isAnyHost()) {
throw new IllegalStateException("registry address == null");
}
// 全局配置加载
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
config.setTestOnBorrow(url.getParameter("test.on.borrow", true));
config.setTestOnReturn(url.getParameter("test.on.return", false));
config.setTestWhileIdle(url.getParameter("test.while.idle", false));
if (url.getParameter("max.idle", 0) > 0) {
config.setMaxIdle(url.getParameter("max.idle", 0));
}
if (url.getParameter("min.idle", 0) > 0) {
config.setMinIdle(url.getParameter("min.idle", 0));
}
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));
}
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(RemotingConstants.BACKUP_KEY, new String[0]);
if (ArrayUtils.isNotEmpty(backups)) {
addresses.addAll(Arrays.asList(backups));
}
// 创建JedisPool加入连接池
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;
}
// 创建jedisPools
this.jedisPools.put(address, new JedisPool(config, host, port,
url.getParameter(TIMEOUT_KEY, DEFAULT_TIMEOUT), StringUtils.isEmpty(url.getPassword()) ? null : url.getPassword(),
url.getParameter("db.index", 0)));
}
// URL中获取属性
this.reconnectPeriod = url.getParameter(REGISTRY_RECONNECT_PERIOD_KEY, DEFAULT_REGISTRY_RECONNECT_PERIOD);
String group = url.getParameter(GROUP_KEY, DEFAULT_ROOT);
if (!group.startsWith(PATH_SEPARATOR)) {
group = PATH_SEPARATOR + group;
}
if (!group.endsWith(PATH_SEPARATOR)) {
group = group + PATH_SEPARATOR;
}
this.root = group;
// 过期时间
this.expirePeriod = url.getParameter(SESSION_TIMEOUT_KEY, DEFAULT_SESSION_TIMEOUT);
// 过期key定时删除
this.expireFuture = expireExecutor.scheduleWithFixedDelay(() -> {
try {
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);
}
deferExpired过期前置处理:
// 过期前置操作:获取动态节点,重新注册
private void deferExpired() {
for (Map.Entry entry : jedisPools.entrySet()) {
JedisPool jedisPool = entry.getValue();
try {
try (Jedis jedis = jedisPool.getResource()) {
for (URL url : new HashSet<>(getRegistered())) {
if (url.getParameter(DYNAMIC_KEY, true)) {
String key = toCategoryPath(url);
// 对key进行延期
if (jedis.hset(key, url.toFullString(), String.valueOf(System.currentTimeMillis() + expirePeriod)) == 1) {
// 发布注册事件
jedis.publish(key, REGISTER);
}
}
}
// 如果中心管理
if (admin) {
clean(jedis);
}
// 如果不需要复制,则退出
if (!replicate) {
break;// If the server side has synchronized data, just write a single machine
}
}
} catch (Throwable t) {
logger.warn("Failed to write provider heartbeat to redis registry. registry: " + entry.getKey() + ", cause: " + t.getMessage(), t);
}
}
}
Clean:核心请求过期数据的逻辑
// 清理过期数据
private void clean(Jedis jedis) {
Set keys = jedis.keys(root + ANY_VALUE);
if (CollectionUtils.isNotEmpty(keys)) {
for (String key : keys) {
// 查询key,map:key:Provider,Consumer等 value 过期时间
Map values = jedis.hgetAll(key);
if (CollectionUtils.isNotEmptyMap(values)) {
boolean delete = false;
long now = System.currentTimeMillis();
for (Map.Entry entry : values.entrySet()) {
URL url = URL.valueOf(entry.getKey());
if (url.getParameter(DYNAMIC_KEY, true)) {
long expire = Long.parseLong(entry.getValue());
// 如果已经过期
if (expire < now) {
// 删除key
jedis.hdel(key, entry.getKey());
delete = true;
if (logger.isWarnEnabled()) {
logger.warn("Delete expired key: " + key + " -> value: " + entry.getKey() + ", expire: " + new Date(expire) + ", now: " + new Date(now));
}
}
}
}
// 发送取消注册时间
if (delete) {
jedis.publish(key, UNREGISTER);
}
}
}
}
}
public boolean isAvailable() 存活方法:
// 如果一个连接正常,表示存活
@Override
public boolean isAvailable() {
for (JedisPool jedisPool : jedisPools.values()) {
try (Jedis jedis = jedisPool.getResource()) {
if (jedis.isConnected()) {
return true; // At least one single machine is available.
}
} catch (Throwable t) {
}
}
return false;
}
销毁方法:
@Override
public void destroy() {
super.destroy();
try {
expireFuture.cancel(true);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
try {
for (Notifier notifier : notifiers.values()) {
notifier.shutdown();
}
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
for (Map.Entry entry : jedisPools.entrySet()) {
JedisPool jedisPool = entry.getValue();
try {
jedisPool.destroy();
} catch (Throwable t) {
logger.warn("Failed to destroy the redis registry client. registry: " + entry.getKey() + ", cause: " + t.getMessage(), t);
}
}
ExecutorUtil.gracefulShutdown(expireExecutor, expirePeriod);
}
注册逻辑:
@Override
public void doRegister(URL url) {
// /dubbo/org.apache.dubbo.test.injvmServie/providers
String key = toCategoryPath(url);
// redis://redis/org.apache.dubbo.test.injvmServie?methods=test1,test2¬ify=false
String value = url.toFullString();
String expire = String.valueOf(System.currentTimeMillis() + expirePeriod);
boolean success = false;
RpcException exception = null;
for (Map.Entry entry : jedisPools.entrySet()) {
JedisPool jedisPool = entry.getValue();
try {
// 存放redis, 发布注册信息
try (Jedis jedis = jedisPool.getResource()) {
jedis.hset(key, value, expire);
jedis.publish(key, REGISTER);
success = true;
if (!replicate) {
break; // If the server side has synchronized data, just write a single machine
}
}
} 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;
}
}
}
取消注册:
@Override
public void doUnregister(URL url) {
// 获取key1, key2
String key = toCategoryPath(url);
String value = url.toFullString();
RpcException exception = null;
boolean success = false;
for (Map.Entry entry : jedisPools.entrySet()) {
JedisPool jedisPool = entry.getValue();
try {
// 删除redis hash 发布取消注册事件
try (Jedis jedis = jedisPool.getResource()) {
jedis.hdel(key, value);
jedis.publish(key, UNREGISTER);
success = true;
if (!replicate) {
break; // If the server side has synchronized data, just write a single machine
}
}
} catch (Throwable t) {
exception = new RpcException("Failed to unregister 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;
}
}
}
订阅:
@Override
public void doSubscribe(final URL url, final NotifyListener listener) {
// 获取service,/dubbo/org.apache.dubbo.test.injvmServie
String service = toServicePath(url);
Notifier notifier = notifiers.get(service);
if (notifier == null) {
Notifier newNotifier = new Notifier(service);
notifiers.putIfAbsent(service, newNotifier);
notifier = notifiers.get(service);
// 保证只有一个启动
if (notifier == newNotifier) {
notifier.start();
}
}
boolean success = false;
RpcException exception = null;
for (Map.Entry entry : jedisPools.entrySet()) {
JedisPool jedisPool = entry.getValue();
try {
try (Jedis jedis = jedisPool.getResource()) {
// 如果订阅所有服务
if (service.endsWith(ANY_VALUE)) {
admin = true;
// 获取所有匹配到了key
// /dubbo/org.apache.dubbo.test.injvmServie/providers
Set keys = jedis.keys(service);
if (CollectionUtils.isNotEmpty(keys)) {
Map> serviceKeys = new HashMap<>();
for (String key : keys) {
String serviceKey = toServicePath(key);
Set sk = serviceKeys.computeIfAbsent(serviceKey, k -> new HashSet<>());
sk.add(key);
}
// /dubbo/org.apache.dubbo.test.injvmServie/providers
for (Set sk : serviceKeys.values()) {
doNotify(jedis, sk, url, Collections.singletonList(listener));
}
}
} else {
doNotify(jedis, jedis.keys(service + PATH_SEPARATOR + ANY_VALUE), url, Collections.singletonList(listener));
}
success = true;
break; // Just read one server's data
}
} catch (Throwable t) { // Try the next server
exception = new RpcException("Failed to subscribe service from redis registry. registry: " + entry.getKey() + ", service: " + url + ", cause: " + t.getMessage(), t);
}
}
if (exception != null) {
if (success) {
logger.warn(exception.getMessage(), exception);
} else {
throw exception;
}
}
}
Notify:监听
private void doNotify(Jedis jedis, String key) {
// 遍历所有的通知器,调用重载方法今天通知
for (Map.Entry> entry : new HashMap>(getSubscribed()).entrySet()) {
doNotify(jedis, Arrays.asList(key), entry.getKey(), new HashSet(entry.getValue()));
}
}
private void doNotify(Jedis jedis, Collection keys, URL url, Collection listeners) {
if (keys == null || keys.isEmpty()
|| listeners == null || listeners.isEmpty()) {
return;
}
long now = System.currentTimeMillis();
List result = new ArrayList();
// 获得分类集合
List categories = Arrays.asList(url.getParameter(Constants.CATEGORY_KEY, new String[0]));
// 通过url获得服务接口
String consumerService = url.getServiceInterface();
// 遍历分类路径,例如/dubbo/com.alibaba.dubbo.demo.DemoService/providers
for (String key : keys) {
// 判断服务是否匹配
if (!Constants.ANY_VALUE.equals(consumerService)) {
String prvoiderService = toServiceName(key);
if (!prvoiderService.equals(consumerService)) {
continue;
}
}
// 从分类路径上获得分类名
String category = toCategoryName(key);
// 判断订阅的分类是否包含该分类
if (!categories.contains(Constants.ANY_VALUE) && !categories.contains(category)) {
continue;
}
List urls = new ArrayList();
// 返回所有的URL集合
Map values = jedis.hgetAll(key);
if (values != null && values.size() > 0) {
for (Map.Entry entry : values.entrySet()) {
URL u = URL.valueOf(entry.getKey());
// 判断是否为动态节点,因为动态节点不受过期限制。并且判断是否过期
if (!u.getParameter(Constants.DYNAMIC_KEY, true)
|| Long.parseLong(entry.getValue()) >= now) {
// 判断url是否合法
if (UrlUtils.isMatch(url, u)) {
urls.add(u);
}
}
}
}
// 若不存在匹配的url,则创建 `empty://` 的 URL返回,用于清空该服务的该分类。
if (urls.isEmpty()) {
urls.add(url.setProtocol(Constants.EMPTY_PROTOCOL)
.setAddress(Constants.ANYHOST_VALUE)
.setPath(toServiceName(key))
.addParameter(Constants.CATEGORY_KEY, category));
}
result.addAll(urls);
if (logger.isInfoEnabled()) {
logger.info("redis notify: " + key + " = " + urls);
}
}
if (result == null || result.isEmpty()) {
return;
}
// 全部数据完成后,调用通知方法,来通知监听器
for (NotifyListener listener : listeners) {
notify(url, listener, result);
}
}
private class NotifySub extends JedisPubSub {
private final JedisPool jedisPool;
public NotifySub(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
// 时间通知行为
@Override
public void onMessage(String key, String msg) {
if (logger.isInfoEnabled()) {
logger.info("redis event: " + key + " = " + msg);
}
// 注册,取消注册
if (msg.equals(REGISTER)
|| msg.equals(UNREGISTER)) {
try {
Jedis jedis = jedisPool.getResource();
// 通知
try {
doNotify(jedis, key);
} finally {
jedis.close();
}
} catch (Throwable t) { // TODO Notification failure does not restore mechanism guarantee
logger.error(t.getMessage(), t);
}
}
}
@Override
public void onPMessage(String pattern, String key, String msg) {
// 响应
onMessage(key, msg);
}
// 自己可以扩展
@Override
public void onSubscribe(String key, int num) {
}
@Override
public void onPSubscribe(String pattern, int num) {
}
@Override
public void onUnsubscribe(String key, int num) {
}
@Override
public void onPUnsubscribe(String pattern, int num) {
}
}