ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Bitnodes如何维护上万个比特币节点长连接:ping.py与gevent绿色线程并发模型深度剖析

Bitnodes如何维护上万个比特币节点长连接:ping.py与gevent绿色线程并发模型深度剖析 Bitnodes如何维护上万个比特币节点长连接ping.py与gevent绿色线程并发模型深度剖析【免费下载链接】bitnodesBitnodes estimates the size of the Bitcoin peer-to-peer network by finding all of its reachable and unreachable nodes.项目地址: https://gitcode.com/gh_mirrors/bi/bitnodesBitnodes 是一个比特币节点网络规模估算工具它通过递归发送 getaddr 消息找到全网所有可达节点并长期保持与每个节点的一条长连接来持续测量网络大小。本文带你完整拆解它的核心进程 ping.py一个单进程如何只靠 gevent 绿色线程同时维系上万条 TCP 长连接而不崩溃。读完你会掌握一套可复用的 IO 密集型高并发架构思路。一、先搞懂 Bitnodes 在做什么Bitnodes 由两部分组成爬虫子crawl.py从种子节点出发做广度优先搜索周期性产出一份可达节点快照JSON 文件长连接维护者ping.py本文主角。它读取最新快照与快照中每一个可达节点建立并永久保持一条 TCP 连接持续发送 ping、记录 RTT往返时延、刷新版本信息最终把这些数据写入 Redis供 export.py 导出网络快照。生产环境的编排脚本 start.sh 一目了然1 个 master 15 个 slave 进程跑 ping.py每个进程内部再开 2000 个绿色线程——上万条长连接就是这么撑起来的。二、为什么用 gevent 绿色线程而不是多线程维护长连接是典型的IO 密集型任务每个线程 99% 的时间都在等网络数据CPU 几乎空闲。如果每个连接开一个操作系统线程上万个线程意味着几十 GB 内存和不可接受的上下文切换开销。Bitnodes 的答案是gevent 绿色线程greenlet协程级别的用户态并发单线程内数千个协程共享解释器切上下文只需保存几个寄存器开销比系统线程低几个数量级。整个项目在 ping.py 开头两行就完成了并发化改造monkey.patch_all() # 把 socket 等阻塞 API 替换为可协作调度版本 pool gevent.pool.Pool(CONF[workers]) # workers 默认 2000monkey.patch_all()是关键一步补丁之后socket.recv()这类原本卡住整个进程的调用会在数据未就绪时主动挂起当前协程让出 CPU 给其他协程——2000 个连接协程因此可以共享一个真正的 OS 线程轮流跑。协议层 protocol.py 的Connection.get_messages()里也显式调用gevent.sleep(0)让出调度避免某个协程长时间霸占 CPU。三、整体并发模型cron 调度器 连接池 连接管理器ping.py 的架构可以概括为三个角色┌────────────┐ 每10秒 ┌──────────────────────────────┐ │ cron 协程 │ ────────▶ │ gevent.pool.Pool(2000) │ │ (每进程1个) │ 按需spawn│ ┌─────────┐ ┌─────────┐ ... │ └────────────┘ │ │ConnManager│ │ConnManager│ │ │ │ Keepalive│ │ Keepalive│ │ └──────────────────────────────┘ ↕ Redis3.1 cron 协程全局心跳cron 是每 10 秒cron_delay唤醒一次的主循环master 角色额外负责换新快照检测到 crawl_dir 下出现新的爬取 JSON 时把所有未持连的节点灌入 Redis 的reachable集合并发布 pub/sub 信号通知其他进程。随后所有进程做同一件事统计reachable里还有多少节点spawn 等量的连接协程。细节里藏着一个精巧的限流设计——把新建连接均匀摊到 5 分钟窗口内backoff_time 300 * random / connections避免快照更新瞬间几千个节点同时发起握手把自己和对方都打爆。3.2 ConnectionManager建立一条连接每个被 spawn 出来的协程执行 ConnectionManager.connect()流程是取任务从 Redis 集合reachable中spop弹出一个节点原子操作天然去重多进程多协程抢任务不会冲突准入检查is_allowed()先按CIDR 前缀限流同一网段最多连 N 个节点避免把连接全堆到一家机房再对open:ip-port键执行SET NX——如果已有别的协程在连这个节点直接放弃握手通过 protocol.py 的Connection类完成 TCP 连接 version 消息握手.onion 节点自动走 Tor 代理移交握手成功后把连接交给 Keepalive 协程长期维护直到连接死亡才回收 Redis 中的占位键。四、Keepalive长连接的心跳引擎Keepalive.keepalive() 是每连接一个的驻留协程死循环里做三件事发 ping默认每 60 秒区间 60~600 秒发一次带随机 nonce 的 ping并把时间戳压入 Redis 的ping:ip-port:nonce列表——等对端回 pong 后就能算出这条连接的 RTT刷版本每天从 Redis 读一次最新的 version 数据节点升级客户端后opendata集合里的记录会自动更新排空缓冲区sink()持续读取对端消息防止 TCP 窗口被撑死同时顺带缓存对方发来的 block inv 消息写入binv:*键。任何一个环节抛出socket.error/ProtocolError协程就清理 Redis 状态并退出——连接死了cron 下一轮会看到reachable里的缺口如果快照还认为它可达自动重连。没有集中式的连接表状态全部落在 Redis 里这就是 16 个 ping 进程能协同管理同一批节点的原因。五、Redis绿色线程之间的共享内存Redis 键类型作用reachableSet待建连节点池协程用spop抢任务opendataSorted Set当前所有存活连接的元数据版本、UA、服务位score 是心跳时间戳master 每轮清理 20 分钟前的僵尸条目open:ip-portString (NX)连接占位锁防止多进程重复连同一节点ping:cidr:网段/前缀Counter同网段连接数限流计数ping:ip-port:nonceList TTLping 时间戳用于计算 RTT多协程/多进程的安全全靠 Redis 的原子原语SPOP、SET NX、INCR项目里几乎看不到一把锁。六、关键参数速查conf/ping.conf.default想调优时conf/ping.conf.default 里这几个参数最值得注意workers 2000单进程绿色线程上限即单进程最大并发长连接数socket_timeout 30/open_timeout 5读超时与建连超时决定死连接的发现速度cron_delay 10调度心跳间隔version_delay 86400每天刷新一次节点版本nodes_per_ipv6_prefix 1IPv6 /64 前缀只保留 1 条连接——同一台机器在 IPv6 下可能换出多个地址这个参数是去重利器。七、总结这套并发模型好在哪单进程扛万级连接gevent 把等 IO变成零成本挂起2000 个协程 ≈ 几 MB 内存无锁分布式任务队列、去重、限流、状态全部下沉到 Redis 原子操作16 个进程水平扩展互不干扰自愈式生命周期cron 周期性对比应有连接数与空闲槽位补差即可节点掉线自动被下一轮补上平滑限流5 分钟窗口摊平建连风暴 CIDR 前缀配额对全网节点足够友好。如果你想动手体验项目里还有完整的单测可以对照阅读比如 tests/test_ping.py 用 MagicMock 精确断言了spop → INCR → SET NX → ZADD → ZREM → DEL的完整 Redis 调用序列是理解这套并发状态机的最佳说明书。【免费下载链接】bitnodesBitnodes estimates the size of the Bitcoin peer-to-peer network by finding all of its reachable and unreachable nodes.项目地址: https://gitcode.com/gh_mirrors/bi/bitnodes创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表