架构设计原则:高内聚低耦合、可扩展性、容错性、幂等性、数据一致性保证
聊到数据流水线的架构设计,我脑子里第一个蹦出来的词就是「原则」。没有原则的架构,就像没有地基的房子,早晚得出事。今天咱们把这五个核心原则掰开揉碎了讲清楚。
1. 高内聚低耦合:模块的「分」与「合」
说白了,高内聚就是一个模块只干一件事,而且干得漂亮。低耦合就是模块之间别扯太多关系,改一个不影响另一个。
我在项目中见过最典型的反面教材:一个数据清洗模块里,既做格式校验,又写业务逻辑,还顺带发了告警邮件。结果每次改业务规则,清洗模块就得重新上线,测试成本高得吓人。
- 抽取模块:只负责从源系统拉数据,不做任何转换
- 清洗模块:只做格式标准化、去重、空值处理
- 转换模块:只做业务逻辑映射、聚合计算
- 加载模块:只负责写入目标存储
模块间的通信,我习惯用「契约式接口」。每个模块暴露的 API 或消息格式,就像签了合同一样,谁都不能随便改。你想想看,如果清洗模块改了输出字段名,下游转换模块就得跟着改,这不就耦合上了吗?
2. 可扩展性:别让架构卡住业务增长
数据量翻倍、业务线增加、实时性要求变高……这些场景我几乎每个项目都会遇到。可扩展性不是「现在够用就行」,而是「未来三年别重写」。
我一般从三个维度考虑扩展性:
- 水平扩展:加机器就能提升吞吐,而不是改代码。Kafka 分区、Spark Executor、Flink Task Slot 都是这个思路。
- 垂直扩展:单机升级硬件,但天花板明显,不推荐作为主要手段。
- 功能扩展:新增数据源或处理逻辑时,不需要改核心框架。用插件化或 SPI 机制,我曾在 Flink 上做过自定义 Source/Sink 的插件注册,效果不错。
3. 容错性:系统要能「扛得住」
数据流水线跑在生产环境,网络抖动、节点宕机、数据异常都是家常便饭。容错性不是「不出错」,而是「出错了能自动恢复」。
我常用的容错手段:
| 容错场景 | 常用策略 | 我踩过的坑 |
|---|---|---|
| 节点宕机 | 主备切换、Checkpoint + 重启 | Checkpoint 间隔设太长,恢复时数据回放过量 |
| 数据异常 | 死信队列、重试机制 | 重试没设退避策略,直接把下游打挂了 |
| 网络抖动 | 超时重试、断路器 | 超时时间设太短,正常慢查询也被熔断了 |
| 依赖服务不可用 | 降级、缓存、限流 | 降级后没告警,数据丢了三天才发现 |
嗯,这里要注意:容错不是「所有错误都自动恢复」。有些错误需要人工介入,比如数据格式彻底变了。自动恢复只适用于可预期的、临时性的故障。
4. 幂等性:重复执行也不怕
幂等性,说白了就是「同一个操作执行一次和执行多次,结果一样」。数据流水线里,重试是常态,如果操作不幂等,重复数据、重复扣款、重复计算……哪个都受不了。
我总结了几种实现幂等的方式:
- 唯一键约束:数据库表加唯一索引,重复插入直接报错或忽略。我常用在目标表上。
- 去重表:记录每次操作的唯一 ID(比如 Kafka 消息的 offset + partition),处理前先查一下。
- 状态机:每个数据记录有状态字段,只有「待处理」才能变成「已处理」,已处理的直接跳过。
- 条件更新:用版本号或时间戳,只更新比当前版本新的数据。
5. 数据一致性保证:最终一致还是强一致?
这是最头疼的问题。分布式系统里,强一致往往意味着低性能。我一般根据业务场景选策略:
- 强一致:金融交易、库存扣减。用分布式事务(XA、Seata)或两阶段提交。代价是吞吐下降,延迟增加。
- 最终一致:日志分析、推荐系统。用消息队列 + 补偿机制。允许短暂不一致,但最终要对得上。
- 因果一致:社交动态、评论系统。保证有因果关系的操作顺序正确,其他可以乱序。
我个人的习惯是:能用最终一致就别上强一致。因为强一致带来的复杂度,往往比数据不一致本身更可怕。你想想看,两阶段提交里协调者挂了怎么办?锁超时了怎么处理?这些坑我踩过不止一次。
知识体系总览
下面这张图,把这五个原则的关系和核心要点串起来了。我建议你保存下来,做架构设计时对照着看。
这五个原则,说起来简单,做起来难。我每次做架构评审,都会拿着这张图一个一个过。哪个原则没想清楚,就停下来讨论,直到大家都认可为止。别急着写代码,原则定好了,后面的事就顺了。