CAP
对于任何分布式系统而言,我们只能保证下面三种属性中的两个:
- 一致性
- 可用性
- 分区容错性
一致性
每一个读请求都能获取最新的数据。
可用性
任何一个请求都有一个相应,哪怕不是最新的数据。
分区容错性
即使存在网络故障,系统仍然能够正常运行。
举例
分布式数据库中由于网络故障(分区),导致一些服务器无法和其他服务器通信。
如果强调一致性,那么一些用户会收到报错,因为不是所有的服务器都有最新的数据。
如果强调可用性,所有的请求都会有响应,那一部分用户获取到的数据不是最新数据。
PACELC 理论
CAP 理论太过狭隘,PACELC 在 CAP 理论上额外引入了延迟概念。
如果网络分区发生 P,那么你必须在 A 和 C 中选择,如果没有发生 E,你必须在延迟 L 和一致性 C 间选择。
BASE 理论
在 NoSql 数据库中,强一致性通常情况下不是可行的,特别是在高性能应用场景下。
BASE = Basically Available + Soft State + Eventual Consistency。
Basically Avaiable:系统在大部分时间内可用,即使系统的一些部分崩溃。
Soft State:数据可以在一段时间内暂时不一致,不需要通过严格的同步机制保证所有节点强制一致,最终会通过系统自身的逻辑自动收敛到一致状态。
Eventual Consistency:不确保所有的事务都立即被所有的用户看到。但是最终,所有的读都会读到最新的值。
SOLID
Single Responsibility
一个类只有一个修改的原因。
例如下面将员工数据管理和员工通知分离开来。
为什么之前的逻辑违反了单一职责原则,因为修改 Employee 类的原因可能有两个:
- 修改计算薪水的逻辑 (数据管理职责)。
- 修改发送 Email 的逻辑 (消息通知职责)。
拆分后,修改这两个类只有一个原因了。
关于单一职责原则,有个简单的判断方法:
如果删除/修改其中一个方法,是否影响其他方法的核心目的?
如果不影响,说明大概率是多个职责。
一个 Calculator 类,包含 add(加)、subtract(减)、multiply(乘)、divide(除)4 个方法:
- 所有方法的核心目的都是 “数学计算”,修改任何一个方法的原因(如优化除法的精度、修复乘法的溢出问题)都属于 “计算逻辑的调整”,因此符合单一职责;
- 但如果 Calculator 类再加一个 printResult(打印计算结果到文件)的方法: printResult 的核心目的是 “文件 IO”,修改它的原因(如改变文件格式、保存路径)与 “数学计算” 无关,此时类就违反了单一职责。
Open Close Principle
类应该对扩展开放,对修改关闭。
- 对扩展开放:新增功能时,只需新增实现类(实现接口/继承抽象类),而无需修改抽象层或者已有实现。
- 对修改关闭:抽象层和已有实现类一旦确定,就无需修改(除非抽象层设计有误),避免引入风险。
需要指出,对修改关闭指的是修改功能的核心逻辑,不是完全不能碰旧代码。
- 如果旧代码存在 bug,此时修改 bug 属于修复错误,而不是变更功能,不违反开闭规则。
- 如果抽象层设计不合理,例如确实了某个参数,那说明这个是设计缺陷,应该尽量避免。
最佳实践:将核心逻辑抽象成接口,后续所有的定制逻辑都实现这个接口,所有对具体实现的依赖都转换为对接口的依赖。
Liskov Substitution Principle
父类的对象可以被替换为子类的对象而不影响程序正确性。
子类在继承父类时,不能破坏父类原有的行为约定(如方法的输入输出、异常抛出、业务逻辑语义等),必须严格遵循父类定义的 “契约”。
LSP 的本质是约束继承关系的合理性,确保子类是对父类的 “扩展” 而非 “篡改”,从而保证系统的稳定性 —— 无论使用父类还是子类,程序的核心逻辑都能正确运行,避免因子类替换导致的隐性 bug。
经典反例:“正方形不是长方形” 假设父类 Rectangle 有 setWidth(int w) 和 setHeight(int h) 方法,核心语义是 “宽和高可独立修改”;子类 Square 继承 Rectangle,但正方形的 “宽 = 高”,因此重写 setWidth 和 setHeight 为 “修改宽时同步修改高,修改高时同步修改宽”。
LSP 是 OCP 的 “基础保障”—— 只有当子类可以无缝替换父类时,才能通过 “新增子类” 扩展系统功能(符合 OCP 的 “对扩展开放”),而无需修改依赖父类的高层代码(符合 OCP 的 “对修改关闭”)。
支付系统的例子:
| |
无论是 WechatPayment 还是 AlipayPayment,都严格遵循父类 Payment 的契约(输入输出、异常、语义),替换父类后程序逻辑完全正确,符合 LSP。
Interface Segregation Principle
客户端不应该依赖他们不需要的接口。
通俗来说,ISP 主张将 “庞大臃肿的接口” 拆分为 “多个小型、专一的接口”,每个接口只包含特定客户端(类或模块)所需的方法,避免客户端被迫实现它用不到的方法,从而降低接口与客户端之间的耦合度,提升系统的灵活性和可维护性。
Dependency Inversion Principle
- 高层模块不能依赖低层模块,两者都应该依赖抽象。
- 抽象不能依赖具体实现,具体实现应该依赖抽象。
- 低层模块:负责具体的 “实现细节”,比如数据库操作(MySQL/MongoDB 读写)、文件 IO、第三方接口调用(短信发送、支付对接)等;
- 高层模块:负责 “业务逻辑整合”,依赖低层模块的功能完成复杂业务,比如电商的 “订单创建流程”(依赖 “订单存储”“金额计算”“短信通知” 等低层功能)、政务系统的 “用户审批流程”(依赖 “用户信息查询”“审批记录存储” 等低层功能)。
传统 “高层依赖低层” 的设计会导致两大核心问题,而 DIP 正是为了规避这些问题:
- 低层模块变更时,高层模块必须跟着改:比如 “订单存储” 从 MySQL 迁移到 MongoDB,如果高层模块直接依赖 MySQLOrderRepository(具体实现),则所有调用该类的高层代码都要修改(替换为 MongoOrderRepository),牵一发而动全身;
- 难以替换低层实现,扩展性差:比如 “短信通知” 需要从 “阿里云短信” 切换到 “腾讯云短信”,如果高层代码直接依赖 AliSmsSender(具体实现),则必须修改所有调用短信的逻辑,无法灵活切换。
核心步骤:先定义抽象接口,再让低层实现抽象,最后让高层依赖抽象(而非具体实现)。

















