GoMind Hub

分布式

计算机知识思维导图:分布式。网页展示前三层结构,可在线查看完整脑图并下载 GoMind 文件。

2026-08-25

计算机知识学习资料思维导图
## 分布式
### 分布式
#### 幂等设计
##### MVCC方案
##### 去重表
##### select + insert
##### 状态机幂等
##### token机制
##### 需要接入商户提交付款请求时附带:source来源,seq序列号。 source+seq在数据库里面做唯一索引,防止多次付款,(并发时,只能处理一个请求)
#### 理论
##### CAP
##### BASE
#### 通信方式
##### 同步
##### 异步
#### 服务治理
##### 服务注册与发现
#### 服务熔断
#### 分布式事务
##### 两阶段提交
##### 三阶段提交
#### 一致性分类
##### 强一致性(strong)
##### 单调一致性(monotonic)
##### 会话一致性 (session)
##### 最终一致性(eventual)
##### 弱一致性(weak)
#### 常用框架
##### Dubbo
##### SpringCloud
##### Untitled node
#### 关键技术
##### 应用整体监控
##### 资源、服务调度
##### 状态、数据调度
##### 流量调度
#### 提高架构性能
##### 缓存
##### 负载均衡
##### 异步调用
##### 数据镜像
##### 数据分区
#### 提高架构稳定性
##### 服务拆分
##### 服务冗余
##### 限流降级
##### 高可用架构
##### 高可用运维
#### 分布式锁
##### Redis实现
##### Zookeeper实现
#### 一致性算法
##### 强一致性
#### 一致性协议
##### 二阶段提交
##### 三阶段提交
##### Paxos算法
#### 缓存
##### 常见模式
##### 缓存删除失败如何处理
##### redis
##### 常见问题
#### 集群扩容
##### 垂直扩容
##### 水平扩容
#### 高并发
##### 应用无状态
##### 拆分
##### 服务化
##### 消息队列
##### 缓存

> 仅展示前三层结构;请在线查看完整脑图或下载 GoMind 文件。

分布式

分布式
MVCC方案
去重表
在插入数据的时候,插入去重表,利用数据库的唯一索引特性(唯一性约束),保证唯一的逻辑。
select + insert
并发不高的后台系统,或者一些任务JOB,为了支持幂等,支持重复执行,简单的处理方法是,先查询下一些关键数据,判断是否已经执行过,在进行业务处理
状态机幂等
在设计单据相关的业务,或者是任务相关的业务,肯定会涉及到状态机,就是业务单据上面有个状态,状态在不同的情况下会发生变更,一般情况下存在有限状态机,这时候,如果状态机已经处于下一个状态,这时候来了一个上一个状态的变更,理论上是不能够变更的,这样的话,保证了有限状态机的幂等。
token机制
数据提交前要向服务的申请token,token放到redis或jvm内存,token有效时间

提交后后台校验token,同时删除token,生成新的token返回

token特点:要申请,一次有效性,可以限流
OpenApi
需要接入商户提交付款请求时附带:source来源,seq序列号。 source+seq在数据库里面做唯一索引,防止多次付款,(并发时,只能处理一个请求)
幂等设计
consistency
Availability
Partition Torlerance
CAP
该理论由加州伯克利分校一名教授于2000年提出,两年后麻省理工学院的两人从理论上证明了其可用性。



分区容错性是最基本要求,因为既然是分布式系统,那么分布式系统中的组件必然需要部署到不同节点,否则无所谓分布式,也因此必然出现子网络。

正因为如此只能再CA中做权衡
响应时间上的损失
功能上的损失
Base Available
Soft state
Causal consistency (因果一致性 )
Read your writes 读己之所写
Session consistency 回话一致性
Monotonic read consistency 单调读一致性
Monotonic write consistency 单调写一致性
Eventually consistent
BASE
由eBay 架构师提出
理论
1. 通讯方式,bio还是nio, 长连接还是短连接
2. 序列化与反序列化,hessian、protobuf、json、kryo等
3. 服务注册与发现
4. 负载均衡及容错
5. 结果缓存
6. 接口多版本控制
实现考虑
motan
新浪微博出的rpc框架
gRPC
1)GRPC尚未提供连接池,需要自行实现 

2)尚未提供“服务发现”、“负载均衡”机制 

3)因为基于HTTP2,绝大部多数HTTP Server、Nginx都尚不支持,即Nginx不能将GRPC请求作为HTTP请求来负载均衡,而是作为普通的TCP请求。(nginx1.9版本已支持) 

4) Protobuf二进制可读性差(貌似提供了Text_Fromat功能)
Apache Thrift
rpcx
go语言版本
dubbox
Dubbo
Http 1.0 短连接
Http 1.1 Keep-Alive 保持长连接
Http 2.0
HTTP - 特殊的RPC
HTTP1.0 协议时,HTTP 调用还只能是短链接调用,一个请求来回之后连接就会关闭。HTTP1.1 在 HTTP1.0 协议的基础上进行了改进,引入了 KeepAlive 特性可以保持 HTTP 连接长时间不断开,以便在同一个连接之上进行多次连续的请求,进一步拉近了 HTTP 和 RPC 之间的距离。



当 HTTP 协议进化到 2.0 之后,Google 开源了一个建立在 HTTP2.0 协议之上的通信框架直接取名为 gRPC,也就是 Google RPC,这时 HTTP 和 RPC 之间已经没有非常明显的界限了
现有案例
RPC
同步
MQ
异步
通信方式
ZAB协议(Zookeeper Atomic Broadcast)
选举机制
1.  各个节点向其他节点发起投票,投票当中包含自己的服务器ID及最新事务ID(ZXID),此时处于Looking状态



2. 节点会用自身的ZXID 与其他节点的ZXID做比较,如果发现其他节点的ZXID比较大,则重新发起投票,投票给目前已知最大的ZXID所属节点



3. 每次投票后,服务器都会统计投票数量,判断是否有某个节点得到半数以上的投票,该节点将会成为准leader, 状态变为 Leading, 其他节点为 Following



4. 发现阶段,检查是否有多个leading状态的节点,即leading接收Followler发来的各自的最新的epoch值,leading从中选出最大的epoch值,基于此值加一,生成epoch分发给各个Follower,

各个Follower 收到最新epoch后返回ACK给leading 节点,带上各自最大的ZXID和历史事务日志



5. 同步阶段,把leader 刚才收集到的最新历史事务日志,同步给集群中所有的Follower, 只有当半数的Follower同步成功,这个leading状态的节点才会成为正式的Leader
Zookeeper
Eureka
etcd
etcd作为一个受到ZooKeeper与doozer启发而催生的项目
Consul
Untitled node
服务注册与发现
服务治理
服务熔断
在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。
TCC
阿里出的柔性事务,是两阶段提交的变种,TCC 即 try, confirm , cancel



1. Try 阶段: 完成所有业务检查,预留必须业务资源



2. Confirm 阶段: 开始执行业务,不做任何业务检查,只使用Try阶段预留的业务资源,该操作满足幂等性



3. Cancel 阶段: 释放Try阶段预留的业务资源,该操作同时满足幂等性
两阶段提交
三阶段提交
分布式事务
强一致性(strong)
单调一致性(monotonic)
会话一致性 (session)
最终一致性(eventual)
弱一致性(weak)
一致性分类
Failover 失败自动切换
当出现失败,重试其它服务器,通常用于读操作(推荐使用)。 重试会带来更长延迟。
Failfast 快速失败
只发起一次调用,失败立即报错,通常用于非幂等性的写操作。 如果有机器正在重启,可能会出现调用失败 。
Failsafe 失败安全
出现异常时,直接忽略,通常用于写入审计日志等操作。 调用信息丢失 可用于生产环境 Monitor。
Failback 失败自动恢复
后台记录失败请求,定时重发。通常用于消息通知操作 ,不可靠,重启丢失。 可用于生产环境 Registry。
Forking 并行调用多个服务器
只要一个成功即返回,通常用于实时性要求较高的读操作。 需要浪费更多服务资源   。
Broadcast 广播调用
广播调用,所有提供逐个调用,任意一台报错则报错。通常用于更新提供方本地状态 速度慢,任意一台报错则报错 。
容错机制
random随机
roundrobin轮询
leastactive最少活跃数
使慢的提供者收到更少请求
consistanthash 一致性哈希
相同参数的请求一定分发到一个 provider 上去,provider 挂掉的时候,会基于虚拟节点均匀分配剩余的流量,抖动不会太大。如果你需要的不是随机负载均衡,是要一类请求都到一个节点,那就走这个一致性 Hash 策略。
负载均衡机制
1. 服务容器启动、加载和运行服务提供者
2. 服务提供者在启动时向注册中心注册自己的服务, 而服务消费者在启动时,在注册中心订阅自己所需的服务
3. 注册中心返回服务提供者地址列表给消费者, 如果有变更,注册中心将基于长连接推送变更数据给消费者
4. 服务消费者从提供地址列表中基于软负载均衡算法 选一台提供者进行调用,如果调用失败再选另一台调用
5. 服务消费者和提供者 ,在内存中累计调用 次数和时间,定时每分钟发送一次统计数据到监控中心
整体流程
dubbo
Dubbo 缺省协议是dubbo协议,采用单一长连接和 NIO 异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。



反之,Dubbo 缺省协议不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低
RMI
RMI协议采用阻塞式(同步)短连接和 JDK 标准序列化方式。适用范围:传入传出参数数据包大小混合,消费者与提供者个数差不多,可传文件。
hessian
Hessian底层采用Http通讯(同步),采用Servlet暴露服务。适用于传入传出参数数据包较大,提供者比消费者个数多,提供者压力较大,可传文件。
协议
Dubbo
1. 服务注册相对要快,因为不需要等注册信息 replicate到其他节点,也不保证注册信息是否replicate成功
2. 当数据出现不一致时,虽然A, B上的注册信息不完全相同, 但每个Eureka节点依然能够正常对外提供服务, 这会出现查询服务信息时如果请求A查不到,但请求B就能查到。 如此保证了可用性但牺牲了一致性。
Netflix Eureka(保证AP)
1.服务注册相比Eureka会稍慢一些。因为Consul的raft协议要 求必须过半数的节点都写入成功才认为注册成功
2.Leader挂掉时,重新选举期间整个consul不可用。保证了强一致性但牺牲了可用性。
Consul(保证CP)
一致性协议与etcd一样使用的是raft协议
服务注册与发现
服务治理
请求路由
版本控制
容错性
服务埋点
缓存
负载均衡
统一接入
限流
降级
流量监控
IP黑白名单
频率限制
频次限制
防止恶意攻击
鉴权
内外网隔离
安全防护
业务隔离
服务网关 Zuul
SpringCloud
Untitled node
常用框架
CPU
网络
IO
内存
...
基础层监控
OS、主机、网络、IO等
消息队列
缓存
数据库
应用容器
网关
RPC
Jvm
...
中间层监控
消息队列、缓存、数据库、应用容器、网关、RPC框架、JVM....
Api请求
吞吐量
响应时间
错误码
调用链路
函数调用栈
业务指标
...
应用层监控
API请求、吞吐量、响应时间、错误码、调用链路、函数调用栈、业务指标等
应用整体监控
CPU
内存
磁盘
网络
...
计算资源调度
考虑虚拟化做支持
服务编排
服务复本
服务容量伸缩
故障服务迁移
服务生命周期管理
...
服务调度
多租户
多租户技术(英语:multi-tenancy technology)或称多重租赁技术,是一种软件架构技术,它是在探讨与实现如何于多用户的环境下共用相同的系统或程序组件,并且仍可确保各用户间数据的隔离性。 多租户简单来说是指一个单独的实例可以为多个组织服务。
架构版本管理
部署、运行、更新、销毁
灰度发布
架构调度
资源、服务调度
多副本保存
数据可用性
读写一致性策略
数据一致性
数据索引、分片
数据分布式
状态、数据调度
服务发现
服务路由
服务降级
服务熔断
服务保护
...
服务治理
负载均衡
流量分配
流量控制
异地灾备
...
流量控制
协议转换
请求校验
数据缓存
数据计算
...
流量管理
流量调度
关键技术
缓存分区
缓存更新
缓存命中
缓存
负载均衡
服务路由
服务发现
负载均衡
消息队列
消息持久
异步事务
异步调用
数据同步
读写分离
数据一致性
数据镜像
分区策略
数据访问层
数据一致性
数据分区
提高架构性能
服务调用
服务依赖
服务隔离
服务拆分
弹性伸缩
故障迁移
服务发现
服务冗余
异步队列
降级控制
服务熔断
限流降级
多租户系统
灾备多活
高可用服务
高可用架构
全栈监控
DevOps
自动化运维
高可用运维
提高架构稳定性
lua脚本
-- 删除锁的时候,找到 key 对应的 value,跟自己传过去的 value 做比较,如果是一样的才删除。

if redis.call("get",KEYS[1]) == ARGV[1] then

    return redis.call("del",KEYS[1])

else

    return 0

end



redis保证脚本会以原子性(atomic)的方式执行:当某个脚本正在运行的时候,不会有其他脚本或 Redis 命令被执行。这和使用 MULTI / EXEC 包围的事务很类似。在其他别的客户端看来,脚本的效果(effect)要么是不可见的(not visible),要么就是已完成的(already completed)。



  另一方面,这也意味着,执行一个运行缓慢的脚本并不是一个好主意。写一个跑得很快很顺溜的脚本并不难,因为脚本的运行开销(overhead)非常少,但是当你不得不使用一些跑得比较慢的脚本时,请小心,因为当这些蜗牛脚本在慢吞吞地运行的时候,其他客户端会因为服务器正忙而无法执行命令。
1. 获取当期毫秒时间,轮流用相同的key和随机值在N个节点上请求锁,在这一步里客户端在每个master上请求锁时,会有一个比总释放时间小的多的超时时间,这样可以防止客户端在某个master 宕机时堵塞过长时间,如果这个master节点不可用我们应尽快尝试下一个master
2. 客户端计算获取锁所花的时间,只有当客户端在大多数master节点上成功获取了锁(在这里是3个),而且总共消耗的时间不超过锁释放时间,这个锁就认为是获取成功了。
3. 如果锁获取成功了,那现在锁自动释放时间就是最初的锁释放时间减去之前获取锁所消耗的时间。
4. 如果锁获取失败了,不管是因为获取成功的锁不超过一半(N/2+1)还是因为总消耗时间超过了锁释放时间,客户端都会到每个master节点上释放锁,即便是那些他认为没有获取成功的锁。
redlock
redisson 官网推荐
Redis实现
可使用setnx , expire指令简单实现,但是无法保证原子性,以及保证各个节点的时钟同步
Zookeeper实现
创建一个永久节点作为锁节点,试图加锁的客户端在锁节点下创建临时顺序节点。Zookeeper会保证子节点的有序性。若锁节点下id最小的节点是为当前客户端创建的节点,说明当前客户端成功加锁。否则加锁失败,订阅上一个顺序节点。当上一个节点被删除时,当前节点为最小,说明加锁成功。操作完成后,删除锁节点释放锁。







1、zk的底层数据结构是树形结构,由一个一个的数据节点组成;



2、zk的节点分为永久节点和临时节点,客户端可以创建临时节点,当客户端会话终止或超时后,zk会自动删除临时节点,该特性可以避免死锁;



3、当节点的状态发生变化时,zk的watch机制会通知监听相应事件的客户端,该特性可以用来实现阻塞等待加锁;



4、客户端可以在某个节点下创建子节点,Zookeeper会根据子节点数量自动生成整数序号,类似于数据库的自增主键;



由于需要频繁的新增和删除节点,性能比较差,不推荐使用。
分布式锁
主从同步(保证CP)
缺点: 一个节点失败,Master堵塞,导致整个集群不可用,保证了一致性,但降低了可用性
多数派
每次写都保证写入大于N/2个节点,每次读保证从大于N/2个节点读。



缺点: 在并发环境下,无法保证系统正确性,顺序非常重要。
1. Client: 系统外部角色,请求发起者,像民众
2. Propser: 接受Client请求,向集群提出提议。 并在冲突发生时,起到冲突协调作用。像议员替民众提出议案。
3. Accepter(Voter) : 提议投票和接受者,只有在形成法定人数 (Quorum, 一般即为majority多数派)时,提议才会最终被接受,像国会。
4. Learner : 提案学习者。一个提案超过半数accpetor通过即可被确定,其他 未确定的Acceptor可以通过learner来同步结果。对集群一致性没影响,像记录员
角色
1. Prepare阶段
proposer提出一个议案,编号为N,N大于此proposer之前提出的提案编号。请求accepters的quorum接受
2. Promise阶段
如果该提案编号N大于accepter之前接受的任何提案编号则承诺接受该提案,否则拒绝。
未超过半数的accpetor回复承诺,则本次提案失败
1. 所有Acceptor都未接收过value(都为null),那么向 所有的Acceptor发起(Propose)自己的value和提案编号n
2. 如果有部分Acceptor接收过value,那么从接受过的value中 选择提案编号最大的value作为本次提案的value,提议编号仍然为n
超过半数的Acceptor回复承诺,又分为不同情况
3. Accept阶段
如果proposer发现接受该议案的accepter达到多数派,proposer会发出accept请求,此请求包含提案编号N,以及提案内容。
4. Accepted阶段
如果accepter在此期间没有收到任何编号大于N的提案,则接受此提案的内容,否则忽略,转而考虑下一个新的提案
流程
Untitled node
时序图
活锁
即在第二阶段有一个条件:若提案阶段发现有其他更新的提案编号N则放弃当前提案,转而考虑最新的提案,万一在考虑最新过程也出现了该情况,那将是一个无限循环
难实现
效率低
经过两轮RPC远程过程调用
缺点
Basic Paxos
Multi Paxos
Fast Paxos
Paxos
Leader Election
怎么选Leader
Log Replication
怎么讲log同步到其他节点上
Safety
怎么保证操作中集群的共识必须一致,

即在非拜占庭错误的情况下:网络延迟,分区,丢包,重复
划分三个子问题
Leader
所有请求的处理者,Leader副本接受client的更新请求,本地处理后再同步至多个其他副本;
Follower
请求的被动更新者,从Leader接受更新请求,然后写入本地日志文件
Candidate
如果Follower副本在一段时间内没有收到Leader副本的心跳,则判断Leader可能已经故障,此时启动选主过程,此时副本会变成Candidate状态,直到选主结束。
角色
选举过程
Raft 协议在集群初始状态下是没有 Leader 的, 集群中所有成员均是 Follower,在选举开始期间所有 Follower 均可参与选举,这时所有 Follower 的角色均转变为 Condidate, Leader 由集群中所有的 Condidate 投票选出,最后获得投票最多的 Condidate 获胜,其角色转变为 Leader 并开始其任期,其余落败的 Condidate 角色转变为 Follower 开始服从 Leader 领导。这里有一种意外的情况会选不出 Leader 就是所有 Condidate 均投票给自己,这样无法决出票数多的一方,Raft 算法为了解决这个问题引入了北洋时期袁世凯获选大总统的谋略,即选不出 Leader 不罢休,直到选出为止,一轮选不出 Leader,便令所有 Condidate 随机 sleap(Raft 论文称为 timeout)一段时间,然后马上开始新一轮的选举,这里的随机 sleep 就起了很关键的因素,第一个从 sleap 状态恢复过来的 Condidate 会向所有 Condidate 发出投票给我的申请,这时还没有苏醒的 Condidate 就只能投票给已经苏醒的 Condidate ,因此可以有效解决 Condiadte 均投票给自己的故障,便可快速的决出 Leader。

选举出 Leader 后 Leader 会定期向所有 Follower 发送 heartbeat 来维护其 Leader 地位,如果 Follower 一段时间后未收到 Leader 的心跳则认为 Leader 已经挂掉,便转变自身角色为 Condidate,同时发起新一轮的选举,产生新的 Leader。



作者:谢烟客

链接:https://www.jianshu.com/p/aa77c8f4cb5c

来源:简书

简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
子主题 4
Raft
参考动态过程: http://thesecretlivesofdata.com/raft/



https://raft.github.com
强一致性
一致性算法
来源: https://www.bilibili.com/video/av21667358?from=search&seid=10292787426433061883
1. 协调者向各个节点发送提交事务的消息
2. 各节点接收到消息后开始执行事务但不提交,同时记录Redo、Undo日志
3. 各节点执行完成事务后向协调者反馈执行结果
此处会引申出两将军问题及拜占庭将军问题
投票
两将军问题:
1. 向各节点发送提交请求
2. 节点开始提交事务
执行事务提交,并在完成后释放期间所占用的资源
1. 全部响应Yes
执行业务的事务失败
网络超时
原因
2. 部分响应No
3. 反馈事务提交结果
向协调者发送ack消息
4. 完成事务
协调者接收到到所有参与者反馈后完成事务
提交事务
1. 发送回滚请求
2. 利用Undo信息回滚事务
3. 反馈事务结果
4. 完成终端机事务
回滚事务
执行
协调者根据各个节点反馈的情况来决定是否进行事务提交,
提交过程
原理简单,实现方便
优点
同步阻塞
单点问题
协调者在整个阶段中起到非常重要的作用,一旦协调者出现问题那整个提交流程将无法运转,如果是在第二个阶段中出现问题,那么其他参与者将一直处于锁定资源状态,而无法完成其他事务操作
数据不一致
过于保守
缺点
二阶段提交
绝大部分的关系型数据库都是采用二阶段提交协议来完成分布式事务的处理
1. 询问各个节点是否可以执行事务
2. 各个节点向协调者反馈询问结果
CanCommit
这期间只询问而不执行事务,也就不会锁定资源
协调者向各个节点发送预提交请求
各个节点执行事务预提交,并记录Redo Undo 日志
各个节点反馈执行结果
执行事务预提交
所有节点都返回yes ,则执行预提交
中断事务
只要有一个节点反馈no 则中断事务执行
PreCommit
发送提交请求
各个节点提交事务
反馈事务提交结果
完成事务
提交事务
中断事务
doCommit
提交过程
三阶段提交
三阶段提交即将二阶段提交协议中的第一步投票阶段拆分为两个阶段:
1. 先询问  2. 再锁定资源。

所以三阶段的核心理念是: 在询问的时候并不锁定资源,除非所有人都同意了,才开始锁资源
Paxos算法
一致性协议
参考文章:
1. 分布式系统的事务处理  https://coolshell.cn/articles/10910.html
1. 若是用户数据则几率较小
(1)修改服务Service连接池,id取模选取服务连接,能够 保证同一个数据的读写都落在同一个后端服务上
(2)修改数据库DB连接池,id取模选取DB连接, 能够保证同一个数据的读写在数据库层面是串行的
2. 对于读服务场景,可以考虑使用一致性哈希,将相同的操 作负载均衡到同一个实例,从而减少并发几率。或设置比较短的过期时间
3. 使用分布式锁,不考虑操作顺序
4. 假如要求顺序操作,则可使用MVCC数据多版本并发控制
即为数据表增加一个版本属性,用于记录
可能存在多个应用实例并发更新的情况
Cache Aside
read-through
业务代码首先调用cache,如果cache没有命中则有cache回源到SoR,而不是业务代码
write-through
穿透写模式或直写模式,业务代码首先调用Cache写数据,然后由cache负责写缓存和SoR,而不是业务代码
write-behind
回写模式,该模式属于异步写模式,异步之后可以实现批量写、合并写、延时和限流
Cache-As-SoR
常见模式
1. 将相应的key放入队列中待后续继续重试
2. 通过订阅数据库的binlog日志来删除 (canal中间件),若仍删除失败则将key放入消息队列
缓存删除失败如何处理
缓存与数据库双写一致性问题
缓存雪崩
缓存雪崩,是指在某一个时间段,缓存集中过期失效。
缓存击穿
缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
并发
缺点
string
list
set
hash
zset
带分值score的set集合,非常适合范围查询。

Sorted Sets中的Score是个64位整数类型,其范围在-9007199254740992到9007199254740992之间,这是一个非常有用的关键点。



我们通常可以用它进行简单的范围查询,比如把年龄当分数,通过ZRANGEBYSCORE查询某个年龄段的所有用户。
数据结构
blpop/brpop
阻塞,Redis去拿数据,假如不存在或者没有,则一直处于等待状态,直到超时或者有数据为止;
没有limit,我们只能一次性获取所有符合条件的key,如果结果有 上百万条,那么等待你的就是“无穷无尽”的字符串输出。
keys命令是遍历算法,时间复杂度是O(N)。这个命令非常容易 导致Redis服务卡顿。因此,我们要尽量避免在生产环境使用该命令。
keys
该命令是获取所有符合条件的key,条件可以是正则表达。keys命令按照正则匹配得到我们需要的key
scan命令的时间复杂度虽然也是O(N),但它是分次进行的,不会阻塞线程。
scan命令提供了limit参数,可以控制每次返回结果的最大条数。
它返回的结果有可能重复,因此需要客户端去重
scan
命令
Pipeline
redis是请求应答的形式,若同时执行大量的命令,则需要等待上一条命令应答后再执行。中间不仅多了往返时间,还频繁调用系统IO。



为了提升效率,这时候Pipeline出现了,它允许客户端可以一次发送多条命令,而不等待上一条命令执行的结果。不仅减少了RTT,同时也减少了IO调用次数(IO调用涉及到用户态到内核态之间的切换)
1. Slave连接Master,首次连接会进行完全同步。Master创建一个RDB文件 (快照方式),发送给Slave,并在发送期间使用缓冲区记录该期间客户端 请求的写命令。RDB发送完毕之后,开始向Slave发送存储在缓冲区中的写命令;
2. Slave丢弃所有旧数据,载入Master发来的RDB文件,之后开始接受Master 发来的写命令。Master每执行一次写命令,将向Slave发送相同的写命令;
3. 如果Slave中断后重新启动,向Master请求数据,会先发送自己的 Replication ID及offset与Master进行对比,如果Replication ID一致, 即可在offset的基础上进行部分缺失数据同步。
psync1
如果 slave 以前没有复制过任何 master,或者之前执行过 SLAVEOF NO ONE 命令,那么 slave 在开始一次新的复制时将向主服务器发送 PSYNC ? -1 命令,主动请求 master 进行完整重同步(因为这时不可能执行部分重同步)。相反地,如果 slave 已经复制过某个 master,那么 slave 在开始一次新的复制时将向 master 发送 PSYNC runid offset 命令:其中 runid 是上一次复制的 master 的运行ID,而 offset 则是 slave 当前的复制偏移量,接收到这个命令的 master 会通过这两个参数来判断应该对 slave 执行哪种同步操作。



根据情况,接收到 PSYNC 命令的 master 会向 slave 返回以下三种回复的其中一种:



如果 master 返回 +FULLRESYNC runid offset 回复,那么表示 master 将与 slave 执行完整重同步操作:其中 runid 是这个 master 的运行 ID,slave 会将这个 ID 保存起来,在下一次发送 PSYNC 命令时使用;而 offset 则是 master 当前的复制偏移量,slave 会将这个值作为自己的初始化偏移量



如果 master 返回 +CONTINUE 回复,那么表示 master 将与 slave 执行部分同步操作,slave 只要等着 master 将自己缺少的那部分数据发送过来就可以了



如果 master 返回 -ERR 回复,那么表示 master 的版本低于 Redis 2.8,它识别不了 psync 命令,slave 将向 master 发送 SYNC 命令,并与 master 执行完整同步操作



由此可见 psync 也有不足之处,当 slave 重启以后 master runid 发生变化,也就意味者 slave 还是会进行全量复制,而在实际的生产中进行 slave 的维护很多时候会进行重启,而正是有由于全量同步需要 master 执行快照,以及数据传输会带不小的影响。因此在 4.0 版本,psync 命令做了改进,我们称之为 psync2。
psync2
Redis 4.0 版本新增 混合持久化,还优化了 psync(以下称 psync2),psync2 最大的变化支持两种场景下的部分重同步,一个场景是 slave 提升为 master 后,其他 slave 可以从新提升的 master 进行部分重同步,这里需要 slave 默认开启复制积压缓冲区;另外一个场景就是 slave 重启后,可以进行部分重同步。这里要注意和 psync1 的运行 ID 相比,这里的复制 ID 有不一样的意义。
流程
全量重同步
让 master 生成并发送 RDB 文件,然后再将保存在缓冲区中的写命令传播给 slave 来进行同步,相当于只有同步和命令传播两个阶段。
replication backlog buffer
复制积压缓冲区是 master 维护的一个固定长度的 FIFO 队列,默认大小为 1MB。当 master 进行命令传播时,不仅将写命令发给 slave 还会同时写进复制积压缓冲区,因此 master 的复制积压缓冲区会保存一部分最近传播的写命令。当 slave 重连上 master 时会将自己的复制偏移量通过 PSYNC 命令发给 master,master 检查自己的复制积压缓冲区,如果发现这部分未同步的命令还在自己的复制积压缓冲区中的话就可以利用这些保存的命令进行部分同步,反之如果断线太久这部分命令已经不在复制缓冲区中了,那没办法只能进行全量同步。
offset
执行主从复制的双方都会分别维护一个复制偏移量,master 每次向 slave 传播 N 个字节,自己的复制偏移量就增加 N;同理 slave 接收 N 个字节,自身的复制偏移量也增加 N。通过对比主从之间的复制偏移量就可以知道主从间的同步状态。
run_id
令人疑惑的是上述逻辑看似已经很圆满了,这个 run_id 是做什么用呢?其实这是因为 master 可能会在 slave 断线期间发生变更,例如可能超时失去联系或者宕机导致断线重连的是一个崭新的 master,不再是断线前复制的那个了。自然崭新的 master 没有之前维护的复制积压缓冲区,只能进行全量同步。因此每个 Redis server 都会有自己的运行 ID,由 40 个随机的十六进制字符组成。当 slave 初次复制 master 时,master 会将自己的运行 ID 发给 slave 进行保存,这样 slave重连时再将这个运行 ID 发送给重连上的 master ,master 会接受这个 ID 并于自身的运行 ID 比较进而判断是否是同一个 master。
部分重同步
部分同步适用于断线重连之后的同步,slave 只需要接收断线期间丢失的写命令就可以,不需要进行全量同步。为了实现部分同步,引入了复制偏移量(offset)、复制积压缓冲区(replication backlog buffer)和运行 ID (run_id)三个概念。
概念
1. Reis 2.8 版本之前 Redis 复制采用 sync 命令,无论是第一次主从复制 还是断线重连后再进行复制都采用全量同步,成本高
2. Reis 2.8 ~ 4.0 之间复制采用 psync 命令,这一特性主要添加了 Redis 在断线重连时候可通过 offset 信息使用部分同步
3. Reis 4.0 版本之后也采用 psync,相比于 2.8 版本的 psync 优化了 增量复制,这里我们称为 psync2,2.8 版本的 psync 可以称为 psync1
演变
Replication主从复制
从 Redis 2.6 到 4.0 开发人员对复制流程进行逐步的优化,以下是演进过程:



2.8 版本之前 Redis 复制采用 sync 命令,无论是第一次主从复制还是断线重连后再进行复制都采用全量同步,成本高

2.8 ~ 4.0 之间复制采用 psync 命令,这一特性主要添加了 Redis 在断线重连时候可通过 offset 信息使用部分同步

4.0 版本之后也采用 psync,相比于 2.8 版本的 psync 优化了增量复制,这里我们称为 psync2,2.8 版本的 psync 可以称为 psync1







为了解决旧版 SYNC 在处理断线重连复制场景下的低效问题,Redis 2.8 采用 PSYNC 代替 SYNC 命令。PSYNC 命令具有全量同步和部分同步两种模式。



https://redis.io/topics/replication



http://antirez.com/news/11
everysec
每秒执行一次,显式的将多个写命令同步到磁盘
no
由操作系统决定何时进行同步
always
严重降低Redis 性能
同步策略
重写或压缩
AOF
RDB 快照
数据丢失较严重, 就针对数据实时性要求不高的场景
持久化
定时过期
每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
惰性过期
只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
定期过期
每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
Redis中同时使用了惰性过期和定期过期两种过期策略
过期策略
noeviction
当内存不足以容纳新写入数据时,新写入操作会报错。
allkeys-lru
当内存不足以容纳新写入数据时,移除最近最少使用的key。
volatile-lru
当内存不足以容纳新写入数据时,对设置了过期时间的key,移除最近最少使用的key。
volatile-lfu
当内存不足以容纳新写入数据时,对设置了过期时间的key,移除最近不经常使用的key。
allkeys-lfu
当内存不足以容纳新写入数据时,移除最近不经常使用的key。
volatile-random
allkeys-random
volatile-ttl
移除存活时间最短的key
LFU算法从4.0版本开始引入
内存淘汰策略
Sorted Set
Redis中的sorted set,是在skiplist, dict和ziplist基础上构建起来的:



当数据较少时,sorted set是由一个ziplist来实现的。

当数据多的时候,sorted set是由一个叫zset的数据结构来实现的,这个zset包含一个dict + 一个skiplist。dict用来查询数据到分数(score)的对应关系,而skiplist用来根据分数查询数据(可能是范围查找)。

在这里我们先来讨论一下前一种情况——基于ziplist实现的sorted set。在本系列前面关于ziplist的文章里,我们介绍过,ziplist就是由很多数据项组成的一大块连续内存。由于sorted set的每一项元素都由数据和score组成,因此,当使用zadd命令插入一个(数据, score)对的时候,底层在相应的ziplist上就插入两个数据项:数据在前,score在后。



ziplist的主要优点是节省内存,但它上面的查找操作只能按顺序查找(可以正序也可以倒序)。因此,sorted set的各个查询操作,就是在ziplist上从前向后(或从后向前)一步步查找,每一步前进两个数据项,跨域一个(数据, score)对。



http://zhangtielei.com/posts/blog-redis-skiplist.html
ziplist
http://zhangtielei.com/posts/blog-redis-ziplist.html
skiplist
http://zhangtielei.com/posts/blog-redis-skiplist.html
实现
主从模式
Redis Sentinel 水平扩容一直都是程序猿心中的痛点,因为 水平扩容牵涉到数据的迁移。迁移过程一方面要保证自己的 业务是可用的,一方面要保证尽量不丢失数据所以数据能不 迁移就尽量不迁移
缺陷
sentinel
cluster
高可用
redis
将没有查询到数据的key也设置到缓存中,设置一定过期时间
使用布隆过滤器,将所有可能存在的数据哈希到一个足够大的位图中
缓存穿透
客户端请求一个不存在的数据,导致缓存无法命中,从而导致每次查询都会直接请求数据库,黑客可利用此漏洞发起攻击。
将热点数据设置为永不过期
基于 redis or zookeeper 实现互斥锁,等待第一个请求 构建完缓存之后,再释放锁,进而其它请求才能通过该 key 访问数据
缓存击穿
一个非常热点的key在过期时被超高并发访问,从而直接请求到数据库
事前 : redis高可用,主从+ 哨兵,redis cluster,避免全盘崩溃
事中: 本地 ehcache缓存 + hystrix 限流& 降级,避免MySQL崩溃
事后: Redis 持久化,一旦重启自动从磁盘上加载数据,快速恢复缓存数据
缓存雪崩
缓存服务宕机导致大量请求直接请求到数据库,数据库面对大量请求无法及时处理也导致其崩溃
常见问题
缓存
垂直扩容
垂直扩容表示通过加内存方式来增加整个缓存体系的容量比如将缓存大小由2G调整到4G,这种扩容不需要应用程序支持
水平扩容
水平扩容表示表示通过增加节点的方式来增加整个缓存体系的容量比如本来有1个节点变成2个节点,这种扩容方式需要应用程序支持
集群扩容
垂直扩容看似最便捷的扩容,但是受到机器的限制,

一个机器的内存是有限的,所以垂直扩容到一定阶段不可避免的要进行水平扩容,如果预留出很多节点感觉又是对资源的一种浪费因为对业务的发展趋势很快预测。
应用无状态
若设计的应用是无状态的,那么应用比较容易进行水平扩展
系统维度
功能维度
AOP维度
读写维度
模块维度
拆分
服务化
消息队列
缓存
高并发