什么是 Watch 功能点
这是 zookeeper 非常重要的功能。如下图所示,
上图中,有两个客户端,client1 创建了 /app/lock 临时节点,临时节点有个好处,client2 在可以在临时节点上建一个 watch (观察点),观察什么呢?可以观察到临时节点创建、删除、修改、子目录的变化,而且是事件性的回调 client2 定义的 watch 接口的代码。
watcher 功能的三种应用场景
在大部分的分布式主从(或者主备)模式下,HA 的实现都是应用了 watch 临时节点的 delete 事件。拿 NameNode 来说吧, 在 NameNode 所在的集群上都有一个 zkfc 专门监控 NameNode 运行状态,如果 NameNode 意外结束,或者整个服务器都宕机了,zookeeper 中的 /hadoop-ha/ActiveStandbyElectorLock 临时节点就会消失,而其他的 standby 节点开始选举新节点的程序。另外多说一下,/hadoop-ha/ActiveBreadCrumb 永久节点是为了防止脑裂的。
在分布式的系统中,有这么一种需求是根据规则为每条记录打标签。听着还挺简单的,但是难的是规则是经常变化的,通常的办法不是修改代码就是修改配置文件来修改规,这种方式需要重启集群才能让新规则生效,要不就是写一个死循环从 redis 这种缓存中不断的查询配置的版本号,如果版本号变化就加载新规则,这种方式克服了冷部署的确定,可以在不停服务器的情况下,替换旧规则。但是有个缺点是,规则在大部分时间内是不需要更改的。如果使用 zookeeper 的 watch 功能监控某个节点的 change 事件,而 change 节点更改的内容就是新的规则。这就是分布式的配置,只有节点中的配置信息被修改了,使用此配置的节点都后收到 change event 事件的通知,然后去同步新的配置。
如果将配置信息替换为命令的话,就能使用 Zookeeper 在分布式系统中发布消息给系统,各个节点在根据命令信息,执行命令。Clickhouse 的副本、分片就是这样实现的。
在雪崩场景下,当 1000 个客户端对 100 个失效的 key 进行请求的话,就造成了 100 个击穿的效果,对 mysql 造成巨大的压力。解决的办法是当出现这种情况,要协调一下这 1000 客户端,让它们先去申请一把锁,当然这把锁是分布式下的锁,可以理解为可重入式锁的升级版。 只有申请到这把锁的客户端才能去 mysql 请求数据,当此客户端请求为数据之后,再将数据更新到 redis ,不就完美了。然后客户端释放这把锁,然后其他线程申请到这把锁,先去 redis 看看有了没有,有个 redis 中已经了需要访问的 key ,则直接取回,然后再释放锁。
以上三个场景就是 watch 共功能的三种场景:管理节点的高可用、分布式系统中的配置热部署、分布式锁。
三种场景的模拟实现
管理节点高可用的实现。每个节点的运行逻辑如下所示:
总结一下流程图吧:
分成两个线程来描述,两个线程分别为主线和 zk 客户端线程。
主线值做两件事,一件事情是启动 zk 客户端,另外一件事情是根据 running 的值来判断是否处理正常的业务逻辑。
zk 客户端也是做两件事件,连接 zk server ,连接上之后尝试建 /ha/rm 这个目录,如果失败说明,已经有其他进程处理业务逻辑了,当前线程就在 /ha/rm 上 watch ,
watch 中的逻辑和 zk 客户端刚启动的逻辑查不多。
上代码。
模拟 ResourceManager 的代码:
import org.apache.zookeeper.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import streaming.zk.uitils.ZkUtils;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @className: ZookeeperTest
* @Description:
* @Author: wangyifei
* @Date: 2022/11/7 16:01
*/
public class ResourceManager {
private static Logger logger = LoggerFactory.getLogger(ResourceManager.class);
private volatile boolean running = false ;
private String name ;
public ResourceManager(String name){
this.name = name ;
}
public void setRunning(boolean running) {
this.running = running;
}
public void entrypoint(){
HAWatchCallback haWatchCallback = new HAWatchCallback();
haWatchCallback.setRm(this);
haWatchCallback.createPath("/ha/rm");
ScheduledExecutorService schedule = Executors.newScheduledThreadPool(1);
schedule.scheduleAtFixedRate(()->{
if(running){
System.out.println("rm(" + name +") active , do business");
}else{
System.out.println("rm(" + name +") standby ");
}
} , 1
,1
, TimeUnit.SECONDS);
}
public static void main(String[] args) throws Exception {
// 启动时候,需要配置 ideal 可以多实例启动,然后修改 name 的名称,这样就能打印除谁是 active 的,谁是 standby 了
ResourceManager rm1 = new ResourceManager("rm2");
rm1.entrypoint();
}
}
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import streaming.zk.uitils.ZkUtils;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;
/**
* @className: HAWatchCallback
* @Description:
* @Author: wangyifei
* @Date: 2022/11/8 20:12
*/
public class HAWatchCallback implements Watcher , AsyncCallback.StatCallback , AsyncCallback.DataCallback {
private static Logger logger = LoggerFactory.getLogger(HAWatchCallback.class);
private CountDownLatch cdl = new CountDownLatch(1);
private ZooKeeper zk ;
private String path ;
private ResourceManager rm ;
public void setRm(ResourceManager rm) {
this.rm = rm;
}
public void createPath(String path){
this.path = path ;
ZkUtils.setWatcher(this);
zk = ZkUtils.newZkClient();
try {
cdl.await();
zk.create(path , "test".getBytes(StandardCharsets.UTF_8) , ZooDefs.Ids.OPEN_ACL_UNSAFE , CreateMode.EPHEMERAL);
rm.setRunning(true);
} catch (KeeperException e) {
if(e instanceof KeeperException.NodeExistsException){
// 如果不存在则对,此节点进行 watch
rm.setRunning(false);
zk.getData(path , this , this , "none");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public ZooKeeper getZk() {
return zk;
}
public void setZk(ZooKeeper zk) {
this.zk = zk;
}
/**
* @return:
* @desc: 监听数据修改的回调函数
* @author
* @date
* @param
*/
@Override
public void processResult(int i