zookeeper中watcher的使用及原理

watcher网上

watcher解决的问题

在进入watcher之前我们先试想在应用服务器集群中可能存在的两个问题:

  1. 因为集群中有很多机器,当某个通用的配置发生变化后,怎么让自动的让所有服务器的配置统一生效?
  2. 当集群中某个节点宕机,如何让集群中的其他节点知道?

为了解决这两个问题,zookeeper引入了watcher机制来实现发布/订阅功能,能够让多个订阅者同时监听某一个主题对象,当这个主题对象自身状态发生变化时,会通知所有订阅者。
watcher基本原理

zookeeper中实现watcher需要有三个部分,如下图所示:

分别是zookeeper服务端、客户端以及客户端的watchManager。
在这里插入图片描述如图所示,客户端向zk注册watcher的同时,会将客户端的watcher对象存储在客户端的WatchManager中;zk服务器触发watch事件后,会向客户端发送通知,客户端线程从watchManager中取出对应watcher执行。

客户端如何实现事件通知的动作

客户端只需定义一个类实现org.apache.zookeeper.Watcher接口并实现接口中的如下方法:

abstract public void process(WatchedEvent event);

即可在得到通知后执行相应的动作。参数org.apache.zookeeper.WatchedEvent是zk服务端传过来的事件,有三个成员:

final private KeeperState keeperState; // 通知状态
final private EventType eventType; // 事件类型
private String path; // 哪个节点发生的事件

keeperState是个枚举对象,代表客户端和zk服务器的链接状态;eventType也是个枚举类型,代表节点发生的事件类型,比如创建新的子节点、改变节点数据等;

在这里插入图片描述
对于NodeDataChanged事件:无论节点数据发生变化还是数据版本发生变化都会触发(即使被更新数据与新数据一样,数据版本都会发生变化)。


对于NodeChildrenChanged事件:新增和删除子节点会触发该事件类型。


需要注意的是:WatchedEvent只是事件相关的通知,并没有对应数据节点的原始数据内容及变更后的新数据内容,因此如果需要知道变更前的数据或变更后的新数据,需要业务保存变更前的数据和调用接口获取新的数据

如何注册watcher

watcher注册api

可以在创建zk客户端实例的时候注册watcher(构造方法中注册watcher):

public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher)
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher,ZKClientConfig conf)
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly, HostProvider aHostProvider)
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly, HostProvider aHostProvider,ZKClientConfig clientConfig)
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly)
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly, ZKClientConfig conf)
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, long sessionId, byte[] sessionPasswd)
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, long sessionId, byte[] sessionPasswd, boolean canBeReadOnly, HostProvider aHostProvider)
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, long sessionId, byte[] sessionPasswd, boolean canBeReadOnly)

ZooKeeper的构造方法中传入的watcher将会作为整个zk会话期间的默认watcher,该watcher会一直保存为客户端ZKWatchManager的defaultWatcher成员,如果有其他的设置,这个watcher会被覆盖。

除了可以通过ZooKeeper类的构造方法注册watcher外,还可以通过ZooKeeper类中其他一些api来注册watcher,只不过这些api注册的watcher就不是默认watcher了(以下每个注册watcher的方法有很多个重载的方法,就不一一列举出来)。

案例

public class ZookeeperServer implements Watcher {

    private static Log log = LogFactory.getLog(ZookeeperServer.class);
    //读取配置文件
    private static final String ZK_ADDRESS = ZookeeperConfig.getAddress();
    private static final String ZK_NODE_PATH = ZookeeperConfig.getZKNodePath();
    private static final String ZK_CODE_PATH = ZK_NODE_PATH + ZookeeperConfig.getZKCodePath();
    private static final String ZK_IP_MODEL_PATH = ZK_NODE_PATH + ZookeeperConfig.getZKIPModelPath();
    
    /**
     * session 会话
     */
    private static final int SESSION_TIMEOUT = ZookeeperConfig.getSessionTimeout();
    private ZooKeeper zk = null;
    private Stat stat = new Stat();
    /**
     * 信号量,阻塞程序执行,用户等待zookeeper连接成功,发送成功信号,
     */
    private CountDownLatch countDownLatch = new CountDownLatch(1);

    private static volatile ZookeeperServer instance = null;

    private ZookeeperServer() {
    }

    public static ZookeeperServer getInstance() {
        if (instance == null) {
            synchronized (ZookeeperServer.class) {
                if (instance == null) {
                    instance = new ZookeeperServer();
                    try {
                        instance.initZookeeperServer();
                    } catch (IOException e) {
                        e.printStackTrace();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (KeeperException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return instance;

    }

    private static Map<String, EsbGateway> gatewayMap = new HashMap<>();

    public static Map<String, EsbGateway> getEsbGatewayMap() {
        return gatewayMap;
    }

    public static EsbGateway getGateway(String gatewayCode) {
        EsbGateway gateway = gatewayMap.get(gatewayCode);
        if (gateway != null) {
            return gateway;
        }
        return null;
    }

    private static Map<String, IpStrategy> ipStrategMap = new HashMap<>();

    public static Map<String, IpStrategy> getIpStrategyMap() {
        return ipStrategMap;
    }

    private static Map<String, IpModel> ipModelMap = new HashMap<>();

    public static Map<String, IpModel> getIpModelMap() {
        return ipModelMap;
    }




    public void initZookeeperServer() throws IOException, InterruptedException, KeeperException {
        log.info("初始化zookeeper------begin");
        log.info("初始化zookeeper地址为------" + ZK_ADDRESS);
        log.info("初始化zookeeper节点路径为------" + ZK_NODE_PATH);
        log.info("初始化zookeeper会话超时时间为------" + SESSION_TIMEOUT);
        zk = new ZooKeeper(ZK_ADDRESS, SESSION_TIMEOUT, this);
        // 进行阻塞
        countDownLatch.await();
        initZookeeperData();
        log.info("初始化zookeeper------end");
    }

    public void initZookeeperData() {
        log.info("zookeeper初始化数据");
        ipStrategMap = IpService.getStrategyListMap();
        ipModelMap = IpService.getipModelsMap();
        gatewayMap = GatewayService.getAllGatewayMap();
        
        try {
            Gson gson = new Gson();
            String gatewaycode = gson.toJson(gatewayMap);
            String ipModel = gson.toJson(ipModelMap);
            String ipStrateg = gson.toJson(ipStrategMap);
           
            createZkData(ZK_NODE_PATH, "");
            createZkData(ZK_CODE_PATH, gatewaycode);
            createZkData(ZK_IP_MODEL_PATH, ipModel);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void createZkData(String path, String data) throws IOException, InterruptedException, KeeperException {
        Stat stat = zk.exists(path, true);
        if (stat == null) {
            zk.create(path, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE,
                    CreateMode.PERSISTENT);
        } else {
            zk.setData(path, data.getBytes(), -1);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public void process(WatchedEvent event) {
        // 获取事件状态
        Event.KeeperState keeperState = event.getState();
        // 获取事件类型
        Event.EventType eventType = event.getType();
        if (Event.KeeperState.SyncConnected == keeperState) {
            if (Event.EventType.None == eventType) {
                countDownLatch.countDown();
                log.info("zookeeperServer启动连接。。。");
            } else if (eventType == Event.EventType.NodeDataChanged) {
                try {
                    Gson gson = new Gson();
                    if (event.getPath().equals(ZK_CODE_PATH)) {
                        String gateway = new String(zk.getData(event.getPath(), true, stat));
                        log.info("zookeeperServer微服务信息发生变化");
                        Type type = new TypeToken<Map<String, EsbGateway>>() {
                        }.getType();
                        gatewayMap = gson.fromJson(gateway, type);
                    } else if (event.getPath().equals(ZK_IP_MODEL_PATH)) {
                        String ipModel = new String(zk.getData(event.getPath(), true, stat));
                        log.info("zookeeperServer监听微服务ip策略发生变化");
                        Type type = new TypeToken<Map<String, IpModel>>() {
                        }.getType();
                        ipModelMap = gson.fromJson(ipModel, type);
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                    log.error("zookeeperServer监听数据异常:" + ExceptionUtil.getExceptionInfo(e));
                }
            }
        } else if (keeperState == Event.KeeperState.Expired) {
            try {
                this.zk.close();
                this.zk = new ZooKeeper(ZK_ADDRESS, SESSION_TIMEOUT, this);
                log.info("zookeeperServer重新连接");
            } catch (Exception e) {
                log.error("zookeeperServer连接失败:" + ExceptionUtil.getExceptionInfo(e));
            }
        }
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值