分布式基础

作者: Cathy 分类: 编程开发 发布时间: 2023-09-23 13:53

CAP 原则?

  • 一个分布式系统中,一致性、可用性、分区容错性这三个基本需求
  • 最多只能同时满足其中的两个
    • 一致性:数据在多个副本之间保持一致
    • 可用性:系统提供的服务必须一直出于可用的状态,每次请求都能获取到非错的响应
    • 分区容错性:分布式系统在遇到网络分区故障时,仍然能够对外提供满足一致性和可用性的服务

为什么 CAP 不可兼得呢?

  • 首先对于分布式系统,分区容错性必须满足
  • 如果保证一致性,在数据不一致的情况下,可用性不能保证
  • 如果保证可用性,立即响应,但可能数据不一致,一致性不能满足

CAP 对应的模型和应用?

  • CA:集群数据库
  • CP:放弃可用性,要求数据之间强一致,分布式数据库、分布式锁
  • AP:Web 缓存

BASE 理论了解吗?

BASE 的核心思想是即使不能达到强一致性,也采取适当的方式达到最终一致性
BASE 的主要含义:

  • 基本可用:系统出现故障,但是还能用,可能会有延迟和服务降级
  • 软状态:允许系统在不同节点的数据副本存在数据延时
  • 最终一致性:在一定时间,到达一个最终的状态,保证所有副本保证数据一致性

分布式锁

有哪些分布式锁的实现方案呢?

  • MySQL 分布式锁
  • ZooKeeper 分布式锁
  • Redis 分布式锁

MySQL 分布式锁如何实现呢?

  • 创建一张锁表,数据库对字段做唯一性约束
  • 加锁的时候,在锁表增加一条记录,释放锁的时候删除记录
  • 如果有并发请求同时提交到数据库,数据库会保证只有一个请求能得到锁

Redis 怎么实现分布式锁?

  • Redis 执行命令是单线程的,Redis 实现分布式锁就是利用这个特性
  • 实现分布式锁最简单的一个命令:setNx set if not exist,如果不存在就更新
  • 在加锁之后如果宕机,锁就无法释放,需要加入过期时间

分布式事务

什么是分布式事务?

  • 分布式事务是相对于本地事务而言的
  • 对于本地事务,利用数据库本身的事务机制,就可以保证事务的 ACID 特性
  • 而分布式环境下,涉及到多个数据库
  • 分布式事务其实就是对同一个库事务的概念扩大到对多个库的事务,目的是为了保证分布式系统中的数据一致性
  • 分布式事务处理的关键是:
    • 需要记录事务在任何节点所做的所有动作
    • 事务进行的所有操作要么全部提交,要么全部回滚

分布式事务有哪些常见的实现方案?

  • 2PC
  • 3PC
  • TCC
  • 本地消息表
  • MQ 消息事务
  • 最大努力通知

说说 2PC 两阶段提交?

  • 2PC 包括两个阶段:准备阶段和提交阶段
  • 准备阶段:所有参与者节点协商是否可以提交事务
  • 提交阶段:统一决定是否提交或者回滚
  • 缺点:由于其阻塞性和单点故障,性能和可用性方面存在问题

3PC(三阶段提交)了解吗?

  • 3PC 是对 2PC 的改进
  • 通过引入一个准备阶段来减少阻塞和单点故障问题
  • 引入超时机制,如果等待预提交请求超时,参与者直接回到准备阶段之前
  • 如果等到提交请求超时,参与者就会提交事务

TCC 了解吗?

  • 两阶段提交的一个变种,针对每个业务操作,都需要其对应的确认和取消操作
  • 当操作成功时调用确认操作
  • 当操作失败时调用取消操作

本地消息表了解吗?

  • 通过将事务相关的消息持久化到数据表中
  • 然后异步处理的方式来实现分布式事务的方法
  • 通常使用在消息队列中使用,当事务成功时,消息被处理,否则回滚

MQ 消息事务了解吗?

  • 将两个事务通过消息中间件进行异步解耦

最大努力通知了解吗?

  • 最终一致性的分布式事务方法
  • 使用异步通知来实现事务
  • 当事务需要在多个节点之间同步时,可能不回立即成功,而是通过多次尝试来达到最终一致性

你们用什么?能说一下 Seata 吗?

  • 常用 Seata,自己实现分布式事务调度比较麻烦
  • Seata 的设计目标对业务无侵入,因此它是从业务无侵入的两阶段提交(全局事务)着手
  • 在传统的两阶段上进行改进,把一个分布式事务理解成一个包含若干分支事务的全局事务
  • 全局事务的职责是协调它管理的分支事务达成一致性,要么一起成功提交,要么一起失败回滚

说说什么是幂等性?

  • 幂等性:幂等性是一个数学概念,用在接口上,可以理解为:同一个接口,多次发出同一个请求,请求的结果是一致的,简单来说就是多次调用如一次
  • 幂等性问题:
    • 填写表单时,保存按钮快速点了两次,数据库表中有两条重复的数据
    • 为了解决接口超时问题,引入重试机制,第一次接口超时,请求方没及时获取到返回结果,所以多次重试,会产生重复的数据
    • mq 消费者在读取消息时,会读到重复消息,也会产生重复的数据
  • 在分布式系统中,只要下游服务有写操作,就可能产生幂等问题

分布式限流

你了解哪些限流算法?

  • 计数器
  • 漏桶算法
  • 令牌桶算法

为什么使用限流

  • 高并发时,为了保证系统的稳定性和可用性,以牺牲部分请求或延迟处理请求为代价,保证系统整体服务可用

限流分类

应用级 – 单机

  • 限制总资源数
  • 限制总并发/连接/请求数
  • 限制某个接口的总并发/请求数
  • 平滑限流某个接口的请求数

分布式

  • Redis + Lua 实现的 Lua 脚本
  • Nginx + Lua 实现的 Lua 脚本
  • 限流框架,比如 Sentinel 实现降级限流熔断

常用限流算法

  • 令牌桶
  • 漏桶
  • 计数器算法

令牌桶和漏桶对比

  • 令牌桶按照固定速率添加令牌,请求是否被处理看桶中的令牌是否足够,当令牌数减为 0 的时候拒绝请求
  • 漏桶是按照固定速率流出请求,流入请求速率任意,当流入的请求数积累到漏桶容量时,则新流入的请求被拒绝
  • 令牌桶限制的是平均流入速率,并允许一定程度突发流量
  • 漏桶限制的是常量流出速率,从而平滑流入速率

计数器算法

计数器限流算法也是比较常用的,用来限制总并发数,比如数据流连接池大小、线程池大小、程序访问并发数

  • 采用原子类 AtomicInteger
  • 采用令牌 Semaphore
  • 采用 ThreadPoolExecutor Java 线程池

分布式缓存

缓存的基本思想

  • 缓存的基本思想就是空间换时间
  • 使用更多的存储空间来存储一些可能重复使用或计算的数据,减少数据的重复获取
  • 例如索引、数据库表字段冗余都是使用这个思想
  • 日常开发过程中用到的缓存,通常存储在内存中,访问速度特别快
  • 为了避免内存中的数据在重启或者宕机的情况丢失,缓存会利用磁盘做持久化

缓存的分类

  • 本地缓存
  • 分布式缓存

什么是本地缓存

  • 本地缓存位于应用内存
  • 请求本地缓存的速度非常快,不存在额外的网络开销

本地缓存的方案有哪些

  • JDK 自带的 HashMap 和 ConcurrentHashMap:只提供缓存功能,没有过期时间、淘汰机制等功能
  • Guava Cache
  • Caffine:通常用来替代 Guava,性能更加优秀

本地缓存的痛点

低依赖、轻量、简单、成本低

  • 本地缓存应用耦合,对分布式架构支持不友好
  • 本地缓存容量受服务部署的机器限制明显

什么是分布式缓存

  • 分布式缓存脱离应用独立存在,多个应用直接的,共同使用同一个分布式缓存服务
  • 使用分布式缓存,缓存服务可以部署在一台单独的服务器上
  • 即使同一个相同的应用部署在多台机器上,也是使用的是同一份缓存

分布式缓存的方案有哪些?

  • Redis

多级缓存

本地缓存 + 分布式缓存的多级缓存方案
适合的业务场景:数据访问量特别大,比如秒杀场景
多级缓存方案中,第一级缓存使用本地内存,例如 Caffeine,第二级缓存使用分布式缓存,比如 Redis

缓存设计

高并发缓存问题 / 什么是缓存击穿、缓存穿透、缓存雪崩?

缓存一致性

  • 缓存中的数据与数据库中的保持一致
  • 缓存节点和副本中的数据保持一致

依赖缓存的过期和更新策略,一般在数据发生更改时,主动更新缓存中的数据或者移除对应的缓存

缓存并发问题/ 缓存击穿

  • 高并发的场景下,有可能有多个请求并发从数据库获取数据,对数据库造成极大冲击,甚至导致雪崩问题
  • 解决方案:
    • 加锁更新:类似锁机制,在缓存更新或者过期的情况下,先尝试获取锁,当更新或者从数据库获取完成后再释放锁,其他请求等待一段时间后,从缓存中继续获取数据
    • 为了防止 key 值集中过期,可以将过期时间组合写在 value 中,通过异步的方式刷新过期时间

缓存穿透

  • 高并发的场景下,如果一个 key 被高并发访问,没有被命中,出于对容错性的考虑,会尝试从后端数据库获取,从而导致大量请求到达数据库
  • 而当 key 为空的情况下,会导致数据库并发执行了很多不必要的查询操作,从而导致巨大压力
  • 解决方案:
    • 缓存空对象
    • 单独过滤处理:对所有可能数据为空的 key 统一存放,并在请求前做拦截

缓存抖动问题

  • 一般由于缓存节点故障导致
  • 解决方案:
    • 通过一致性 Hash 算法解决

缓存雪崩问题

  • 由于缓存的原因,导致大量请求到达数据库,从而导致数据库崩溃
  • 缓存并发、缓存穿透、缓存抖动都有可能导致
  • 某个时间点,系统预加载的缓存周期集中失效
  • 解决方案:
    • 提高缓存的可用性:使用集群部署或多级缓存
    • 均匀缓存的过期时间
    • 熔断降级

怎么处理热 key?

  • 监控热点 key
  • 将热点 key 打散到不同的服务器,降低压力
  • 加入二级缓存,提前加载热点 key 数据到内存中,如果 Redis 宕机,走内存查询

缓存预热怎么做呢?

缓存预热:提前把数据库里的数据刷新缓存里

  • 数据量不大,可以在项目启动时自动加载
  • 定时任务刷新缓存
  • 使用缓存接口,手动刷新

如何保证缓存和数据库数据的⼀致性?

  • 选择合适的缓存更新策略
    • 删除缓存而不是更新缓存,减少读脏数据的概率
    • 先更新数据库,再删除缓存
  • 缓存不一致处理
    • 原因:缓存 key 删除失败;并发写入了脏数据
    • 使用消息队列保证 key 被删除
    • 设计缓存过期时间

如何保证本地缓存和分布式缓存的一致?

本地缓存:Caffeine
分布式缓存:Redis

  • 使用消息队列,保证消息的可靠性
  • 设置合理的过期时间,本地缓存设置相对短一些的过期时间

缓存更新策略

  • 旁路缓存模式:使用的比较多,适合读请求多的场景
  • 读写穿透
  • 异步缓存写入

旁路缓存模式

写操作:

  • 先更新数据库
  • 再删除缓存

读操作:

  • 从缓存中读取数据,读到直接返回
  • 读不到,从数据库中返回
  • 然后把读到的数据放到缓存中

为什么删除缓存,不是更新缓存

  • 对服务器资源造成浪费
  • 减少读脏数据的概率

写操作中,可以先删除缓存,后更新数据库吗

  • 不行
  • 会造成数据库和缓存数据不一致

写操作中,先更新数据库,后删除缓存就没有问题啦吗

  • 理论上来说,还可能出现数据不一致问题,但是概率非常小
  • 因为缓存的写入速度比数据库的写入速度快很多

首次请求数据一定不在缓存的问题

  • 可以把热点数据提前放入缓存中

写操作频繁的话导致缓存中的数据会被频繁删除,影响缓存的命中率

  • 数据库和缓存数据强一致场景:更新数据库的同时更新缓存,需要加一个锁来保证更新缓存的时候不存在线程安全问题
  • 可以短暂允许数据库和缓存数据不一致的场景:更新数据库的同时更新缓存,但是给缓存加一个比较短的过期时间,这样即使数据不一致影响也比较小

降级

在高并发的环境下,服务之间的依赖关系导致调用失败,解决的方式通常是:
限流 -> 熔断 -> 隔离 -> 降级
其目的是防止雪崩效应

基本的容错模式

  • 主动超时:HTTP 请求主动设置一个超时时间,超时就直接返回,不会造成服务堆积
  • 限流:限制最大并发数
  • 熔断:当错误数超过阙值,快速失败,不调用后端服务,同时隔一段时间放几个请求去重试后端服务是否能正常调用,如果成功就关闭熔断状态,失败则继续快速失败
  • 隔离:每个依赖或调用的服务都隔离,防止级联失败引起整体服务不可用
  • 降级:服务失败或异常后,返回指定的默认信息
    ![[Pasted image 20230907092452.png]]

服务降级

降级服务的特征

  • 原因:整体负荷超过整体负载负荷能力
  • 目的:保证重要或基本服务正常运行,非重要服务延迟使用或暂停使用
  • 大小:降低服务粒度
  • 可控性:在服务粒度大小的基础上增加服务的可控性,后台服务开关的功能是一项必要选项
  • 次序:一般从外围延伸服务开始降级,重要性低的优先降级

降级方式

  • 延迟服务:比如发表评论,重要服务,显示保证正常,积分服务先放入缓存,等服务平稳之后执行
  • 粒度范围内关闭服务:关闭推荐区
  • 写降级:比如秒杀,可以只进行 Cache 的更新,然后异步同步扣减库存到数据库,保持最终一致性即可
  • 读降级:比如多级缓存模式,如果后端服务有问题,可以降级为只读缓存,这种方式适用于对读一致性要求不高的场景

熔断

下游服务访问压力过大,响应变慢或者失败的时候,上游服务为了保证系统的整体可用性,可以暂时切断对下游服务的调用

相关概念

  • 服务雪崩:多个微服务之间调用的时候,整个链路的调用过长,导致服务不可用,进而引起系统崩溃
  • 断路器:本身是一种开关装置,当某个服务单元发生故障监控,向调用方法放回备选响应(Fallback),而不是长时间的等待或者抛出不能处理的异常
  • 服务熔断:熔断机制是应对雪崩效应的微服务链路处理机制,当整个链路的某个微服务不可用或者响应时间过长的时候,会对服务进行降级,从而熔断该节点的调用,快速返回备选的响应信息
  • Hystrix:用于分布式系统的延迟和容错的开源库

服务降级和熔断的区别

  • 服务熔断通常是下游服务故障引起
  • 服务降级通常是为整体系统而考虑

Sentinel

  • Sentinel 是阿里中间件团队开源的,面向分布式服务架构的高可用、流量控制组件
  • 主要以流量为切入点
  • 从流量控制、熔断降级、系统负载保护等多个维度来帮助用户保护服务的稳定性
  • Sentinel 中的分布式限流是通过令牌桶算法和滑动窗口算法实现的

Sentinel 限流策略

  • 直接拒绝
  • 预热冷启动
  • 排队等待
  • 慢启动
  • 平滑降级
  • 热点参数限流
  • 熔断降级

Sentinel 隔离策略

  • 线程池隔离
  • 信号量隔离

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注