模拟验证 分布式CAP定理

一、回顾分布式CAP定理

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

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

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

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

二、AP组合

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

java 复制代码
// 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";
    }
}
java 复制代码
// 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获取数据。

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

三、CP组合

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

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

java 复制代码
// 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定理的理解,若有错误,还望批评指正

相关推荐
zy happy几秒前
黑马点评前端Nginx启动失败问题解决记录
java·运维·前端·spring boot·nginx·spring
lxyker6 分钟前
MongoDB大数据量的优化——mongoTemplate.stream()方法使用
java·数据库·mongodb·性能优化·数据库调优
煤灰24224 分钟前
简单用c++的类实现的string
java·开发语言·c++
vibag1 小时前
第十六届蓝桥杯复盘
java·算法·蓝桥杯·竞赛
珹洺1 小时前
计算机操作系统(十一)调度器/调度程序,闲逛调度与调度算法的评价指标
android·java·算法
墨着染霜华1 小时前
JAVA8怎么使用9的List.of
java·list
Volunteer Technology1 小时前
Redisson分布式锁案列和源码解读
分布式·redis分布式锁
编程、小哥哥2 小时前
Java求职面经分享:Spring Boot到微服务,从理论到实践
java·hadoop·spring boot·微服务·kafka
有梦想的攻城狮2 小时前
spring中的BeanFactoryAware接口详解
java·后端·spring·beanfactory
若汝棋茗2 小时前
C#在 .NET 9.0 中启用二进制序列化:配置、风险与替代方案
java·c#·.net·序列化