作者介绍:简历上没有一个精通的运维工程师。请点击上方的蓝色《运维小路》关注我,下面的思维导图也是预计更新的内容和当前进度(不定时更新)。
前面我们介绍介绍了几个常用的代理服务器,本章节我们讲来讲解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读取信息,然后写入本地,当然实际情况需要根据各自的业务逻辑来考虑。
运维小路
一个不会开发的运维!一个要学开发的运维!一个学不会开发的运维!欢迎大家骚扰的运维!
关注微信公众号《运维小路》获取更多内容。