很多开发者对CAP定理的认知,停留在“分布式系统中,一致性、可用性、分区容错性三者只能选其二”的表层结论上,却忽略了定理的严格定义、适用边界和本质逻辑。这种片面认知,导致了无数分布式系统设计的灾难性失误:本该用AP架构的社交场景,强行上CP导致用户体验雪崩;本该用CP架构的支付场景,盲目追求AP导致资金损失。本文将从定理的本源出发,拆解CAP的底层逻辑,纠正普遍存在的认知误区,结合实战代码,帮你真正掌握分布式系统设计的取舍之道。
一、CAP定理的本源与严格学术定义
CAP定理的诞生有明确的学术脉络:1998年,Eric Brewer在ACM PODC会议上首次提出CAP猜想;2002年,MIT的Seth Gilbert和Nancy Lynch在顶会论文中完成了严格的数学证明,将猜想升级为定理,成为分布式系统领域的基石理论。
必须明确的是,CAP定理的所有结论,都建立在异步网络模型的前提之下——这是分布式系统的真实运行环境:网络没有全局时钟,节点之间只能通过消息传递通信,消息可能丢失、延迟、乱序,节点可能宕机。脱离这个前提,谈论CAP没有任何意义。
接下来是三个核心属性的严格学术定义,这是所有认知的基础,任何口语化的曲解都会导致设计失误:
- 一致性(Consistency):这里特指线性一致性(Linearizability),也叫原子一致性、强一致性。其严格定义是:对于系统中的任意一个数据对象,任何一次读操作,都必须返回该对象最近一次写操作写入的值;所有节点对该数据的操作顺序,与全局时钟下的操作顺序完全一致。通俗解释:你在A节点写入了一条数据,之后无论你访问集群中的哪个节点,都必须能立刻读到这条最新的数据,就像整个集群只有一个副本一样。
- 可用性(Availability):系统中每一个非故障节点,对于收到的每一个用户请求,都必须在有限时间内返回合法的响应。这里有三个不可忽略的核心约束:必须是非故障节点(故障节点不纳入可用性统计范围);必须是有限时间(不能无限阻塞用户请求,超时响应等同于不可用);必须是合法响应(不能返回错误码、异常信息,必须是符合业务语义的成功/失败结果)。
- 分区容错性(Partition Tolerance):当网络发生分区时,系统仍然能继续正常工作。网络分区,指的是分布式系统的节点被网络故障分割成多个互不连通的子集,子集之间无法正常通信。比如跨机房部署的系统,两个机房之间的光纤被挖断,就形成了两个网络分区。必须明确的是:在分布式系统中,网络分区是必然事件,只是发生的概率高低、持续时间长短不同。无论是光纤中断、交换机故障、网络拥塞,还是节点宕机,都会导致网络分区的出现。
二、CAP定理的本质:不是三选二,而是分区场景下的必选取舍
这是90%开发者都踩中的核心误区:认为CAP是三个属性中任意选两个,甚至很多人会设计所谓的“CA系统”。
根据Gilbert和Lynch的严格证明,CAP定理的完整结论是:在异步网络模型中,不可能存在一个系统,同时满足线性一致性、可用性和分区容错性三个属性。但在实际的分布式系统设计中,这个结论有一个无法回避的前提:分区容错性P是必选项,没有任何商量的余地。
原因很简单:分布式系统的本质,就是多个节点通过网络协同工作,而网络永远不可能100%可靠。如果你放弃了分区容错性P,就意味着你要求系统的网络永远不会出现分区,一旦出现分区,系统就会完全崩溃。这样的系统,本质上就是一个单机系统——因为只要你做了分布式部署,就必然面临分区的风险。
所以,CAP定理的本质,从来都不是“三选二”,而是:在分区容错性P必须满足的前提下,当网络分区发生时,你只能在一致性C和可用性A之间,选择其中一个。
这里还有一个极其关键的认知点:CAP的取舍,只在网络分区发生时生效。在网络正常、没有分区的情况下,系统完全可以同时满足一致性C和可用性A。
举个最典型的例子:ZooKeeper是公认的CP系统。在网络正常的情况下,ZooKeeper的leader节点可以正常处理读写请求,follower节点可以正常同步数据,客户端可以正常访问,同时满足了一致性和可用性。只有当网络出现分区,leader节点和过半follower节点失联,触发选主流程时,ZooKeeper才会暂时拒绝客户端的写请求,牺牲可用性,保证整个集群的数据一致性。
同样,Eureka是公认的AP系统。在网络正常的情况下,Eureka的各个节点可以正常同步服务注册信息,客户端可以拿到一致的服务列表,同时满足一致性和可用性。只有当网络出现分区,Eureka节点之间无法通信时,每个节点才会继续提供服务注册和发现能力,牺牲强一致性,保证服务的可用性,等网络恢复后再同步数据,达到最终一致。
三、CAP落地的6大致命误区,90%的开发者都踩过
误区1:把CAP的C和ACID的C混为一谈
这是最常见的概念混淆。ACID是关系型数据库事务的四大属性,其中的C(Consistency)指的是事务一致性:事务执行前后,数据库的数据完整性约束(比如主键唯一、外键关联、金额总和不变、字段校验规则等)不会被破坏。
而CAP中的C(Consistency)指的是线性一致性,是分布式系统中多个副本之间的数据同步一致性。两者完全是两个维度的概念:ACID的C是事务执行前后的数据合法性,CAP的C是分布式多副本之间的数据同步性。哪怕是单机数据库,也需要满足ACID的C,但完全不涉及CAP的C;而分布式数据库,哪怕满足了CAP的C,也可能因为事务逻辑错误,破坏ACID的C。
误区2:把CAP的A和系统高可用HA划等号
很多人认为,CAP的A就是系统的高可用(High Availability),比如99.99%的可用率。这是完全错误的。
CAP的A,定义的是分区发生时,非故障节点必须对每个请求返回合法响应,是一个严格的理论属性;而HA,定义的是系统整体的服务可用时间占比,是一个工程上的统计指标。
两者的核心区别:CP系统完全可以有极高的HA,比如ZooKeeper,虽然在选主期间会暂时不可用,但选主时间通常只有几百毫秒,全年的不可用时间加起来可能只有几秒,完全可以达到99.99%的高可用率;AP系统也可能出现HA很低的情况,比如一个AP架构的系统,所有节点都部署在同一个机房,机房断电导致所有节点故障,此时系统完全不可用,HA为0,但它仍然是AP系统——因为CAP的A只要求非故障节点能响应,所有节点都故障了,就不纳入CAP的A的统计范围。
误区3:认为CP系统就是完全不可用,AP系统就是完全不一致
很多开发者对CP和AP有极端的误解:认为选了CP,系统就会经常不可用;选了AP,系统的数据就永远不一致。
这完全是对CAP定理的曲解。如前所述,CAP的取舍只在网络分区发生时生效。在99.9%以上的时间里,网络都是正常的,没有分区,此时CP和AP系统都能同时满足一致性和可用性。
CP系统的核心逻辑是:当分区发生时,宁可拒绝用户的请求,也不允许出现脏数据,保证数据的绝对一致;AP系统的核心逻辑是:当分区发生时,宁可暂时出现数据不一致,也要保证用户的请求能正常处理,等网络恢复后再同步数据,达到最终一致。两者的区别,只是分区发生时的应急策略不同,而不是平时的运行状态不同。
误区4:认为BASE理论是CAP的对立面
很多人会把CAP和BASE对立起来,认为选了CAP就不能用BASE,选了BASE就违背了CAP。
实际上,BASE理论是CAP定理的工程化延伸,是AP架构的最佳实践方案。BASE的三个核心属性:
- Basically Available(基本可用):当系统出现故障时,允许损失部分可用性(比如降级、限流、熔断),保证核心功能可用,对应CAP的A;
- Soft State(软状态):允许系统中的数据存在中间状态,不同副本之间的数据同步有延迟,对应CAP中分区发生时对C的牺牲;
- Eventually Consistent(最终一致性):系统中的所有数据副本,在经过一段时间的同步后,最终能达到一致的状态,是AP架构的最终目标。
BASE理论,本质上就是在CAP定理的框架下,为AP架构提供了一套可落地的工程实现方案,两者完全不冲突,更不是对立面。
误区5:分布式事务必须用CP架构
很多开发者认为,只要涉及分布式事务,就必须用CP架构,保证强一致性。这是典型的场景错配。
分布式事务分为两种核心类型:
- 刚性事务:比如2PC、3PC,本质上是CP架构的事务方案,要求所有分支事务要么全部成功,要么全部回滚,保证强一致性。适用于金融支付、账户转账等绝对不能出现数据不一致的场景,哪怕牺牲部分可用性,也不能出现资金损失。
- 柔性事务:比如TCC、SAGA、可靠消息最终一致性,本质上是AP架构的事务方案,不要求实时的强一致性,只要求经过一系列的操作后,数据最终能达到一致的状态。适用于订单创建、库存扣减、物流同步等可以接受短暂不一致的场景,优先保证系统的可用性。
不同的业务场景,需要选择不同的事务方案,而不是所有分布式事务都必须用CP。
误区6:无分区场景下强行做CAP取舍
很多开发者在设计系统时,不管有没有分区风险,上来就先选CP还是AP,甚至在单机系统里也谈CAP取舍,这完全是脱离了定理的适用边界。
CAP定理只适用于分布式系统,只有当系统存在网络分区的风险时,CAP的取舍才有意义。如果你的系统是单机部署,或者所有节点都部署在同一个物理机上,网络完全不会出现分区,那么你完全可以同时满足C和A,根本不需要做CAP取舍。
四、CAP架构取舍决策模型与落地场景
很多开发者不知道什么时候该选CP,什么时候该选AP。这里给出一套可直接落地的决策模型,帮你快速做出正确的选择。
CP架构的核心适用场景
CP架构的核心优势是数据强一致性,劣势是分区发生时会牺牲部分可用性。适合数据正确性优先,绝对不能出现脏数据的场景:
- 金融支付场景:比如账户余额修改、转账交易、支付扣款,必须保证数据的强一致性,哪怕系统暂时不可用,也不能出现多扣、少扣、重复扣款的情况。
- 库存管理场景:比如电商商品的库存扣减,必须保证不超卖,哪怕高峰期暂时拒绝部分请求,也不能出现库存为负的情况。
- 分布式协调场景:比如分布式锁、配置中心、服务注册中心的元数据管理,必须保证数据的强一致性,否则会导致整个分布式系统的逻辑混乱。
- 数据存储场景:比如分布式数据库的核心业务表,必须保证多副本之间的数据强一致,否则会出现数据丢失、错乱的问题。
AP架构的核心适用场景
AP架构的核心优势是高可用,劣势是分区发生时会牺牲强一致性,只能保证最终一致性。适合用户体验优先,可接受短暂数据不一致的场景:
- 社交互动场景:比如点赞、评论、转发、关注,用户更关心的是自己的操作能立刻得到响应,哪怕不同用户看到的点赞数暂时不一致,也不会影响核心体验,最终同步一致即可。
- 内容推荐场景:比如首页feed流、商品推荐、热门榜单,不同用户看到的内容哪怕有短暂的不一致,也不会影响核心业务,优先保证系统的可用性,让用户能正常刷到内容。
- 日志统计场景:比如用户行为日志、系统监控指标、PV/UV统计,数据有短暂的延迟和不一致,完全不影响业务,优先保证日志能正常写入,不丢失,最终统计一致即可。
- 非核心数据缓存场景:比如商品详情、用户头像、内容正文,这些数据更新频率低,哪怕缓存暂时不一致,也不会影响核心业务,优先保证查询的可用性。
五、CAP架构落地实战代码示例
接下来,我们通过两个完整的项目,分别实现CP架构和AP架构的核心业务场景,帮你理解CAP架构的工程化落地。
项目基础环境
项目采用主流的Java技术栈,基于Spring Boot构建,持久层采用MyBatis Plus,数据库采用MySQL,缓存采用Redis,分布式锁采用Redisson,API文档采用Swagger3。
项目依赖配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.4</version>
<relativePath/>
</parent>
<groupId>com.jam</groupId>
<artifactId>cap-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>cap-demo</name>
<description>CAP定理落地示例项目</description>
<properties>
<java.version>17</java.version>
<mybatis-plus.version>3.5.6</mybatis-plus.version>
<redisson.version>3.27.0</redisson.version>
<fastjson2.version>2.0.49</fastjson2.version>
<guava.version>33.1.0-jre</guava.version>
<springdoc.version>2.5.0</springdoc.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>${redisson.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
数据库表结构
-- 商品库存表,CP场景用
CREATE TABLE IF NOT EXISTS t_product_stock (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
product_id BIGINT NOT NULL COMMENT '商品ID',
stock_num INT NOT NULL DEFAULT 0 COMMENT '库存数量',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_product_id (product_id),
KEY idx_update_time (update_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商品库存表';
-- 点赞统计表,AP场景用
CREATE TABLE IF NOT EXISTS t_content_like (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
content_id BIGINT NOT NULL COMMENT '内容ID',
like_count BIGINT NOT NULL DEFAULT 0 COMMENT '点赞数量',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_content_id (content_id),
KEY idx_update_time (update_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='内容点赞表';
应用配置文件
server:
port: 8080
spring:
application:
name: cap-demo
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/cap_demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: root
data:
redis:
host: 127.0.0.1
port: 6379
database: 0
timeout: 3000ms
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
springdoc:
swagger-ui:
path: /swagger-ui.html
enabled: true
api-docs:
enabled: true
path: /v3/api-docs
CP架构实战:分布式强一致性库存扣减
电商商品库存扣减是典型的CP架构场景,必须保证数据的强一致性,绝对不能出现超卖的情况。哪怕网络出现分区,系统宁可拒绝部分请求,也不能允许库存为负。
系统架构设计
核心代码实现
实体类
package com.jam.demo.entity;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("t_product_stock")
@Schema(description = "商品库存实体")
public class ProductStock {
@Schema(description = "主键ID", example = "1")
@TableId(type = IdType.AUTO)
private Long id;
@Schema(description = "商品ID", example = "1001")
@TableField("product_id")
private Long productId;
@Schema(description = "库存数量", example = "100")
@TableField("stock_num")
private Integer stockNum;
@Schema(description = "乐观锁版本号", example = "0")
@Version
@TableField("version")
private Integer version;
@Schema(description = "创建时间")
@TableField(value = "create_time", fill = FieldFill.INSERT)
private LocalDateTime createTime;
@Schema(description = "更新时间")
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
Mapper接口
package com.jam.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.ProductStock;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
@Mapper
public interface ProductStockMapper extends BaseMapper<ProductStock> {
@Update("UPDATE t_product_stock SET stock_num = stock_num - #{num}, version = version + 1 " +
"WHERE product_id = #{productId} AND stock_num >= #{num}")
int deductStock(@Param("productId") Long productId, @Param("num") Integer num);
}
服务接口
package com.jam.demo.service;
import com.jam.demo.entity.ProductStock;
public interface ProductStockService {
boolean deductStock(Long productId, Integer deductNum);
ProductStock getStockByProductId(Long productId);
}
服务实现类
package com.jam.demo.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jam.demo.entity.ProductStock;
import com.jam.demo.mapper.ProductStockMapper;
import com.jam.demo.service.ProductStockService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.util.ObjectUtils;
import java.util.concurrent.TimeUnit;
@Slf4j
@Service
public class ProductStockServiceImpl implements ProductStockService {
private final ProductStockMapper productStockMapper;
private final RedissonClient redissonClient;
private final PlatformTransactionManager transactionManager;
private static final String LOCK_KEY_PREFIX = "stock:deduct:";
private static final long LOCK_WAIT_TIME = 3L;
private static final long LOCK_LEASE_TIME = 30L;
public ProductStockServiceImpl(ProductStockMapper productStockMapper,
RedissonClient redissonClient,
PlatformTransactionManager transactionManager) {
this.productStockMapper = productStockMapper;
this.redissonClient = redissonClient;
this.transactionManager = transactionManager;
}
@Override
public boolean deductStock(Long productId, Integer deductNum) {
if (ObjectUtils.isEmpty(productId) || ObjectUtils.isEmpty(deductNum) || deductNum <= 0) {
log.warn("库存扣减参数非法,productId:{}, deductNum:{}", productId, deductNum);
return false;
}
String lockKey = LOCK_KEY_PREFIX + productId;
RLock rLock = redissonClient.getFairLock(lockKey);
TransactionStatus transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
boolean lockAcquired = rLock.tryLock(LOCK_WAIT_TIME, LOCK_LEASE_TIME, TimeUnit.SECONDS);
if (!lockAcquired) {
log.error("获取分布式锁失败,商品ID:{}", productId);
transactionManager.rollback(transactionStatus);
return false;
}
ProductStock stock = getStockByProductId(productId);
if (ObjectUtils.isEmpty(stock)) {
log.warn("商品库存不存在,商品ID:{}", productId);
transactionManager.rollback(transactionStatus);
return false;
}
if (stock.getStockNum() < deductNum) {
log.warn("商品库存不足,商品ID:{}, 当前库存:{}, 扣减数量:{}", productId, stock.getStockNum(), deductNum);
transactionManager.rollback(transactionStatus);
return false;
}
int affectedRows = productStockMapper.deductStock(productId, deductNum);
if (affectedRows <= 0) {
log.error("库存扣减SQL执行失败,商品ID:{}", productId);
transactionManager.rollback(transactionStatus);
return false;
}
transactionManager.commit(transactionStatus);
log.info("库存扣减成功,商品ID:{}, 扣减数量:{}", productId, deductNum);
return true;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("获取分布式锁被中断,商品ID:{}", productId, e);
transactionManager.rollback(transactionStatus);
return false;
} catch (Exception e) {
log.error("库存扣减系统异常,商品ID:{}", productId, e);
transactionManager.rollback(transactionStatus);
return false;
} finally {
if (rLock.isHeldByCurrentThread()) {
rLock.unlock();
}
}
}
@Override
public ProductStock getStockByProductId(Long productId) {
LambdaQueryWrapper<ProductStock> queryWrapper = new LambdaQueryWrapper<ProductStock>()
.eq(ProductStock::getProductId, productId);
return productStockMapper.selectOne(queryWrapper);
}
}
接口层
package com.jam.demo.controller;
import com.jam.demo.entity.ProductStock;
import com.jam.demo.service.ProductStockService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@Slf4j
@RestController
@RequestMapping("/api/stock")
@Tag(name = "商品库存接口", description = "CP架构分布式库存示例接口")
public class StockController {
private final ProductStockService productStockService;
public StockController(ProductStockService productStockService) {
this.productStockService = productStockService;
}
@PostMapping("/deduct")
@Operation(summary = "扣减商品库存", description = "CP架构强一致性库存扣减,保证不超卖")
public ResponseEntity<Boolean> deductStock(
@Parameter(description = "商品ID", required = true) @RequestParam Long productId,
@Parameter(description = "扣减数量", required = true) @RequestParam Integer deductNum) {
boolean result = productStockService.deductStock(productId, deductNum);
return ResponseEntity.ok(result);
}
@GetMapping("/query")
@Operation(summary = "查询商品库存", description = "查询商品实时库存数量")
public ResponseEntity<ProductStock> queryStock(
@Parameter(description = "商品ID", required = true) @RequestParam Long productId) {
ProductStock stock = productStockService.getStockByProductId(productId);
return ResponseEntity.ok(stock);
}
}
基础配置类
package com.jam.demo.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.LocalDateTime;
@Configuration
public class MybatisPlusConfig implements MetaObjectHandler {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime::now, LocalDateTime.class);
this.strictInsertFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
}
}
CP架构实现核心说明
- 分布式锁:采用Redisson的公平锁,基于Raft协议的Redis集群实现,保证锁的强一致性,只有获取到锁的请求才能执行库存扣减,避免并发超卖。
- 编程式事务:采用Spring的编程式事务,精准控制事务的提交和回滚,避免声明式事务的失效问题,保证库存扣减的原子性。
- 悲观锁扣减:SQL层面采用悲观锁的扣减逻辑,只有库存充足时才会执行扣减,双重保证不超卖。
- 分区场景处理:当网络出现分区,Redis集群无法获取锁,或者MySQL集群无法执行SQL时,系统会直接回滚事务,返回失败,绝对不会出现脏数据,保证了CP架构的核心特性。
AP架构实战:分布式高可用点赞系统
内容点赞是典型的AP架构场景,用户更关心的是自己的点赞操作能立刻得到响应,哪怕不同用户看到的点赞数暂时不一致,也不会影响核心体验。优先保证系统的可用性,保证数据的最终一致性。
核心代码实现
实体类
package com.jam.demo.entity;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("t_content_like")
@Schema(description = "内容点赞实体")
public class ContentLike {
@Schema(description = "主键ID", example = "1")
@TableId(type = IdType.AUTO)
private Long id;
@Schema(description = "内容ID", example = "2001")
@TableField("content_id")
private Long contentId;
@Schema(description = "点赞数量", example = "1000")
@TableField("like_count")
private Long likeCount;
@Schema(description = "创建时间")
@TableField(value = "create_time", fill = FieldFill.INSERT)
private LocalDateTime createTime;
@Schema(description = "更新时间")
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
Mapper接口
package com.jam.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.ContentLike;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
@Mapper
public interface ContentLikeMapper extends BaseMapper<ContentLike> {
@Update("UPDATE t_content_like SET like_count = like_count + #{num} WHERE content_id = #{contentId}")
int addLikeCount(@Param("contentId") Long contentId, @Param("num") Integer num);
}
服务接口
package com.jam.demo.service;
import com.jam.demo.entity.ContentLike;
public interface ContentLikeService {
boolean like(Long contentId);
ContentLike getLikeByContentId(Long contentId);
void syncLikeCount(Long contentId);
}
服务实现类
package com.jam.demo.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.jam.demo.entity.ContentLike;
import com.jam.demo.mapper.ContentLikeMapper;
import com.jam.demo.service.ContentLikeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import java.util.concurrent.TimeUnit;
@Slf4j
@Service
public class ContentLikeServiceImpl implements ContentLikeService {
private final ContentLikeMapper contentLikeMapper;
private final StringRedisTemplate stringRedisTemplate;
private static final String LIKE_KEY_PREFIX = "content:like:";
private static final long CACHE_EXPIRE_TIME = 24L;
public ContentLikeServiceImpl(ContentLikeMapper contentLikeMapper, StringRedisTemplate stringRedisTemplate) {
this.contentLikeMapper = contentLikeMapper;
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public boolean like(Long contentId) {
if (ObjectUtils.isEmpty(contentId)) {
log.warn("点赞参数非法,contentId:{}", contentId);
return false;
}
String cacheKey = LIKE_KEY_PREFIX + contentId;
try {
Long incremented = stringRedisTemplate.opsForValue().increment(cacheKey);
if (ObjectUtils.isEmpty(incremented)) {
log.error("Redis点赞计数失败,contentId:{}", contentId);
return false;
}
if (incremented == 1) {
stringRedisTemplate.expire(cacheKey, CACHE_EXPIRE_TIME, TimeUnit.HOURS);
}
syncLikeCount(contentId);
log.info("点赞操作成功,contentId:{}, 当前点赞数:{}", contentId, incremented);
return true;
} catch (Exception e) {
log.error("点赞系统异常,contentId:{}", contentId, e);
return false;
}
}
@Override
public ContentLike getLikeByContentId(Long contentId) {
if (ObjectUtils.isEmpty(contentId)) {
return null;
}
String cacheKey = LIKE_KEY_PREFIX + contentId;
ContentLike contentLike = new ContentLike();
contentLike.setContentId(contentId);
try {
String cacheCount = stringRedisTemplate.opsForValue().get(cacheKey);
if (ObjectUtils.isEmpty(cacheCount)) {
LambdaQueryWrapper<ContentLike> queryWrapper = new LambdaQueryWrapper<ContentLike>()
.eq(ContentLike::getContentId, contentId);
ContentLike dbLike = contentLikeMapper.selectOne(queryWrapper);
if (!ObjectUtils.isEmpty(dbLike)) {
stringRedisTemplate.opsForValue().set(cacheKey, String.valueOf(dbLike.getLikeCount()), CACHE_EXPIRE_TIME, TimeUnit.HOURS);
return dbLike;
}
contentLike.setLikeCount(0L);
return contentLike;
}
contentLike.setLikeCount(Long.parseLong(cacheCount));
return contentLike;
} catch (Exception e) {
log.error("查询点赞数异常,contentId:{}", contentId, e);
LambdaQueryWrapper<ContentLike> queryWrapper = new LambdaQueryWrapper<ContentLike>()
.eq(ContentLike::getContentId, contentId);
ContentLike dbLike = contentLikeMapper.selectOne(queryWrapper);
return ObjectUtils.isEmpty(dbLike) ? contentLike : dbLike;
}
}
@Async
@Override
public void syncLikeCount(Long contentId) {
if (ObjectUtils.isEmpty(contentId)) {
return;
}
String cacheKey = LIKE_KEY_PREFIX + contentId;
try {
String cacheCount = stringRedisTemplate.opsForValue().get(cacheKey);
if (ObjectUtils.isEmpty(cacheCount)) {
return;
}
long likeCount = Long.parseLong(cacheCount);
LambdaQueryWrapper<ContentLike> queryWrapper = new LambdaQueryWrapper<ContentLike>()
.eq(ContentLike::getContentId, contentId);
ContentLike dbLike = contentLikeMapper.selectOne(queryWrapper);
if (ObjectUtils.isEmpty(dbLike)) {
ContentLike newLike = new ContentLike();
newLike.setContentId(contentId);
newLike.setLikeCount(likeCount);
contentLikeMapper.insert(newLike);
return;
}
LambdaUpdateWrapper<ContentLike> updateWrapper = new LambdaUpdateWrapper<ContentLike>()
.eq(ContentLike::getContentId, contentId)
.set(ContentLike::getLikeCount, likeCount);
contentLikeMapper.update(null, updateWrapper);
} catch (Exception e) {
log.error("同步点赞数到数据库异常,contentId:{}", contentId, e);
}
}
}
接口层
package com.jam.demo.controller;
import com.jam.demo.entity.ContentLike;
import com.jam.demo.service.ContentLikeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@Slf4j
@RestController
@RequestMapping("/api/like")
@Tag(name = "内容点赞接口", description = "AP架构高可用点赞示例接口")
public class LikeController {
private final ContentLikeService contentLikeService;
public LikeController(ContentLikeService contentLikeService) {
this.contentLikeService = contentLikeService;
}
@PostMapping("/add")
@Operation(summary = "内容点赞", description = "AP架构高可用点赞操作,保证最终一致性")
public ResponseEntity<Boolean> addLike(
@Parameter(description = "内容ID", required = true) @RequestParam Long contentId) {
boolean result = contentLikeService.like(contentId);
return ResponseEntity.ok(result);
}
@GetMapping("/query")
@Operation(summary = "查询点赞数", description = "查询内容当前点赞数量")
public ResponseEntity<ContentLike> queryLikeCount(
@Parameter(description = "内容ID", required = true) @RequestParam Long contentId) {
ContentLike contentLike = contentLikeService.getLikeByContentId(contentId);
return ResponseEntity.ok(contentLike);
}
}
异步线程池配置
package com.jam.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@EnableAsync
public class AsyncConfig {
private static final int CORE_POOL_SIZE = 10;
private static final int MAX_POOL_SIZE = 50;
private static final int QUEUE_CAPACITY = 100;
private static final int KEEP_ALIVE_SECONDS = 60;
private static final String THREAD_NAME_PREFIX = "like-sync-";
@Bean("likeSyncExecutor")
public Executor likeSyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(CORE_POOL_SIZE);
executor.setMaxPoolSize(MAX_POOL_SIZE);
executor.setQueueCapacity(QUEUE_CAPACITY);
executor.setKeepAliveSeconds(KEEP_ALIVE_SECONDS);
executor.setThreadNamePrefix(THREAD_NAME_PREFIX);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(30);
executor.initialize();
return executor;
}
}
AP架构实现核心说明
- 读写优先走缓存:点赞操作和查询操作优先走Redis缓存,保证极致的响应速度和可用性,哪怕数据库出现故障,用户的点赞操作也能正常执行。
- 异步同步数据:点赞数的持久化采用异步线程池执行,不阻塞用户的主请求,保证用户操作的流畅性。
- 降级兜底策略:当Redis出现故障时,查询操作会直接降级到数据库,保证用户能正常看到点赞数,不会出现请求失败的情况。
- 分区场景处理:当网络出现分区,数据库和应用节点之间无法通信时,用户的点赞操作仍然能正常写入Redis,等网络恢复后,异步线程会把缓存中的点赞数同步到数据库,保证数据的最终一致性,实现了AP架构的核心特性。
六、主流分布式组件的CAP架构选型
很多开发者在选择分布式组件时,不知道该选CP还是AP的组件。这里整理了主流分布式组件的CAP架构属性,帮你做出正确的选型。
| 组件名称 | CAP架构 | 核心特性 | 适用场景 |
| ZooKeeper | CP | 基于ZAB协议,保证强一致性,选主期间不可用 | 分布式锁、配置中心、元数据管理 |
| Nacos | 支持CP/AP切换 | 注册中心默认AP,配置中心默认CP | 微服务注册中心、配置中心 |
| Eureka | AP | 去中心化设计,节点平等,分区时仍可提供服务 | 微服务注册中心(已停止更新) |
| Redis Cluster | AP | 主从异步复制,分区时可能丢失数据,保证可用性 | 分布式缓存、非核心数据存储 |
| Redlock | CP | 基于多Redis节点的分布式锁,过半写入成功才算成功 | 分布式强一致性锁 |
| MySQL MGR | CP | 基于Paxos协议,过半写入成功才算成功,保证强一致性 | 分布式数据库集群 |
| Kafka | AP | 主从异步复制,分区时优先保证可用性,可能丢失数据 | 消息队列、日志采集、数据流处理 |
| RocketMQ | 支持CP/AP切换 | 同步刷盘+同步复制为CP,异步刷盘+异步复制为AP | 金融级消息队列、业务消息处理 |
七、分布式系统CAP设计的核心落地原则
- 业务优先原则:CAP的取舍,永远要以业务需求为核心,而不是技术炫技。金融支付场景,哪怕牺牲再多的可用性,也要保证数据的强一致性;社交互动场景,哪怕有短暂的数据不一致,也要保证用户的操作体验。
- 分区常态原则:永远要假设网络分区一定会发生,不要设计所谓的“CA系统”。在系统设计之初,就要考虑分区发生时的处理策略,是选择牺牲可用性保证一致性,还是牺牲一致性保证可用性。
- 粒度控制原则:CAP的取舍,不是整个系统只能选CP或AP,而是可以到业务模块、甚至接口粒度。同一个系统中,支付模块可以用CP架构,点赞模块可以用AP架构,不同的业务模块,选择不同的架构策略。
- 最终一致原则:AP架构的核心,不是放弃一致性,而是放弃实时的强一致性,保证最终一致性。在AP架构的设计中,必须要有完善的数据同步、校验、修复机制,保证数据最终能达到一致的状态。
- 故障隔离原则:无论是CP还是AP架构,都必须做好故障隔离。CP架构中,要避免单个节点的故障导致整个集群不可用;AP架构中,要避免单个分区的数据不一致,扩散到整个系统。
结尾
CAP定理不是一个死板的“三选二”公式,而是分布式系统设计的一套思维框架。它的核心价值,不是让你记住三个字母的含义,而是让你理解分布式系统的本质矛盾:在不可靠的网络环境中,如何在数据一致性和服务可用性之间,做出最符合业务需求的取舍。
很多分布式系统的设计失误,本质上都是脱离了业务需求,盲目追求技术上的“完美”,要么强行在AP场景上CP,导致用户体验雪崩;要么在CP场景上AP,导致数据错乱。
真正优秀者,从来都不是只会死记硬背定理的理论派,而是能深刻理解业务需求,结合CAP定理的本质,在不同的场景下做出最合理的取舍,设计出真正符合业务需求的分布式系统。