n8n 是开源的工作流自动化工具,将节点(Node)按有向图连接执行任务。每个节点完成一类操作:HTTP 请求、数据转换、写入数据库、发送通知等。
常见编排模式
- 触发 + 处理 + 通知:以 Webhook / Cron 触发,中间用 Function 或 Set 节点整理数据,末尾通过邮件 / Telegram 推送结果。
- 条件分支:使用 IF / Switch 节点按字段值分流,分支后再合并到 Merge 节点统一输出。
- 批量与并发控制:用 SplitInBatches 节点将大数组拆分成小批,避免对下游 API 触发限流。
容易踩到的坑
- Webhook 节点在测试模式与生产模式下使用不同 URL,部署后需要切换到 Production URL 才会真正生效。
- Function 节点中
$input.all() 返回数组、$json 是当前条目,混用容易丢数据。
- Credential 在工作流导出时不会带出,迁移到新环境需重新配置。
参考:n8n 官方文档 docs.n8n.io,社区论坛 community.n8n.io。
Cron 是 Linux 下最常用的定时任务调度方式,但默认实现没有重试机制,任务失败容易被忽略。本笔记记录给 cron 加上重试与告警的常见做法。
失败可见性
- 在 crontab 顶部设置
MAILTO= 可将 stderr 自动发到本地邮箱(前提是装了 MTA)。
- 没有 MTA 时,更通用的做法是把 stderr/stdout 重定向到日志,并在脚本里检测退出码后主动推送。
简单的重试包装
#!/usr/bin/env bash
MAX=3
for i in $(seq 1 $MAX); do
./job.sh && exit 0
echo "[try $i] failed, sleep 30s" >&2
sleep 30
done
curl -s -X POST "$WEBHOOK" \
-d "text=job.sh failed after $MAX retries on $(hostname)"
exit 1
关键点:用退出码而非日志匹配判断成功;指数或固定退避;最终失败再触发告警,避免噪音。
参考:man 5 crontab,cronitor.io 关于 cron 监控的最佳实践文章。
GitHub Actions 适合给个人项目做轻量级 CI/CD,免费额度对小型站点足够。一个最小可用的部署流程通常包含三步:检出代码、构建产物、远程发布。
典型部署 Workflow
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Sync via rsync
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_KEY }}
REMOTE_HOST: ${{ secrets.HOST }}
REMOTE_USER: deploy
SOURCE: "./dist/"
TARGET: "/var/www/site"
实践要点
- SSH 私钥放进 Repository Secrets,不要硬编码在 yml 里。
- 给部署用户做最小权限:仅允许写目标目录,禁止 shell 登录。
- 构建步骤尽量加缓存(actions/cache),缩短跑测时间。
参考:docs.github.com/actions,GitHub Actions Marketplace。
Webhook 是事件驱动自动化的入口。一个稳健的 Webhook 接收端通常需要解决三个问题:身份验证、幂等性、消息分发。
验签
多数平台(GitHub、Stripe、飞书)会用 HMAC-SHA256 对请求体签名,放在 Header 中。接收端用共享 Secret 重新计算后用常量时间比较,不要用 ==,防止时序攻击。
幂等性
同一事件可能因网络抖动重投,应使用平台提供的 Event-Id 去重;处理成功后写入一个短期缓存(如 Redis 24h),重复事件直接 200 返回。
分发
不要在 Webhook 处理函数里做耗时操作(HTTP 调用、文件处理),平台通常 5 秒内必须返回 2xx。建议接收后立即入队(Redis Stream / RabbitMQ),交后台 worker 处理。
参考:webhooks.fyi,Stripe / GitHub 官方关于 webhook 验签的文档。
文件批量整理脚本(按时间 / 类型分文件夹、批量改名、清理老旧文件)一般会作为 cron 定时任务长期运行,需要重点关注幂等和日志。
常用工具组合
find 配合 -mtime、-name 选择目标文件,-print0 + xargs -0 处理含空格文件名。
rsync 做带删除的镜像同步:rsync -av --delete src/ dst/。
logrotate 管理脚本日志,避免无限增长。
日志结构
LOG=/var/log/cleanup.log
exec >>"$LOG" 2>&1
echo "==== $(date -Iseconds) start ===="
find /data -mtime +30 -type f -print -delete
echo "==== $(date -Iseconds) done ===="
关键点:每次记录开始 / 结束时间和影响行数;操作前先 --dry-run 验证;高危操作(删除)尽量先 mv 到回收目录而不是直接 rm。
参考:man find / man rsync / man logrotate。
跨服务同步(A 库 → B 库、本地 → 云存储)一旦失败重跑,最怕产生重复数据。幂等性是这种流程的核心要求。
常见做法
- 幂等键:用业务上唯一的字段(订单号、消息 id)作为目标库的唯一索引,重跑时通过
INSERT ... ON CONFLICT DO NOTHING 或 UPSERT 跳过。
- 水位线:记录 last_synced_at / last_id,只同步增量;失败回滚到上次成功位置。
- 软删除:删除操作改为标记
deleted_at,防止重跑因找不到记录而报错。
重试策略
建议指数退避(1s → 2s → 4s → 8s),并设置最大重试次数;对网络错误重试,对业务错误(4xx)直接告警,不要无限重试。
参考:Designing Data-Intensive Applications 第 11 章;AWS Builder's Library "Making retries safe with idempotent APIs"。
三个工具都做工作流自动化,但定位差异明显,选型要看自己的使用场景。
横向对比
- Zapier:集成最多(7000+),开箱即用,定价按任务数;适合非技术人员搭流程,长期使用偏贵。
- Make(前 Integromat):可视化连线更细腻,按 Operation 计费,适合数据量中等、流程复杂的场景。
- n8n:开源、可自托管,节点支持自己写 JavaScript / Python;技术门槛略高,但成本低、隐私可控。
个人选择
个人技术学习和小流量任务用 n8n 自托管最划算,结合 Docker 部署一次能跑很久;商业项目优先 Make,复杂度收益高;只需要简单触发并接入大量 SaaS 时,Zapier 仍是最快上手的方案。
参考:各产品官网定价页;n8n vs Zapier 在 reddit.com/r/automation 的讨论。
Shell 脚本接受参数有三种主流方式,各有适用场景。
三种方式
- 位置参数 + case:完全手写,最灵活,但代码长,处理短/长选项混合时容易出错。
- 内建 getopts:Bash 自带,POSIX 兼容,但只支持短选项(
-a),不支持 --all。
- 外部 getopt(util-linux):支持长选项和参数重排,但 macOS 自带版本不兼容,需要 brew 安装 GNU 版。
getopts 模板
while getopts ":hf:v" opt; do
case $opt in
h) usage; exit 0 ;;
f) FILE=$OPTARG ;;
v) VERBOSE=1 ;;
\?) echo "unknown -$OPTARG"; exit 1 ;;
esac
done
shift $((OPTIND-1))
需要长选项又想跨平台时,更推荐直接换 Python + argparse。
参考:man getopt / help getopts;BashGuide 的 Arguments 章节。
近两年 Python 生态出现了 Rust 写的 uv,速度比 pip 快一个数量级,但 venv + pip 仍是最通用的方式。
venv + pip(标准)
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
uv(推荐日常使用)
uv venv # 创建虚拟环境
uv pip install -r req.txt # 兼容 pip 命令
uv pip compile requirements.in -o requirements.txt # 锁版本
选择建议
- 个人项目、CI 流水线:用 uv,主要是快(缓存命中时几乎瞬时)。
- 生产部署、与团队协作:仍建议保留标准
requirements.txt 锁文件,uv 与 pip 完全兼容。
- 不要全局 pip install,所有依赖必须装在虚拟环境里。
参考:docs.python.org/3/library/venv.html;github.com/astral-sh/uv。
argparse 是 Python 标准库里写 CLI 的首选,比手动解析 sys.argv 可读性高得多,并且自动生成 --help。
基本用法
import argparse
parser = argparse.ArgumentParser(description="批量处理图片")
parser.add_argument("input", help="输入目录")
parser.add_argument("-o", "--output", default="./out")
parser.add_argument("-v", "--verbose", action="store_true")
parser.add_argument("--size", type=int, default=1024)
args = parser.parse_args()
子命令(subparsers)
当 CLI 类似 git commit / git push 这种多动作结构时,用 subparsers 拆分,每个子命令独立一组参数。
实践经验
action="store_true" 适合开关;nargs="+" 接收多个值。
- 用
type=Path(pathlib.Path)让 argparse 自动转路径对象。
- 需要互斥参数时用
add_mutually_exclusive_group()。
参考:docs.python.org/3/library/argparse.html。
requests 是 Python 写 HTTP 客户端的事实标准,httpx 是后起之秀,API 几乎一致但额外支持异步与 HTTP/2。
关键差异
- 同步:两者性能差不多,requests 生态更成熟。
- 异步:requests 不支持 async;httpx 提供
AsyncClient,配合 asyncio 可以并发上千请求。
- HTTP/2:httpx 支持
http2=True,对长连接、流式接口友好。
- 超时默认值:requests 默认无限等待(务必显式设 timeout);httpx 默认 5 秒。
异步示例
import httpx, asyncio
async def fetch(client, url):
r = await client.get(url, timeout=10)
return r.status_code
async def main(urls):
async with httpx.AsyncClient() as c:
return await asyncio.gather(*(fetch(c, u) for u in urls))
同步脚本继续用 requests;需要并发或长连接的服务端代码,直接选 httpx。
参考:requests.readthedocs.io;www.python-httpx.org。
项目稍大就需要把 logging 抽出来统一封装,避免每个文件各自 print,定位问题时无从下手。
最小封装
import logging
from logging.handlers import RotatingFileHandler
def get_logger(name="app", logfile="app.log"):
log = logging.getLogger(name)
if log.handlers: return log
log.setLevel(logging.INFO)
fmt = logging.Formatter(
"%(asctime)s [%(levelname)s] %(name)s - %(message)s")
fh = RotatingFileHandler(logfile, maxBytes=5_000_000, backupCount=3)
fh.setFormatter(fmt)
sh = logging.StreamHandler()
sh.setFormatter(fmt)
log.addHandler(fh); log.addHandler(sh)
return log
实践要点
- 用
logging.getLogger(__name__) 让日志按模块分级,可单独调级别。
- RotatingFileHandler 按大小切;TimedRotatingFileHandler 按时间切,按需选。
- 结构化日志推荐
structlog 或自定义 JSON Formatter,便于后续 ELK 收集。
参考:docs.python.org/3/howto/logging.html;structlog.org。
jq 是处理 JSON 的瑞士军刀,在 Shell 里链式过滤、改写、聚合 JSON 都比 Python 一行命令快。
常用片段
# 取嵌套字段
jq '.data.users[0].email' file.json
# 过滤数组
jq '.users[] | select(.active == true)' file.json
# 重塑结构
jq '.users | map({id, name})' file.json
# 计算总和
jq '[.items[].price] | add' file.json
# 递归找所有 "url" 字段
jq '.. | objects | .url? // empty' file.json
易错点
- 管道
| 在 jq 里作用于上一个表达式的输出,与 shell 管道概念不同。
- 处理大文件用
--stream 避免一次性载入内存。
- shell 中 jq 输出加
-r 去掉双引号,才能赋值给变量。
参考:jqlang.github.io/jq/manual;jqplay.org(在线练习)。
批量重命名脚本一旦遇到含空格、emoji、Windows 来的 GBK 文件名就容易翻车,几个边界情况要单独处理。
常见坑
- 含空格 / 特殊字符:用
find -print0 + xargs -0,避免被 shell 拆词。
- 编码问题:从 Windows 拷过来的中文文件名可能是 GBK,Linux 默认 UTF-8 会乱码,用
convmv 转换。
- 命名冲突:批量加序号时先扫描已存在文件,避免覆盖;可以加
--no-clobber 标志或重命名前 stat 检查。
- 大小写:在 macOS / Windows 这种大小写不敏感的文件系统上,
mv File.txt file.txt 会失败,需要先重命名为临时名再改回。
更稳的做法
用 Python 的 pathlib + try/except 写脚本,比纯 shell 更容易处理异常;先 --dry-run 输出预期改名清单确认无误后再执行。
参考:man rename(util-linux 与 perl 版语法不同);man convmv。
从 Python 3.6 开始 pathlib 已经稳定,相比 os.path 字符串拼接更直观,建议新项目直接用。
常见替换
from pathlib import Path
p = Path("/var/log") / "nginx" / "access.log" # 拼路径
p.exists(); p.is_file(); p.is_dir() # 判断
p.parent; p.name; p.stem; p.suffix # 拆分
p.read_text(encoding="utf-8") # 读
p.write_text("hi", encoding="utf-8") # 写
list(Path("/data").rglob("*.json")) # 递归 glob
p.with_suffix(".bak") # 改后缀
Path.home(); Path.cwd() # 特殊路径
注意点
- 第三方库(cv2、pandas)有些只接受 str,把 Path 用
str(p) 转一下。
- Windows 路径用
PureWindowsPath;跨平台脚本不要硬编码分隔符。
Path.glob 不递归、rglob 才递归。
参考:docs.python.org/3/library/pathlib.html。
Nginx 的 location 匹配优先级是高频踩坑点,多个 location 同时匹配时实际生效的不一定是写在前面的那条。
优先级(从高到低)
= 精确匹配:location = /favicon.ico
^~ 前缀匹配且不再继续匹配正则:location ^~ /static/
~ / ~* 正则匹配(区分 / 不区分大小写)
- 普通前缀匹配(最长匹配胜出)
反向代理常用模板
location /api/ {
proxy_pass http://127.0.0.1:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
注意:proxy_pass 末尾加不加 / 行为完全不同——加了会替换前缀,不加会原样转发。
参考:nginx.org/en/docs/http/ngx_http_core_module.html#location。
Let's Encrypt 提供免费的 90 天证书,用 certbot 一条命令就能签发并自动配置 Nginx。
初次申请(webroot 模式)
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
自动续期
certbot 安装时会自动注册 systemd timer 或 cron,每天检查一次,剩余 30 天内自动续。手动测试:
sudo certbot renew --dry-run
注意事项
- 申请前 80 端口必须可访问(HTTP-01 验证),云厂商安全组要放行。
- 泛域名证书需要 DNS-01 验证,certbot 配合
--preferred-challenges dns。
- 速率限制:同一域名每周只能签 50 次,调试阶段加
--staging 用沙箱环境。
参考:letsencrypt.org/docs;eff-certbot.readthedocs.io。
把脚本变成系统服务最稳妥的方式是写一个 systemd unit,自动重启、随机启动、统一日志,比 nohup / screen 健壮得多。
最小 unit 文件
# /etc/systemd/system/myapp.service
[Unit]
Description=My App
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/venv/bin/python app.py
Restart=on-failure
RestartSec=5
Environment=PYTHONUNBUFFERED=1
[Install]
WantedBy=multi-user.target
常用命令
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp
journalctl -u myapp -f # 跟踪日志
关键字段:Restart=on-failure 仅异常退出时重启;Type=notify 适合显式 sd_notify 的程序;用专用低权限用户跑服务。
参考:man systemd.service / systemd.unit;freedesktop.org/wiki/Software/systemd。
ufw(Uncomplicated Firewall)是 iptables 的友好封装,Ubuntu / Debian 默认提供。
常用命令
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp # SSH
sudo ufw allow 'Nginx Full' # 80 + 443
sudo ufw allow from 1.2.3.4 to any port 5432 # 限定源 IP
sudo ufw status numbered # 带编号查看
sudo ufw delete 3 # 按编号删
sudo ufw enable
调试要点
- 规则按顺序匹配,
deny 与 allow 先后顺序很重要;用 insert 插入到指定位置。
- 开启日志:
sudo ufw logging on,日志在 /var/log/ufw.log。
- 容器(Docker)会绕过 ufw 规则直接走 iptables,需要单独配置或修改
/etc/default/ufw 中的 FORWARD 策略。
- 修改后用
sudo ufw reload,不要直接 disable + enable,避免短暂掉线。
参考:help.ubuntu.com/community/UFW;man ufw。
密码登录暴露在公网会有大量暴力破解尝试,必须改为密钥登录并加 Fail2ban。
密钥登录
# 本地生成
ssh-keygen -t ed25519 -C "me@host"
ssh-copy-id user@server
# 服务端硬化 /etc/ssh/sshd_config
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
KbdInteractiveAuthentication no
sudo systemctl reload ssh
务必在禁用密码登录前,开一个新终端验证密钥能登入,避免把自己锁在外面。
Fail2ban 基础
sudo apt install fail2ban
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
# 编辑 jail.local:
[sshd]
enabled = true
maxretry = 5
findtime = 10m
bantime = 1h
查看封禁:sudo fail2ban-client status sshd。
参考:man sshd_config;fail2ban.readthedocs.io。
"磁盘满了"是最常见的运维 P0,掌握快速定位流程能省下大量时间。
定位顺序
df -h # 哪个分区满了
sudo du -h -d 1 / | sort -hr | head # 顶层目录排序
sudo ncdu / # 交互式(更直观)
lsof | grep deleted # 找已删除但句柄未释放的大文件
常见占用源
- journalctl 日志:
sudo journalctl --vacuum-size=200M
- apt 缓存:
sudo apt clean
- Docker:
docker system prune -a --volumes(确认无业务数据再删)
- 大日志文件:用
logrotate 配置自动切,不要手动 rm(活动写入会持有 inode)
- core dump:
/var/lib/systemd/coredump
inode 满(df -i)但磁盘没满时,问题通常在大量小文件目录(缓存、邮件队列)。
参考:man du / ncdu;linuxhandbook.com 的磁盘排查文章。
Docker 容器网络默认 bridge,端口映射通过 -p host:container 完成,但有几个常见误解。
三种网络模式
- bridge(默认):容器有独立 IP,通过 NAT 出网;需要
-p 暴露端口。
- host:共享主机网络栈,性能最好但安全性最差;端口直接占用主机端口。
- 自定义 bridge:
docker network create,容器间可用容器名互相访问(自动 DNS)。
常见问题
-p 8080:80 默认绑 0.0.0.0,公网可访问;想只对本机用 -p 127.0.0.1:8080:80。
- 容器内连主机用
host.docker.internal(Mac/Windows)或 --add-host=host.docker.internal:host-gateway(Linux)。
- 同 docker-compose.yml 内容器互相访问,用服务名而非 IP;底层走自定义 bridge。
- Docker 修改 iptables 会绕过 ufw 规则,端口暴露后即使 ufw 拒绝也仍可访问。
参考:docs.docker.com/network;github.com/chaifeng/ufw-docker。
rsync 是最常用的增量同步工具,配合 --link-dest 可以做出类似 Time Machine 的快照备份,存储非常省。
常用参数
-a:归档模式,相当于 -rlptgoD,保留权限、时间戳、符号链接。
-v / --progress:观察传输过程。
-z:传输时压缩(远程同步使用)。
--delete:源端删除的目标端也删(镜像同步)。
--link-dest:把没变的文件硬链到上次备份,节省空间。
每日快照脚本骨架
SRC=/data/
DEST=/backup
TODAY=$(date +%F)
LATEST=$DEST/latest
rsync -a --delete --link-dest="$LATEST" "$SRC" "$DEST/$TODAY/"
rm -f "$LATEST"
ln -s "$TODAY" "$LATEST"
每天产生一个完整的快照目录,但未变文件用硬链共享,磁盘占用接近增量备份。
参考:man rsync;rsync.samba.org/examples.html。
跨大版本升级 Ubuntu(如 20.04 → 22.04 → 24.04)有概率挂掉,操作前一定要做检查与快照。
升级前检查
- 云厂商控制台先打整盘快照,挂掉可秒级回滚。
- 记录关键服务版本:
nginx -v、python3 -V、node -v,防止升级后兼容性问题。
- 清理第三方 PPA:
ls /etc/apt/sources.list.d/,跨版本时这些 PPA 可能没对应包。
- 磁盘剩余至少 5GB;
sudo apt autoremove 清理无用包。
- 提前更新到当前小版本最新:
sudo apt update && sudo apt full-upgrade。
执行
sudo do-release-upgrade # LTS 之间
sudo do-release-upgrade -d # 升级到开发版(一般别用)
升级期间 SSH 会断一次(systemd 重启),用 screen / tmux 包住,避免连接掉了升级中断。
参考:ubuntu.com/server/docs/upgrade-introduction。
Claude Code 是 Anthropic 官方的命令行 AI 编程助手,项目级配置主要靠两个文件:CLAUDE.md 和 .claude/settings.json。
CLAUDE.md
放在项目根目录,每次启动都会自动加载到上下文。适合写:项目概览、目录结构、约定(命名 / 测试 / 提交规范)、危险操作清单。注意简洁,太长反而稀释指令权重。
settings.json
{
"permissions": {
"allow": ["Bash(git:*)", "Bash(npm test:*)"],
"deny": ["Bash(rm -rf:*)"]
},
"hooks": {
"PostToolUse": [...]
}
}
实践经验
- 把项目特有上下文(如 "数据库迁移用 alembic 而不是手写 SQL")写进 CLAUDE.md 比每次复述高效。
- 用户级配置放
~/.claude/CLAUDE.md,所有项目共享;项目级覆盖用户级。
- 启动后用
/init 让 Claude 自己扫描项目生成初稿,再人工修订。
参考:docs.claude.com/en/docs/claude-code;anthropic.com/news/claude-code。
同样是 AI 辅助编程,Cursor 和 VS Code + Copilot / Claude Code 在重构场景表现差异挺明显。
Cursor
- 内置 Composer 模式,可一次性跨多文件改动并预览 diff,重构起来最顺。
- Cmd+K 行内修改、Cmd+L 聊天、Tab 补全无缝衔接。
- 对话上下文管理较弱,复杂会话久了容易跑偏。
VS Code + Copilot
- 补全质量高,但跨文件改动需要手动指挥,重构需要拆步骤。
- 胜在生态——所有 VS Code 插件都能用。
VS Code + Claude Code(CLI)
- 跑在终端里,可以直接执行 Bash、读写文件、跑测试,适合需要"边改边验证"的重构。
- 多步任务可控性最高,但需要手动切窗口看 diff,没有原生 inline 编辑。
个人小项目重构用 Cursor 最快;需要严格控制每一步、跑测试验证的场景,Claude Code 更稳。
参考:cursor.com;docs.github.com/copilot;docs.claude.com。
提示词写得好不好,直接决定 AI 输出质量。一个稳定的结构通常是分层的。
分层结构
- 角色 / 系统设定:你是谁、面向谁。
- 任务目标:要完成什么,输入是什么。
- 约束 / 风格:必须做什么、绝不能做什么。
- 示例(Few-shot):1~3 个高质量示例,比抽象描述更有效。
- 输出格式:要 JSON / Markdown / 纯文本。
实战建议
- 用 XML 标签(
<context>、<task>)分块,Claude 类模型对 XML 标签的解析尤其稳定。
- "约束"放在最后或最显眼位置,否则容易被前面的内容稀释。
- 需要严格输出格式时,给一个空模板让模型填空,比描述格式有效。
- 不要堆砌过多角色("你是世界顶级XX专家"),实测对结果几乎无影响。
参考:docs.claude.com/en/docs/build-with-claude/prompt-engineering;www.promptingguide.ai。
本地化整理技术资料的常见目标:把分散的笔记 / PDF / 网页存到本地,可被 AI 检索回答。
典型方案
- 简单版:Obsidian + 手工分类 + 全文搜索;适合个人长期沉淀,无需 AI。
- RAG 方案:把文档切块 → embedding 向量化 → 存入向量库(Chroma / Qdrant)→ 提问时先检索再交给模型。
- 开箱即用:AnythingLLM、LM Studio、Open WebUI 都自带文档导入 + 本地 RAG。
实践经验
- 切块(chunking)大小一般 500~1000 tokens,重叠 100~200,太小容易丢上下文。
- embedding 模型用
bge-m3、nomic-embed-text 之类开源模型即可,效果接近商业模型。
- 检索准确性取决于切块策略和 query 重写,比模型本身影响更大。
- 个人量级(< 10万段)SQLite + sqlite-vss 就够,不必上向量数据库。
参考:anythingllm.com;docs.trychroma.com;huggingface.co/BAAI/bge-m3。
OpenAI Whisper 模型可以本地部署,用于会议记录、视频字幕、语音笔记的离线转写。
常见实现
- 原版 Whisper(PyTorch):参考实现,但速度慢。
- faster-whisper(CTranslate2):相同精度下快 4 倍以上,显存占用更低。
- whisper.cpp:C++ 实现,CPU 也能跑,适合 Mac / 老笔记本。
faster-whisper 上手
pip install faster-whisper
from faster_whisper import WhisperModel
model = WhisperModel("medium", device="cuda", compute_type="float16")
segments, info = model.transcribe("audio.mp3", language="zh")
for s in segments:
print(f"[{s.start:.1f} -> {s.end:.1f}] {s.text}")
建议
- 中文识别用
medium 起步,large-v3 效果最好但 10GB 显存。
- 长音频先 ffmpeg 转成 16kHz 单声道 wav,省显存。
- 用
vad_filter=True 自动跳过静音段,明显提速。
参考:github.com/openai/whisper;github.com/SYSTRAN/faster-whisper。
同一道编程题让不同模型写,风格差异比想象中大。日常使用过程中观察到的几个特点:
主流模型风格
- Claude(Sonnet / Opus):注释克制、命名考究、倾向显式 type hint,重构能力强;偶尔会过于谨慎,加多余的 try/except。
- GPT-4 / GPT-5:解题速度快、解释清晰,喜欢"教学式"的注释;写小工具够用,复杂跨文件改动需要更多引导。
- Gemini:长上下文表现突出,整段代码生成稳定;调试场景的对话感稍逊。
- 本地模型(Qwen / DeepSeek-Coder / CodeLlama):补全速度快,离线可用;复杂逻辑仍不及云端旗舰。
实践
没有"最好"的模型,按场景选:架构 / 重构 → Claude;快速生成 / 解释 → GPT;超长文档 → Gemini;隐私敏感 / 离线 → 本地模型。多个模型交叉验证比迷信单一模型更稳。
参考:lmarena.ai(盲测排行榜);livecodebench.github.io。
把一份很长的文档(论文、规范、年报)丢给 AI 让它总结或答疑,直接整篇塞进上下文经常超限或失焦,需要拆分策略。
常见拆法
- 按章节:保留语义边界,最稳;适合结构清晰的文档(PDF 论文、技术规范)。
- 按 token 数滑动窗口:固定 2k~4k token 一段,相邻段重叠 200~500,避免边界信息丢失。
- 按问题驱动:先读目录 / 摘要让模型生成索引,再带着具体问题去检索相关段落。
分级总结(Map-Reduce)
- 每段单独总结成 200 字以内的摘要(map)。
- 把所有摘要合在一起,再让模型生成总览(reduce)。
- 需要细节时回到原段落问。
这种结构既能控制单次输入量,也避免模型在长文中"丢中段"(lost in the middle 现象)。
参考:arxiv.org/abs/2307.03172(Lost in the Middle);langchain 文档中的 Map-Reduce / Refine summarization 章节。
502 Bad Gateway 表示 Nginx 等反向代理无法从上游(upstream)获取有效响应。排查路径相对固定。
排查顺序
- 看 Nginx 错误日志:
tail -f /var/log/nginx/error.log,会有具体原因(connection refused / timeout / upstream prematurely closed)。
- upstream 是否存活:
curl -v http://127.0.0.1:8080/ 直接打上游,确认服务跑着。
- 端口和地址:
ss -ltnp | grep 8080,确认服务绑在 Nginx 配置的同一个地址(127.0.0.1 vs 0.0.0.0 是常见错配)。
- 超时:上游处理慢,调高
proxy_read_timeout / proxy_connect_timeout。
- SELinux / AppArmor:CentOS 上 SELinux 默认禁止 Nginx 连本地端口,
setsebool -P httpd_can_network_connect 1。
常见错误对应
Connection refused:上游没启动 / 端口写错。
upstream timed out:上游慢或卡死,先看上游日志。
upstream prematurely closed connection:上游进程被 OOM Killer 干掉。
参考:nginx.org/en/docs/http/ngx_http_proxy_module.html。
"我装了 A,结果 B 不能用了"是 Python 项目最常见的依赖问题,本质是包之间版本不兼容。
定位工具
pip check # 检查已安装包之间是否冲突
pip install pipdeptree
pipdeptree # 树状展示依赖
pipdeptree --reverse --packages X # 谁依赖了 X
pip install pip-tools
pip-compile requirements.in # 解析锁定到 requirements.txt
解决思路
- 先锁版本再装:所有项目都建议生成
requirements.txt 锁文件,杜绝 "下次装出来不一样"。
- 分环境:pip 装到全局是灾难,永远用 venv / uv venv。
- 逐个回退:发现冲突后从最近一次新增的依赖开始
pip install X==<旧版>。
- 清空重建:lock 文件能重现的环境,最快恢复方式是删 venv 重装。
参考:pip.pypa.io;github.com/jazzband/pip-tools。
看到 CPU 100% 时,目标是找到具体哪个进程的哪个线程在干什么。
快速定位
top / htop 找占用最高的 PID。
top -H -p <PID> 进一步看线程级 CPU。
ps -o pid,ppid,cmd,%cpu --sort=-%cpu | head 完整命令行。
深入分析
- 系统调用:
strace -p <PID> -c 看是 syscall 频繁还是用户态计算。
- 采样火焰图:
perf record -F 99 -p <PID> -g -- sleep 30 + perf report。
- Python 进程:用
py-spy top --pid <PID> 或 py-spy dump,无侵入查看。
- JVM:
jstack <PID> 抓线程栈对照高 CPU 线程的 nid。
常见原因:死循环 / 正则回溯 / GC 风暴 / 大量小请求 / 线程池过小导致重排。
参考:brendangregg.com 性能工具系列;github.com/benfred/py-spy。
nginx -s reload 失败基本是配置错了,但具体错在哪儿需要花点时间。
排查流程
sudo nginx -t:先做语法检查,会精确指出文件和行号。
- 看错误日志:
tail -n 50 /var/log/nginx/error.log。
- 检查 include 进来的文件:
sudo nginx -T | less 输出整合后的完整配置。
常见原因
- 端口冲突:另一个进程占用了 80/443,
ss -ltnp | grep :80。
- SSL 证书路径错:路径错误或权限问题(nginx 用户读不到 key 文件)。
- upstream 名字与 server 块不一致:
proxy_pass http://api 但 upstream api {} 没定义。
- server_name 重复:同 IP:port 上两个 server 块用了相同 server_name,nginx 会警告并按第一个生效。
- limit_req_zone 等指令必须放 http 块,放进 server 块会报错。
参考:nginx.org/en/docs;DigitalOcean 关于 nginx debug 的系列教程。
"网址打不开"看似 DNS 问题,其实可能在很多层。逐层排查最快。
从近到远
- 本地缓存:systemd-resolved 缓存了旧记录,
sudo resolvectl flush-caches。
- hosts 文件:
cat /etc/hosts,是否被静态指向某个 IP。
- 本地 DNS:
cat /etc/resolv.conf 用的什么 DNS;dig example.com 看返回结果。
- 换 DNS 试:
dig @8.8.8.8 example.com 与本地 DNS 对比,权威 vs 公共差异。
- 上游 / 权威:
dig +trace example.com 一路到根,看哪一跳出错。
- 权威服务器配置:登录域名 DNS 商面板,检查 A / CNAME 是否正确,TTL 是否过长。
常见症状
- 刚改完 DNS 部分用户能访问、部分不行:TTL 没过期,等 / 强制刷新。
- NXDOMAIN:记录不存在或 DNS 商配置漏了。
- SERVFAIL:DNSSEC 验证失败 / 上游 DNS 故障。
参考:man dig;jvns.ca 的 dns debug 系列文章。
Git 误操作能不能恢复,关键看引用是否还在 reflog 里。
合并冲突处理
git status 看哪些文件冲突。
- 编辑器打开冲突文件,处理
<<< / === / >>> 标记。
git add <file> 标记已解决。
git commit(merge 上下文会自动生成消息)或 git rebase --continue。
- 放弃整个 merge:
git merge --abort / git rebase --abort。
误操作恢复
git reflog # 看本地引用历史
git reset --hard HEAD@{3} # 回到 reflog 中某状态
git checkout -b rescue HEAD@{5} # 把丢失的提交挽回成新分支
底线
- commit 过的内容,30~90 天内(reflog 默认保留期)几乎都能找回来。
- 没 commit 直接
checkout / reset --hard 覆盖工作区,没救。
- 共享分支不要 force push;个人分支 force push 前先
git push --force-with-lease 防误。
参考:git-scm.com/docs;ohshitgit.com(场景化恢复手册)。
浏览器报"证书无效 / 不受信任",开发者经常以为是证书过期,其实更多是证书链不完整。
原理
SSL 证书需要发送:服务器证书 + 中间 CA 证书。Let's Encrypt 给的 fullchain.pem 已经把两者拼好了,应当配置这一个,而不是只配 cert.pem。
验证
# 检查链是否完整
openssl s_client -connect example.com:443 -servername example.com -showcerts
# 在线工具
curl -I https://www.ssllabs.com/ssltest/analyze.html?d=example.com
SSL Labs 报告里如果出现 "Chain issues: Incomplete",就是缺中间证书。
修复
- Nginx:
ssl_certificate 指向 fullchain.pem,不要指向 cert.pem。
- Apache:用
SSLCertificateChainFile 单独配置中间证书,或合并后用 SSLCertificateFile。
- iOS / Android 部分客户端对链的容忍度比浏览器低,必须完整。
参考:letsencrypt.org/docs/chain-of-trust;ssllabs.com/ssltest。
"手动跑 OK,cron 跑就出错"是经典问题,原因 95% 在于 cron 的执行环境与登录 shell 不同。
常见差异
- PATH:cron 默认 PATH 通常只有
/usr/bin:/bin,没有 /usr/local/bin,调用 node / python3 / docker 时找不到。
- 没有 .bashrc / .profile:cron 不读 shell 启动文件,依赖
nvm use / conda activate 这种环境的命令会失败。
- HOME / USER:可能是默认值;脚本里
~ 解析路径要小心。
- 无 TTY:交互式工具(apt、ssh 不带 -i)会卡住或失败。
- locale:默认 C 而非 UTF-8,处理中文时乱码。
稳妥做法
* * * * * /bin/bash -lc '/path/to/script.sh' >> /var/log/job.log 2>&1
用 bash -l 启动登录 shell;脚本顶部显式 export PATH=...;所有命令写绝对路径;输出务必重定向,否则 cron 默认 mail 给本地账户。
参考:man 5 crontab;help.ubuntu.com/community/CronHowto。
容器里看到的时间和宿主机不一致,会让日志、定时任务、过期判断全乱。原因是镜像默认时区一般是 UTC。
三种修复方式
注意
- Alpine 镜像默认不带 tzdata,需要
apk add --no-cache tzdata。
- 仅设
TZ 但容器里没安装 zoneinfo,仍然按 UTC。
- docker-compose 里给每个服务单独配
environment: TZ: Asia/Shanghai。
- Java 应用要额外加 JVM 参数
-Duser.timezone=Asia/Shanghai。
参考:docs.docker.com;wiki.alpinelinux.org/wiki/Setting_the_timezone。