2025/3/19

This commit is contained in:
2025-03-19 14:36:09 +08:00
commit abb37c49a7
18 changed files with 2005 additions and 0 deletions

View File

@@ -0,0 +1,108 @@
## 1、分布式系统基本概念
### 1、CAP理论基础
分布式系统的最大难点就是各个节点的状态如何同步。CAP 定理是这方面的基本定理,也是理解分布式系统的起点。
1998年加州大学的计算机科学家 Eric Brewer 提出,分布式系统有三个指标:
> * Consistency
> * Availability
> * Partition tolerance
它们的第一个字母分别是 `C``A``P`。Eric Brewer 说,这三个指标不可能同时做到。这个结论就叫做 `CAP` 定理。
它指出对于一个分布式计算系统来说,不可能同时满足以下三点:
* 一致性Consistency :等同于所有节点访问同一份最新的数据副本,或者说同一数据在不同节点上的副本在同一逻辑时钟应当是相同的内容。
* 可用性Availability每次请求都能获取到非错的响应以及尽量保证低延迟但是不保证获取的数据为最新数据。
* 分区容错性Partition tolerance以实际效果而言分区相当于对**通信的时限要求**。要求任意节点故障时,系统仍然可以对外服务。
### 2、数据一致性C侧
一些分布式系统通过复制数据来提高系统的可靠性和容错性,并且将数据的不同的副本存放在不同的机器,由于维护数据副本的一致性代价高,因此许多系统**采用弱一致性来提高性能**,一些不同的一致性模型也相继被提出。
* **强一致性** 要求无论更新操作实在哪一个副本执行,之后所有的读操作都要能获得最新的数据。
* **弱一致性**:用户读到某一操作对系统特定数据的更新需要一段时间,我们称这段时间为“不一致性窗口”。
* **最终一致性**:是弱一致性的一种特例,保证用户\*\*最终(即窗口尽量长)\*\*能够读取到某操作对系统特定数据的更新。
#### 一致性解决方案
1. 分布式事务:两段提交
2. 分布式锁
3. 消息队列、消息持久化、重试、幂等操作
4. Raft / Paxos 等一致性算法
### 3、服务可用性A侧
可用性,意思是只要收到用户的请求,服务器就必须给出回应。
#### 高可用解决方案
* **负载均衡**:尽力将网络流量平均分发到多个服务器上,以提高系统整体的响应速度和可用性。
* **降级**:当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。
* **熔断**:对于目标服务的请求和调用大量超时或失败,这时应该熔断该服务的所有调用,并且对于后续调用应直接返回,从而快速释放资源。确保在目标服务不可用的这段时间内,所有对它的调用都是立即返回的、不会阻塞的,等到目标服务好转后进行接口恢复。
* **流量控制**流量控制可以有效的防止由于网络中瞬间的大量数据对网络带来的冲击保证用户网络高效而稳定的运行类似于TCP拥塞控制方法。
* **异地多活**:在不同地区维护不同子系统,并保证子系统的可用性
熔断是减少由于下游服务故障对自己的影响;而降级则是在整个系统的角度上,考虑业务整体流量,保护核心业务稳定。
### 4、分区容错性P侧
大多数分布式系统都分布在多个子网络。每个子网络就叫做一个区partition。分区容错的意思是区间通信可能失败。比如一台服务器放在中国另一台服务器放在美国这就是两个区它们之间可能无法通信。
般来说,分区容错无法避免,因此可以认为 CAP 的 `P` 总是成立。CAP 定理告诉我们,剩下的 `C``A` 无法同时做到。
## 2、系统一致性
### 1、基本要求
规范的说,理想的分布式系统一致性应该满足:
1. 可终止性Termination一致的结果在有限时间内能完成
2. 共识性Consensus不同节点最终完成决策的结果应该相同
3. 合法性Validity决策的结果必须是其它进程提出的提案。
第一点很容易理解,这是计算机系统可以被使用的前提。需要注意,在现实生活中这点并不是总能得到保障的,例如取款机有时候会是 `服务中断` 状态,电话有时候是 `无法连通` 的。
第二点看似容易,但是隐藏了一些潜在信息。算法考虑的是任意的情形,凡事一旦推广到任意情形,就往往有一些惊人的结果。例如现在就剩一张票了,中关村和西单的电影院也分别刚确认过这张票的存在,然后两个电影院同时来了一个顾客要买票,从各自观察看来,自己的顾客都是第一个到的……怎么能达成结果的共识呢?记住我们的唯一秘诀:**核心在于需要把两件事情进行排序,而且这个顺序还得是合理的、大家都认可的**。
第三点看似绕口,但是其实比较容易理解,即达成的结果必须是节点执行操作的结果。仍以卖票为例,如果两个影院各自卖出去一千张,那么达成的结果就是还剩八千张,决不能认为票售光了。
### 2、强一致性
#### 线性一致性
线性一致性或称 **原子一致性****严格一致性** 指的是程序在执行的历史中在存在可线性化点P的执行模型这意味着一个操作将在程序的调用和返回之间的某个点P起作用。这里“起作用”的意思是被系统中并发运行的所有其他线程所感知。要求如下
1. **写后读** 这里写和读是两个操作,如果写操作在完成之后,读才开始,读要能读到最新的数据,而且保证以后也能读操作也都能读到这个最新的数据。
2. **所有操作的时序与真实物理时间一致**,要求即使不相关的两个操作,如果执行有先后顺序,线性一致性要求最终执行的结果也需要满足这个先后顺序。比如,操作序列(写A读A写B读B)那么不仅读A读B能读到最新A值和B值而且要保证如果读B读到最新值时读A一定也能读到最新值也就是需要保证执行时序与真实时序相同。
3. 如果两个操作是并发的比如读A没有结束时写B开始了那么这个并发时序不确定但从最终执行的结果来看要确保所有线程(进程,节点)看到的执行序列是一致的。
#### 顺序一致性
相比线性一致性,主要区别在于,**对于物理上有先后顺序的操作,不保证这个时序**。具体而言,对于单个线程,操作的顺序仍然要保留,对于多个线程(进程,节点),执行的事件的先后顺序与物理时钟顺序不保证。但是要求,从执行结果来看,所有线程(进程,节点)看到的执行序列是一样的。
#### 因果一致性
因果一致性,被认为是比`顺序一致性`更弱的一致性,在因果一致性中,只对**有因果关系的事件**有顺序要求。
### 3、带约束的一致性
绝对理想的 **强一致性Strong Consistency** 代价很大。除非不发生任何故障,所有节点之间的通信无需任何时间,这个时候其实就等价于一台机器了。实际上,越强的一致性要求往往意味着越弱的性能、越低的可用性。
强一致的系统往往比较难实现。很多时候,人们发现实际需求并没有那么强,可以适当放宽一致性要求,降低系统实现的难度。例如在一定约束下实现所谓 **最终一致性Eventual Consistency**,即总会存在一个时刻(而不是立刻),系统达到一致的状态,这对于大部分的 Web 系统来说已经足够了。这一类弱化的一致性,被笼统称为 **弱一致性Weak Consistency**
#### 最终一致性
最终一致性也被称为 **乐观复制(optimistic replication)**,用户只能读到某次更新后的值,但系统保证数据将最终达到完全一致的状态,只是所需时间不能保障。这个达成一致所需要的时间,我们称为 **窗口时间**
我们常见的 **异步复制的主从架构实现的是最终一致性** 。它的一个典型常见是用户读取异步从库时,可能读取到较旧的信息,因为该从库尚未完全与主库同步。注意,同步复制的主从架构会出现任一节点宕机导致的单点问题。
### 4、一致性Consistency与共识Consensus的关系
我们常说的 **一致性Consistency** 在分布式系统中指的是 `副本Replication` 问题中对于同一个数据的多个副本,其对外表现的数据一致性,如 `线性一致性``因果一致性``最终一致性`等,都是用来描述副本问题中的一致性的。
**共识Consensus** 则不同,共识问题中所有的节点要最终达成共识,由于最终目标是所有节点都要达成一致,所以根本 **不存在一致性强弱** 之分。
![](http://oss.interviewguide.cn/img/202206222244561.png) ![](http://oss.interviewguide.cn/img/202206222244409.png)

View File

@@ -0,0 +1,77 @@
## 常用鉴权方式
我们常用的鉴权有四种:
1. HTTP Basic Authentication
2. session-cookie
3. Token 验证
4. OAuth(开放授权)
## 看到你项目用了JWT说一下原理。
JWT令牌由三个部分组成头部Header、载荷Payload和签名Signature。其中头部和载荷均为JSON格式使用Base64编码进行序列化而签名部分是对头部、载荷和密钥进行签名后的结果。
![img](https://cdn.xiaolincoding.com//picgo/3d66c3403196eaad4774a6dbd1d868ff.png)
在传统的基于会话和Cookie的身份验证方式中会话信息通常存储在服务器的内存或数据库中。但在集群部署中不同服务器之间没有共享的会话信息这会导致用户在不同服务器之间切换时需要重新登录或者需要引入额外的共享机制如Redis增加了复杂性和性能开销。
![img](https://cdn.xiaolincoding.com//picgo/format%2Cpng-20250122202229506.png)
而JWT令牌通过在令牌中包含所有必要的身份验证和会话信息使得服务器无需存储会话信息从而解决了集群部署中的身份验证和会话管理问题。当用户进行登录认证后服务器将生成一个JWT令牌并返回给客户端。客户端在后续的请求中携带该令牌服务器可以通过对令牌进行验证和解析来获取用户身份和权限信息而无需访问共享的会话存储。
由于JWT令牌是自包含的服务器可以独立地对令牌进行验证而不需要依赖其他服务器或共享存储。这使得集群中的每个服务器都可以独立处理请求提高了系统的可伸缩性和容错性。
JWT 的缺点是一旦派发出去在失效之前都是有效的没办法即使撤销JWT。要解决这个问题的话得在业务层增加判断逻辑比如增加黑名单机制。使用内存数据库比如 Redis 维护一个黑名单,如果想让某个 JWT 失效的话就直接将这个 JWT 加入到 **黑名单** 即可。然后,每次使用 JWT 进行请求的话都会先判断这个 JWT 是否存在于黑名单中。
## 常用限流算法
### 1 . 计数器(固定窗口)算法
计数器算法是使用计数器在周期内累加访问次数,当达到设定的限流值时,触发限流策略。下一个周期开始时,进行清零,重新计数。
此算法在单机还是分布式环境下实现都非常简单使用redis的incr原子自增性和线程安全即可轻松实现。
![](https://i-blog.csdnimg.cn/blog_migrate/fb8172e4efd3005fc0525dfcbc5d87f8.png)
这个算法通常用于QPS限流和统计总访问量对于秒级以上的时间周期来说会存在一个非常严重的问题那就是临界问题如下图
![](https://i-blog.csdnimg.cn/blog_migrate/bc19069ae2cdc671be225f23a46a17c1.png)
假设1min内服务器的负载能力为100因此一个周期的访问量限制在100然而在第一个周期的最后5秒和下一个周期的开始5秒时间段内分别涌入100的访问量虽然没有超过每个周期的限制量但是整体上10秒内已达到200的访问量已远远超过服务器的负载能力由此可见计数器算法方式限流对于周期比较长的限流存在很大的弊端。
### 2. 滑动窗口算法
滑动窗口算法是将时间周期分为N个小周期分别记录每个小周期内访问次数并且根据时间滑动删除过期的小周期。
如下图假设时间周期为1min将1min再分为2个小周期统计每个小周期的访问数量则可以看到第一个时间周期内访问数量为75第二个时间周期内访问数量为100超过100的访问则被限流掉了   
![](https://i-blog.csdnimg.cn/blog_migrate/d974c4fb8938f68e09326357010f7af0.png)
由此可见,当滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑,限流的统计就会越精确。
此算法可以很好的解决固定窗口算法的临界问题。
### 3. 漏桶算法
漏桶算法是访问请求到达时直接放入漏桶,如当前容量已达到上限(限流值),则进行丢弃(触发限流策略)。漏桶以固定的速率进行释放访问请求(即请求通过),直到漏桶为空。
![](https://i-blog.csdnimg.cn/blog_migrate/c1982f155cd83440f6ac718a3b20e6f9.png)
### 4. 令牌桶算法
令牌桶算法是程序以rr=时间周期/限流值)的速度向令牌桶中增加令牌,直到令牌桶满,请求到达时向令牌桶请求令牌,如获取到令牌则通过请求,否则触发限流策略
![](https://i-blog.csdnimg.cn/blog_migrate/094e03e513958322772f03434d54c53b.png)
### **各个算法比较**
| 算法 | 确定参数 | 空间复杂度 | 时间复杂度 | 限制突发流量 | 平滑限流 | 分布式环境下实现难度 |
| ---- | ----------------------- | ------------------------------- | ----- | ------ | ------------------------------- | ---------- |
| 固定窗口 | 计数周期T、<br><br>周期内最大访问数N | 低O(1)<br><br>(记录周期内访问次数及周期开始时间) | 低O(1) | 否 | 否 | 低 |
| 滑动窗口 | 计数周期T、<br><br>周期内最大访问数N | 高O(N)<br><br>(记录每个小周期中的访问数量) | 中O(N) | 是 | 相对实现。滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑 | 中 |
| 漏桶 | 漏桶流出速度r、漏桶容量N | 低O(1)<br><br>(记录当前漏桶中容量) | 高O(N) | 是 | 是 | 高 |
| 令牌桶 | 令牌产生速度r、令牌桶容量N | 低O(1)<br><br>(记录当前令牌桶中令牌数) | 高O(N) | 是 | 是 | 高 |

View File

@@ -0,0 +1,19 @@
### 1、系统拆分
将一个系统拆分为多个子系统,用 RPC 来搞。然后每个系统连一个数据库,这样本来就一个库,现在多个数据库,不也可以扛高并发么。(微服务)
### 2、缓存
大部分的高并发场景,都是读多写少,那你完全可以在数据库和缓存里都写一份,然后读的时候大量走缓存不就得了。毕竟 Redis 轻轻松松单机几万的并发。所以你可以考虑考虑你的项目里,那些承载主要请求的读场景,怎么用缓存来抗高并发。
### 3、消息队列
可能你还是会出现高并发写的场景,比如说一个业务操作里要频繁搞数据库几十次,增删改增删改。那高并发绝对搞挂你的系统,你要是用 Redis 来承载写那肯定不行,人家是缓存,数据随时就被 LRU 了,数据格式还无比简单,没有事务支持。所以该用 MySQL 还得用 MySQL 啊。那你咋办?用 MQ 吧,大量的写请求灌入 MQ 里,后边系统消费后慢慢写,控制在 MySQL 承载范围之内。所以你得考虑考虑你的项目里,那些承载复杂写业务逻辑的场景里,如何用 MQ 来异步写,提升并发性。
### 4、分库分表
分库分表,可能到了最后数据库层面还是免不了抗高并发的要求,好吧,那么就将一个数据库拆分为多个库,多个库来扛更高的并发;然后将一个表拆分为多个表,每个表的数据量保持少一点,提高 SQL 跑的性能。
### 5、读写分离
读写分离,这个就是说大部分时候数据库可能也是读多写少,没必要所有请求都集中在一个库上吧,可以搞个主从架构,主库写入,从库读取,搞一个读写分离。读流量太多的时候,还可以加更多的从库。