likes
comments
collection
share

模拟验证 分布式CAP定理

作者站长头像
站长
· 阅读数 4

一、回顾分布式CAP定理

CAP定理又称CAP原则,指的是在一个分布式系统中,Consistency(一致性)Availability(可用性)Partition tolerance(分区容错性),最多只能同时三个特性中的两个,三者不可兼得。

  • 一致性:意味着所有节点同时看到相同的数据。简单来说,如果在一致的系统上执行读操作,它应该返回最近的写操作的值。即读取应该所有节点返回相同的数据,即最近写入的值。
  • 可用性:分布式系统中的可用性确保系统100%的时间保持运行。无论节点的单独状态如何,每个请求都会获得(非错误)响应。但请注意,这并不能保证响应包含最新的写入。
  • 分区容错性:此条件表明,无论消息在系统中的节点之间是否丢失或延迟,系统都不会失败。在分布式系统中,分区容错已经成为一种必需品,而不是一种选择。这是通过跨节点和网络的组合充分复制记录来实现的。

下面试着写一个分布式demo来验证CAP定理,项目结构如下 模拟验证 分布式CAP定理 maven中两个模块distributed-A和distributed-B模拟分布式的两个节点,A节点和B节点;其中A节点8081端口启动,B节点8082端口;

向A节点写入数据,写入后向B节点同步该数据,同时访问B节点的数据,来验证CAP定理。

二、AP组合

分布式系统满足AP,即放弃一致性,也就是在数据同步的过程中对其中节点进行数据访问,返回的可能不是最新的数据。按照AP定理的说法,示例中只要A接收到写入的数据后,直接通过网络向B节点同步即可,因为网络有延迟和不可靠性,向B节点同步数据需要耗时,而这期间B节点返回一个非错误的响应。

// A节点的PushController
@RestController
@Slf4j
public class APushController {
    
    // 模拟数据存储
    private List<String> dataList = new ArrayList<>();
    
    // 模拟A节点接收到数据写入
    @GetMapping("/push")
    public String push(@RequestParam("data") String data) {
        // 本地数据存储
        dataList.add(data);
        // 同时同步数据至B节点
        // 这里使用SpringBoot3官方推荐的RestClient进行http调用
        RestClient restClient = RestClient.create();
        String response = restClient.get()
                .uri("http://localhost:8082/sendData?data=" + data)
                .retrieve().body(String.class);
        log.info("同步数据返回响应:{}", response);
        return "success";
    }
}
// B节点的PushController
@RestController
@Slf4j
public class PushController {

    // 模拟数据存储
    private List<String> dataList = new ArrayList<>();
    
    // 用于接收来自A节点的同步数据
    @GetMapping("/sendData")
    @SneakyThrows
    public String sendData(@RequestParam("data") String data) {
        log.info("接收到数据:{}", data);
        // 睡眠10s 模拟网络延迟
        Thread.sleep(10000);
        dataList.add(data);
        return "success";
    }
    
    // 访问数据
    @GetMapping("/getData")
    public List<String> getData() {
        return dataList;
    }
}

访问A节点localhost:8081/push?data=1数据写入1,写入成功后A节点调用B节点的/sendData接口进行数据同步,B节点接收到数据后Thread.sleep(10000);模拟网络延迟。

同步期间调用B节点localhost:8082/getData获取数据。 模拟验证 分布式CAP定理

10s后再次访问localhost:8082/getData获取数据 模拟验证 分布式CAP定理 此时已验证了CAP定理中的AP,虽然A节点在往B节点同步数据,但B节点依旧能正常向外提供服务,都会获得(非错误)响应,虽然不是最新的数据。

三、CP组合

分布式系统满足AP,即要求强一致性;即B节点应该返回的是最新写入的值1,而不是空列表。

对B节点的PushController进行稍加改造。

// B节点的PushController
@RestController
@Slf4j
public class PushController {

    // 模拟数据存储
    private List<String> dataList = new ArrayList<>();

    private Object object = new Object();

    // 用于接收来自A节点的同步数据
    @GetMapping("/sendData")
    @SneakyThrows
    public String sendData(@RequestParam("data") String data) {
        log.info("接收到数据:{}", data);
        synchronized (object){
            Thread.sleep(10000);
            dataList.add(data);
        }
        return "success";
    }

    @GetMapping("/getData")
    public List<String> getData() {
        synchronized (object){
            return dataList;
        }
    }
}

此时B节点接收到A节点的同步数据,使用synchronized进行上锁,同时getData方法也进行上锁,并且锁的是同一个对象,这样两个方法就达到了互斥,保证了在调用getData获取数据时是最新的数据;在数据同步时getData请求会阻塞,导致B节点无法向外提供服务(或返回错误响应),满足了CP,牺牲了可用性。

同步期间向B节点访问数据的请求被阻塞 无法及时响应 模拟验证 分布式CAP定理

同步完成之后响应最新的数据 模拟验证 分布式CAP定理

四、总结

CAP定理 分布式系统中,一致性、可用性和分区容忍性无法同时满足,根据实际业务场景需求权衡选择,确定最合适的方案。

以上为个人对CAP定理的理解,若有错误,还望批评指正