凌晨两点,一位骑手把电量见底的电池放进换电柜。对他来说,系统是否采用微服务、数据库用了几台机器,都没有眼前这件事重要:能不能顺利换出一块满电电池,继续完成下一单。

这也是SWAPHD讨论“0奔溃”的出发点。服务器可以检修,软件需要升级,运营商网络也会波动,但这些技术世界里的变化,应当尽可能被消化在系统内部,而不是变成骑手站在柜前的等待。把换电行业的宕机变成历史,靠的不是一台永远不会坏的服务器,而是一套能够容纳故障、限制影响、恢复业务并核对结果的工程体系。

本文以SWAPHD的连续服务设计理念为主线,结合成熟的高可用工程方法,拆解从云端数据到柜端执行的完整原理。文中的多机部署、边缘容灾和容量数字是架构方案与计算示例,具体交付范围和服务指标应以实际部署及验收结果为准。“0奔溃”表达的是尽可能让用户不感知局部故障的目标,不能用来替代可用性记录和恢复演练。

一、先理解换电系统:它比一个网站多了半个物理世界

普通网站的一次请求,往往在页面和数据库之间就能结束。换电业务不同,它需要同时协调用户身份、套餐权益、订单、旧电池、目标电池、仓门、充电控制板、设备网络和支付渠道。页面显示“成功”,并不意味着仓门已经打开;网关收到回执,也不一定意味着用户已经取走电池。

因此,换电平台的稳定性必须沿整条业务链来定义。如果云端正常但物联网卡欠费,骑手仍可能换不了电;如果柜机在线但库存错误,系统仍可能分配一块不存在的电池;如果开门成功而订单没有结算,平台看起来恢复了,账务却埋下了下一次事故。

图1|一次换电跨越的五个层次
骑手与运营人员
↓ 身份、权益、订单申请
接入层与换电交易服务
↓ 事务、事件、设备指令
设备网关与蜂窝网络
↓ 协议交互、执行回执
换电柜、仓门、电池与BMS
↓ 现场状态、资产归属、最终对账
交易完成与运营审计

这条链路的每一层,都要回答三个问题:如何发现异常,异常会影响多大范围,恢复之后如何确认没有丢单或重复执行。只有这三个问题都有答案,“稳定”才有可操作的含义。

二、数据架构:把交易事实、设备遥测和查询视图分开

换电平台最容易被低估的压力,往往来自设备数据。假设十万块电池每分钟上报一次,仅电池遥测每天就会产生一亿四千四百万条记录。这是便于理解规模的算术示例,并非SWAPHD的实际设备量。再加上柜机心跳、仓口状态和告警,数据增长很快会超过早期后台系统的直觉。

如果把订单、支付、设备实时状态和全部历史报文混在一起查询,管理人员打开一张统计报表,也可能与骑手换电争抢磁盘和数据库连接。可靠的数据架构首先需要分层,让不同业务使用适合自己的存储与访问方式。

第一层是交易事实层。用户权益、换电订单、资产归属、支付流水等关键数据,适合放在支持事务的关系数据库中,例如PostgreSQL。这里追求的是明确的约束和可追溯的变更,金额采用整数最小货币单位表达,订单编号、支付流水号与幂等键设置唯一约束,防止重复回调造成重复入账。

第二层是设备事件与遥测层。电压、电流、温度、定位和原始通信报文通常具有时间序列特征,可以按时间分区、批量写入,并根据查询需求选择时序数据库或分析存储。近期高频数据保持便捷访问,较老数据归档到低成本存储,避免在线交易库无限膨胀。

第三层是最新状态与读模型。运营列表通常只需要看到设备最近一次有效状态,不必每次从上亿条历史中找最大时间。单独维护latest快照、库存读模型和经营汇总,可以让高频查询保持轻量。读模型可以重建,交易事实不能依赖浏览器缓存或者某个服务进程的内存。

图2|分层存储的职责
订单、权益、资产变更 → 事务事实库
设备原始事件、遥测 → 分区历史与归档
设备列表、地图、报表 → 最新状态与读模型
三类数据通过稳定标识关联,避免互相拖慢。

这种设计还有一个好处:当报表延迟或历史查询暂时不可用时,核心换电可以继续。降级必须发生在可延迟的能力上,而不是让交易事实变得含糊。

三、事务边界:一笔换电不能被写成四笔互不相干的更新

设想一个常见事故:订单已经创建,电池却没有预占成功;或者仓位预占成功,服务在写订单之前退出。下一位用户进入时,可能会拿到同一块电池,也可能看到一个永远被占用的仓位。

解决办法并不是把所有外部调用都塞进数据库事务。设备开门和支付请求可能持续数秒甚至更久,长时间持有数据库锁,会把局部等待放大为全站阻塞。合理的方式是缩短数据库内的原子操作:校验当前权益、检查资源版本、预占目标资源、写入订单状态和待投递事件,一起提交;随后再异步驱动设备动作。

多个请求争抢同一电池时,可以使用带条件的更新、行锁或唯一约束来保证只有一方成功。锁的粒度应收敛到订单、资产或仓口,而不是整张表。预占还需要租约或明确到期时间,并通过受控补偿释放,避免一个异常进程永久占住资源。

这里最关键的原则是:数据库提交成功与物理动作执行成功,是两种不同事实。系统必须把它们分别记录,并用后续回执、现场状态与对账任务连接起来。跨越数据库和现实设备,很难获得无条件的“恰好执行一次”;可落地的方案通常是允许消息重复到达,通过幂等和状态约束让业务效果不重复。

四、多服务器架构:备份数量之外,更要考虑故障域

一台服务器运行多个进程,可以隔离部分软件故障,却无法抵抗宿主机断电或磁盘损坏。把两台服务器放在同一条电源、同一台交换机后面,也可能在一次机房事故中同时失效。多机架构真正需要关注的,是故障域是否彼此独立。

一种可演进的部署方式是:入口层配置负载均衡,应用层运行多个无状态实例,换电交易、设备网关与后台任务按职责拆分,数据库采用主备和备份体系。不同节点应尽量分散到独立宿主机或可用区,并为网络、证书、域名解析和运维控制面识别共同依赖。

应用实例之间不应依赖本机内存保存唯一的订单状态或登录挑战。实例退出后,新的请求能够从共享的可靠存储恢复上下文。负载均衡则依据就绪状态摘除不能处理业务的节点,并结合超时和连接排空,减少切换过程中的请求中断。

图3|多机容错的基本关系
统一入口 → 应用节点A / 应用节点B
交易服务 → 主数据库 / 备用数据库
网关集群 → 连接归属与指令路由
后台任务 → 持久事件与补偿队列
独立备份 → 保留历史恢复点

多机也会引入新问题:网络分区时谁可以继续写入,旧主节点恢复后会不会形成双主,切换后的任务会不会重复运行。因此数据库升主需要可靠的仲裁与隔离机制,旧主必须被阻止继续写入。多台机器只有在故障切换规则清楚时,才会变成可用性收益。

五、蓝绿发布:更新软件时,让业务有一条退路

不少宕机并不是硬件坏了,而是发布过程把可用版本覆盖掉了。蓝绿发布的思路是保留正在服务的版本,同时启动一套新版本,待依赖检查和关键流程验证完成后,再逐步或一次性切换入口流量。

一个完整流程应包括:构建可复现的制品,启动新实例,检查数据库和内部服务依赖,执行代表性请求,切换流量,观察错误率和延迟,再决定是否保留。旧实例先停止接收新请求,给正在处理的请求留下完成时间,避免粗暴结束进程。

对于设备TCP长连接,HTTP入口切换不等于连接已经迁移。网关需要独立的排空与重连策略,避免在发布时让全部柜机同时掉线并同时重连。业务发布、网关发布和数据库变更应根据各自风险制定节奏。

数据库迁移尤其需要兼容窗口。先增加新字段和兼容读写,完成数据回填及验证,待旧版本退出后再考虑移除旧结构。不能在新版本启动前直接删除旧版本依赖的字段,然后指望切回代码就能恢复。真正可用的回滚,必须同时考虑应用、数据和设备协议。

六、物联网卡监测:不能只看一枚绿色的“在线”标识

物联网卡是换电柜与云端之间的一条生命线,但“卡正常”与“业务可用”并不是同一回事。SIM卡可能已经激活,却因流量耗尽无法正常通信;设备可能有信号,却没有完成数据注册;TCP连接可能仍被操作系统保留,但实际链路早已失效。

因此,物联网卡监测至少要连接四类信息。第一类来自运营商侧:激活、停复机、套餐余量、使用周期、卡机绑定规则与欠费风险。第二类来自通信模组:注册状态、接入制式、信号强度与质量、重拨次数。第三类来自传输链路:连接时长、心跳间隔、断线频次、收发字节和往返时延。第四类来自业务:最近一次有效遥测、召测响应和指令闭环结果。

ICCID用于识别SIM卡,IMEI用于识别通信设备,设备业务编码用于识别平台资产。它们需要建立可追溯的关联关系,但不能混为一个字段。换卡、换模组或更换主板时,关联变更也要留下记录,否则排障人员很容易拿着一张正常的卡去解释另一台故障设备。

信号分析也不能只靠一个阈值。不同通信制式和模组提供的RSSI、RSRP、RSRQ、SINR等指标含义不同,应先规范单位和来源,再结合历史趋势判断。一次瞬时弱信号可以观察,持续弱信号且重连频繁则应升级处理;同一区域大量设备同时离线,更应优先排查运营商与基站侧共同故障。

图4|物联网链路诊断顺序
卡状态与套餐是否正常?
↓
模组是否注册、信号是否稳定?
↓
TCP是否双向可达、心跳是否新鲜?
↓
有效报文和业务指令是否闭环?
逐层缩小范围,避免“离线就重启”。

对于具备硬件条件的设备,可以评估双卡或不同运营商链路,但自动切换需要冷却时间、次数限制和效果验证。频繁切卡或反复重启模组,可能消耗更多流量,也可能让原本短暂的网络抖动演变为持续离线。

七、设备网关监测:连接还在,不代表设备真的健康

网关负责把不稳定的外部通信,转化成平台内部可理解、可审计的设备事件。它既要处理TCP粘包、拆包、异常长度和连接重建,也要理解不同厂商协议里的地址、功能码、序号与校验规则。

一帧报文进入系统,应该先验证长度与结构,再校验CRC或协议规定的完整性字段,随后核对设备身份和注册绑定,最后才进入业务处理。CRC可以帮助发现传输错误,却不能替代身份认证、加密或报文防重放;攻击者能够重算CRC,这一点在设备安全设计中必须明确。

在线状态应具有时效性。系统需要区分传输层有连接、应用层有心跳、业务层有有效数据这几个状态。最近通信时间要基于服务端接收时刻,同时保留设备上报时间用于分析,避免设备时钟漂移让一块断线电池一直显示“刚刚在线”。

对于支持休眠的BMS,也不能简单套用常在线柜机的阈值。合理做法是根据设备类型、供电条件、协议心跳和运营商链路特征,设置不同的观察窗口、离线阈值与恢复条件,并保留状态变化原因。

网关集群还需要知道某台设备的连接当前归谁管理。连接归属记录可以携带租约和代际编号,指令下发前检查有效归属,防止旧节点在网络恢复后继续操作已经转移的设备。设备重连时采用退避和随机抖动,避免大面积断网恢复后出现“重连洪峰”。

八、指令闭环:从“已发送”到“已完成”,中间必须有证据

换电业务最危险的误判之一,是把socket写入成功解释为仓门已经打开。事实上,指令可能只进入了本机缓冲区;设备可能收到但拒绝执行;控制板可能返回接收回执,而门锁因机械故障没有动作。

所以指令应拥有独立编号、目标设备、关联订单、创建时间、失效时间和执行状态。至少区分已创建、待投递、已发送、已应答、执行确认、失败与结果待核实,并记录每次状态转换的来源。协议不支持精细回执时,也要诚实保留证据缺口,不能凭空补成成功。

图5|一条开门指令的证据链
订单已预占 → 指令已持久化
→ 网关已接收 → 设备已应答
→ 门锁或仓口状态确认
→ 电池变化与订单结果核对
任一环节超时 → 查询现场状态或转人工核实

重试策略必须按指令性质区分。查询电量通常可以安全重试,开门、切断输出、修改参数则需要更严格的幂等与权限控制。如果无法确定上一条开门命令是否已执行,优先读取仓门和电池状态,而不是无限重复下发。

迟到回执也不能随意覆盖结果。假设订单已进入人工处理,一条几分钟前的成功回执此时到达,系统需要依据指令编号、执行代际和状态迁移规则处理,必要时产生核对任务。看似麻烦的状态机,正是在替运营团队承接现实世界的不确定性。

九、Outbox与后台补偿:让进程退出不等于业务遗忘

业务数据库提交成功后,如果服务还没来得及发送消息就退出,后续设备动作可能永远没有发生。事务Outbox的做法,是把业务变化和待投递事件写在同一个数据库事务里。后台Worker再读取这些持久事件,执行投递、重试与补偿。

Worker可能在完成外部动作之后、标记事件完成之前退出,所以消费者仍然必须幂等。消息队列、Outbox和重试系统通常提供的是至少一次交付,业务需要通过唯一键、版本和状态约束消化重复,而不是把“用了队列”当作不会重复的证明。

重试要设置退避、随机抖动、最大次数和总时间预算。持续失败的任务进入死信或人工处理队列,并附带最后错误、业务关联和建议动作。如果只是把错误写进日志,却没有人能找到并处理这些事件,补偿机制就只完成了一半。

所谓换电持续性,还应包含恢复时的状态重建:服务重启后能找回未完成订单、未确认指令和待核对资产,知道哪些动作可以自动继续,哪些动作需要现场证据。把关键状态落到可靠存储,并定期检验恢复流程,比单纯配置进程自动拉起更有价值。

十、服务器瓶颈:CPU不高,系统也可能已经堵住

很多人判断服务器是否健康,只看CPU和内存。实际生产中,CPU只有百分之二十,也可能因为数据库连接全部被慢查询占用,导致新请求排队;内存看起来充裕,也可能因为磁盘延迟、文件描述符耗尽或事件循环阻塞,让接口迟迟没有响应。

应当从流量、延迟、错误、资源饱和度四个维度观察。流量看每秒请求与设备消息量,延迟看P50、P95和P99,错误看失败类型与业务影响,饱和度看连接池等待、队列积压、磁盘I/O和线程或事件循环压力。平均响应很快,无法解释少数骑手为什么每次都等十秒。

一个直观的容量关系是:系统内平均并发量,约等于平均到达速率乘以平均停留时间。在相应稳定条件下,如果每秒进入五百个请求,平均停留两秒,就可能有约一千个请求同时处在系统中。这里的数字仅用于解释原理。依赖变慢会让并发积压增加,即使入口流量没有增长,也可能把系统拖垮。

因此要把超时、并发上限、队列长度、连接池容量和背压一起设计。数据库连接池不是越大越好,过多活跃连接会增加竞争与内存成本。慢查询应查看执行计划、索引选择、扫描行数和锁等待;高频列表应分页并限制筛选范围;大批量导出则转为后台任务。

对Node.js服务,还应关注事件循环延迟和垃圾回收。同步压缩、大JSON处理、无边界日志输出或密集计算,都可能阻塞其他请求。把重任务移到合适的Worker或独立处理链路,并给入口设置资源预算,才能防止“一个请求拖住一台机器”。

十一、磁盘与备份:400GB不是容量策略

服务器磁盘写满,往往不是业务突然变大,而是日志没有轮转、旧发布目录没有保留上限、数据库WAL持续积压、备份反复落在本机,或者设备原始报文被无期限保存。容量看板需要拆分系统、日志、数据库、归档和发布制品,不能只给出一个总百分比。

容量告警应结合增长速度,而不只是固定阈值。举例来说,剩余十GB看起来不少,但如果最近每小时增长两GB,恢复窗口就很短;反过来,一个长期稳定的存储卷不能只凭单次百分比判断紧急程度。预测应使用近期趋势,并识别突发写入,避免线性外推制造虚假安全感。

清理必须知道对象的用途。运行中的数据库文件、仍被复制或恢复需要的WAL,不能因为文件大就直接删除。备份应放到独立故障域,配置保留周期、完整性检查和访问控制;本机副本可以加速恢复,但机器损坏时它也可能一起丢失。

“每天有备份”只是开始。还要把备份恢复到隔离环境,确认账号、资产、订单和关键约束可用,并记录恢复耗时。数据库复制不能替代备份,因为误删除、逻辑损坏和错误更新也可能迅速复制到备用节点。

十二、监控体系:服务器活着,与骑手能换电,是两条曲线

监控应同时覆盖基础设施、应用、设备与业务。基础设施关注主机、磁盘和网络;应用关注接口、依赖与队列;设备关注在线率、数据新鲜度和指令回执;业务关注换电完成率、处理中订单时长、支付核对差异以及可用电池库存。

健康检查也要分层。存活检查回答进程是否还在响应,就绪检查回答它是否具备处理请求所需的关键条件。外部第三方短暂波动不应轻易触发所有实例反复重启,否则监控反而会制造故障风暴。检查频率、超时和阈值都需要与恢复动作匹配。

日志中使用请求编号、订单编号、指令编号和设备标识贯穿链路,排障人员才能把一次失败从页面追到数据库和柜机。密码、密钥、令牌和个人敏感信息需要脱敏,原始报文也要设置访问权限和保留期限。能看清问题,不代表必须把所有数据无限公开和保存。

告警应指向可执行的动作:哪类业务受到影响,主要故障范围是什么,谁来处理,先检查什么,什么时候需要升级。一次区域网络故障不必产生上千条独立手机提醒,应按地区、运营商和共同依赖聚合,让值守人员先看到最值得解决的根因。

十三、用三个故障场景检验架构有没有用

场景一:晚高峰,一个应用节点突然退出。入口停止向它分配新请求,其他节点承接后续流量;正在处理的订单依靠持久状态恢复。客户端如果重试同一申请,应携带同一幂等标识,获得已有订单或明确结果,而不是多创建一笔交易。是否真的对骑手无感,取决于超时、容量余量和恢复路径的实际表现。

场景二:某区域运营商网络抖动。监控发现同一地域设备心跳新鲜度下降,且重连次数同步上升,将问题聚合到通信链路。平台保留进行中交易,降低失联柜机的推荐优先级,提示附近可用站点;链路恢复后按退避节奏重连,补传事件并核对现场库存,避免瞬时洪峰冲垮网关。

场景三:数据库主节点异常。系统先确认故障并隔离旧主,再按既定策略切换到备用节点。对于同步或异步复制,需要明确各自的延迟和数据风险。网络分区期间无法确认唯一写入者时,涉及资产和资金的操作应暂停或进入可解释的等待状态,不能为了页面一直显示成功而冒双写风险。

如果启用柜端离线授权,也只能在硬件、安全条件和交付规则允许的范围内运行,例如有期限的授权、受限资产集合、本地防重放账本与恢复后对账。云端不可达不意味着设备可以绕过BMS保护、资产校验或无限开放仓门。连续服务必须建立在安全边界之内。

十四、怎样证明“更接近0奔溃”:把口号变成可测量指标

评价稳定性,首先要定义统计口径。例如“具备有效权益、设备满足服务条件的换电请求,在规定时间内获得确定业务结果的比例”,就比“服务器在线率”更接近骑手体验。失败排除项、统计窗口和数据来源也要公开,否则不同数字无法比较。

可用性目标需要和恢复目标一起看。RTO表示故障后希望多久恢复服务,RPO表示最多能够接受丢失多长时间范围的数据;两者必须通过演练验证。以三十天计算,99.99%的时间可用性对应约4.32分钟的不可用预算,这只是数学换算,并不是本文对SWAPHD当前运行成绩的宣称。

压测也不能只在空数据库里测一个简单接口。应覆盖真实数量级的数据、设备持续上报、批量重连、慢查询、支付回调重复、节点退出和依赖超时,并观察错误率、P99延迟、积压清空速度以及恢复后的数据一致性。容量余量需要按单节点故障后剩余节点仍能承担峰值来规划。

故障演练应从可控环境开始,明确范围、终止条件、观察指标和回退路径,再逐步进入受控生产验证。演练之后检查有没有丢失已确认订单、重复执行危险指令、错误转移资产或留下无人处理的中间状态。能复原业务现场,比只看到进程重新启动更能说明架构质量。

十五、SWAPHD想改变的,是行业对宕机的处理方式

换电网络扩张之后,稳定性会从技术问题变成运营基础设施问题。骑手需要确定的服务,运营商需要可解释的订单,设备厂商需要清楚的协议边界,平台需要能追溯的资产与资金事实。任何一方的信息缺失,都可能让一次小故障演变为一整夜的人工对账。

SWAPHD所倡导的“0奔溃”,就是把这些问题提前放进架构:用分层数据避免相互争抢,用独立故障域限制事故范围,用物联网卡与网关监测发现链路异常,用指令状态机确认真实执行,用Outbox和补偿承接中断,用蓝绿发布减少更新风险,用备份、演练和容量治理给恢复留下余地。

这项工作的验收标准,不是架构图画了多少层,也不是机器买了多少台,而是每一次异常都能被发现,每一笔交易都能找到去向,每一次恢复都能核对结果,每一次更新都保留退路。让系统复杂的部分留在工程内部,让骑手得到尽可能简单、稳定的换电体验。

让换电行业的宕机成为历史,是一个需要持续投入的方向。通往这个方向的道路很具体:把单点变成可切换的节点,把未知变成可观察的状态,把遗漏变成可补偿的事件,把经验变成可演练的流程。下一次局部故障发生时,业务仍然能够继续,这才是“0奔溃”最有分量的技术解释。

本文涉及的通用原理,可进一步参阅《Site Reliability Engineering》关于SLO与错误预算的章节、PostgreSQL官方文档关于事务隔离、预写日志与流复制的说明,以及AWS Prescriptive Guidance中的Transactional Outbox模式。