门店断网了告警还在吗:边缘站点的缓存与续传架构

说明:专栏分布式篇的门店断网演练里有一条隐藏缺口:断网期间门店本地发生的事件,恢复后如何补到总部?本文设计边缘站点的缓存与续传架构:本地环形缓冲、断网状态机、恢复后增量上报与去重,让边缘站点在"孤岛期"既能自治又能补账。代码可直接改造进边缘网关。不放站外链接。


一、孤岛期的两个问题

门店断网(宽带故障、光缆施工、欠费停机)时,专栏分布式篇的架构下会发生什么:

  • 总部失明:OOB 通道(专栏带外篇)能报"门店失联",但失联期间门店里发生了什么------本地告警、设备离线、异常恢复------总部一无所知;
  • 恢复后丢账 :网络恢复,总部只看到"门店回来了",中间几小时的事件永远消失。

孤岛期的目标因此是两句话:孤岛期间本地自治照常(声光播报不依赖总部),恢复后增量补账(事件不丢、不重、有序)。这就是边缘计算的"最终一致":边缘与总部短暂分叉,最终汇合。


二、边缘架构:本地自治 + 环形缓冲

边缘网关(门店侧的小主机或路由器插件)承担两个角色:

  1. 自治面:本地告警照常触发声光终端(本地链路,专栏分布式篇的"本地响")------断网不影响门店自己的告警能力;
  2. 缓冲面 :所有本应上报总部的事件写入本地环形缓冲,网络恢复后按序补传。
python 复制代码
# edge_buffer.py --- 边缘事件缓冲:本地写、恢复后按序续传、去重
import datetime as dt
import json
import pathlib
import sqlite3
import time

import requests

DB = pathlib.Path("edge_buffer.db")
HQ = "https://hq-bus.internal/ingest"
BATCH = 50
RETRY_BACKOFF = [5, 15, 60, 300]               # 恢复后的重试退避(秒)


def buffer(event: dict, now: float = None) -> None:
    """任何本应上报的事件先落本地库------断网时这是唯一动作"""
    now = now or time.time()
    conn = sqlite3.connect(DB)
    conn.execute(
        "INSERT INTO events(ts, device, payload, sent) VALUES(?,?,?,0)",
        (now, event.get("device", "?"),
         json.dumps(event, ensure_ascii=False)))
    conn.commit()


def flush() -> int:
    """网络恢复后按时间序分批上报:成功才标记 sent,失败退避重试"""
    conn = sqlite3.connect(DB)
    rows = conn.execute(
        "SELECT rowid, ts, payload FROM events WHERE sent=0 "
        "ORDER BY ts LIMIT ?", (BATCH,)).fetchall()
    if not rows:
        return 0
    payload = [{"ts": ts, "event": json.loads(pl)} for _, ts, pl in rows]
    try:
        r = requests.post(HQ, json={"events": payload}, timeout=10)
        r.raise_for_status()
    except requests.RequestException:
        delay = RETRY_BACKOFF[min(len(RETRY_BACKOFF) - 1, _fail_streak())]
        time.sleep(delay)
        return 0
    ids = [rid for rid, _, _ in rows]
    conn.executemany("UPDATE events SET sent=1 WHERE rowid=?", [(i,) for i in ids])
    conn.commit()
    return len(ids)


def _fail_streak() -> int:
    return 0                                    # 实现略:连续失败计数存内存

四个关键设计:先落库再谈上报 (事件的第一站是本地 SQLite,不是网络);按时间序补传 (总部收到的是有序流,专栏审计篇的对账才有意义);成功才标记 (半批失败整批重试,服务端按事件 ID 幂等去重------专栏总线篇的 dedup_key);退避重试(恢复瞬间不冲击总线)。


三、去重与对账:补传不等于重放

续传最大的风险是重复:断线重连边界上的事件、重试批次与定时批次的交叠。三重防线:

  1. 事件 ID 幂等:每条事件带边缘侧生成的唯一 ID(设备前缀 + 序号),总部按 ID 去重;
  2. 水位确认:总部返回"已收到的最大序列号",边缘只补发之后的事件;
  3. 补账标记 :补传事件带 backfill=true 标志------总部看板把它们与实时告警区分展示(补账事件不触发声光重播,只进记录),避免"恢复后把几小时前的告警再喊一遍"。

补账与总部的对账合并成一张表:断网时长 × 缓冲条数 × 补传条数,三个数字对得上,孤岛期才算真正"无损失"。


四、状态机:边缘网关的网络状态管理

python 复制代码
# edge_state.py --- 边缘网关的网络状态机
from enum import Enum


class NetState(Enum):
    ONLINE = "online"
    DEGRADED = "degraded"        # 时通时断:只缓冲高优先级事件
    OFFLINE = "offline"          # 全缓冲,本地自治


def next_state(state: NetState, ok_count: int, fail_count: int,
               offline_minutes: float) -> NetState:
    """双阈值滞回:避免在弱网边界来回抖动"""
    if state == NetState.ONLINE and fail_count >= 3:
        return NetState.OFFLINE
    if state == NetState.OFFLINE and ok_count >= 5:
        return NetState.ONLINE
    if state == NetState.ONLINE and offline_minutes >= 1:
        return NetState.DEGRADED
    return state

三态各有限流策略:ONLINE 全量实时上报;DEGRADED (弱网)只缓冲 critical/warning,info 类直接本地丢弃(弱网带宽优先保关键事件);OFFLINE 全缓冲。状态机与专栏 MQTT 篇的遗嘱配合------边缘网关自己也向总部发遗嘱,"网关死了"与"网关在缓存"总部可区分。


五、常见坑

  1. 本地库无限增长 → 长期断网把 SD 卡写满,环形缓冲要有上限(如 10 万条)与覆盖策略(覆盖最旧,并在恢复后标注"有丢弃");
  2. 恢复瞬间冲击总线 → 500 家门店同时恢复,总线的 Redis 瞬间被打爆;补传分批 + 全局令牌桶(专栏总线篇);
  3. 重复告警重播 → 补账事件没带 backfill 标志,恢复后喇叭把旧告警再喊一遍;
  4. 边缘时钟漂移 → 断网期间边缘 RTC 漂移,事件时间戳失真;恢复后按总部时间校准并标注偏移量;
  5. 缓冲的 critical 永不升级 → 孤岛期的 critical 只有本地声光,总部不知情------OOB 通道(专栏带外篇)负责把"门店离线 + 离线期间有 critical"先送出去;
  6. 只缓冲不告警 → 以为"反正会补传",本地声光也停了------本地自治是第一原则;
  7. 续传成功但总部解析失败 → schema 版本不匹配,补传批次被静默丢弃------响应里要有接收确认计数;
  8. 边缘网关单点 → 网关挂了缓冲全丢,网关进程守护 + 关键事件双写(SD + 内存快照)。

六、验收清单

  • 断网演练:拔线 2 小时,本地声光照常,恢复后 15 分钟内补传完成;
  • 补传零重复:总部按事件 ID 去重后计数 = 边缘缓冲计数;
  • 补账事件带 backfill 标志,不触发声光重播;
  • 三态状态机验证:弱网模拟下 critical 全缓冲、info 正常丢弃;
  • 断网时长 / 缓冲数 / 补传数三数字对账表生成;
  • 边缘时钟恢复后校准并标注偏移,长期断网环形覆盖有告警。

七、小结

边缘缓存与续传回答的是分布式告警的终极问题:**总部看不见的时候,边缘还算不算体系的一部分?**答案是:算------本地自治保证它仍有告警能力,环形缓冲保证它的事件不丢,补传机制保证账最终能对上。三者合起来,孤岛期从"体系盲区"变成"延迟可见的一等公民"。

它与专栏既有各篇严丝合缝:本地自治是分布式篇的"本地响",缓冲是总线篇削峰的边缘版,去重是 dedup_key 的跨站应用,对账是日志篇的方法论。架构没有新增哲学,只是把"最终一致"四个字落到了门店的网关里------光缆再被挖断一次,你的账本这次是全的。

相关推荐
Java后端的Ai之路1 小时前
07React_Props验证器
人工智能·python·react.js·架构·fastapi
AIGC小尼1 小时前
本地 AI 漫剧流水线模型缓存与特征向量持久化:IPAdapter 预编码、特征池复用与内存池管理实战
人工智能·spring boot·缓存·预编码算法·comfyui·ai漫剧
Imagination官方博客2 小时前
PowerVR 上的 LLM 性能与加速
人工智能·架构·llama·gpu算力
腾讯云大数据2 小时前
本体立骨架,语义供刻度——DataBuddy对企业AI语义架构的一次完整回答
大数据·人工智能·架构·云计算·腾讯云
lightning_bug2 小时前
docker部署黑马笔记
后端·架构
论文复现现场4 小时前
DeepSeek V4.1 Flash 各平台价格怎么比?缓存、峰谷计费与 Python 成本计算
开发语言·python·缓存·大模型·成本优化·deepseek
泡干脆面就番茄4 小时前
Hive入门:架构解析与安装部署实录
大数据·数据仓库·hive·hadoop·架构
yyk333244 小时前
Hive架构
hive·hadoop·架构
豆角焖肉4 小时前
SpringBoot + Redis 缓存实战
java·spring boot·redis·后端·缓存·菜单缓存