躬行笔记 - 2026年7月
type: channel
link: https://fuzhoupyy.work/index.php/2026/07/
description: 古人学问无遗力，少壮工夫老始成。 纸上得来终觉浅，绝知此事要躬行。
items: Python 打包到底怎么选？我把 6 种主流方式全跑了一遍，优缺点一次说透
items: 别再用 for 循环串行执行了！Python asyncio 异步编程实战（运维自用版）
items: 凌晨3点被502叫醒后，我把排查全流程写成了手册
items: 服务器上线就被扫？一套完整的Linux安全加固方案，照着做能少踩80%的坑（建议收藏）
items: 我们团队踩了2年坑，才总结出这套企业级Jenkins CI/CD搭建方案（建议收藏）
items: 90% 运维只会 top+free：真正救命的是这 20 个工具（建议收藏）
items: Kafka 监控这块我踩了两年坑，今天把从 JMX 到 Grafana 的全套方案掏给你
items: 一次 Kafka 集群磁盘写满后，我把这些底层原理全捋了一遍
items: tcpdump抓包分析实战：从入门到精通，运维必备的"网络显微镜"
items: 网络出问题时，我是怎么用流量监控一步步把"凶手"揪出来的

Python 打包到底怎么选？我把 6 种主流方式全跑了一遍，优缺点一次说透
type: item
date: 2026-07-30T13:53:41+00:00
description: 最近在给团队做内部工具的时候，又被 Python 打包这事折腾了一遍。说真的，这玩意看起来简单，真要让你选个合适的方案，没个三五天根本摸不清门道。我团队那帮人，java、go、PHP 的都有，每次看我搞 Python 部署就笑话我。"你这啥玩意儿？打个包搞一下午？" 你还别说，真就一下午，还不一定能搞利索。所以这次我决定把市面上主流的 Python 打包方式都跑一遍，每个都写写感受，说说坑，给大家一个真实参考。写之前我没看任何官方文档，全凭自己动手踩坑，这样写出来的东西才有烟火气。先说结论：没有银弹，只有适合。不同场景用不同方案，强行用错工具只会让自己加班。那些年，我被 Python 打包支配的恐惧先聊聊为啥要写这篇文章。事情是这样的，上个月接了个活，要把公司一个数据处理脚本做成可执行文件，给运营同事用。他们电脑是 windows，没装 Python，也不会装。我当时心想，pyinstaller 一把梭呗，结果搞了一下午。为啥？因为这脚本依赖了 pandas、numpy、requests，还有一堆奇奇怪怪的库。打包出来 300 多 M，运营同事传个文件发邮件都不方便。更要命的是，第一次在他电脑上跑，直接报错说缺什么 dll，我人都麻了。后来换了几种方式，发现每种都有它自己的脾气。今天就把我试过的几种全讲讲。不过别期待太高，Python 打包这个领域，本身就是"能用"和"好用"之间的巨大鸿沟。你要是想找完美方案，那真的没有。PyInstaller：这个老朋友，我又爱又恨先说 PyInstaller，估计 90% 的 Python 开发者都用过它。用起来真的很简单。一行命令搞定：pyinstaller -F your_script.py-F 是单文件模式，打包出来就一个 exe，用户拿着就能跑。对开发者友好度这块，PyInstaller 真的没得说。我第一次用的时候，感觉这玩意简直是神器。我写了个小工具，打包成 exe 发给同事，他双击就能用，那种成就感，懂的人都懂。但坑也多。最典型的就是体积问题。我那个数据处理脚本，打包出来 280M。同事问我："你这啥程序啊，怎么比英雄联盟客户端还大？" 我无言以对。为啥这么大？因为 PyInstaller 是把整个 Python 解释器和所有依赖库都塞进去了。pandas、numpy 这些库本身就大，再加上 PyInstaller 的运行时，动辄上百 M 起步。还有更恶心的兼容性问题。我那次在 Windows 10 打包的 exe，拿到 Windows 7 上跑不起来。报什么 "api-ms-win-crt" 错误，查了半天是缺 VC++ 运行库。还有在某些精简版系统上，连 tkinter 都跑不起来，要手动指定 hidden import。pyinstaller --hidden-import=tkinter your_script.py这种坑，第一次遇到真的能让人崩溃。不过话说回来，PyInstaller 还是我最常用的。因为它跨平台，Windows、Linux、macOS 都能打，社区也活跃，遇到问题基本搜得到。对于一些内部小工具，不在乎体积的情况下，PyInstaller 仍然是首选。有一次我做个小爬虫工具，就一个 py 文件，依赖只有 requests 和 beautifulsoup4，打包出来才 30M，体验就很好。所以体积这事，看你依赖什么库。纯 Python 库小，带 numpy、pandas 这些科学计算库就炸。cx_Freeze：跨平台的另一种选择cx_Freeze 这玩意，我接触得比 PyInstaller 少，但也有故事。当时做项目要在 Linux 服务器上跑一个 Python 脚本，但服务器是个精简版系统，连 pip 都没有。我想着搞个绿色版，直接拷过去就能用。cx_Freeze 生成的目录结构比较合理，library 和可执行文件分开。看着比 PyInstaller 整洁一些。但这玩意用起来比 PyInstaller 麻烦。要写 setup.py：from cx_Freeze import setup, Executable setup( name="myapp", executables=[Executable("your_script.py")] )然后 python setup.py build，出一堆文件。配置项多，新手容易懵。优点是跨平台做得不错，生成的结构清晰，库和可执行文件分离，对于一些需要定制部署的场景比较友好。缺点就是配置复杂，文档也不如 PyInstaller 详细。我那次配置参数搞了半天，最后还是放弃了，转头用了 PyInstaller。要是你项目有特殊需求，比如要打进一些自定义的 .so 文件或者 dll，cx_Freeze 的配置反而更灵…

别再用 for 循环串行执行了！Python asyncio 异步编程实战（运维自用版）
type: item
date: 2026-07-29T15:49:35+00:00
description: 凌晨 4 点，我盯着屏幕上那个跑了快 20 分钟的 Python 脚本，咖啡喝了三杯，困得要死。脚本干啥呢？检查 80 台服务器的网络连通性，每台超时设 5 秒，串行跑的。当时我就想，这要是哪天服务器数量翻到 200 台，我不得跑到天亮？后来同事看了我的代码，就说了句：你咋不用 asyncio？我一愣，asyncio 是啥？查了一晚上资料，代码重构之后，同样的活，40 秒干完了。提速 30 倍。从那之后，我就把异步编程当成自己写脚本的"标配"了。不管是批量查 API、并发执行 SSH 命令、还是拉一堆监控数据，只要涉及到"等待"，我第一反应就是能不能异步。今天这篇文章不讲那些虚头巴脑的理论（什么事件循环原理、底层 epoll 机制），就讲怎么用、用在哪、踩过什么坑。看完你也能把自己脚本的效率拉起来，少加几个小时的班。运维写脚本的痛，谁写谁知道搞运维的，估计都干过这事：写个脚本批量处理一堆任务，逻辑也不复杂，就是循环里调个 API 或者连一下服务器，结果等得花儿都谢了。常见的场景我列一下，你看看中了几条：批量 ping 一堆机器看通不通调云厂商 API 拉几十台 ECS 的监控数据并发执行 SSH 命令收集日志定时任务里要调 N 个外部接口拿数据爬内部系统页面抓信息这些场景有个共同点：每个任务都要"等"。等网络返回、等 IO 完成、等对端响应。而 Python 默认的同步代码，就是一个任务完成再做下一个。我之前那个检查连通性的脚本，每台 5 秒超时，80 台理论最差要等 400 秒。但实际上很多机器不通要等满 5 秒，所以最后跑了将近 20 分钟。如果用异步呢？所有请求"同时"发出去，谁先回来先处理谁，总时间就取决于最慢的那一个，而不是所有加起来。这就是异步编程的核心价值：把"等待"的时间利用起来。异步不是新概念，只是 Python 终于有了"正经"语法在说代码之前，先聊点背景，避免你看着看着懵。异步这个概念早就有了，从最早的 Node.js 到 Go 语言，异步都是标配。Python 因为历史原因（GIL、全局解释器锁），多线程一直被诟病，所以异步这个特性迟迟没搞起来。直到 Python 3.4 出了 asyncio 模块，3.5 加入了 async/await 语法，异步编程才在 Python 这边真正能用。简单理解 asyncio 的几个关键概念：协程（Coroutine）：可以暂停的函数。用 async def 定义的函数就是协程函数，调用它不会立即执行，而是返回一个协程对象，得用 await 或者 asyncio.run() 之类的方式驱动它。事件循环（Event Loop）：asyncio 的核心，负责调度所有协程。你可以理解成"大管家"，所有协程都注册到它上面，它来决定什么时候执行哪个。任务（Task）：对协程的进一步封装，让协程能被事件循环调度执行。asyncio.create_task() 就是干这个的。Future：表示一个还没完成的结果，通常我们不用直接操作它。这玩意有点像餐厅服务员。同步就是你点完菜干等着，菜上完才点下一道；异步就是你点完菜服务员给你个号牌，你接着点下一道，菜好了服务员喊你。听起来挺玄乎，下面看代码就明白了。async/await 基础语法最基础的异步函数长这样：import asyncio async def hello(): print("Hello") await asyncio.sleep(1) # 模拟 IO 等待 print("World")就这么简单。async def 定义异步函数，await 后面接"可等待对象"（通常是另一个协程）。调用的时候不能直接 hello()，那只会返回一个协程对象，函数体不会执行。得这么干：asyncio.run(hello()) # Python 3.7+ 推荐写法asyncio.run() 会创建事件循环，运行协程，然后关闭循环。一句话搞定。但这有啥用？单个协程和同步没啥区别。真正的威力在并发。真正的提速：gather 和 create_taskasyncio 里两个最常用的并发工具：asyncio.gather 和 asyncio.create_task。先说 gather，最直观的并发方式：import asyncio import aiohttp # 异步 HTTP 库 async def fetch(url): async with aiohttp.ClientSession() as session: async with session.get(url) as response: return await response.text() async def main(): urls = [ "https://api1.examp…

凌晨3点被502叫醒后，我把排查全流程写成了手册
type: item
date: 2026-07-28T13:36:00+00:00
description: 睡梦中手机震个不停，迷迷糊糊摸过来一看，好家伙，告警群里已经炸了：核心业务接口大面积502，客服那边电话快被打爆了。我盯着天花板愣了两秒，然后爬起来开电脑，远程连上跳板机那一刻，说实话手都是抖的。这种场景做运维的应该都不陌生吧？大晚上被叫起来，脑子还没转过弯来，屏幕上全是红色告警，脑子里只有一个念头：这锅今天是不是得我来背？后来那次故障复盘会上，领导问我一个问题：502到底是怎么来的？你下次能不能10分钟之内定位清楚？我当时没敢接话。但事后我花了整整一周时间，把502的所有可能场景、排查路径、命令清单、根因分类全部整理了一遍。今天这篇文章，就是我整理出来的成果。不敢说100%覆盖，但生产环境里能踩的坑，基本都在里面了。你要是看完能少熬两个夜，少背几次锅，这文章就没白写。502到底是个什么东西在开干之前，得先搞清楚502到底是什么，不然排查的时候就是瞎子摸象。502的全称是Bad Gateway，翻成人话就是：网关或者代理服务器在往上游拿数据的时候，没拿到，或者拿到的东西不对，于是就给你返回了一个502。这句话听起来简单，但里面的信息量很大。第一个关键词：网关。也就是说502基本不会出现在直连后端的场景里，它一定是有中间层的情况下才会出现。Nginx、Apache、Haproxy、SLB、CDN、F5这些都是常见的网关。第二个关键词：上游。502是网关在告诉客户端：我没拿到上游的正常响应。至于是没连上、超时了、还是上游给我返回了一个乱七八糟的东西，那就是后面要具体排查的内容了。所以你记住一句话：502的锅，永远不在用户和网关之间，一定是网关到上游这段路出了问题。这也是为什么很多新手排查502的时候，一上来就去看后端服务的日志，看半天发现服务其实跑得好好的，于是就懵了——其实方向就不对。排查前的三件准备工作讲具体排查步骤之前，先说几个准备工作，这几个步骤能帮你少走一半弯路。第一，先确认影响范围。是所有接口都502，还是只有部分接口？是所有用户都中招，还是只有某个地区、某个运营商的用户？这个信息决定了你是S级故障还是A级故障，也决定了你需要叫多少人起来一起干。我之前就吃过亏，大半夜一看到告警就埋头排查，结果搞了半天才发现是某个边缘节点的局部问题，影响范围非常小，但当时我慌得一批，根本没先评估范围。第二，先抓现场。故障窗口期是金贵的，你排查过程中产生的所有数据，最好都保留下来。日志、监控截图、网络包、命令输出，能留的都留。为啥？因为等你查完已经天亮了，领导问你"你确定是这个问题？"的时候，你得有东西给他看。空口无凭的故障复盘，谁都不会买账。第三，先沟通。发现故障的第一时间，别自己一个人闷头干。在群里吼一声，说"现在线上有502问题，正在排查，影响范围XXX"，这个动作比排查本身还重要。沟通到位了，出了问题大家一起扛；沟通不到位，最后就是你的锅。这不是甩锅，这是基本的职业素养。好，准备工作说完，进入正餐。Nginx返回502的几种典型场景生产环境里502最多的场景，还是Nginx作为反向代理的时候。所以我重点讲这块。Nginx的error.log是排查502的第一现场，基本所有502的原因都能在这个日志里找到蛛丝马迹。所以排查的第一步，永远是去看Nginx的error.log。一般路径是 /var/log/nginx/error.log，tail一下最近的错误，基本就能看出个大概。下面是我这些年遇到的高频错误形态。connect() failed (111: Connection refused)这种错误的意思是：Nginx去连上游的时候，连接被拒了。翻译成普通话就是：上游服务根本没在监听那个端口，或者服务挂了。排查思路很直接：上游服务是不是活着？直接curl一下上游的IP+端口，看能不能连上上游服务的端口是不是变了？有时候发版的时候改了端口，Nginx配置没同步上游服务是不是只监听了127.0.0.1？这种情况下外部根本连不进去我之前有个项目，后端用的是Spring Boot，默认配置下服务只监听127.0.0.1，本地测试一切正常，一上线就502。后来排查了半天，定位到是这个原因，加上server.address配置才解决。这种情况其实挺常见的，尤其是开发环境和生产环境配置不一致的时候，特别容易出这种幺蛾子。connect() failed (110: Connection timed out)这个比Connection refused更恶心，因为它意味着连接请求发出去之后，对方一直没回应，直到超时。这种情况一般是网络层面的问题，比如：上游服务器宕机或者网络断了防火墙拦截了请求安全组规则没放行上游服务卡死了，没法accept新连接排查这种问题的时候，telnet是最直接的工具。telnet 上游IP 上游端口要是telnet都连不上…

服务器上线就被扫？一套完整的Linux安全加固方案，照着做能少踩80%的坑（建议收藏）
type: item
date: 2026-07-27T09:12:02+00:00
description: 你新买了一台云服务器，装好系统、部署完应用，觉得万事大吉了。但你打开/var/log/auth.log看一眼就知道，从你服务器暴露在公网那一刻起，全世界的扫描器已经在排队敲门了。一天几万条暴力破解记录，不是夸张，是常态。这篇文章我把每一步加固操作写到具体命令级别。不讲道理，直接上手。系统账户和认证这块，是重灾区大部分入侵事件的入口就是SSH暴力破解。你去看任何一台暴露在公网的Linux机器，/var/log/auth.log或者/var/log/secure里面一定有大量失败登录记录。禁止root直接SSH登录这是最基础的一步，但很多人懒得改。vim /etc/ssh/sshd_config找到这一行，改成no：PermitRootLogin no然后重启sshd：systemctl restart sshd改之前记得先建好一个普通用户并加入sudo组，不然你改完自己也登不上去了。我见过有人改完直接断连，机房又不在身边，只能提工单让机房人员接显示器重置——这种低级错误犯一次就够了。useradd -m -s /bin/bash devops passwd devops usermod -aG sudo devops # Debian/Ubuntu # 或者 usermod -aG wheel devops # CentOS/RHEL修改SSH默认端口22端口是所有扫描器的第一目标。改成一个高位端口，比如58222，虽然不能完全防住定向攻击，但能过滤掉99%的自动化扫描。# /etc/ssh/sshd_config Port 58222改端口之后有个坑：如果你开了firewalld或者ufw，一定要先放行新端口再重启sshd。顺序反了你又得去机房了。# ufw的情况 ufw allow 58222/tcp systemctl restart sshd # firewalld的情况 firewall-cmd --permanent --add-port=58222/tcp firewall-cmd --reload systemctl restart sshd强制使用密钥登录，禁用密码认证这一步做完，暴力破解基本就废了。本地机器上生成密钥对：ssh-keygen -t ed25519 -C "your-server-name"把公钥传到服务器上：ssh-copy-id -p 58222 devops@your-server-ip确认密钥能登录之后，再禁用密码：# /etc/ssh/sshd_config PasswordAuthentication no PubkeyAuthentication yes重启sshd生效。这里有个细节很多教程不提：如果你用的是CentOS 8或者更新的系统，sshd_config里可能有个Include /etc/ssh/sshd_config.d/*.conf，子配置文件里的设置会覆盖主文件。我就踩过这个坑，明明改了主文件，密码登录还是能用，最后发现是子配置里有一行PasswordAuthentication yes在生效。grep -r "PasswordAuthentication" /etc/ssh/sshd_config.d/检查一下有没有冲突配置。登录失败锁定用fail2ban来做。装完就能用，配置也简单：apt install fail2ban # Debian/Ubuntu yum install fail2ban # CentOS systemctl enable fail2ban systemctl start fail2ban默认配置已经能防SSH暴力破解了，但我一般会调整一下参数。创建一个本地配置文件：cat > /etc/fail2ban/jail.local << 'EOF' [sshd] enabled = true port = 58222 maxretry = 3 bantime = 3600 findtime = 600 EOF意思是：10分钟内失败3次，封禁1小时。生产环境我会把bantime调到86400（一天）甚至更长。systemctl restart fail2ban查看当前封禁状态：fail2ban-client status sshd防火墙策略：默认拒绝，按需放行很多人的防火墙配置是反的——默认放行，然后去堵某些端口。正确做法是默认拒绝所有入站流量，只开放你确实需要的端口。UFW（Ubuntu/Debian推荐）ufw default deny incoming ufw default allow outgoing ufw allow 58222/tcp comment 'SSH' ufw allow 80/tcp comment 'HTTP' uf…

我们团队踩了2年坑，才总结出这套企业级Jenkins CI/CD搭建方案（建议收藏）
type: item
date: 2026-07-23T15:03:38+00:00
description: 200多个微服务同时构建失败，线上发版卡死，开发堵在办公室门口骂娘。我盯着Jenkins那台破服务器，磁盘100%，内存飘红，连后台都进不去。那一刻我才真正明白：Jenkins不是装上就能用的，企业级的CI/CD，是一套体系，不是一个工具。装上Jenkins到用好Jenkins，中间隔着一百个坑。今天把这两年的踩坑经验全掏出来，从架构到Pipeline，从插件选型到高可用，从权限管控到性能调优，掰开了讲。看完你直接能落地，少走半年弯路。从一次深夜故障说起事情是这样的。那天搞活动，所有业务线都在赶发版。我们的Jenkins集群突然就崩了，Master机器CPU直接100%，整个构建系统卡死。排查过程堪称教科书式的狼狈。先看top，发现Java进程吃满了CPU。再看Jenkins的thread dump，发现大量线程卡在Git操作上。查构建队列，积压了400多个任务，整个Master被拖到喘不过气。根因查出来让人哭笑不得：有个老项目没做Git LFS，把一堆二进制文件全塞进Git仓库了，单次拉取要20多分钟。每次构建全量拉取，整个Master被拖垮。怎么解决的：第一步，立刻扩容Master机器到8核16G临时顶住，让发版能继续走。第二步，强制清理那个20多G的仓库，二进制文件迁移到Nexus。第三步，加了Git仓库大小监控，超过500M的报警。第四步，重要Agent节点限流，配置合理的并发数。这个案例让我彻底明白一个道理：CI/CD系统的稳定性，取决于最弱的那个环节。你Jenkins装得再高端，架构设计得再完美，一个失控的Git仓库就能搞死你。先搞清楚Jenkins到底是个啥很多人对Jenkins的认知停留在"装上就能用的工具"，但企业用起来，会发现完全不是那么回事。Jenkins本质上是个事件驱动的执行引擎。它监听代码变更、定时任务、webhook这些事件，然后调度Master和Agent去干活。Master负责调度和界面展示，Agent负责实际构建。这套Master/Agent架构听起来简单，但企业用起来，全是坑。它和GitLab CI、GitHub Actions最大的区别在于插件生态。Jenkins有1500多个官方插件，几乎能集成所有你能想到的工具。这种开放性是双刃剑，好处是扩展性强，坏处是插件质量参差不齐，装多了容易出兼容性问题。而Jenkins Pipeline，是它区别于传统CI工具的核心特性。它是一组插件，让你可以用Groovy DSL把整个构建流程写成代码，然后签入到Git仓库里管理。这就是所谓的"Pipeline as Code"——把构建流水线当成代码一样去版本控制、审查、迭代。说实话，我见过太多团队还在用Freestyle Job建项目，建了几百个Job，改个参数要改到手断。企业级必须上Pipeline，这是血泪教训。架构选型：别一上来就堆K8s我们第一版架构就吃了亏：上来就搞Jenkins on K8s，动态Agent拉满，觉得这就是最佳实践。结果网络问题、PV挂载问题、调度问题全冒出来，团队天天救火。后来才悟出来：架构要匹配团队规模，不要为了用新技术而用新技术。10人以下的团队，单Master就够，搞那么多花活纯属给自己挖坑。装个Jenkins，配几个Agent，先把流程跑通再说。50人左右的团队，Master+静态Agent够用。搭个Nginx反向代理做高可用，Agent按语言分组管理，这套架构能扛住绝大部分中型公司的构建压力。200人以上的大团队，才需要考虑K8s动态Agent。K8s的好处是弹性伸缩，构建高峰期自动扩容，低谷期自动释放。但运维成本也上去了，需要有专门的SRE团队维护。我们现在的架构跑了两年，稳得很：2台Master做主备，用Nginx+Keepalived漂移VIP，构建Agent按语言分组（Java一组、Python一组、前端一组），构建产物推Nexus，镜像推Harbor。这套扛了日均2000+次构建，稳定性99.9%以上。环境准备：这些坑提前避开Jenkins的安装本身不复杂，但环境准备阶段有几个坑，新手必踩。第一个坑：运行用户。Jenkins千万别用root启动，但目录权限一定要给足。我们当时chown改权限，改完一堆任务报错，查了半天才发现是.ssh目录和known_hosts的问题。jenkins用户的SSH密钥、known_hosts文件都要正确配置，不然Agent通信会出问题。第二个坑：JDK版本。Jenkins 2.346以后的版本必须JDK11，我们当时还在用JDK8，启动直接报错。升级JDK又牵扯到一堆老插件兼容性，又是半天折腾。所以选Jenkins版本前，先查清楚对应的JDK要求。第三个坑：磁盘空间。Jenkins的workspace会越积越多，构建历史、…

90% 运维只会 top+free：真正救命的是这 20 个工具（建议收藏）
type: item
date: 2026-07-22T15:46:33+00:00
description: 为什么光会 strace 远远不够先说个大实话。很多刚入行的兄弟一提排查就上 strace，觉得这玩意儿万能。strace 确实好用，追系统调用和信号一把好手。可它有个致命毛病——性能开销太大了。你在一个每秒几万请求的高并发服务上直接 strace，好家伙，进程直接被你拖慢好几倍，本来没崩的可能被你 strace 崩了。我早年就干过这种蠢事，线上抓 strace 把一个本来只是慢的服务直接搞到超时雪崩，被组长在群里点名，社死现场。而且 strace 只能看系统调用。程序卡在用户态的库函数里、卡在锁上、卡在某个死循环里，strace 是看不出来的，它只会显示进程一直卡在某个 read 或者 futex 上，然后你就懵了。排查故障的核心逻辑就一句话：先定位是哪一类资源出了问题，再用对应的专项工具往下钻。 别一上来就闷头抓包、闷头 strace，那是没头苍蝇。下面我按场景拆开讲。追踪剖析类：strace 的那些「兄弟」ltrace —— 看库函数调用strace 追内核调用，ltrace 追用户态的动态库函数，俩是互补的。比如你怀疑程序疯狂 malloc 内存、或者在某个字符串处理函数里死循环，strace 是看不见的，这时候 ltrace 就派上用场了：ltrace -p 12345 # attach 到进程 ltrace -c ./your_app # 统计各库函数调用次数和耗时那个 -c 参数特别有用，能给你统计出哪个库函数被调用了几十万次、占了多少时间，一眼就能看出程序在哪儿磨蹭。不过说句实话，ltrace 在生产上我用得也少，开销跟 strace 半斤八两，一般是测试环境或者能接受短暂影响的时候才上。eBPF 全家桶（bpftrace / bcc-tools）—— 生产环境的救命稻草重点来了，这个我要多说两句。前面提到我凌晨那次故障，最后就是靠 eBPF 里的工具定位的。eBPF 这东西现在是真香，基于内核的 BPF 虚拟机跑，性能损耗极低，低到你可以在生产高并发环境直接开，几乎感觉不到影响。bcc-tools 里一堆现成脚本，拿来就用：execsnoop # 实时追踪谁在启动新进程，排查异常启动、定时任务乱跑神器 opensnoop # 追踪谁在打开哪些文件 ext4slower 10 # 揪出超过 10ms 的慢磁盘 I/O，直接告诉你哪个进程哪个文件慢 biolatency # 磁盘 I/O 延迟直方图 tcpconnect # 追踪 TCP 主动连接我那次就是用 ext4slower 发现某个进程在疯狂读一个小文件，一秒好几万次，磁盘 await 被它顶爆了。后来查出来是代码里配置读取没做缓存，每个请求都去磁盘捞一次配置文件。bpftrace 更灵活，能写单行脚本自定义追踪，比如统计某个系统调用的分布：bpftrace -e 'tracepoint:syscalls:sys_enter_read { @[comm] = count(); }'这行意思是统计各进程调用 read 的次数，跑几秒 Ctrl+C，直接给你排好序。学会 bpftrace，很多以前要写复杂脚本才能干的事，一行搞定。安装嘛，yum install bcc-tools 或者 apt install bpfcc-tools，脚本一般在 /usr/share/bcc/tools/ 下面。内核版本得 4.9 以上，太老的内核用不了，这是唯一的门槛。perf —— CPU 热点分析的标准答案CPU 飙高但你不知道飙在哪个函数上，perf 就是干这个的。perf top # 实时看当前占 CPU 最多的函数 perf record -F 99 -p 12345 -g -- sleep 30 # 采样 30 秒 perf report # 看结果perf 最强的搭配是火焰图（FlameGraph）。把采样数据生成火焰图，一张图上哪个函数占 CPU 一目了然，越宽的越占。我强烈建议每个运维都把火焰图这套流程练熟，定位 CPU 问题效率翻好几倍：perf record -F 99 -p 12345 -g -- sleep 30 perf script | ./stackcollapse-perf.pl | ./flamegraph.pl > cpu.svg生成的 svg 用浏览器打开，能交互，点哪块放大哪块。gdb —— 程序卡死了就靠它服务卡死、疑似死锁，进程还在但就是不响应，这时候 gdb attach 上去看堆栈：gdb -p 12345 (gdb) bt # 看当前调用栈 (gdb) thread apply all bt # 看所有线程的栈thread apply all bt 这个命令排查死锁贼好用，能把所有线程卡在哪儿全打出来，你一眼就能看到两个线…

Kafka 监控这块我踩了两年坑，今天把从 JMX 到 Grafana 的全套方案掏给你
type: item
date: 2026-07-22T15:45:59+00:00
description: 我把这两年折腾 Kafka 监控的经验整理出来，从 JMX 指标怎么暴露、Exporter 怎么选、Prometheus 怎么抓、Grafana 面板怎么配，到告警规则怎么写，全流程给你讲清楚。看完你照着搭一套，基本能覆盖 90% 的 Kafka 线上问题。建议先收藏，搭的时候对着看。先说清楚，Kafka 到底该监控啥很多人一上来就问用啥工具，其实工具是最后一步。你得先知道 Kafka 出问题的时候，都是哪些指标先变脸。我自己踩下来，最关键的就这么几类：消费延迟（Consumer Lag），这是重中之重。Lag 就是消息生产了但还没被消费的数量。它一涨，要么是消费者处理不过来，要么是消费者挂了。我开头那次事故，就是 Lag 没监控。Broker 的存活和吞吐。哪个 broker 挂了、每秒进出多少字节、请求处理耗时多少，这些直接反映集群健不健康。Under Replicated Partitions（副本不同步分区数）。正常情况这个值应该是 0。一旦大于 0，说明有分区的副本没跟上，可能是某个 broker 压力大或者网络抖动，这是集群不稳的前兆。Controller 数量。整个集群应该只有一个 Controller，如果监控显示 0 个或者 2 个，那你集群基本要出大事了，脑裂了。磁盘、堆内存、GC。Kafka 是重度依赖磁盘和 PageCache 的，磁盘满了直接写不进去；堆内存和 GC 卡顿会让 broker 响应变慢。先记住这几个，后面配面板和告警都围绕它们来。JMX 是一切的源头Kafka 是 Scala/Java 写的，它的内部指标全部通过 JMX（Java Management Extensions）暴露出来。你想拿到任何一个指标，本质上都是从 JMX 里读的。默认情况下 Kafka 的 JMX 端口是不开的，你得手动开。在启动 Kafka 之前设置一个环境变量：export JMX_PORT=9999或者直接改 kafka-server-start.sh，在里面加上：export KAFKA_JMX_OPTS="-Dcom.sun.management.jmxremote \ -Dcom.sun.management.jmxremote.authenticate=false \ -Dcom.sun.management.jmxremote.ssl=false \ -Djava.rmi.server.hostname=你的服务器IP \ -Dcom.sun.management.jmxremote.port=9999 \ -Dcom.sun.management.jmxremote.rmi.port=9999"这里有个坑我提醒一下，java.rmi.server.hostname 这个一定要写对，写成 127.0.0.1 或者不写，远程连 JMX 就会连不上，报一堆莫名其妙的 RMI 错误。我第一次搭的时候在这卡了俩小时，就是因为没配这个 hostname，本地能连远程死活连不上。配好之后重启 Kafka，你可以用 jconsole 或者 jmxterm 连上去验证一下端口通不通。生产环境我一般会写个简单的检查：echo "beans" | java -jar jmxterm.jar -l localhost:9999 -n | grep kafka能看到一堆 kafka.server 开头的 bean，就说明 JMX 正常暴露了。Exporter 怎么选，我给你说实话JMX 数据 Prometheus 是直接读不了的，得有个中间转换的东西，这就是 Exporter。市面上主要有两条路：第一条：JMX Exporter。这是官方 Prometheus 出的通用 JMX 转 Prometheus 工具。它有两种跑法，一种是作为 javaagent 挂在 Kafka 进程里跑，另一种是单独起个进程去连 JMX 端口。我强烈推荐用 javaagent 模式，因为它性能好，直接在进程内读，不用走 RMI。配置方式是在 Kafka 启动参数里加：export KAFKA_OPTS="-javaagent:/opt/jmx_exporter/jmx_prometheus_javaagent.jar=7071:/opt/jmx_exporter/kafka.yml"这里 7071 是暴露给 Prometheus 抓取的端口，kafka.yml 是配置文件，里面写你要转换哪些 JMX 指标。这个配置文件是个大工程，因为 JMX 的 bean 名字特别长特别乱，你得写正则把它们映射成规范的 Prometheus 指标名。好消息是官方仓库里有现成的 kafka-2_0_0.yml 配置，直接拿来改改就能用，不用自己从头写。我贴一小段…

一次 Kafka 集群磁盘写满后，我把这些底层原理全捋了一遍
type: item
date: 2026-07-21T14:15:45+00:00
description: 上个月某天凌晨，告警群里突然炸了：Kafka 集群某台 broker 磁盘使用率 95%，马上要撑爆。我赶紧爬起来处理，登上去一看，日志目录里堆了一堆 .log 文件，大的有几十 G，小的也有几个 G。当时心里挺懵的，Kafka 不是号称"高吞吐、零丢失"吗，怎么磁盘会爆？后来花了半宿才把问题搞定，顺带把 Kafka 底层存储这块东西翻了个底朝天。今天把这次踩坑的收获，还有我整理的一些核心原理全部分享出来，能让你少走不少弯路。Kafka 到底是咋工作的先把 Kafka 的运行机制理一遍，后面讲的所有东西都跟这个有关。Kafka 是个分布式消息系统，由几类角色组成：Producer（生产者）发消息，Consumer（消费者）收消息，Broker 是 Kafka 服务本身，集群就是多台机器起多个 broker 进程。消息按 Topic（主题）分类，一个 Topic 可以分成多个 Partition（分区），分区分布在不同 broker 上。Consumer 启动后加入 Consumer Group（消费组），组内每个 Partition 只被一个 Consumer 消费。早期版本依赖 ZooKeeper 存元数据、协调集群，新版本（2.8+）开始支持 KRaft 模式，可以脱离 ZooKeeper 跑。Kafka 既能当消息队列（解耦、削峰、异步），也能当存储系统（消息持久化、多副本），还能当流处理平台的数据源。不过生产上最常见的用法还是第一种。明白了这些，再看下面的内容就不费劲了。目录结构：分区和分段存储Kafka 的存储是分层的，几个核心概念得搞明白。主题（Topic）是逻辑上的概念，下面分为多个分区（Partition），分区是物理存储的基本单位。每个分区对应磁盘上一个文件夹，文件夹名字一般是 主题名-分区编号，比如 order-topic-0。分区下面不是一个大文件，而是分成了多个日志分段（LogSegment），每个 LogSegment 包含三类文件：.log 文件：实际的消息数据.index 文件：偏移量索引.timeindex 文件：时间戳索引比如你看到这样的目录结构：order-topic-0/ ├── 00000000000000000000.log ├── 00000000000000000000.index ├── 00000000000000000000.timeindex ├── 00000000000000123456.log ├── 00000000000000123456.index └── 00000000000000123456.timeindex每个 LogSegment 默认是 1G（由 log.segment.bytes 控制），当一个 .log 文件写满了，就会滚动生成新的 LogSegment，文件名就是第一条消息的 offset。这就解释了为什么我那台机器磁盘会爆：分区里 .log 文件一个接一个，副本数又是 1，没法通过副本分散压力。消息顺序性的保证：按 Key 路由很多人问 Kafka 怎么保证消息顺序，严格来说，Kafka 只能保证单个分区内的消息是有序的。生产者发消息的时候，如果没有指定分区，会走一个分区器（Partitioner）。Kafka 默认的分区策略是这样的：List<PartitionInfo> partitions = cluster.partitionsForTopic(topic); return Math.abs(key.hashCode()) % partitions.size();也就是说，同一个 Key 的消息一定会进同一个分区，同一个分区内的消息是顺序写、顺序读的。我之前遇到过一个生产事故，某个业务把消息的 Key 随机生成（用的 UUID），结果消息被分散到所有分区，消费者并行消费的时候，消息顺序就乱了。后来把 Key 改成业务主键 ID，问题才解决。所以如果你对消息顺序有要求，一定要保证 Key 是固定的业务标识，比如订单 ID、用户 ID。生产者客户端的两个线程Kafka 的生产者客户端跑得这么溜，靠的是两个线程的协作：主线程和 Sender 线程（也叫发送线程）。主线程负责把消息经过拦截器、序列化器、分区器之后，放到消息累加器（RecordAccumulator）里。注意这个累加器不是简单地把消息扔进去，它内部是按分区分组的，每个分区对应一个双端队列，队列里是一批一批的消息（Batch）。Sender 线程在后台不断轮询，从累加器里把攒够的 Batch 发送到 broker。为什么要攒批？减少网络 IO 次数啊，一批发几十条肯定比一条一条发快得多。这里面有几个关键参数：batch.size：攒批的大小，默认 16KBlinger.ms：最…

tcpdump抓包分析实战：从入门到精通，运维必备的"网络显微镜"
type: item
date: 2026-07-20T14:32:05+00:00
description: 凌晨2点，线上服务突然告警，业务反馈接口超时。监控显示服务正常，防火墙没动，配置也没改。你登服务器查了半天，越查越懵——网络明明是通的，业务就是跑不起来。这种场景我太熟悉了，ping、telnet、netstat 这些老伙计轮番上场，问题依旧悬而未决。最后往往是抓个包一看，才发现是某个TCP重传、或者某个SYN被默默丢弃了。今天这篇，把tcpdump的实战用法彻底讲清楚。文章比较长，建议先收藏，需要的时候翻出来对照着练。认识tcpdump：为什么它这么重要tcpdump是Linux下最经典的网络抓包工具，它工作在网络层，能捕获所有流经网卡的数据包。和wireshark相比，tcpdump是命令行工具，更适合在服务器上直接使用，尤其是在没有图形界面的生产环境中，几乎是唯一选择。我一直把它比作"网络显微镜"——ping只能告诉你通不通，telnet只能告诉你端口开没开，而tcpdump能让你看到每一个数据包的真实面貌。这在排查疑难网络问题时，往往是决定胜负的关键。基础环境准备开始之前，先准备两台虚拟机，配置如下：主机IP系统rocky-web（服务端）192.168.159.167Rocky Linux 9rocky-client（客户端）192.168.159.168Rocky Linux 9安装tcpdump很简单：# CentOS/RHEL/Rocky yum install -y tcpdump # Ubuntu/Debian apt-get install -y tcpdump必会的10个核心命令1. 抓取指定网卡的包最基础的用法，指定网卡开始抓包：tcpdump -i ens160执行后会一直抓取该网卡上的所有流量，按Ctrl+C停止。刚接触tcpdump的同学会一脸懵——屏幕上刷刷刷地滚，根本看不清啥是啥。别急，下面这些过滤参数会帮你精准定位。2. 抓取指定数量的包抓几个包看看就行：tcpdump -c 5 -i ens160-c 5 表示只抓5个包就停止，避免一直刷屏看不清。3. 不解析域名和端口（强烈推荐）tcpdump -nn -i ens160nn参数告诉tcpdump不要把IP解析成域名、把端口解析成服务名。这一条在生产环境几乎必加，原因有两个：一是提升性能，避免DNS查询拖慢抓包；二是显示更直观，看到的端口号就是真实的端口号。4. 显示完整时间戳tcpdump -tttt -i ens160默认时间显示是11:34:19.760275这种只有时分秒的格式，加了-tttt后会变成2025-06-03 13:53:09.891524，排查问题时能精确到毫秒。5. 指定协议类型# 只抓ARP包 tcpdump -i ens160 arp # 只抓TCP包 tcpdump -i ens160 tcp # 只抓UDP包 tcpdump -i ens160 udp # 只抓ICMP包（ping用的就是这个协议） tcpdump -i ens160 icmp排查不同类型的问题，用不同的协议过滤，能极大减少干扰信息。6. 指定端口# 抓取80端口的包 tcpdump -i ens160 port 80 # 抓取多个端口 tcpdump -i ens160 'port 80 or port 443' # 抓取端口范围 tcpdump -i ens160 portrange 8000-9000注意多条件要用引号包起来，避免shell解析出错。7. 指定主机（最常用）# 抓取和特定IP相关的所有包 tcpdump -i ens160 host 192.168.159.168 # 只抓源IP是该IP的包 tcpdump -i ens160 src host 192.168.159.168 # 只抓目标IP是该IP的包 tcpdump -i ens160 dst host 192.168.159.1688. 组合过滤（实战核心）单个条件往往不够用，需要组合：# 抓取从168到167的80端口请求 tcpdump -i ens160 src host 192.168.159.168 and dst host 192.168.159.167 and port 80 # 抓取特定网段 tcpdump -i ens160 net 192.168.159.0/24 # 排除某IP的流量 tcpdump -i ens160 not host 192.168.159.1支持的逻辑运算符有三个：and、or、not，组合起来能应对绝大多数场景。9. 保存到文件（必备技能）# 保存到pcap文件 tcpdump -i ens160 -w capture.pcap # 抓100个包就停止 tcpdump -i ens160 -c 100 -w cap…

网络出问题时，我是怎么用流量监控一步步把"凶手"揪出来的
type: item
date: 2026-07-19T15:12:28+00:00
description: 大半夜被电话吵醒那种滋味，干过运维的兄弟都懂。上周五凌晨两点多，我正睡得香，一个电话直接把我从梦里拽出来——某业务系统卡得不行，研发那边查了一通没找到原因，怀疑是网络问题，让我赶紧看看。说实话，接到这种电话第一反应肯定是烦躁，但活儿来了还是得干。我揉着眼睛打开电脑，先远程登录到核心交换机上瞄了一眼端口状态，乍一看没啥问题，端口也没down。但业务反馈就是慢，这就很烦了——肉眼看不到的问题最难搞。先别急着抓包，确定范围很重要很多新手一遇到网络问题就想着抓包分析，这个思路不能说错，但很多时候效率很低。流量分析也好，抓包也好，都是有成本的，尤其是大流量的生产环境，你一上来就在核心链路上开抓包，可能会把链路打满，搞得更糟。我一般会先做一件事：缩小范围。这个业务慢，到底是一个人慢，还是一个部门慢，还是全公司都慢？如果只是某个人慢，那大概率是终端或者他接入的那一段问题；如果是整个部门都慢，那问题可能在上游的汇聚交换机或者防火墙；如果全公司都慢，那基本就是核心设备或者出口链路的问题了。问了一圈下来，发现是研发部整层楼都慢，行政那边正常，市场部也正常。这就基本可以判断问题在研发部那一段的接入层往上。范围一下子缩小了很多。用什么工具看流量？别一上来就上Wireshark说到流量监控工具，很多人第一个想到的就是Wireshark。Wireshark确实强大，协议分析做得非常细致，但它有个问题——太重了。在生产环境尤其是高流量场景下，你不可能在每一台设备上都跑Wireshark实时抓包，那对性能影响很大。我自己的习惯是先从轻量级的开始。比如交换机上看看端口计数，看看有没有CRC错误、丢包、冲突这些指标。大部分硬件厂商的交换机都有这个功能，华为的display interface、華三的display interface brief、思科的show interface都能看到。配置低的设备可能数据不准确，但参考一下还是可以的。这次我先登录到研发部接入层的交换机，敲了display interface，发现有一个端口的input errors和CRC都明显比别的端口高。这个端口下面接的是一台汇聚交换机，基本上可以判断问题点就在这段链路或者这台汇聚设备上。接着我又看了下设备的CPU和内存，CPU飙到90%多，内存也快满了。这就是典型的"交换机累趴下了"的表现。设备一旦CPU高，丢包是必然的，因为报文处理不过来了。抓到证据：上sFlow或者NetFlow看具体流量光看端口计数还是不够细致的。要想知道到底是哪些流量在搞事情，我一般会开sFlow或者NetFlow。这两个东西原理差不多，都是把流量的统计信息（不是原始报文）发到采集器，对设备性能影响很小，特别适合长时间监控。我这边的环境是华为交换机，用的是NetStream（华为对NetFlow的实现）。配置其实不复杂：system-view netstream export ip 192.168.1.100 9995 // 指定采集器地址 netstream export version 9 // 用v9版本 interface GigabitEthernet0/0/24 netstream inbound netstream outbound然后在采集器上我用ntopng来收，这个工具是开源的，部署也简单，装在一台Linux机器上就能用。配置完等个五到十分钟，数据就开始往上报了。打开ntopng的web界面一看，好家伙，有个IP的流量占了整个接口的60%多。再点进去看详情，这个IP发出去的包大部分都流向了一个外部地址。看了下目的端口，是8443。研发那边主要用8443做啥呢？想了想，应该是某个内部系统的接口调用，但这个流量明显不正常，比平时高了几十倍。继续深挖：必要时还是得上Wireshark到这里其实已经基本能定位了，但为了搞清楚到底发的什么内容，我还是抓了个包。抓包我一般不在问题设备上抓，而是在它上游或者下游抓，这样对问题链路的影响小一些。用tcpdump在采集器上抓了几分钟：tcpdump -i eth0 -w /tmp/cap.pcap host 10.1.2.100把抓包文件拖到Wireshark里一分析，发现这个IP在疯狂地往一个外部域名发请求，而且请求的频率高得离谱，1秒钟上百次。正常业务调用不可能这么频繁。再仔细看HTTP头（虽然是8443但内部走了HTTP），这哥们儿居然在循环重试，每次都报504超时。问题就很明显了：业务代码死循环调用某个接口，但接口本身一直超时返回，导致请求越积越多，最终把网络带宽和交换机CPU都吃满了。定位到具体设备，解决问题到这里我基本可以下结论了：研发部业务系统调用外部接口失败代码进入死循环重试逻辑产生大量重复请求把接入层交换机CPU打满导致整个研发部网络卡顿临时…