ZooKeeper-监控(Monitor)

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

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

我们虽然前面讲过4字命令和自带的Shell脚本可以读取ZooKeeper的运行状态,但是它并不成体系。还有就是目前云原生的情况下,我们的的监控也需要接入Prometheus和Grafana,所以我们下面就来讲解下我们的监控。目前官方可以直接下载的ZooKeeper版本是3.7.2 和 3.8.4 和3.9.3从配置文件来看都是支持通过metrics方式暴露监控指标。

ini 复制代码
#取消所有注释,重启服务则可以看到
## Metrics Providers
#
# https://prometheus.io Metrics Exporter
metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider
metricsProvider.httpHost=0.0.0.0
metricsProvider.httpPort=7000
metricsProvider.exportJvmInfo=true

#访问地址
http://ip地址:7000/metrics

这里的监控默认包含两个方面的内容:一个就是ZooKeeper的指标(类似前面的四字命令输出)。另外一个它作为使用JAVA语言的应用,内部都有一个JAVA虚拟机,如果我们要分析性能问题就必须分析JAVA虚拟机的堆栈信息(所有的JAVA应用都有)。针对JAVA虚拟机都有一个jvm监控,如果要关闭必须明确关闭设置。

ini 复制代码
#必须显示设置关闭,注释都不行 metrics
Provider.exportJvmInfo=false

#如果开启了可以从日志里面看出来 
2025-04-28 00:36:23,701 [myid:] - INFO  [main:PrometheusMetricsProvider@74] - Initializing metrics, configuration: {httpPort=7000}
2025-04-28 00:36:23,701 [myid:] - INFO  [main:PrometheusMetricsProvider@82] - Starting /metrics HTTP endpoint at port 7000 exportJvmInfo: true

下面这个图就是我从Grafana官方网站找出来的通过metrics的指标在grafana上的图。

ruby 复制代码
#下载地址
https://grafana.com/grafana/dashboards/12338-zookeeper/

当然如果只是简单监控,未接入云原生则可以采用下面的脚本,这里不仅仅需要监控集群状态,还需要监控znode和zxid,因为我是遇到过集群状态正常,但是zxid不一致的情况。

当然下面的代码是我让DeepSeek生成的,格式还有功能可以根据自己需要调整。并且还可以进行调整作为检查本机的ZooKeeper是否正常的的脚本,然后如果不正常还可以对它进行重启。

python 复制代码
import socket
from concurrent.futures import ThreadPoolExecutor
import concurrent.futures

def parse_zoo_cfg(file_path):
    """解析zoo.cfg文件,获取服务器列表和客户端端口"""
    client_port = 2181  # 默认端口
    servers = []
    with open(file_path, 'r') as f:
        for line in f:
            line = line.strip()
            if line.startswith('#') or not line:
                continue
            if '=' in line:
                key, value = line.split('=', 1)
                key = key.strip()
                value = value.strip()
                if key == 'clientPort':
                    try:
                        client_port = int(value)
                    except ValueError:
                        pass  # 保持默认端口
                elif key.startswith('server.'):
                    parts = value.split(':')
                    if parts:
                        host = parts[0]
                        servers.append(host)
    return servers, client_port

def get_node_stat(host, port, timeout=5):
    """通过四字命令获取节点状态"""
    try:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.settimeout(timeout)
            s.connect((host, port))
            s.sendall(b'stat\n')
            response = b''
            while True:
                data = s.recv(1024)
                if not data:
                    break
                response += data
            return response.decode('utf-8')
    except Exception as e:
        return f"Error: {str(e)}"

def parse_stat_response(response):
    """解析stat命令的响应"""
    stats = {}
    lines = response.split('\n')
    for line in lines:
        if line.startswith('Mode:'):
            stats['mode'] = line.split(':', 1)[1].strip()
        elif line.startswith('Zxid:'):
            stats['zxid'] = line.split(':', 1)[1].strip()
        elif line.startswith('Node count:'):
            stats['node_count'] = line.split(':', 1)[1].strip()
    return stats

def main():
    import sys
    cfg_file = sys.argv[1] if len(sys.argv) > 1 else 'zoo.cfg'
    servers, client_port = parse_zoo_cfg(cfg_file)

    if not servers:
        print("未找到服务器配置。")
        return

    print(f"检查 {len(servers)} 个节点...\n")

    results = []
    with ThreadPoolExecutor(max_workers=3) as executor:
        future_to_host = {executor.submit(get_node_stat, host, client_port): host for host in servers}
        for future in concurrent.futures.as_completed(future_to_host):
            host = future_to_host[future]
            try:
                response = future.result()
                if response.startswith("Error"):
                    stats = {'error': response}
                else:
                    stats = parse_stat_response(response)
                results.append((host, stats))
            except Exception as e:
                stats = {'error': str(e)}
                results.append((host, stats))

    # 打印结果
    for host, stats in results:
        print(f"节点: {host}:{client_port}")
        if 'error' in stats:
            print(f"  错误: {stats['error']}")
        else:
            print(f"  状态: {stats.get('mode', 'N/A')}")
            print(f"  Znode数量: {stats.get('node_count', 'N/A')}")
            print(f"  ZXID: {stats.get('zxid', 'N/A')}")
        print()

if __name__ == "__main__":
    main()

当前集群检查就是不符合预期的。监控的目的是让我们及时发现异常,然后根据异常情况及时做除修复操作,避免更大的故障。

makefile 复制代码
检查 3 个节点...

节点: 192.168.31.140:2181
  状态: follower
  Znode数量: 9
  ZXID: 0x2000000d1

节点: 192.168.31.141:2181
  状态: leader
  Znode数量: 9
  ZXID: 0x500000000

节点: 192.168.31.142:2181
  状态: follower
  Znode数量: 9
  ZXID: 0x500000000

运维小路

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

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

相关推荐
rqtz39 分钟前
【Linux-shell】探索Dialog 工具在 Shell 图形化编程中的高效范式重构
linux·运维·重构
行止61 小时前
LVS+Keepalived高可用群集
linux·lvs·keepalived
风清再凯1 小时前
docker基础入门于应用的实践
运维·docker·容器
未来并未来2 小时前
解锁微服务潜能:深入浅出 Nacos
运维·微服务·架构
风好衣轻2 小时前
【环境配置】在Ubuntu Server上安装5090 PyTorch环境
linux·pytorch·ubuntu
华清远见成都中心2 小时前
Linux嵌入式和单片机嵌入式的区别?
linux·运维·单片机·嵌入式
lisanmengmeng3 小时前
rabbitMQ 高可用
linux·分布式·rabbitmq
小妖6663 小时前
ubuntu 22.04 更换阿里源 (wsl2 参照)
linux·运维·ubuntu
凉、介3 小时前
SylixOS 下的消息队列
linux·wpf·sylixos