包结构:
正如上图所示,一个注册抽象类ServiceRegistry和三个实现类LocalServiceRegistry,XxlRegistryServiceRegistry,ZkServiceRegistry
ServiceRegistry 抽象类,定义了注册客户端启动,停止,注册,发现的抽象方法。很简单。
从注册,发现方法中可以看出,作者使用TreeSet来存储着某个服务的所有地址。为啥使用这个数据结构呢?这是与后面的负载均衡里面的一致性哈希算法有关。
public abstract class ServiceRegistry {
/**
* start 启动
*/
public abstract void start(Map param);
/**
* 停止
*/
public abstract void stop();
/**
* registry service, for mult
*
* @param keys service key
* @param value service value/ip:port
* @return
*/
public abstract boolean registry(Set keys, String value);
/**
* remove service, for mult
*
* @param keys
* @param value
* @return
*/
public abstract boolean remove(Set keys, String value);
/**
* discovery services, for mult
*
* @param keys
* @return
*/
public abstract Map> discovery(Set keys);
/**
* discovery service, for one
*
* @param key service key
* @return service value/ip:port
*/
public abstract TreeSet discovery(String key);
}
LocalServiceRegistry 继承ServiceRegistry 实现所有抽象方法。将服务注册到本地(项目内)。
就一个registryData 成员,因为本项目中,所以发现数据与注册数据就用同一个数据结构存储就行了。这个应用场景很少
/**
* registry data // 存放注册数据
*/
private Map> registryData;
start,stop 方法,start 实例化一个registryData,stop 就清空里面内容。
@Override
public void start(Map param) {
registryData = new HashMap>();
}
@Override
public void stop() {
registryData.clear();
}
服务注册方法registry:
这个方法也很简单,就是往registryData中增量注册。有一点不明白的就是为啥key是多个。。。
@Override
public boolean registry(Set keys, String value) {
if (keys==null || keys.size()==0 || value==null || value.trim().length()==0) {
return false;
}
for (String key : keys) {
TreeSet values = registryData.get(key);
if (values == null) {
values = new TreeSet<>();
registryData.put(key, values);
}
values.add(value);
}
return true;
}
移除方法:
也很简单,就是map里面remove
@Override
public boolean remove(Set keys, String value) {
if (keys==null || keys.size()==0 || value==null || value.trim().length()==0) {
return false;
}
for (String key : keys) {
TreeSet values = registryData.get(key);
if (values != null) {
values.remove(value);
}
}
return true;
}
服务发现:
有两个重载方法 一个是获取多个key的一个是获取一个key的
@Override
public Map> discovery(Set keys) {
if (keys==null || keys.size()==0) {
return null;
}
Map> registryDataTmp = new HashMap>();
for (String key : keys) {
TreeSet valueSetTmp = discovery(key);
if (valueSetTmp != null) {
registryDataTmp.put(key, valueSetTmp);
}
}
return registryDataTmp;
}
@Override
public TreeSet discovery(String key) {
return registryData.get(key);
}
这个使用的注册中心是作者自己开源的。看使用代码感觉很好上手,api很简单。
public static final String XXL_REGISTRY_ADDRESS = "XXL_REGISTRY_ADDRESS";
public static final String ACCESS_TOKEN = "ACCESS_TOKEN";
public static final String BIZ = "BIZ";
public static final String ENV = "ENV";
private XxlRegistryClient xxlRegistryClient;
public XxlRegistryClient getXxlRegistryClient() {
return xxlRegistryClient;
}
@Override
public void start(Map param) {
// 创建客户端,用于交互
String xxlRegistryAddress = param.get(XXL_REGISTRY_ADDRESS);
String accessToken = param.get(ACCESS_TOKEN);
String biz = param.get(BIZ);
String env = param.get(ENV);
// fill
biz = (biz!=null&&biz.trim().length()>0)?biz:"default";
env = (env!=null&&env.trim().length()>0)?env:"default";
xxlRegistryClient = new XxlRegistryClient(xxlRegistryAddress, accessToken, biz, env);
}
@Override
public void stop() {
// 停止 客户端
if (xxlRegistryClient != null) {
xxlRegistryClient.stop();
}
}
@Override
public boolean registry(Set keys, String value) {
if (keys==null || keys.size() == 0 || value == null) {
return false;
}
// init
List registryDataList = new ArrayList<>();
for (String key:keys) {
registryDataList.add(new XxlRegistryDataParamVO(key, value));
}
return xxlRegistryClient.registry(registryDataList);
}
@Override
public boolean remove(Set keys, String value) {
if (keys==null || keys.size() == 0 || value == null) {
return false;
}
// init
List registryDataList = new ArrayList<>();
for (String key:keys) {
registryDataList.add(new XxlRegistryDataParamVO(key, value));
}
return xxlRegistryClient.remove(registryDataList);
}
@Override
public Map> discovery(Set keys) {
return xxlRegistryClient.discovery(keys);
}
@Override
public TreeSet discovery(String key) {
return xxlRegistryClient.discovery(key);
}
使用zookeeper作为注册中心。
官网文档给出的存储结构图:
原理:
XXL-RPC中每个服务在zookeeper中对应一个节点,如图"iface name"节点,该服务的每一个provider机器对应"iface name"节点下的一个子节点,如图中"192.168.0.1:9999"、“192.168.0.2:9999"和"192.168.0.3:9999”,子节点类型为zookeeper的EPHMERAL类型,该类型节点有个特点,当机器和zookeeper集群断掉连接后节点将会被移除。consumer底层可以从zookeeper获取到可提供服务的provider集群地址列表,从而可以向其中一个机器发起RPC调用。
private static Logger logger = LoggerFactory.getLogger(ZkServiceRegistry.class);
// param
public static final String ENV = "env"; // zk env
public static final String ZK_ADDRESS = "zkaddress"; // zk registry address, like "ip1:port,ip2:port,ip3:port"
public static final String ZK_DIGEST = "zkdigest"; // zk registry digest
// ------------------------------ zk conf ------------------------------
// config
private static final String zkBasePath = "/xxl-rpc";
private String zkEnvPath;
private XxlZkClient xxlZkClient = null;
private Thread refreshThread; // 用来刷新的线程
private volatile boolean refreshThreadStop = false;// 线程停止标志
// 用来存储注册服务
private volatile ConcurrentMap> registryData = new ConcurrentHashMap>();
// 用来存储 发现服务
private volatile ConcurrentMap> discoveryData = new ConcurrentHashMap>();
2.4.2.1 start
这个方法主要是干了两件事情, 一是 创建zookeeper client , 二是创建守护线程每60秒更新下数据,包括服务注册,服务发现。这里作者将zookeeper client 创建,以及 创建节点,删除节点 封装成XxlZkClient 类。
@Override
public void start(Map param) {
String zkaddress = param.get(Environment.ZK_ADDRESS);
String zkdigest = param.get(Environment.ZK_DIGEST);
String env = param.get(Environment.ENV);
// valid
if (zkaddress==null || zkaddress.trim().length()==0) {
throw new XxlRpcException("xxl-rpc zkaddress can not be empty");
}
// init zkpath
if (env==null || env.trim().length()==0) {
throw new XxlRpcException("xxl-rpc env can not be empty");
}
zkEnvPath = zkBasePath.concat("/").concat(env);
// init
xxlZkClient = new XxlZkClient(zkaddress, zkEnvPath, zkdigest, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
try {
logger.debug(">>>>>>>>>> xxl-rpc: watcher:{}", watchedEvent);
// session expire, close old and create new 客户端超时
if (watchedEvent.getState() == Event.KeeperState.Expired) {
// 销毁旧的
xxlZkClient.destroy();
// 获取新的
xxlZkClient.getClient();
// refreshDiscoveryData (all):expire retry 重新从zk里面获取
refreshDiscoveryData(null);
logger.info(">>>>>>>>>> xxl-rpc, zk re-connect reloadAll success.");
}
// watch + refresh
String path = watchedEvent.getPath();
// 将path转换成路径
String key = pathToKey(path);
if (key != null) {
// keep watch conf key:add One-time trigger
xxlZkClient.getClient().exists(path, true);
// refresh 节点改变的时候
if (watchedEvent.getType() == Event.EventType.NodeChildrenChanged) {
// refreshDiscoveryData (one):one change 刷新这个key的数据
refreshDiscoveryData(key);
} else if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
// 客户端连接完成触发
logger.info("reload all 111");
}
}
} catch (KeeperException e) {
logger.error(e.getMessage(), e);
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
}
});
// refresh thread 创建一个刷新线程,用于 注册 与服务发现
refreshThread = new Thread(new Runnable() {
@Override
public void run() {
while (!refreshThreadStop) {
try {
TimeUnit.SECONDS.sleep(60);
// refreshDiscoveryData (all):cycle check
// 服务发现 ,根据本地发现 key 从注册中心 获取数据
refreshDiscoveryData(null);
// refresh RegistryData 将本地数据更新到注册中心
refreshRegistryData();
} catch (Exception e) {
if (!refreshThreadStop) {
logger.error(">>>>>>>>>> xxl-rpc, refresh thread error.", e);
}
}
}
logger.info(">>>>>>>>>> xxl-rpc, refresh thread stoped.");
}
});
//设置线程名,守护线程,启动线程
refreshThread.setName("xxl-rpc, ZkServiceRegistry refresh thread.");
refreshThread.setDaemon(true);
refreshThread.start();
logger.info(">>>>>>>>>> xxl-rpc, ZkServiceRegistry init success. [env={}]", env);
}
2.4.2.2 stop
关闭zk client , 设置刷新线程停止标志,中断线程
@Override
public void stop() {
if (xxlZkClient!=null) {
xxlZkClient.destroy();
}
if (refreshThread != null) {
refreshThreadStop = true;
refreshThread.interrupt();
}
}
2.4.2.3 refreshDiscoveryData
更新服务发现数据,参数key是null就更新本地缓存的所有key,key不是null更新key。
其中xxlZkClient.getChildPathData(path) ,作者封装了底层的细节。
/**
* refresh discovery data, and cache
* 刷新发现数据
* @param key
*/
private void refreshDiscoveryData(String key){
Set keys = new HashSet();
if (key!=null && key.trim().length()>0) {// 刷新指定key
keys.add(key);
} else {
if (discoveryData.size() > 0) { // 刷新 本地 缓存的 所有key
keys.addAll(discoveryData.keySet());
}
}
if (keys.size() > 0) {
for (String keyItem: keys) { //
// add-values 将key 转换成path
String path = keyToPath(keyItem);
///获取child 路径下的 data
Map childPathData = xxlZkClient.getChildPathData(path);
// exist-values
TreeSet existValues = discoveryData.get(keyItem);
if (existValues == null) {
existValues = new TreeSet();
discoveryData.put(keyItem, existValues);
}
if (childPathData.size() > 0) {
existValues.addAll(childPathData.keySet());
}
}
logger.info(">>>>>>>>>> xxl-rpc, refresh discovery data success, discoveryData = {}", discoveryData);
}
}
2.4.2.4 refreshRegistryData
刷新注册数据,将本地的缓存刷新的zookeeper
**
* refresh registry data
*/
private void refreshRegistryData(){
if (registryData.size() > 0) {
for (Map.Entry> item: registryData.entrySet()) {
String key = item.getKey();
for (String value:item.getValue()) {
// make path, child path
String path = keyToPath(key);
xxlZkClient.setChildPathData(path, value, "");
}
}
logger.info(">>>>>>>>>> xxl-rpc, refresh registry data success, registryData = {}", registryData);
}
}
2.4.2.5 registry
先添加到本地的缓存中, 后注册到zk中
@Override
public boolean registry(String key, String value) {
// local cache 先加入本地cache
TreeSet values = registryData.get(key);
if (values == null) {
values = new TreeSet<>();
registryData.put(key, values);
}
values.add(value);
// make path, child path 后加入 zookeeper
String path = keyToPath(key);
xxlZkClient.setChildPathData(path, value, "");
logger.info(">>>>>>>>>> xxl-rpc, registry success, key = {}, value = {}", key, value);
return true;
}
2.4.2.6 remove
先从缓存中删除,后从zk中移除
@Override
public boolean remove(String key, String value) {
TreeSet values = discoveryData.get(key);
if (values != null) {
values.remove(value);
}
String path = keyToPath(key);
xxlZkClient.deleteChildPath(path, value);
return true;
}
2.4.2.7 discovery
根据key获取服务列表,先从缓存中查找,没有就从zk中获取。
@Override
public TreeSet discovery(String key) {
// local cache
TreeSet values = discoveryData.get(key);
if (values == null) {
// refreshDiscoveryData (one):first use 重新从zk中获取
refreshDiscoveryData(key);
values = discoveryData.get(key);
}
return values;
}
在util包中有XxlZkClient这么个类,分装了与zookeeper 交互的细节,包括创建连接,设置节点,删除节点,设置节点数据等等。我们来看看构造,与获取client 连接的方法。
构造
public XxlZkClient(String zkaddress, String zkpath, String zkdigest, Watcher watcher) {
// 设置属性
this.zkaddress = zkaddress;
this.zkpath = zkpath;
this.zkdigest = zkdigest;
this.watcher = watcher;
// reconnect when expire 超时重连处理
if (this.watcher == null) {
// 没有 watcher 设置默认的watcher
// watcher(One-time trigger)
this.watcher = new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
logger.info(">>>>>>>>>> xxl-rpc: watcher:{}", watchedEvent);
// session expire, close old and create new
if (watchedEvent.getState() == Event.KeeperState.Expired) {
destroy();
getClient();
}
}
};
}
// 获取连接
getClient();
}
获取连接
// ------------------------------ zookeeper client ------------------------------
private ZooKeeper zooKeeper;
private ReentrantLock INSTANCE_INIT_LOCK = new ReentrantLock(true); // 公平锁 。 竞争失败线程被放到 list中
// 获取zookeeper 连接
public ZooKeeper getClient(){
if (zooKeeper==null) {
try {
if (INSTANCE_INIT_LOCK.tryLock(2, TimeUnit.SECONDS)) {
if (zooKeeper==null) { // 二次校验,防止并发创建client
// init new-client
ZooKeeper newZk = null;
try {
// 创建zookeeper连接,session时间10000
newZk = new ZooKeeper(zkaddress, 10000, watcher);
if (zkdigest!=null && zkdigest.trim().length()>0) {
// auth
newZk.addAuthInfo("digest",zkdigest.getBytes()); // like "account:password"
}
newZk.exists(zkpath, false); // sync wait until succcess conn
// set success new-client
zooKeeper = newZk;
logger.info(">>>>>>>>>> xxl-rpc, XxlZkClient init success.");
} catch (Exception e) {
// close fail new-client
if (newZk != null) {
newZk.close();
}
logger.error(e.getMessage(), e);
} finally {
INSTANCE_INIT_LOCK.unlock();
}
}
}
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
}
if (zooKeeper == null) {
throw new XxlRpcException("XxlZkClient.zooKeeper is null.");
}
return zooKeeper;
}