【️什么是分布式系统的一致性 ?】

在这里插入图片描述

😊引言

🎖️本篇博文约8000字,阅读大约30分钟,亲爱的读者,如果本博文对您有帮助,欢迎点赞关注!😊😊😊

🖥️什么是分布式系统的一致性 ?

  • ✅分布式系统的一致性
    • ✅线性一致性 & 顺序一致性
    • ✅顺序一致性 & 最终一致性

✅分布式系统的一致性


所谓一致性,是指数据在多个副本之间是否能够保持一致的特性,再聊一致性的时间,其实要搞清楚一致性模型。
分布式系统中的一致性模型是一组管理分布式系统行为的规则。它决定了在分布式系统中如何访问和更新数据,以及如何将这些更新提供给客户端。面对网络延迟和局部故障等分布式计算难题,分布式系统的一致性模型对保证系统的一致性和可靠性起着关键作用。在分布式系统中有多种一致性模型可用,每个模型都有其优点和缺点,选择模型取决于系统的具体要求。

大的分类上面,主要有三种,分别是强一致性弱一致性最终一致性

  • 强一致性(Strong Consistency)
         在强一致性模型下,系统保证每个读操作都将返回最近的写操作的结果,即任何时间点,客户端都将看到相同的数据视图。这包括线性一致性(Linearizability) 、顺序一致性(Sequential Consistency) 和严格可串行性 (Strict Serializability) 等子模型。强 致性模型通常牺牲了可用性来实现数据一致性。

  • 弱一致性(Weak Consistency)
         弱一致性模型放宽了一致性保证,它允许在不同节点之间的数据访问之间存在一定程度的不一致性,以换取更高的性能和可用性。这包括因果致性(Causal Consistency)会话一致性(Session Consistency) 和单调一致性(Monotonic Consistency) 等子模型。弱一致性模型常更注重可用性,允许一定程度的数据不一致性。

  • 最终一致性(Eventual Consistency)
         最终一致性模型是一种最大程度放宽了一致性要求的模型。它允许在系统发生分区或网络故障后,经过一段时间,系统将最终达到一致状态。这个模型在某些情况下提供了很高的可用性,但在一段时间内可能会出现数据不一致的情况。

我们看下代码巩固一下我们理论:

⛳第一个:先看一个分布式系统一致性的简单Demo

import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  
import java.util.concurrent.TimeUnit;  

/**
*	@author 昕宝爸爸爱编程
*	分布式系统一致性的简单Demo
*/  
public class DistributedConsistencyExample {  
  
    private static final int NUM_OF_NODES = 3; // 假设有3个节点  
    private static final int MAX_TIME = 5; // 最大等待时间为5秒  
  
    public static void main(String[] args) throws InterruptedException {  
        ExecutorService executorService = Executors.newFixedThreadPool(NUM_OF_NODES);  
        for (int i = 0; i < NUM_OF_NODES; i++) {  
            executorService.execute(new Node(i));  
        }  
        executorService.shutdown();  
        executorService.awaitTermination(MAX_TIME, TimeUnit.SECONDS);  
    }  
  
    static class Node implements Runnable {  
        private int id;  
  
        public Node(int id) {  
            this.id = id;  
        }  
  
        @Override  
        public void run() {  
            try {  
                // 执行任务,这里只是简单地打印节点ID  
                System.out.println("节点" + id + "正在执行任务...");  
                Thread.sleep(1000); // 模拟任务执行时间  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
}

可以看到,我创建了一个包含3个节点的分布式系统。每个节点都是一个线程,它们在启动后开始执行任务。这里我们只是简单地打印节点ID,并模拟任务执行时间。

⛳第二个:增加分布式锁和事务处理来实现一致性

这一个就相对上面一段代码来讲,增加了些复杂度,使用了分布式锁和事务处理来实现一致性:

import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  
import java.util.concurrent.TimeUnit;  
import java.util.concurrent.locks.Lock;  
import java.util.concurrent.locks.ReentrantLock;  
/**
* @author 昕宝爸爸爱编程
* 增加分布式锁和事务处理来实现一致性
*/
public class DistributedConsistencyExample {  
  
    private static final int NUM_OF_NODES = 3; // 假设有3个节点  
    private static final int MAX_TIME = 5; // 最大等待时间为5秒  
    private static final Lock lock = new ReentrantLock(); // 分布式锁  
  
    public static void main(String[] args) throws InterruptedException {  
        ExecutorService executorService = Executors.newFixedThreadPool(NUM_OF_NODES);  
        for (int i = 0; i < NUM_OF_NODES; i++) {  
            executorService.execute(new Node(i));  
        }  
        executorService.shutdown();  
        executorService.awaitTermination(MAX_TIME, TimeUnit.SECONDS);  
    }  
  
    static class Node implements Runnable {  
        private int id;  
  
        public Node(int id) {  
            this.id = id;  
        }  
  
        @Override  
        public void run() {  
            try {  
                // 执行任务,这里模拟一个需要加锁的操作  
                lock.lock(); // 加锁  
                try {  
                    // 执行任务逻辑,这里只是简单地打印节点ID和加锁情况  
                    System.out.println("节点" + id + "正在执行任务...");  
                    System.out.println("节点" + id + "获取了锁...");  
                    Thread.sleep(1000); // 模拟任务执行时间  
                } finally {  
                    lock.unlock(); // 释放锁  
                }  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
}

我们使用了ReentrantLock来实现分布式锁。当一个节点获取了锁之后,其他节点必须等待该节点释放锁后才能执行任务。这样可以保证同一时刻只有一个节点可以执行任务,从而实现了分布式系统的一致性。同时,我们还使用了事务处理的方式,确保任务的原子性和一致性。在执行任务时,我们首先获取锁,然后执行任务逻辑,最后释放锁。这样可以保证在任务执行过程中不会被其他节点干扰,从而保证了数据的一致性。

⛳第三个:增加使用了分布式锁、事务处理和消息队列来实现一致性保证

import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  
import java.util.concurrent.TimeUnit;  
import java.util.concurrent.locks.Lock;  
import java.util.concurrent.locks.ReentrantLock;  

/**
* @author 昕宝爸爸爱编程
*/  
public class DistributedConsistencyExample {  
  
    private static final int NUM_OF_NODES = 3; // 假设有3个节点  
    private static final int MAX_TIME = 5; // 最大等待时间为5秒  
    private static final Lock lock = new ReentrantLock(); // 分布式锁  
    private static final String QUEUE_NAME = "distributed-queue"; // 消息队列名称  
  
    public static void main(String[] args) throws InterruptedException {  
        ExecutorService executorService = Executors.newFixedThreadPool(NUM_OF_NODES);  
        for (int i = 0; i < NUM_OF_NODES; i++) {  
            executorService.execute(new Node(i));  
        }  
        executorService.shutdown();  
        executorService.awaitTermination(MAX_TIME, TimeUnit.SECONDS);  
    }  
  
    static class Node implements Runnable {  
        private int id;  
  
        public Node(int id) {  
            this.id = id;  
        }  
  
        @Override  
        public void run() {  
            try {  
                // 执行任务,这里模拟一个需要加锁和消息队列的操作  
                lock.lock(); // 加锁  
                try {  
                    // 发送消息到消息队列  
                    String message = "节点" + id + "正在执行任务...";  
                    System.out.println(message);  
                    // 模拟发送消息到消息队列的时间  
                    Thread.sleep(1000);   
                } finally {  
                    lock.unlock(); // 释放锁  
                }  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
}

可以看到,我使用了ReentrantLock来实现分布式锁。当一个节点获取了锁之后,其他节点必须等待该节点释放锁后才能执行任务。这样可以保证同一时刻只有一个节点可以执行任务,从而实现了分布式系统的一致性。同时,我们还使用了消息队列来协调节点之间的通信。在执行任务时,我们首先获取锁,然后发送消息到消息队列,最后释放锁。这样可以保证在任务执行过程中不会被其他节点干扰,从而保证了数据的一致性。同时,通过消息队列的传递机制,可以确保消息的可靠性和顺序性,进一步保证了分布式系统的一致性。

✅线性一致性 & 顺序一致性

线性一致性(Linearizability) 和顺序 致性(Sequential Consistency) 是两种强 致性模型。

线性一致性是一种最强的一致性模型,它强调在分布式系统中的任何时间点,读操作都应该返回最近的写操作的结果。

顺序一致性也是一种强一致性模型,但相对于线性一致性而言,它放宽了一些限制。在顺序一致性模型中,系统维护一个全局的操作顺序,以确保每个客户端看到的操作顺序都是一致的。

与线性一致性不同,顺序一致性不强调实时性,只要操作的顺序是一致的,就可以接受一些延迟

他们的主要区别在于强调实时性。线性一致性要求操作在实际时间上的顺序保持一致,而顺序一致性只要求操作的顺序是一致的,但不一定要求操作的实际时间顺序。

⛳同样的我们结合代码来看一看。

1、⛳线性一致性代码展示:

import java.util.concurrent.atomic.AtomicInteger;  

/**
* @author 昕宝爸爸爱编程
*/  
public class LinearConsistencyExample {  
    private static final AtomicInteger counter = new AtomicInteger(0);  
  
    public static void main(String[] args) throws InterruptedException {  
        Thread thread1 = new Thread(() -> {  
            counter.incrementAndGet();  
            System.out.println("Thread 1 incremented counter to: " + counter.get());  
        });  
  
        Thread thread2 = new Thread(() -> {  
            counter.incrementAndGet();  
            System.out.println("Thread 2 incremented counter to: " + counter.get());  
        });  
  
        thread1.start();  
        thread2.start();  
        thread1.join();  
        thread2.join();  
    }  
}

用到了AtomicInteger类,该类提供了原子性的操作来保证线性一致性。两个线程同时递增计数器,并打印计数器的值。由于AtomicInteger的递增操作是原子的,因此可以保证两个线程之间的操作是线性一致的。无论线程的执行顺序如何,最终打印的值都是递增的。

2、⛳顺序一致性代码展示:

import java.util.concurrent.*;  

/**
* @author 昕宝爸爸爱编程
*/ 
public class SequentialConsistencyExample {  
    private static final int NUM_OF_THREADS = 3;  
    private static final ExecutorService executorService = Executors.newFixedThreadPool(NUM_OF_THREADS);  
    private static final Semaphore semaphore = new Semaphore(1); // 用于实现互斥的信号量  
    private static int sharedData = 0; // 共享数据  
  
    public static void main(String[] args) throws InterruptedException {  
        for (int i = 0; i < NUM_OF_THREADS; i++) {  
            executorService.execute(() -> {  
                try {  
                    semaphore.acquire(); // 获取信号量,实现互斥访问  
                    sharedData++; // 更新共享数据  
                    System.out.println("Thread " + Thread.currentThread().getId() + " updated shared data to: " + sharedData);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                } finally {  
                    semaphore.release(); // 释放信号量,允许其他线程访问  
                }  
            });  
        }  
        executorService.shutdown();  
        executorService.awaitTermination(1, TimeUnit.HOURS); // 等待所有任务执行完毕  
    }  
}

使用Semaphore类来实现互斥访问共享数据,以保证顺序一致性。多个线程通过获取信号量来访问共享数据,并在更新共享数据后释放信号量。

由于只有一个线程可以同时访问共享数据,因此可以保证线程之间的操作是按照它们启动的顺序执行的。无论线程的执行顺序如何,最终打印的值都是递增的。

所有线程看到的数据依照他们操作执行的顺序而变化。

✅顺序一致性 & 最终一致性

先看一下最终一致性的代码:

import java.util.concurrent.CopyOnWriteArrayList;  
import java.util.concurrent.atomic.AtomicReference;  
 
 /**
* @author 昕宝爸爸爱编程
*/ 
public class EventualConsistencyExample {  
    private static final CopyOnWriteArrayList<String> eventLog = new CopyOnWriteArrayList<>();  
    private static final AtomicReference<String> latestEvent = new AtomicReference<>();  
  
    public static void main(String[] args) {  
        Thread writerThread = new Thread(() -> {  
            for (int i = 0; i < 10; i++) {  
                eventLog.add("Event " + i);  
                latestEvent.set("Event " + i);  
                try {  
                    Thread.sleep(1000); // 模拟异步操作,延迟1秒  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        });  
  
        Thread readerThread = new Thread(() -> {  
            while (true) {  
                String latestEvent = latestEvent.get();  
                if (latestEvent != null) {  
                    System.out.println("Reader read event: " + latestEvent);  
                } else {  
                    System.out.println("Reader read event: null (no event available)");  
                }  
                try {  
                    Thread.sleep(500); // 模拟异步操作,延迟0.5秒  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        });  
  
        writerThread.start();  
        readerThread.start();  
    }  
}

CopyOnWriteArrayList和AtomicReference来模拟最终一致性的场景。eventLog是一个线程安全的列表,用于存储事件日志。latestEvent是一个原子引用,用于存储最新的事件。

在主线程中,我们创建了一个写线程和一个读线程。写线程将模拟写入事件到eventLog中,并将最新的事件存储到latestEvent中。读线程将不断读取latestEvent中的最新事件,并打印出来。由于写线程和读线程是异步执行的,因此它们之间的操作可能会存在一定的延迟。但是,由于使用了线程安全的列表和原子引用,最终一致性得到了保证。无论何时读取latestEvent,都总是能够获得最新的事件,或者在读取时事件还未发生而返回null。这正是最终一致性的特点:在没有更新数据的一段时间里,系统将通过广播保证副本之间的数据一致性。

很多人看完线性一致性和顺序一致性的区别之后,会容易懵,看上去顺序一致性和我们理解的最终一致性有点像?

💡那么他们的区别是啥呢?

在时间上,虽然顺序一致性和最终一致性都不强要求实时性,但是最终一致性的时间放的会更宽。并且最终一致性其实并不强调顺序,他只需要保证最终的结果一致就行了,而顺序一致性要求操作顺序必须一致。

并且,顺序一致性还是一种强一致性,比如在Zookeeper中,其实就是通过ZAB算法来保证的顺序一致性,即各人节点之间的写入顺序要求一致。并且要半数以上的节点写入成功才算成功。所以,顺序一致性的典型应用场景就是数据库管理系统以及分布式系统。

而最终一致性通常适用于互联网三高架构的业务开发,如电商网站,社交媒体网站等.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/248941.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++】POCO学习总结(十七):日志系统(级别、通道、格式化、记录流)

【C】郭老二博文之&#xff1a;C目录 1、Poco::Message 日志消息 1.1 说明 所有日志消息都在Poco::Message对象中存储和传输。 头文件&#xff1a;#include “Poco/Message.h” 一条消息包含如下内容&#xff1a;优先级、来源、一个文本、一个时间戳、进程和线程标识符、可选…

MacOS下载配置OpenCV

主要参考的是OpenCV官方的这篇文章&#xff1a;OpenCV: Installation in MacOS 安装OpenCV需要下载一些安装包&#xff1a;CMake3.9、Git、Python这些我之前已经下载好&#xff0c;这里就不过多阐述了&#xff0c;自行百度安装即可 1.从Git库获取OpenCV&#xff1a; git clon…

Chrome安装Vue插件vue-devtools

1.下载 下载扩展文件&#xff0c;可以去官网下载 GitHub - vuejs/devtools: ⚙️ Browser devtools extension for debugging Vue.js applications. 可以在这里下载&#xff0c;比较方便 https://gitee.com/zhang_banglong/vue-devtools 2.解压 下载好之后解压文件 3.打开控制…

vue-实现高德地图-省级行政区地块显示+悬浮显示+标签显示

<template><div><div id"container" /><div click"showFn">显示</div><div click"removeFn">移除</div></div> </template><script> import AMapLoader from amap/amap-jsapi-load…

基于Nexus搭建Maven私服基础入门

什么是Nexus&#xff1f;它有什么优势&#xff1f; 要了解为什么需要nexus的存在&#xff0c;我们不妨从以下几个问题来简单了解一下: 为什么需要搭建私服&#xff1f;如果没有私服会出现什么问题&#xff1f; 对于企业开发而言&#xff0c;如果没有私服&#xff0c;我们所有…

浅析AI视频分析与视频管理系统EasyCVR平台及场景应用

人工智能的战略重要性导致对视频智能分析的需求不断增加。鉴于人工智能视觉技术的巨大潜力&#xff0c;人们的注意力正在从传统的视频监控转移到计算机视觉的监控过程自动化。 1、什么是视频分析&#xff1f; 视频分析或视频识别技术&#xff0c;是指从视频片段中提取有用信息…

自动驾驶学习笔记(十八)——Lidar感知

#Apollo开发者# 学习课程的传送门如下&#xff0c;当您也准备学习自动驾驶时&#xff0c;可以和我一同前往&#xff1a; 《自动驾驶新人之旅》免费课程—> 传送门 《Apollo 社区开发者圆桌会》免费报名—>传送门 文章目录 前言 Lidar感知 运动补偿 点云分割 总结…

2021年数维杯国际大学生数学建模A题新冠肺炎背景下港口资源优化配置策略求解全过程文档及程序

2021年数维杯国际大学生数学建模 A题 新冠肺炎背景下港口资源优化配置策略 原题再现&#xff1a; 2020年初&#xff0c;新型冠状病毒&#xff08;COVID-19&#xff09;在全球迅速蔓延。根据世界卫生组织2021年7月31日的报告&#xff0c;新冠病毒疫情对人类的影响可能比原先预…

动手学深度学习-自然语言处理-预训练

词嵌入模型 将单词映射到实向量的技术称为词嵌入。 为什么独热向量不能表达词之间的相似性&#xff1f; 自监督的word2vec。 word2vec将每个词映射到一个固定长度的向量&#xff0c;这些向量能更好的表达不同词之间的相似性和类比关系。 word2vec分为两类&#xff0c;两类…

RT-DETR 图片目标计数 | 特定目标进行计数

全类别计数特定类别计数如何使用 RT-DETR 进行对象计数 有很多同学留言说想学 RT-DETR 目标计数。那么今天这篇博客,我将教大家如何使用 RT-DETR 进行对象计数。RT-DETR 是一种非常强大的对象检测模型,它可以识别图像中的各种对象。我们将学习如何利用这个模型对特定对象进行…

JAVA 异常分类及处理

JAVA 异常分类及处理 概念 如果某个方法不能按照正常的途径完成任务&#xff0c;就可以通过另一种路径退出方法。在这种情况下会抛出一个封装了错误信息的对象。此时&#xff0c;这个方法会立刻退出同时不返回任何值。另外&#xff0c;调用这个方法的其他代码也无法继续执行&…

如何查看Linux中glibc的Version

用ldd --version ldd --version 运行libc.so 你没有看错&#xff0c;libc.so是一个可执行程序。 但前提是你要找到它。因为它并不在PATH所包含的目录下。 ppdell:~$ ldd which cat | grep libclibc.so.6 > /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0e6fb34000)ppdell:~…

【单调栈]LeetCode84: 柱状图中最大的矩形

作者推荐 【动态规划】【广度优先搜索】LeetCode:2617 网格图中最少访问的格子数 本文涉及的知识点 单调栈 题目 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形…

【JVM从入门到实战】(七)运行时数据区的组成

运行时数据区: Java虚拟机在运行Java程序过程中管理的内存区域&#xff0c;称之为运行时数据区。 《Java虚拟机规范》中规定了每一部分的作用 线程不共享&#xff1a;程序计数器、虚拟机栈、本地方法栈 线程共享&#xff1a;方法区&#xff0c;堆 1. 程序计数器(Program Count…

代码随想录刷题题Day14

刷题的第十四天&#xff0c;希望自己能够不断坚持下去&#xff0c;迎来蜕变。&#x1f600;&#x1f600;&#x1f600; 刷题语言&#xff1a;C Day14 任务 ● 110.平衡二叉树 ● 257. 二叉树的所有路径 ● 404.左叶子之和 1 平衡二叉树 二叉树节点的深度&#xff1a;指从根节…

​LeetCode解法汇总1631. 最小体力消耗路径

目录链接&#xff1a; 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目&#xff1a; https://github.com/September26/java-algorithms 原题链接&#xff1a; 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 描述&#xff1a; 你准备参…

spring 笔记八 SpringMVC异常处理和SpringMVC拦截器

文章目录 SpringMVC拦截器拦截器&#xff08;interceptor&#xff09;的作用拦截器和过滤器区别拦截器是快速入门拦截器方法说明 SpringMVC拦截器拦截器&#xff08;interceptor&#xff09;的作用拦截器和过滤器区别拦截器是快速入门拦截器方法说明 SpringMVC异常处理异常处理…

JMeter安装RabbitMQ测试插件

整体流程如下&#xff1a;先下载AMQP插件源码&#xff0c;可以通过antivy在本地编译成jar包&#xff0c;再将jar包导入JMeter目录下&#xff0c;重启JMeter生效。 Apache Ant 是一个基于 Java 的构建工具。Ant 可用于自动化构建和部署 Java 应用程序&#xff0c;使开发人员更轻…

MATLAB 计算点云坐标的最大最小值 (38)

MATLAB 计算点云坐标的最大最小值 (38) 一、算法介绍二、算法实现1.代码一、算法介绍 沿着X Y Z三个坐标轴方向,点云坐标存在对应的最大最小值,这在计算点云体积或者其他方面有使用,这里使用MATLAB快速获取xmax xmin ymax ymin zmax zmin6个最大最小值 二、算法实现 1.代…

jmeter,断言:响应断言、Json断言

一、响应断言 接口A请求正常返回值如下&#xff1a; {"status": 10013, "message": "user sign timeout"} 在该接口下创建【响应断言】元件&#xff0c;配置如下&#xff1a; 若断言成功&#xff0c;则查看结果树的接口显示绿色&#xff0c;若…