ZooKeeper-案例(注册订阅)

89 阅读3分钟

作者介绍:简历上没有一个精通的运维工程师。请点击上方的蓝色《运维小路》关注我,下面的思维导图也是预计更新的内容和当前进度(不定时更新)。

前面我们介绍介绍了几个常用的代理服务器,本章节我们讲来讲解Zookeeper这个中间件。

我们前面讲解了ZooKeeper的基本操作和原理,后面几个小节将通过几个案例来更加深入ZooKeeper。我们的第二个案例就是把ZooKeeper作为订阅中心,来实现信息更新,也就是我们前面讲过的watch机制。

服务a

服务a的目的是为了发布信息,作为信息发布者,把信息发布到对应的Znode节点。

import json
import time
import random
from kazoo.client import KazooClient

class ClusterMessagePublisher:
    def __init__(self, zk_hosts, zk_path):
        # 连接ZK集群(多个地址逗号分隔)
        self.zk = KazooClient(hosts=zk_hosts)
        self.zk.start()
        self.zk_path = zk_path
        self._init_message_pool()
        self._ensure_node()

    def _init_message_pool(self):
        """消息模板池"""
        self.templates = {
            'weather': [
                "城市{city}气温: {temp}℃",
                "{city}空气质量: {aqi}"
            ],
            'device': [
                "设备{id}状态: {status}",
                "传感器{id}数值: {value}"
            ]
        }

    def _ensure_node(self):
        """确保节点存在(持久节点)"""
        if not self.zk.exists(self.zk_path):
            self.zk.create(self.zk_path, b"", makepath=True)
        print(f"[A] 已连接ZK集群,节点路径: {self.zk_path}")

    def _gen_random_data(self):
        """生成随机消息数据"""
        msg_type = random.choice(list(self.templates.keys()))
        template = random.choice(self.templates[msg_type])

        params = {
            'city': random.choice(["北京", "上海", "广州", "深圳"]),
            'temp': random.randint(-10, 40),
            'aqi': random.randint(30, 300),
            'id': f"D{random.randint(1000,9999)}",
            'status': random.choice(["在线", "离线", "故障"]),
            'value': round(random.uniform(0, 100), 2)
        }

        return {
            "type": msg_type,
            "content": template.format(**params),
            "timestamp": time.time()
        }

    def publish(self):
        """发布消息到ZK节点"""
        data = json.dumps(self._gen_random_data()).encode()
        self.zk.set(self.zk_path, data)
        print(f"[A] 消息已更新到集群节点")

    def run(self, interval=10):
        """每10秒发布消息"""
        try:
            while True:
                self.publish()
                time.sleep(interval)
        except KeyboardInterrupt:
            self.zk.stop()

if __name__ == "__main__":
    # 使用多个ZK地址(示例)
    service = ClusterMessagePublisher(
        zk_hosts="192.168.31.140:2181,192.168.31.141:2181,192.168.31.142:2181",  # 集群地址
        zk_path="/cluster_messages"
    )
    service.run()

启动以后,服务a会向固定的znode写入数据,这里为了演示方便数据写入稍微快一点。

[root@localhost ~]# python a1.py
[A] 已连接ZK集群,节点路径: /cluster_messages
[A] 消息已更新到集群节点
[A] 消息已更新到集群节点
[A] 消息已更新到集群节点
[A] 消息已更新到集群节点
[A] 消息已更新到集群节点

服务b

服务b的作用是监听ZooKeeper里面对应的路径,然后根据监听的信息变化来写入本地文件,当然我这里的逻辑和简单,只是为了方便演示。

import json
import time
from kazoo.client import KazooClient

class ClusterMessageWriter:
    def __init__(self, zk_hosts, zk_path, output_file):
        self.zk = KazooClient(hosts=zk_hosts)
        self.zk.start()
        self.zk_path = zk_path
        self.output_file = output_file
        self._setup_watch()
        print(f"[B] 已连接 ZK 集群,监听节点: {zk_path}")

    def _setup_watch(self):
        @self.zk.DataWatch(self.zk_path)
        def watch_handler(data, stat):
            if data:
                try:
                    message = json.loads(data.decode('utf-8'))
                    self._process_message(message)
                except Exception as e:
                    print(f"[B] 错误: {str(e)}")

    def _process_message(self, message):
        """ 关键修复点:修正 f-string 括号 """
        log_line = (
            # 修复括号闭合问题(注意两个闭合括号)
            f"[{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(message['timestamp']))}] "  # 这里修复
            f"{message['type'].upper()}: {message['content']}\n"
        )

        with open(self.output_file, "a", encoding="utf-8") as f:
            f.write(log_line)
        print(f"[B] 已写入消息: {message['content'][:20]}...")

    def run(self):
        try:
            while True: time.sleep(1)
        except KeyboardInterrupt:
            self.zk.stop()

if __name__ == "__main__":
    writer = ClusterMessageWriter(
        zk_hosts="192.168.31.140:2181,192.168.31.141:2181,192.168.31.142:2181",
        zk_path="/cluster_messages",
        output_file="messages.log"
    )
    writer.run()

[root@localhost ~]# python b1.py
[B] 已写入消息: 城市深圳气温: 6℃...
[B] 已连接 ZK 集群,监听节点: /cluster_messages
[B] 已写入消息: 传感器D4819数值: 20.09...
[B] 已写入消息: 北京空气质量: 135...
[B] 已写入消息: 城市上海气温: 28℃...
[B] 已写入消息: 传感器D1802数值: 49.38..

[root@localhost ~]# tail -f messages.log 
[2025-04-27 00:37:10] WEATHER: 城市深圳气温: 6℃
[2025-04-27 00:37:20] DEVICE: 传感器D4819数值: 20.09
[2025-04-27 00:37:30] WEATHER: 北京空气质量: 135
[2025-04-27 00:37:40] WEATHER: 城市上海气温: 28

这样我们就实现了服务a负责写消息,服务b从ZooKeeper读取信息,然后写入本地,当然实际情况需要根据各自的业务逻辑来考虑。

运维小路

一个不会开发的运维!一个要学开发的运维!一个学不会开发的运维!欢迎大家骚扰的运维!

关注微信公众号《运维小路》获取更多内容。