深度解析xxl-rpc之服务注册与发现

一.介绍

包结构:
深度解析xxl-rpc之服务注册与发现_第1张图片
正如上图所示,一个注册抽象类ServiceRegistry和三个实现类LocalServiceRegistry,XxlRegistryServiceRegistry,ZkServiceRegistry
深度解析xxl-rpc之服务注册与发现_第2张图片

二.源码解析

2.1ServiceRegistry

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);

}
2.2LocalServiceRegistry

LocalServiceRegistry 继承ServiceRegistry 实现所有抽象方法。将服务注册到本地(项目内)。

2.2.1 字段

就一个registryData 成员,因为本项目中,所以发现数据与注册数据就用同一个数据结构存储就行了。这个应用场景很少

/**
     * registry data   // 存放注册数据
     */
 private Map> registryData;
2.2.2 方法

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);
    }

2.3XxlRegistryServiceRegistry

这个使用的注册中心是作者自己开源的。看使用代码感觉很好上手,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);
    }

2.4ZkServiceRegistry

使用zookeeper作为注册中心。
官网文档给出的存储结构图:
深度解析xxl-rpc之服务注册与发现_第3张图片
原理:
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调用。

2.4.1字段
	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 方法

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;
    }
2.5 XxlZkClient

在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;
	}

你可能感兴趣的:(java,rpc,深度解析xxl-rpc)