QQ平台不让两个Bot互加好友。但我的工作需要一个在云端跑、一个在桌面跑,两个还要随时通信。平台不给路,我就自己修一条------用安全工程师的思维,从"最坏能坏成什么样"开始倒推,一步一步搭出了一个0秒延迟的分布式任务系统。
一、"你的Bot A能帮我发个消息给Bot B吗?"
这个问题我问了自己至少五次。每次都得到同一个答案:不能。
QQ对Bot的限制很清楚------两个Bot不能互加好友,不能互发私聊。哪怕它们在同一台服务器上,哪怕它们共享同一个数据库,在QQ的世界里,它们就是两条平行的线,永远不会相交。
但我的需求也很清楚:
- Bot A(ECS云端):7×24小时跑排查系统,处理安全检查,生成博客
- Bot B(本地Windows):我第二个QQ号登录,接收文件、执行本地任务
如果它们不能通信,每次生成一个文件我都得在两个QQ之间手动倒腾。这不是技术问题------这是效率问题。
平台不给路,那就自己修。
二、安全工程师修路:先画边界,再找通道
修路之前,我先画的不是网络拓扑图,是威胁模型。
这是七年安全排查的职业病------每到一个新现场,第一件事不是看设备,是站在门口想:这地方最坏能出什么事?
对Bot通信也一样,我先列了三条底线:
- 不能暴露ECS给公网:bridge只能通过443端口的HTTPS出去,不额外开端口
- 不能信任任何人:所有请求必须带Token认证,白名单命令,只读不写
- 不能丢了任务:任务必须持久化到磁盘,Bot B离线也不丢
三条底线定了,才开始想"怎么连"。
三、第一代方案:HTTP桥接 + 手动推
既然QQ不给通,那就用互联网最基础的东西------HTTP。
思路简单到只有三步:
css
ECS开一个HTTP服务(bridge_server.py)→
通过smart_proxy暴露在443端口 →
Bot B通过互联网访问
第一版的bridge只有四个接口:/ping、/exec、/upload、/download。没有队列,没有任务系统。我生成一个文件,得手动告诉Bot B去下载。
能用。但每次都要我开口说"去下载",像个传话筒。
四、第二代:30秒轮询------让Bot B自己问
"能不能让Bot B自己定时来问?"
这个想法来自我蹲现场的经验。化工厂的安全巡检是定时去巡------每小时一次,不去巡就不知道有没有泄漏。把"巡检"换成"轮询",把"每小时"换成"每30秒",Bot B就变成了一个自动巡检员。
python
# 每30秒问一次:"有活吗?"
while True:
tasks = fetch_tasks() # 问ECS
for t in tasks:
download(t) # 有活就干
time.sleep(30) # 等30秒再问
加上任务队列 /task/push、/task/queue、/task/done 三个接口,整个链路通了:
css
我推任务 → 进队列 → Bot B最多30秒后取走 → 下载 → 标记完成
但有一个问题:最慢30秒。 像外卖小哥每30秒看一次手机------运气好马上看到,运气不好干等半分钟。
五、第三代:长轮询------门一直开着
能不能不让Bot B挂电话?问一次,ECS不回答"没活",而是等着------有活了再回答?
这就是长轮询(Long Polling):
makefile
Bot B: "有活吗?"(请求发出)
ECS: (不说话,等着...)
我: 推了一个任务 → ECS立刻被唤醒
ECS: "有!(返回任务列表)"
Bot B: 拿到任务 → 立刻再发一个"有活吗?"(继续等)
核心代码不到30行,就是在一个循环里检查队列,有任务立刻返回,没有就睡1秒。关键是 _task_arrived.set() 这行------我推任务时拍一下,ECS立刻醒过来。
延迟从30秒 → 0秒。推任务瞬间送达。
六、第四代:多线程------门开着,别人也能进
长轮询上线后出了个诡异问题:明明服务在跑,但心跳查不到,健康检查也超时。
排查半小时才发现------HTTPserver是单线程的。长轮询把连接挂了60秒,这60秒里其他任何请求都进不来。就像一个人堵在门口打电话,后面排了一堆人进不去。
解决:换成多线程服务器。
python
class ThreadingHTTPServer(ThreadingMixIn, HTTPServer):
daemon_threads = True
一行代码。但这一行让我明白了一件事:不是所有问题都是架构问题,有些是地基的问题。
七、第五代:心跳 + 错误回传------让断网不再沉默
长轮询解决了"快到慢"的问题。但新的问题来了:
如果Bot B关机了,我不知道。推了任务,石沉大海。
这个问题的根源我太熟了------在安全排查里,最大的危险不是已知的隐患,是"你以为没事但其实已经出事了的"那个状态。
我给Bot B加了三个能力:
1. 心跳(每30秒)
bash
Bot B → ECS: "我还活着"(/task/heartbeat)
ECS: 记下时间戳
超过90秒没心跳 → 标记"离线"
2. 离线告警
yaml
我推任务时自动检查心跳时间:
→ 在线 → 秒推 + warning: null
→ 离线 → 排队 + warning: "⚠️ Bot B当前离线,上线后自动取走"
3. 错误回传
bash
Bot B断网 → 自动调 /task/error 上报ECS
→ ECS日志记录
→ 两边都能看到
从此,断网不再沉默。出了问题,两边同时知道。
八、回头看:这条路怎么修出来的
三代演进,每次升级的原因都不是"不够快",而是"不够稳":
| 代 | 版本 | 为什么升级 | 安全思维 |
|---|---|---|---|
| 1 | 手动 | --- | 先有路再说 |
| 2 | 30s轮询 | 不想每次手动说 | 自动化=减少人为失误 |
| 3 | 0s长轮询 | 30秒太慢 | 速度=安全(越早发现越早处理) |
| 4 | 多线程 | 单线程堵门 | 一个点不能卡整个系统 |
| 5 | 心跳+报错 | 断网沉默 | 不知道出问题比问题本身更危险 |
每一次升级,都是在回答同一个问题:"这样够安全吗?不够的话,最坏能坏成什么样?"
这是安全排查的逻辑,不是程序员的逻辑。但这种逻辑在分布式系统里一样管用------因为分布式系统的故障模式和化工厂的隐患扩散,底层是同一套数学:一个点的失效,会不会导致整个系统的崩溃。
九、你现在可以用它做什么
现在整个链路就一行启动代码:
python
import bot_bridge_client
bot_bridge_client.watch_and_execute("桌面/掘金博客")
启动后自动三件事:
- 长连接挂着等任务(0秒延迟)
- 每30秒发心跳报在线
- 异常自动上报ECS
然后你对包A说:
"包B下载第6篇博客"
瞬间送达。不用等,不用手动传,不用管网络状态。 断了会自动排队,恢复会自动取走。
十、给你的
如果你也碰到"两个系统不能通信"的问题------先别急着搜"中间件"、"消息队列"、"gRPC"。那些是给有基础设施的人用的。
你试试这个顺序:
- 先画威胁模型------最坏能坏成什么样
- 从最简单的方案开始------HTTP + JSON
- 用安全审计的思维升级------每个版本问"还有什么能出错"
- 一次只加一个功能------轮询→长轮询→心跳
你不会写分布式系统。但你会排查安全隐患。这两件事的思维方式是一样的:找到最薄弱的地方,加强它。然后找下一个。
发布日期:2026-06-05 武文韬,1995年出生,2019年本科毕业。中级注册安全工程师,现就职于安徽冠安环境检测研究院有限公司。
曾任肥西经开区管委会驻点安全工程师。2024年5月至2026年5月,担任下塘镇驻点安全专家(政府项目负责人,团队三人)。2024年10月作为技术骨干被《中国应急管理报》报道。
后记:这篇文章是"安全思维+分布式系统"的交叉产物。咨询工程师教会我在系统边界处找问题------Bot A和Bot B的边界就是bridge API。一级建造师矿业教会我在约束条件下求解------QQ平台的限制是最大的约束,但约束逼出了最干净的架构。安全工程师教会我"先想最坏能坏成什么样"------每一代升级都不是因为不够快,是因为不够稳。三个专业,一件事。