JD配置中心源码分析

JD·config配置中心

主要功能:
提供动态的Key,Value配置读写服务,基于zookeeper的原子性与Watcher机制,保障了数据的一致性与时效性

优点:
1、二次检索的数据会注入到Jvm缓存中,增强了数据的检索效率
2、数据会定期copy到磁盘,当zk不可用时,保障了服务的可用性
3、内部自动zk失效重连
缺点:
1、占用客户端内存、磁盘空间

Spring配置项:
<bean id="configCenterClient" class="***.client.DefaultConfigCenterClient" init-method="init" destroy-method="destroy">
<!--配置中心地址-->
<property name="zkServers" value="${config.center.zkServers}" />
<!--本地快照-->
<property name="storeFilePath" value="${config.center.snapshotPath}"/>
<!--zk节点目录-->
<property name="zNodePath" value="${config.center.znode}" />
</bean>

Jar包class:在这里插入图片描述

关键Class的功能

DefaultConfigCenterClient主要职责:
1、负责与外界交互,配置中心 Base类
2、负责zk数据变化时,调用者的注册、监听
3、负责与ZkClient交互数据并更新数据到Jvm缓存,提升检索效率
4、负责与zk失联时,从本地磁盘获得数据

ZkClient职责:
1、负责与zk直接交互(连接、重连、获取数据、注册监听)
2、负责监听zk数据变化、连接变化
3、负责维护zk监听列表&config配置列表

spring初始化工作

在这里插入图片描述从以上代码基本判定初始化步骤:1、获取zk连接 2、初始化空数据同步监听 3、初始化快照同步监听

第一步:initZookeeper() 方法关键性代码:

DefaultConfigCenterClient类:
  private void initZookeeper() {
        //初始化参数
        this.zkClient = new ZkClient(this.zkServers, this.sessionTimeout);
        //注册监听,--后面有用
        this.zkClient.subscribeZkConnectionListener(this);
        try {
            this.connectZk(2);
            _logger.info("connect to zk successful!");
        } catch (ConfigCenterException var2) {
            _logger.error("connect to zk fail! ", var2);
        }
    }
  //这里是真实的连接zk集群(客户端长连接),通过递归获得重试,2次
  private void connectZk(int toTryCount) throws ConfigCenterException {
        try {
            this.zkClient.connect();
        } catch (IOException var3) {
            if (toTryCount <= 0) {
                throw new ConfigCenterException("try " + toTryCount + " time, connect still fail! ", var3);
            }
            this.zkClient.close();
            this.connectZk(toTryCount - 1);
        }
    }

第二步:failWatchMonitor初始化方法关键性代码:

FailWatchMonitor类:
   	//无数据时,间歇时间1分钟
    private final long tickTime = 60000L;
    //未检测到的key值Set
    private Set<String> failWatchKeysQueue = new HashSet(1000);
    //未检测到的key值Set
    private DefaultConfigCenterClient client = null;
    //重入锁保护failWatchKeysQueue的读、写线程安全
    private final ReentrantLock _lock = new ReentrantLock();
    //为_lock重入锁服务
    private final Condition _notEmpty;

//线程方法,初始化时启动
public void run() {
	//todo:3个while(true) 么看懂?
        while(true) {
            while(true) {
                while(true) {
                    try {
                    	//尝试拿到锁
                        if (this._lock.tryLock()) {
                            try {
                            	//无数据时,休息10分钟
                                if (this.failWatchKeysQueue.isEmpty()) {
                                    _logger.info("failWatchKeysQueue is empty, lockEmptyWorkQueue");
                                    this._notEmpty.await(600000L, TimeUnit.MILLISECONDS);
                                    continue;	
                                }
                                //有数据时,更新数据
                                this.watchSets();
                            } catch (InterruptedException var7) {
                                _logger.error("lock error! " + var7.getMessage());
                            } catch (Exception var8) {
                                _logger.error("lock error!", var8);
                            } finally {
                                this._lock.unlock();
                            }
							//更新周期:1分钟一次
                            sleep(60000L);
                        }
                    } catch (Exception var10) {
                        _logger.error("FailWatchMonitor error! ", var10);
                    }
                }
            }
        }
    }

    private void watchSets() {
        Iterator it = this.failWatchKeysQueue.iterator();
        //遍历空数据list,不加重入锁可能会抛异常(遍历+删除)
        while(it.hasNext()) {
            String key = (String)it.next();
            try {
            	//直接从zk中拿数据
                String value = this.client.getDataFromZkAndWatch(key, 1);
                //有数据场景
                if (value != null) {
                	//加载数据至JVM
                    this.client.put(key, value);
                    //移除空值list
                    it.remove();
                    _logger.info("readData and Watch success! key=" + key + ", value = " + value);
                }
            } catch (Exception var4) {
                if (_logger.isDebugEnabled()) {
                    _logger.debug(var4.getMessage());
                }
            }
        }
    }
   以上代码可得出结论:
   正常情况下,FailWatchMonitor线程会默认每隔1分钟更新数据至jvm缓存,为的是提升缓存命中率

第三步:snapshotTimer初始化方法关键性代码:

SnapshotTimer类:
	//快照循环更新间隔时间:30分钟
    public static final long DEFAULT_INTERVAL = 1800000L;
    //快照同步数据对象
    private SnapshotAble snapshotAble = null;
    //快照操作对象
    private SnapshotManager snapshotManager = null;
    //
    private long interval = 1800000L;
    //是否开启快照功能标记
    private volatile boolean stop = false;
    
 public void run() {
        while(!this.stop) {
            try {
            	//更新间隔时间
                sleep(this.interval);
                _logger.info("snapshot work will execute!");
                //直接从jvm缓存中获得数据
                Map<String, String> snapshot = this.snapshotAble.getSnapshot();
                Iterator it = snapshot.entrySet().iterator();
                //
                while(it.hasNext()) {
                    Entry<String, String> entry = (Entry)it.next();
                    String key = (String)entry.getKey();
                    String value = (String)entry.getValue();
                    this.snapshot(key, value);
                    if (this.stop) {
                        break;
                    }
                }
                _logger.info("snapshot work execute successful!");
            } catch (Exception var6) {
            }
        }
    }
    // this.snapshot(key, value)具体实现
    //先删除在更新,写使用PrintWriter字符输出流
   public void storeSnapshot(String key, String snapshot) throws IOException {
        File file = this.getTargetFile(key);
        file.delete();
        this.saveSnapshot(key, snapshot);
    }
   以上代码可得出结论:
   SnapshotTimer线程会默认每隔30分钟更新jvm数据至快照

第四步(补充):zk Sync初始化方法关键性代码:

 //这个方法是为了提升【调用Default类的客户端】注册数据变化监听的成功率
 //init()代码块方法:
 if (this.connected == 0) {
                        try {
                            _logger.info("begin to wait response of zk!");
                            this.doneSignal.await(10000L, TimeUnit.MILLISECONDS);
                        } catch (InterruptedException var3) {
                        }
                    }
  //DefaultConfigCenterClient类方法:                  
      //获得zk某客户端服务器连接时,后面会讲到
    public void syncConnected() {
        this.connected = 1;
        this.doneSignal.countDown();
        //监听,不确定注册的客户端和zk启动的先后顺序?,注册的时候已经调用了this.get(key)方法
        this.reloadConfigChangedListeners();
        _logger.info("zk connected");
    }
	//【调用Default类的客户端】通过此方法进行监听
	   public void registerConfigChangedListener(String key, ConfigChangedListener configChangedListener) {
        this.registeredConfigChangedListeners.put(key, configChangedListener);
        //开启zk客户端监听并将kv值注入jvm
        this.get(key);
    }
	//具体监听的逻辑
    private void reloadConfigChangedListeners() {
        Iterator it = this.registeredConfigChangedListeners.keySet().iterator();
        while(it.hasNext()) {
            this.get((String)it.next());
        }
    }

以上代码可得出结论:
1、【调用Default类的客户端】注册监听时,会将结果存储到registeredConfigChangedListeners对象中,后续zk监听触发时调用
2、【调用Default类的客户端】注册监听与zk初始化都会调用一次this.get(key)方法来进行zk的注册监听以及将kv值注入jvm

获取数据

DefaultConfigCenterClient类:
 //获得k对应的v
 public String get(String key) {
        if (StringUtils.isEmpty(key)) {
            throw new IllegalArgumentException("key can not be empty!");
        } else {
        	//jvm中获取数据
            String value = (String)this.localCache.get(key);
            //Failover模式或已拿到数据,直接返回数据
            if (this.status != -1 && value == null) {
            	//注册key变化监听到zkClient
                this.zkClient.subscribeDataChangeListener(this.generateZkPath(key), this);
                //优先从zk中获取数据,允许失败1次
                value = this.getDataFromZkAndWatch(key, 2);
                if (value != null) {
                    _logger.info("getDataFromZkAndWatch success, key=" + key + ", value=" + value);
                }
                if (value == null) {
                    this.failWatchMonitor.register(key);  //failWatchMonitor中failWatchKeysQueue数据就是从这来的
                    _logger.info("register into FailWatchMonitor, key=" + key);
                    //当zk也无数据时,从快照中获得数据
                    value = this.getDataFromSnapshot(key);
                    if (value != null) {
                        _logger.info("getDataFromSnapshot success, key=" + key + ", value=" + value);
                    } }
                if (value != null) {
                    this.localCache.put(key, value);
                }
                return value;
            } else {
                return value;
            }   }   }
       以上代码可得出结论(get):
   		1、缓存中无数据时,zkClient注册数据监听,换句话说,缓存中有就不需要重复监听了
   		2、获取数据时,访问顺序:Jvm缓存 -> Zk -> 快照

通过ZK的Watcher机制实现监听

首先简单了解一下zk的watcher状态:

事件类型触发条件
KeeperState.Expired客户端和服务器在ticktime的时间周期内,是要发送心跳通知的。这是租约协议的一个实现。客户端发送request,告诉服务器其上一个租约时间,服务器收到这个请求后,告诉客户端其下一个租约时间是哪个时间点。当客户端时间戳达到最后一个租约时间,而没有收到服务器发来的任何新租约时间,即认为自己下线(此后客户端会废弃这次连接,并试图重新建立连接)。这个过期状态就是Expired状态
KeeperState.Disconnected就像上面那个状态所述,当客户端断开一个连接(可能是租约期满,也可能是客户端主动断开)这是客户端和服务器的连接就是Disconnected状态
KeeperState.SyncConnected一旦客户端和服务器的某一个节点建立连接,并完成一次version、zxid的同步,这时的客户端和服务器的连接状态就是SyncConnected
KeeperState.AuthFailedzookeeper客户端进行连接认证失败时,发生该状态
事件类型触发条件
EventType.NodeCreated当节点被创建时,触发
EventType.NodeChildrenChanged当节点的直接子节点被创建、被删除、子节点数据发生变更时,触发
.EventType.NodeDataChanged当节点的数据发生变更时,触发
EventType.NodeDeleted当节点被删除时,触发
EventType.Nonezookeeper客户端的连接状态发生状态切换时,触发

ZkClient实现Watcher的process方法监听

    public void process(WatchedEvent event) {
        String path = event.getPath();
        _logger.info("path : " + path + ", type :" + event.getType() + ", state = " + event.getState());
        //数据变化的3种状态
        //数据变化时,DefaultConfigCenterClient实现,可以重写
        if (event.getType() == EventType.NodeDataChanged) {
            this.fireDataChangedEvents(path);
         //数据删除时,由基类(DefaultConfigCenterClient) Impl类实现
        } else if (event.getType() == EventType.NodeDeleted) {
            this.fireNodeDeleted(path);
         //数据创建时,同上
        } else if (event.getType() == EventType.NodeCreated) {
            this.fireNodeCreated(path);
        }
         //连接状态变化的3种状态
        Iterator i$;
        ZkConnectionListener listenerZk;
        //连接过期
        if (event.getState() == KeeperState.Expired) {
            this.reconnect();
            i$ = this.registeredZkConnectionListeners.iterator();
            while(i$.hasNext()) {
                listenerZk = (ZkConnectionListener)i$.next();
                listenerZk.expired();
            }
        //获得zk某客户端服务器连接时
        } else if (path == null && event.getState() == KeeperState.SyncConnected) {
        	//更新zkClient key值监听
            this.subscribeAll();
            i$ = this.registeredZkConnectionListeners.iterator();
            //通知Default注册对象做自己的事
            while(i$.hasNext()) {
                listenerZk = (ZkConnectionListener)i$.next();
                listenerZk.syncConnected();
            }
        //连接失效时
        } else if (event.getState() == KeeperState.Disconnected) {
            i$ = this.registeredZkConnectionListeners.iterator();
            while(i$.hasNext()) {
                listenerZk = (ZkConnectionListener)i$.next();
                listenerZk.disconnected();
            }
        }
    }

		数据变化时:
		ZkClient类:
		    private void fireDataChangedEvents(String path) {
		        ZkDataListener listener = (ZkDataListener)this.registeredDataListeners.get(path);
		        if (listener != null) {
		            try {
		            	//从zk中读取数据+继续监听
		                Object data = this.readData(path, (Stat)null, true);
		                //触发【调用Default类的客户端】监听方法
		                listener.handleDataChange(path, data);
		            } catch (ZkException var4) {
		                _logger.error(listener + " : ", var4);
		     }  }  }

		数据增加+删除时:
		ZkClient类:
		    private void fireNodeDeleted(String path) {
		        ZkDataListener listener = (ZkDataListener)this.registeredDataListeners.get(path);
		        if (listener != null) {
		            try {
		               //触发DefaultConfigCenterClient类方法,默认do nothing
		                listener.handleDataDeleted(path);
		            } catch (ZkException var4) {
		                _logger.error(listener + " : ", var4);
		            }
		        }
		    }
		获得zk某客户端服务器连接时:
		ZkClient类:
		    private void subscribeAll() {
		        _logger.info("subscribeAll");
		        //注册数据监听,担心遗漏,所以+synchronized
		        synchronized(this.registeredDataListeners) {
		            Iterator i$ = this.registeredDataListeners.keySet().iterator();
		            while(i$.hasNext()) {
		                String path = (String)i$.next();
		                this.watchForData(path);
		            }
		        }
		    }
		zk连接失效时:
		ZkClient类:
		 if (event.getState() == KeeperState.Expired) {
		 			//重连
		            this.reconnect();
		            //触发DefaultConfigCenterClient类方法,默认do nothing
		            i$ = this.registeredZkConnectionListeners.iterator();
		            while(i$.hasNext()) {
		                listenerZk = (ZkConnectionListener)i$.next();
		                listenerZk.expired();
		 }
       以上代码可得出结论:
   		1、zk数据或连接状态的变更都会触发:DefaultConfigCenterClient的对应方法
   		2、zk连接失效后会进行重连,获得一个新的zk连接
   		3、数据变化会更新jvm、重新开启数据监听,触发【调用Default类的客户端】方法
   		4、数据删除默认不触发任何事件?假设从配置中心干掉一条数据,缓存中的数据会一直存在?
   		5、数据新增默认不触发任何事件?实际是很难做到,但假设【调用Default类的客户端】先注册监听key1,后在配置中心新增key1,那么key1发生变化是无法监测到的,除非先调用一次get(key1)方法
   		6、从过期状态->重新连接->Sync状态,期间【调用Default类的客户端】监听会失效
   		
   		
   		
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值