从零到架构师:以 PU/FTU 配电终端为样本的完整系统学习路径
从零到架构师:以 PU/FTU 配电终端为样本的完整系统学习路径
目标不是“把代码看完”,而是建立一套从业务问题、质量属性、系统边界、架构分层、数据模型、并发模型、故障模型,到构建、测试、部署和演进的完整设计能力。
写在前面:架构师到底要会什么
架构师不是画框图的人,也不是写代码最多的人。一个合格的架构师需要同时回答五类问题:
- 系统为什么存在:解决什么业务问题,边界在哪里。
- 系统如何工作:数据、控制、时间和故障如何在系统中流动。
- 系统为什么这样设计:每个关键决策牺牲了什么,又换来了什么。
- 系统如何证明可靠:怎样构建、测试、观测、发布、回滚和恢复。
- 系统如何持续演进:新需求怎样加入而不破坏已有行为。
PU/FTU 工程是一个很好的学习样本。它同时包含嵌入式 Linux、C 语言、多线程、数据库、实时数据、工业通信协议、硬件设备、看门狗、升级和故障恢复。把这类工程真正吃透,得到的不是某个模块的经验,而是一套可以迁移到网关、机器人、工业控制器、边缘计算节点和高可靠服务端系统的方法。
第一部分:建立系统全景
1. 从业务问题开始,而不是从目录开始
配电终端的核心任务,可以抽象为四件事:
- 感知现场:采集开关状态、电压、电流、功率、电量、温度和设备状态。
- 做出处理:计算、越限判断、故障判断、保护逻辑和状态聚合。
- 对外通信:通过 IEC 60870-5-101/104 等协议向主站上送数据、接收参数和控制命令。
- 持续可靠运行:断网、掉电、数据库损坏、线程退出和升级失败时仍能恢复。
由此可以得到系统的顶层输入与输出:
输入 ├─ 模拟量:电压、电流、功率、温度 ├─ 开关量:开关位置、告警、硬压板 ├─ 通信报文:总召、对时、参数、遥控 ├─ 本地操作:液晶、维护工具 └─ 系统事件:启动、升级、网络变化、线程退出
处理 ├─ 采集与标准化 ├─ 数据质量判断 ├─ 状态机与业务规则 ├─ 数据持久化和历史归档 ├─ 协议编码/解码 └─ 故障检测与恢复
输出 ├─ IEC101/104 上送 ├─ 遥控执行 ├─ 参数更新 ├─ 液晶展示 ├─ 日志、SOE、冻结和录波 └─ 重启、告警和升级结果架构分析的第一个产物应是“系统上下文图”,而不是代码调用图。
2. 明确系统边界
PU 应用内部负责:
- 业务线程管理;
- 参数、实时和历史数据管理;
- 通信规约实现;
- 通道与设备适配;
- 系统配置、对时和网络管理;
- 液晶与维护接口;
- 应用级故障恢复。
PU 应用外部依赖:
- Linux 内核与设备驱动;
- 串口、SPI、RTC、PPS、看门狗等设备节点;
- GPSD、chrony 等系统服务;
- SQLite、OpenSSL、FFTW 等库;
- 启动、守护和升级脚本;
- 主站、维护工具和现场硬件。
边界决定责任。如果驱动没有产生 PPS 信号,应用不能假装它能通过重试解决;如果数据库事务没有提交,协议线程也不能靠重复发送修复数据一致性。架构师必须明确每一类故障属于哪一层。
3. PU 工程的顶层架构
┌──────────────────────────────────────────────┐│ 主进程 ││ PU.c:启动、线程编排、退出策略、看门狗 │└──────────────────────┬───────────────────────┘ │ 按数据库配置启动线程┌──────────────────────▼───────────────────────┐│ 业务线程层 ││ 通道管理 / 系统配置 / IEC101 / IEC104 ││ 液晶 / 维护 / GPS / 电源 / 组合遥信 │└───────────────┬──────────────────┬───────────┘ │ │┌───────────────▼──────────┐ ┌─────▼───────────┐│ 数据与领域层 │ │ 协议适配层 ││ 参数/遥信/遥测/电量/遥控 │ │ ASDU/链路/映射 ││ 实时事件/历史/转发表 │ │ 安全和运维协议 │└───────────────┬──────────┘ └─────┬───────────┘ │ │┌───────────────▼──────────────────▼───────────┐│ 基础设施层 ││ SQLite / pthread / socket / serial / SPI ││ 文件系统 / 网络 / GPSD / watchdog / 脚本 │└──────────────────────────────────────────────┘这是一种“单进程、多业务线程、共享数据库、外部脚本守护”的架构。
它的优点是部署简单、跨模块共享数据方便、进程间通信成本低;代价是线程隔离弱、共享资源容易耦合、单个严重故障可能拖垮整个进程。因此工程额外使用线程退出码、关键线程分类、局部重启、进程重启和看门狗来补偿隔离不足。
第二部分:从零理解底层原理
4. C 程序从源码到运行的全过程
必须真正理解下面这条链路:
.c/.h 源码 → 预处理:展开 #include、宏和条件编译 → 编译:C 代码变为汇编 → 汇编:汇编变为目标文件 .o → 链接:目标文件和库合成为 ELF 可执行文件 → 加载:Linux 映射代码段、数据段、堆栈和动态库 → 运行:从入口进入 main()需要掌握的基础:
- 声明与定义;
- 头文件保护和链接可见性;
static、extern、符号表;- 栈、堆、全局区和只读区;
- 指针、数组、结构体和对齐;
- 位域、大小端和二进制协议;
- 静态库、动态库和运行时加载;
DEBUG_SQDB、X86BPS、RELEASE条件编译。
实践验收
你应该能做到:
- 从某个函数追到它最终链接自哪个目标文件。
- 解释同一份源码为何在 x86 和 ARM 上行为不同。
- 用预处理结果解释某段条件编译代码是否存在于最终程序。
- 看到未定义符号时判断是缺源码、缺库还是声明不一致。
5. Linux 进程、线程和调度
PU 是单进程多线程系统。进程拥有独立虚拟地址空间,线程共享代码、堆、全局变量和打开的文件描述符,但每个线程有自己的栈和调度状态。
共享带来效率,也带来三类问题:
- 竞争条件:两个线程同时读写同一状态。
- 死锁:线程以不同顺序持有多把锁。
- 可见性:编译器和 CPU 重排导致另一个线程看不到预期顺序。
工程中用到的典型手段:
pthread_mutex_t:保护复合状态;- 递归互斥锁:允许同线程重复进入,但容易掩盖设计问题;
- C11 原子类型:适合计数器和简单状态;
pthread_once:一次性线程安全初始化;pthread_tryjoin_np:主线程非阻塞检查业务线程退出。
架构师不只要问“有没有加锁”,还要问:
- 被保护的不变量是什么?
- 锁的持有范围有多大?
- 是否在持锁时执行数据库、串口或文件 IO?
- 锁顺序是否全局一致?
- 线程退出时是否可能遗留锁、事务和文件描述符?
实践验收
为工程画一张共享资源表:
| 资源 | 写线程 | 读线程 | 同步方式 | 失败影响 |
|---|---|---|---|---|
| 参数数据库 | 系统/维护/规约 | 多个业务线程 | SQLite + 参数锁 | 配置不一致 |
| 实时数据库 | 采集/业务线程 | 规约/液晶 | SQLite WAL | 实时数据延迟 |
| 串口描述符 | 通道管理 | 规约/模块 | 通道锁 | 通信中断 |
| SPI 安全芯片 | 通道/安全模块 | 多线程 | SPI 锁 | 加密通信失败 |
如果不能明确写出资源所有者,就还没有真正理解并发架构。
6. 文件描述符和设备抽象
在 Linux 中,文件、串口、socket、设备节点、管道都可以被抽象为文件描述符。应用通过 open/read/write/ioctl/close 与内核交互。
关键原理:
- 文件描述符只是进程内的整数索引;
read/write的短读短写是正常情况;- 阻塞、非阻塞、超时和事件驱动是不同模型;
ioctl用于设备特有控制;- 设备消失、驱动重置和网络断开都会使旧描述符失效;
- 关闭和重开必须与其他线程协调。
PU 对串口、SPI、GPS、看门狗的处理,本质都是资源生命周期设计:创建、配置、使用、错误检测、关闭、重连和最终释放。
7. 网络与工业协议的底层模型
IEC101 和 IEC104 在业务语义上接近,但承载层不同:
- IEC101 通常运行在串行链路上;
- IEC104 将 IEC60870 应用层承载在 TCP/IP 上。
理解协议时要分四层:
- 传输层:字节如何可靠到达。
- 链路层:连接、确认、序号、超时、重发。
- 应用层:ASDU 类型、传送原因、公共地址、信息体地址。
- 领域语义:遥信、遥测、遥控、总召、对时、参数和文件。
协议实现的核心不是 switch(type),而是状态机:
未连接 → 建立连接 → 链路启动 → 可传输 → 总召/周期/变化上送 → 异常、超时或关闭 → 清理并重连架构设计必须明确:
- 每个状态允许什么输入;
- 输入产生什么状态迁移和副作用;
- 超时由谁维护;
- 重复报文是否幂等;
- 断线后哪些数据需要补发;
- 时间、品质和点号如何映射。
8. SQLite 的底层设计意义
SQLite 不是简单的“存数据工具”。在 PU 中它承担了四个架构角色:
- 配置中心:通道、装置、参数和规约配置。
- 共享状态总线:线程通过实时库交换数据。
- 查询与映射引擎:视图和转发表把内部点映射为协议点。
- 历史容器:日志、SOE、冻结和录波元数据。
必须理解:
- ACID 与事务边界;
- WAL 的读写并发模型;
- busy timeout 只能缓解锁冲突,不能修复错误事务设计;
- prepared statement、参数绑定和 SQL 注入;
- 索引为何加速读取又增加写入成本;
- 视图是查询抽象,不是数据副本;
/dev/shm数据在重启后会消失;- 数据库升级是一种模式演进,而不是复制文件。
第三部分:理解现有系统的设计思路
9. 配置驱动,而不是硬编码驱动
主程序不是直接写死“启动几个 104 线程”,而是从参数数据库读取通道的规约类型,再查映射表启动对应业务线程。
这是配置驱动架构:
数据库配置 → 规约名称 → 线程启动规格 → 业务入口优点:
- 同一程序适配不同设备配置;
- 通道启停和规约选择可配置;
- 设备模型和协议实现解耦。
代价:
- 配置错误变成运行时错误;
- 字符串名称形成隐式契约;
- 数据库版本必须与程序兼容;
- 启动前必须验证配置完整性。
架构改进方向是把隐式字符串契约变成可校验的模式、枚举、版本和启动诊断报告。
10. 参数库、实时库、历史库分离
工程没有把所有数据塞进一个数据库,因为不同数据的质量属性不同:
| 数据 | 主要特点 | 设计目标 |
|---|---|---|
| 参数 | 低频写、必须持久、要升级兼容 | 正确性和可恢复 |
| 实时 | 高频更新、断电可重建、并发读取 | 低延迟和吞吐 |
| 历史 | 持续增长、需要查询和归档 | 容量控制和可追溯 |
这是按数据生命周期分区,而不只是按业务模块分表。
参数库先在临时文件中完整创建,再整体转正;实时库由参数库派生。这体现了两个重要设计原则:
- 构建成功后再发布,避免半成品状态。
- 派生数据可重建,源数据才需要长期保存。
11. 转发表:领域模型与协议模型之间的桥
现场内部点号、数据库点号和主站协议点号不一定相同。转发表承担映射:
内部装置 + 分类 + 点号 → 转发序号 → 协议信息体地址 → 类型、系数、取反、位宽、单位它本质上是 Anti-Corruption Layer:隔离内部模型与外部协议模型。
架构师必须关注映射规则的:
- 唯一性;
- 可追溯性;
- 版本兼容;
- 导入校验;
- 运行时性能;
- 变更后的缓存失效。
12. 线程故障分级与恢复
工程把线程分为关键和非关键,并定义三种退出语义:
- 正常退出;
- 请求线程级重启;
- 请求进程级重启。
非关键线程可以局部重启,但短时间内多次失败会升级为进程重启;关键线程异常通常直接请求进程重启。进程外还有守护脚本和硬件看门狗。
这形成多级恢复链:
函数返回错误 → 业务线程自行恢复 → 主进程局部重启线程 → 主进程退出 → 守护脚本重启进程 → 硬件看门狗复位系统架构上的关键不是“多重启几次”,而是避免恢复机制互相打架。每一级都要有明确触发条件、次数限制、退避策略和日志证据。
13. 脚本作为应用生命周期的一部分
Release 不只是一个二进制文件。完整运行系统包括:
- 稳定入口脚本;
- 升级脚本;
- 应用守护脚本;
- 内核模块加载;
- 应用二进制;
- 配置和数据库;
- 日志及升级包。
因此“应用架构”必须覆盖进程外生命周期。只看 main() 不看启动和升级脚本,得到的是残缺架构。
第四部分:架构师的分析方法
14. 用四种流读系统
不要按文件顺序阅读。架构师应沿四种流分析:
14.1 控制流
谁启动谁,谁调用谁,异常时控制权回到哪里。
14.2 数据流
数据从设备、报文或用户输入进入后,经过哪些结构、数据库和转换,最终输出到哪里。
14.3 时间流
哪些逻辑由周期、超时、冻结时刻、SOE 时间戳和看门狗驱动。
14.4 故障流
错误如何产生、传播、记录、隔离、重试、降级和恢复。
对每个核心用例,都画出这四种流。这样得到的是系统行为,不只是静态模块图。
15. 用质量属性驱动架构
架构不是按“模块看起来整齐”来评判,而是按质量属性:
可靠性
- 线程退出能否恢复?
- 数据库损坏能否检测?
- 升级失败能否回滚?
实时性
- 遥信变化到主站上送的最坏延迟是多少?
- 数据库锁冲突是否影响周期任务?
一致性
- 参数预置、确认和固化是否原子?
- 遥控选择与执行是否绑定同一操作者和同一值?
可维护性
- 新增规约需要改多少地方?
- 表字段修改是否会破坏旧版本?
可观测性
- 现场只有日志时能否还原故障链?
- 是否能区分驱动、通道、协议、数据库和业务失败?
安全性
- 外部输入是否校验?
- 动态 SQL、文件路径、升级包和命令执行是否可信?
- 控制命令是否鉴权、审计和防重放?
16. 用架构决策记录代替口头经验
每个重要决策写成 ADR:
标题:为什么实时库放在 /dev/shm
背景:实时数据高频写,重启后可由参数库重新生成。
决策:实时数据库放在内存文件系统,启用 WAL。
收益:减少闪存写入,提高并发读取性能。
代价:掉电后实时数据丢失,启动流程必须可靠重建。
替代方案:单一持久数据库、共享内存、自定义内存表。
验证:启动重建时间、写吞吐、锁等待、掉电恢复测试。架构师的价值很大一部分体现在“让未来的人知道为什么”。
第五部分:完整学习路线
17. 阶段 0:建立安全实验环境
目标:所有学习和试验可重复、可回滚、不触碰真实设备数据。
任务:
- 保留只读的原始工程副本。
- 在独立本地副本或临时目录中实验。
- 禁止实验程序访问真实
/userdata。 - 禁止执行可能
reboot、insmod、改密码或改网络的脚本。 - 为临时数据库、日志和模拟设备指定独立目录。
验收产物:
- 实验环境说明;
- 允许和禁止的命令清单;
- 数据清理与恢复方法。
18. 阶段 1:补齐计算机系统基础
学习内容:
- C 编译链接;
- 内存布局与指针;
- Linux 进程线程;
- 文件描述符与系统调用;
- TCP、串口和二进制协议;
- SQLite 事务与索引;
- shell 启动和进程守护。
实践:
- 写一个两线程生产者/消费者程序。
- 写一个带超时和重连的 TCP 客户端。
- 写一个 SQLite WAL 并发读写实验。
- 写一个简单二进制帧编码/解码器。
- 用脚本守护一个故意退出的小程序。
通过标准:能解释每个实验的正常路径、失败路径和资源清理路径。
19. 阶段 2:跑通构建链
学习顺序:
makefile → sources.mk → 各目录 subdir.mk → 编译参数 → 目标文件 → 链接库 → 可执行文件 → Release 打包脚本当前工程的首要阻塞是构建配置引用缺失的 src/proctl/ 和 src/lineLosses/。在确认来源前,不应通过删除构建项制造“假成功”。
验收产物:
- 构建依赖清单;
- 三种构建配置差异表;
- 从干净环境构建的步骤;
- 所有外部库及版本;
- 一份可复现的构建日志。
20. 阶段 3:读懂主进程生命周期
阅读顺序:
src/PU.c的main()。- 看门狗初始化和运行阶段切换。
sysConfigInit()。- 参数库和实时库打开。
- 通道到线程的映射。
- 线程退出与恢复。
- Release 守护脚本。
实践问题:
- 启动失败共有多少出口?
- 哪些失败会退出进程,哪些会重试?
- 看门狗在启动和运行阶段的超时为何不同?
- 正式程序和仿真程序为何要互斥?
- 主进程退出后谁负责拉起?
验收产物:启动时序图、线程清单、退出码矩阵和恢复状态机。
21. 阶段 4:独立验证数据库架构
先脱离硬件做数据库实验:
- 在临时路径创建参数库。
- 查看所有表、视图、索引和触发器。
- 插入默认装置和参数。
- 从参数库生成实时库。
- 初始化一个
SQ_DEVICE_NOTE。 - 完成参数、遥信、遥测、电量和遥控的最小读写闭环。
- 模拟事务失败和数据库损坏。
- 验证升级、备份和回滚。
验收标准:
- 能画出数据库实体关系;
- 能解释每张核心表的所有者;
- 能证明转发表如何连接内部点和协议点;
- 能用数据库快照证明写入前后变化;
- 能演示失败后没有半成品库。
22. 阶段 5:打通一条遥信链路
选择一个单点遥信,从头追踪:
硬件/模拟输入 → 遥信采集结构 → 实时遥信表 → SOE 判定 → 遥信转发表 → IEC ASDU → 发送队列 → 主站报文必须记录:
- 点号和编码;
- 时间戳生成位置;
- 品质位来源;
- 变化检测逻辑;
- SOE 与普通遥信的区别;
- 断线时数据如何处理;
- 重复上送是否可能发生。
23. 阶段 6:打通遥测和遥控
遥测重点:
- 原始值、工程值、归一化值和短浮点;
- 系数、单位、零漂、死区和越限;
- 极大值、极小值和冻结;
- 周期上送与变化上送。
遥控重点:
- 选择、执行、撤销;
- 操作者与占用;
- 值一致性;
- 超时;
- 命令落库和设备执行;
- 结果反馈与审计。
遥控是安全关键路径。必须用状态机和不变量描述,不能只看几个函数调用。
24. 阶段 7:理解 IEC101/104
学习顺序:
- 帧结构和地址长度。
- ASDU 类型和传送原因。
- 链路启动、测试和确认。
- 总召与周期数据。
- 遥信、遥测、遥控。
- 对时、参数、文件和安全扩展。
- 断链、重连、序号和超时。
实践:
- 捕获并逐字节解释一帧总召;
- 构造一帧单点遥信;
- 验证错误公共地址和未知类型处理;
- 模拟 TCP 半包、粘包、断线和重连;
- 验证控制命令重复到达时的行为。
25. 阶段 8:理解通道和硬件适配
围绕资源生命周期学习:
发现设备 → 打开 → 配置 → 收发 → 健康检查 → 错误检测 → 关闭 → 退避重连分别分析串口、4G、蓝牙、安全芯片、GPS、RTC、PPS 和看门狗。每个资源都写出所有者、共享者、锁、超时和重连策略。
26. 阶段 9:理解历史、日志和可观测性
学习内容:
- 普通日志与审计日志;
- SOE;
- 遥控记录;
- 极值、定点、冻结和潮流变化;
- COMTRADE 录波;
- 文件目录和分段读取。
架构问题:
- 日志是否包含时间、模块、通道、装置和错误原因?
- 记录是否有容量上限和淘汰策略?
- 现场故障是否能只靠日志还原?
- 日志写入失败是否会影响主业务?
27. 阶段 10:理解升级和演进
完整升级不是“覆盖二进制”:
发现升级包 → 校验 → 解压到临时目录 → 部署文件 → 执行一次性补丁 → 检测关键脚本变化 → sync → 必要时重启 → 新程序启动 → 数据库兼容检查与迁移实践必须覆盖:
- 包缺文件;
- 解压中断;
- 磁盘空间不足;
- 脚本更新;
- 程序更新但数据库迁移失败;
- 重启后程序无法启动;
- 回滚后旧程序读取新数据库。
第六部分:用测试把“看懂”变成“证明”
28. 四层验证体系
第一层:静态证据
- 模块依赖;
- 函数调用;
- 数据结构;
- SQL 模式;
- 线程和锁;
- 条件编译。
静态阅读只能证明“代码看起来会这样运行”。
第二层:组件测试
- 数据库建库和迁移;
- ASDU 编解码;
- 时间转换;
- 参数范围;
- 状态机;
- 文件格式。
第三层:集成测试
- 模拟主站与 IEC104 通信;
- 模拟串口与 IEC101 通信;
- 数据库与规约闭环;
- 通道断开与恢复;
- 升级与守护。
第四层:设备与故障注入
- 拔网线;
- 串口无响应;
- GPS 丢失;
- 数据库锁冲突;
- 磁盘写满;
- 进程崩溃;
- 线程退出;
- 突然断电;
- 升级中断。
只有四层证据相互支持,才能说真正了解系统。
29. 为每个结论设计反证实验
不要只问“怎样证明它正确”,还要问“什么实验能证明我的理解是错的”。
例子:
结论:实时库可以由参数库重建。
反证实验:删除临时实时库,执行重建,比较表、视图、索引、触发器和默认数据。如果不能完全恢复,结论不成立。
结论:非关键线程退出不会导致整个进程退出。
反证实验:让该线程返回不同退出码并超过重启窗口,观察何时升级为进程级重启。
这种可证伪思维,是从“读代码”走向“做工程”的分界线。
30. 最小可用测试矩阵
| 场景 | 正常路径 | 边界条件 | 故障路径 | 恢复验证 |
|---|---|---|---|---|
| 建库 | 全新创建 | 旧库存在 | 创建中断 | 临时库回滚 |
| 参数 | 读写成功 | 上下限 | 类型错误 | 旧值保持 |
| 遥信 | 状态变化 | 重复值 | 数据库忙 | 重试/告警 |
| 遥控 | 选择执行 | 超时 | 用户冲突 | 状态释放 |
| IEC104 | 建连总召 | 半包粘包 | 断网 | 重连恢复 |
| 升级 | 正常部署 | 同版本 | 包损坏 | 保持旧版本 |
第七部分:从零设计一个新系统
31. 第一步:写清需求和质量属性
不要先选线程或数据库。先写场景:
当 1000 个遥信点中任一点变化时,系统应在 200 ms 内完成时间标记、持久化并进入上送队列;即使主站断线,事件也不得丢失;恢复连接后按时间顺序补发;重复上送不得导致主站状态错误。这种场景同时定义刺激、环境、系统响应和度量,比“系统要高性能”有用得多。
32. 第二步:划分领域和边界
推荐划分:
- 设备接入域;
- 数据采集域;
- 实时状态域;
- 参数配置域;
- 控制命令域;
- 协议通信域;
- 历史与审计域;
- 系统管理域;
- 升级与生命周期域。
每个域定义:输入、输出、状态、所有者、不变量和失败模式。
33. 第三步:选择并发模型
候选方案:
- 单进程多线程;
- 多进程隔离;
- 事件循环;
- Actor/消息队列;
- 混合模型。
选择依据:
- 实时性;
- 故障隔离;
- 内存成本;
- 数据共享频率;
- 调试和部署复杂度;
- 第三方库线程模型。
不要因为旧工程用了线程,新系统就一定继续用线程。
34. 第四步:设计数据所有权
每份可变数据必须有唯一所有者。其他模块通过接口、消息或事务访问,而不是任意修改全局变量。
设计时明确:
- 权威数据源;
- 缓存和派生数据;
- 一致性范围;
- 事务边界;
- 数据版本;
- 迁移策略;
- 保留与淘汰策略。
35. 第五步:设计接口和状态机
接口不仅要定义参数,还要定义:
- 前置条件;
- 后置条件;
- 幂等性;
- 超时;
- 错误语义;
- 所有权;
- 并发约束;
- 版本兼容。
对连接、遥控、升级、参数预置等行为,用状态机表达,不要依靠散落的布尔变量。
36. 第六步:先设计故障,再设计成功
对每个外部依赖写 FMEA:
| 依赖 | 故障 | 检测 | 局部影响 | 恢复 | 最坏结果 |
|---|---|---|---|---|---|
| 参数库 | 损坏 | integrity_check | 无法启动 | 备份恢复 | 配置丢失 |
| 主站网络 | 断开 | socket 错误/超时 | 暂停上送 | 退避重连 | 事件积压 |
| 串口 | 无响应 | 超时 | 单通道失败 | 重开设备 | 数据中断 |
| 看门狗 | 喂狗失败 | write/ioctl 错误 | 系统可能复位 | 记录并退出 | 整机重启 |
37. 第七步:设计可观测性
至少规划:
- 结构化日志;
- 关键指标;
- 线程和通道健康状态;
- 数据库延迟和锁等待;
- 重连次数;
- 队列深度;
- 最近错误;
- 构建版本和配置版本;
- 现场诊断导出包。
38. 第八步:设计发布、升级和回滚
构建产物必须可追溯:源码版本、工具链、编译参数、依赖库、配置模式和升级脚本属于同一个版本单元。
升级设计需要回答:
- 包如何签名与校验;
- 是否原子部署;
- 数据库如何前向和后向兼容;
- 失败如何回滚;
- 谁决定重启;
- 如何确认升级成功;
- 如何避免无限重启。
第八部分:16 周学习计划
第 1~2 周:C、Linux 和构建
产物:编译链说明、内存实验、线程实验、文件描述符实验。
第 3 周:工程全景
产物:上下文图、模块图、外部依赖表、质量属性清单。
第 4 周:主进程与生命周期
产物:启动时序图、线程表、退出码矩阵、看门狗状态图。
第 5~6 周:数据库
产物:ER 图、建库顺序、转发表说明、临时库闭环测试。
第 7 周:遥信
产物:一条遥信端到端数据流和测试证据。
第 8 周:遥测与历史
产物:值转换、越限、冻结、极值和历史容量模型。
第 9 周:遥控与参数
产物:遥控状态机、参数预置/固化事务模型和异常测试。
第 10~11 周:IEC101/104
产物:链路状态机、ASDU 手工解析、总召和断链恢复测试。
第 12 周:通道与硬件
产物:设备资源生命周期图、锁与重连策略表。
第 13 周:升级与守护
产物:升级时序、失败矩阵、回滚方案和看门狗协同说明。
第 14 周:安全与可观测性
产物:威胁模型、审计清单、指标和现场诊断方案。
第 15 周:架构评审
产物:至少 5 份 ADR、技术债清单、演进优先级。
第 16 周:从零设计
设计一个简化的新配电终端系统,提交完整架构包并接受评审。
第九部分:架构师毕业标准
39. 你必须能独立交付的文档
- 系统上下文图;
- 容器/进程/线程图;
- 模块依赖图;
- 启动和关闭时序图;
- 核心数据流;
- 数据模型与数据生命周期;
- 状态机;
- 故障模型与恢复矩阵;
- 安全威胁模型;
- 测试策略;
- 部署与升级架构;
- ADR;
- 容量与性能预算;
- 技术债与演进路线。
40. 你必须能回答的问题
- 为什么选择这种进程和线程模型?
- 哪些数据是权威数据,哪些可以重建?
- 最关键的系统不变量是什么?
- 单点故障在哪里?
- 最坏延迟和容量上限如何计算?
- 升级失败如何恢复?
- 新增一个规约会影响哪些层?
- 如何证明系统在断网、断电、数据库损坏时仍符合要求?
- 哪些决策是业务约束,哪些只是历史偶然?
- 如果重做一次,哪些设计保留,哪些替换,为什么?
41. 最终毕业项目
从零设计一个可运行的简化终端:
- 至少 100 个遥信点、100 个遥测点和 20 个遥控点;
- 配置驱动;
- 参数库、实时状态和历史事件;
- TCP 模拟主站;
- 总召、变化上送和遥控状态机;
- 线程或事件循环并发模型;
- 断线重连;
- 看门狗或守护进程;
- 可升级、可回滚;
- 自动测试和故障注入;
- 完整架构文档。
评审不只看能否运行,还要看:
- 决策是否有依据;
- 边界是否清晰;
- 数据所有权是否明确;
- 故障是否被隔离;
- 测试是否能证伪;
- 系统是否可演进。
结语:从“会改代码”到“能设计系统”
真正的成长顺序是:
看见代码 → 理解模块 → 追踪行为 → 识别不变量 → 建立故障模型 → 用测试证明 → 解释设计权衡 → 从零做出新的系统不要以“读完多少文件”衡量进度。应该以“能否画清、讲清、测清、推翻自己的错误理解,并重新设计”衡量。
当你能够从业务场景出发,主动定义质量属性、系统边界、数据所有权、并发模型、状态机、故障恢复、可观测性和升级策略,并用实验和数据证明这些决策时,你就不再只是模块开发者,而是在真正做架构。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!







