Nginx upstream 负载均衡配置:轮询、ip_hash、权重、least_conn 与 PHPStudy 位置问题
基本结构 upstream 定义后端服务器组,proxy_pass 引用该组名: upstream backend { server 192.168.1.10:8000; server 192.168.1.11:8000; server 192.168.1.12:8000; }server { location / { proxy_pass http://backend; # ... 其他 proxy 配置 } }四种均衡策略 轮询(默认):依次分配请求,适合无状态服务: upstream backend { server 192.168.1.10:8000; server 192.168.1.11:8000; }ip_hash:同一客户端 IP 固定打到同一台服务器,适合有 Session 的服务: upstream backend { ip_hash; server 192.168.1.10:8000; server 192.168.1.11:8000; }weight(加权轮询):按权重比例分配流量: upstream backend { server 192.168.1.10:8000 weight=4; server 192.168.1.11:8000 weight=2; server 192.168.1.12:8000 weight=1; }least_conn(最少连接):新请求优先分配给当前连接数最少的服务器,适合耗时不均匀的服务(如 AI 推理、文件处理): upstream backend { least_conn; server 192.168.1.10:8000; server 192.168.1.11:8000; }健康检查 max_fails 次失败后暂时摘除节点,fail_timeout 秒后重试: upstream backend { server 192.168.1.10:8000 max_fails=3 fail_timeout=30s; server 192.168.1.11:8000 max_fails=3 fail_timeout=30s; }错误:upstream directive is not allowed here [emerg] "upstream" directive is not allowed here in vhosts/xxx.conf:24原因:upstream 只能放在 http {} 块内,不能放在 server {} 或 location {} 内。 PHPStudy 等面板生成的 vhosts 文件是 server {} 块,不能在里面定义 upstream。 解决方案:在主 nginx.conf 的 http {} 块中定义: # nginx.conf http { upstream backend { server 192.168.1.10:8000; server 192.168.1.11:8000; } include vhosts/*.conf; # vhosts 文件只包含 server{} 块 }配置验证与重载 nginx -t # 检查配置语法 nginx -s reload # 平滑重载(不中断现有连接)
Nginx 反向代理改造成负载均衡:踩坑与正确写法
原本站点是这样的反向代理: location / { proxy_pass http://192.168.5.31:8000; proxy_set_header Host $host:$server_port; proxy_set_header X-Real-IP $remote_addr; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; }后端加机器了,要改成负载均衡。看着简单,但一路踩了两个坑。 步骤一:定义 upstream 轮询模式最简单: upstream backend { server 192.168.5.31:8000; server 192.168.5.32:8000; server 192.168.5.33:8000; }location 里的 proxy_pass 改成: proxy_pass http://backend;三种策略怎么选 WebSocket / 长连接:ip_hash 同一个客户端 IP 固定打到同一台机器,session 不会乱: upstream backend { ip_hash; server 192.168.5.31:8000; server 192.168.5.32:8000; }机器性能不同:weight upstream backend { server 192.168.5.31:8000 weight=4; server 192.168.5.32:8000 weight=2; server 192.168.5.33:8000 weight=1; }流量大致按 57% / 29% / 14% 分。 AI 推理、长请求:least_conn 轮询在这种场景很糟糕——某台机器可能同时挤了 5 个几十秒的推理请求,其他机器闲着。least_conn 会挑当前连接数最少的: upstream backend { least_conn; server 192.168.5.31:8000; server 192.168.5.32:8000; server 192.168.5.33:8000; }顺手加个健康检查,挂了的自动摘除: server 192.168.5.31:8000 max_fails=3 fail_timeout=30s;坑一:upstream directive is not allowed here 改完 reload,直接报: nginx: [emerg] "upstream" directive is not allowed here in D:\phpstudy_pro\Extensions\Nginx1.15.11/conf/vhosts/127.0.0.1_8000.conf:24upstream 只能写在 http {} 块里,不能写进 server {} 或 location {}。而 PHPStudy 类面板的 vhost 文件通常等价于一个 server 配置片段,里面就不能定义 upstream。 正确做法:把 upstream backend {...} 挪进主 nginx.conf 的 http {} 里,vhost 文件里只放 location。 # nginx.conf http { upstream backend { least_conn; server 192.168.5.31:8000; server 192.168.5.32:8000; } include vhosts/*.conf; }坑二:proxy_pass 少了协议头 写成: proxy_pass backend;Nginx 会直接报语法错误。proxy_pass 必须带上 http:// 或 https://: proxy_pass http://backend;检查与重载 nginx -t # 语法检查 nginx -s reload # 平滑重载一句话总结 upstream 放 http {}、proxy_pass 别忘 http://、AI/长任务优先 least_conn。三点一起做好,反代改负载均衡基本一次成。
MongoDB 导出数据:mongodump 和 mongoexport 怎么选
MongoDB 导出数据有两个官方工具,用途各不同:mongodump — 二进制 BSON,用于完整备份和恢复(配合 mongorestore) mongoexport — JSON / CSV 明文,用于数据处理和迁移到其它系统搞混了会踩坑:拿 BSON 想给别的系统吃是不行的,拿 JSON 恢复也丢索引和元数据。 mongodump:完整备份 mongodump \ --host localhost \ --port 27017 \ --db test \ --out ./backup产物: backup/ └── test/ ├── users.bson # 数据(BSON 格式) ├── users.metadata.json # 索引、options 等元信息 └── orders.bson恢复: mongorestore ./backup # 或指定库 mongorestore --nsInclude "test.*" ./backupgzip 压缩: mongodump --db test --archive=backup.gz --gzip mongorestore --archive=backup.gz --gzip生产备份用这个——单文件、自带索引元数据、恢复一步到位。 mongoexport:导出 JSON / CSV mongoexport \ --db test \ --collection users \ --out users.json默认是 NDJSON(每行一个 JSON 对象): {"_id":{"$oid":"..."},"name":"Tom","age":18} {"_id":{"$oid":"..."},"name":"Jerry","age":22}想要标准 JSON 数组: mongoexport --db test --collection users --jsonArray --out users.json导出 CSV: mongoexport \ --db test \ --collection users \ --type=csv \ --fields=name,age,email \ --out users.csvCSV 必须显式列出 --fields,不给字段名会报错。 条件导出 --query 是 MongoDB shell 的 JSON 语法: mongoexport \ --db test \ --collection users \ --query '{"age":{"$gt":18},"status":"active"}' \ --out active_users.json日期区间过滤: --query '{"createdAt":{"$gte":{"$date":"2026-01-01T00:00:00Z"}}}'带账号密码 推荐用 URI 一把传: mongoexport \ --uri="mongodb://admin:PASSWORD@127.0.0.1:27017/test?authSource=admin" \ --collection users \ --jsonArray \ --out users.json或者分开: mongodump \ --host localhost --port 27017 \ --username admin --password PASSWORD \ --authenticationDatabase admin \ --db test --out backupauthSource=admin 很重要——默认认证库是当前 db,绝大多数生产环境把用户建在 admin 库。 Docker 里的 MongoDB 进容器里跑: docker exec -it mongo mongodump --db test --out /tmp/backup docker cp mongo:/tmp/backup ./backup或者从宿主机直连(容器映射了端口): mongodump --host 127.0.0.1 --port 27017 --db test --out ./backupPython 版本 from pymongo import MongoClient import jsonclient = MongoClient("mongodb://localhost:27017") col = client["test"]["users"]# 不要 _id 字段,方便导入到其它系统 data = list(col.find({}, {"_id": 0}))with open("users.json", "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2)按条件流式导出(大数据集省内存): import jsonwith open("users.ndjson", "w", encoding="utf-8") as f: for doc in col.find({"age": {"$gt": 18}}, {"_id": 0}): f.write(json.dumps(doc, ensure_ascii=False) + "\n")一张对比表需求 用什么备份 + 恢复到 MongoDB mongodump迁移到 MongoDB(跨版本、跨集群) mongodump + mongorestore导出给下游数据管道用 mongoexport --jsonArray导出给 Excel / BI mongoexport --type=csv定制字段结构、脱敏 Python + pymongo增量导出 --query + 时间戳过滤一句话总结 备份恢复用 mongodump,跨系统迁移用 mongoexport。Docker 容器里跑要注意 exec 进去或映射端口。生产环境永远用 URI + authSource=admin。
OpenCode MCP 服务器配置:type 和 enabled 字段必填
错误信息 在 ~/.config/opencode/opencode.jsonc 里配置 MCP server 时,如果只写了 command 和 args,启动 OpenCode 会报: Configuration is invalid at ...opencode.jsonc ↳ Expected { readonly "type": "local", ... } | { readonly "type": "remote", ... }, got {"my-server":{"command":"node","args":["dist/index.js"]}} mcp.servers ↳ Missing key mcp.servers.enabled旧写法 vs 新写法 旧版本格式(现在会报错): { "mcp": { "servers": { "my-server": { "command": "node", "args": ["dist/index.js"] } } } }新版本要求每个 server 必须包含 type 和 enabled: { "mcp": { "servers": { "my-server": { "type": "local", "enabled": true, "command": "node", "args": ["dist/index.js"] } } } }type 的两个合法值type 用途 必填字段"local" 本机可执行文件,由 OpenCode 直接 fork 进程 command、args"remote" 远程 HTTP/HTTPS MCP 端点 urllocal 示例(Node.js 脚本): { "mcp": { "servers": { "my-mcp": { "type": "local", "enabled": true, "command": "node", "args": ["D:\\project\\my-mcp\\dist\\index.js"] } } } }remote 示例(HTTP 端点): { "mcp": { "servers": { "remote-mcp": { "type": "remote", "enabled": true, "url": "http://localhost:3001/mcp" } } } }多个 server 示例 { "mcp": { "servers": { "database-tools": { "type": "local", "enabled": true, "command": "node", "args": ["dist/db-mcp.js"] }, "file-tools": { "type": "local", "enabled": false, "command": "python", "args": ["-m", "file_mcp"] } } } }enabled: false 可以临时禁用某个 server 而不删除配置。 Windows 路径注意 Windows 路径中反斜杠在 JSON 里需要转义: "args": ["D:\\project\\dist\\index.js"]或者用正斜杠(Node.js 和大多数 CLI 工具都支持): "args": ["D:/project/dist/index.js"]
Python 多线程检查 HTTP 状态码并删除 403 文件
批量检查 data/*.json 里的图片 URL,删除所有返回 403 的 JSON 文件。 单线程版本 import json from pathlib import Pathimport requestsfor json_file in Path("data").glob("*.json"): try: data = json.loads(json_file.read_text(encoding="utf-8")) if not data: continue url = data[0].get("imgHttpUrl") if not url: continue r = requests.get(url, stream=True, timeout=10) if r.status_code == 403: print(f"删除 {json_file}") json_file.unlink() except Exception as e: print(json_file, e)只检查数组第一个元素的 URL,够快。若一个文件里所有 URL 都要检查,把 data[0] 改成循环即可。 多线程版本(100 并发) import json from pathlib import Path from concurrent.futures import ThreadPoolExecutor, as_completedimport requestsDATA_DIR = Path("data") MAX_WORKERS = 100session = requests.Session()def check_file(json_file: Path): try: data = json.loads(json_file.read_text(encoding="utf-8")) if not data: return url = data[0].get("imgHttpUrl") if not url: return # 优先用 HEAD,减少带宽 r = session.head(url, timeout=10, allow_redirects=True) # 部分服务器不支持 HEAD,降级到 GET if r.status_code == 405: r = session.get(url, stream=True, timeout=10) if r.status_code == 403: print(f"[删除] {json_file}") json_file.unlink() except Exception as e: print(f"[错误] {json_file}: {e}")files = list(DATA_DIR.glob("*.json"))with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor: futures = [executor.submit(check_file, f) for f in files] for _ in as_completed(futures): passprint("完成")requests.Session 在多线程中是线程安全的,复用 TCP 连接比每次创建新 session 快。 并发数选择文件数 建议 workers< 1000 501000~10000 100> 10000 考虑 aiohttp + asyncioHTTP 请求是 IO 密集型,100 线程一般没问题。若服务端有限流,调小 MAX_WORKERS。 aiohttp 异步版(超大规模) 文件数超过 10 万时,aiohttp 比线程池快几倍: import asyncio import json from pathlib import Pathimport aiohttp import aiofilesDATA_DIR = Path("data") CONCURRENCY = 500async def check_file(session, json_file: Path): try: async with aiofiles.open(json_file, encoding="utf-8") as f: content = await f.read() data = json.loads(content) if not data: return url = data[0].get("imgHttpUrl") if not url: return async with session.head(url, allow_redirects=True, timeout=aiohttp.ClientTimeout(total=10)) as r: if r.status == 403: print(f"[删除] {json_file}") json_file.unlink() except Exception as e: print(f"[错误] {json_file}: {e}")async def main(): files = list(DATA_DIR.glob("*.json")) sem = asyncio.Semaphore(CONCURRENCY) async def bounded(f): async with sem: await check_file(session, f) async with aiohttp.ClientSession() as session: await asyncio.gather(*(bounded(f) for f in files))asyncio.run(main())按 answer 目录过滤删除(补充场景) 如果是 downloads/ 里没有对应 answer/ 文件就删除,可以改为本地比对: from pathlib import Pathdownloads_dir = Path("downloads") answer_dir = Path("answer")# answer 目录中所有文件名(不带后缀) answer_stems = {f.stem for f in answer_dir.iterdir() if f.is_file()}for file in downloads_dir.iterdir(): if not file.is_file(): continue if file.stem not in answer_stems: print("删除", file) file.unlink()不需要网络请求,纯本地文件对比,速度极快。
