共计 6779 个字符,预计需要花费 17 分钟才能阅读完成。
Redis 数据结构
- String 字符串
-
Hash
- 存储对象
-
List
- 微博关注列表、音讯列表
- 双向链表,反对反向查找和遍历
- lrange 命令反对从某个元素开始读取多少个元素,实现分页查问,基于 Redis 实现简略的高性能分页,性能高
-
Set
- 主动排重,很轻易实现交加、并集、差集的操作。比方独特关注,独特好友等性能
-
Sorted Set
- 排序的实现:减少了一个权重参数 score,使汇合中的元素可能依照 score 进行有序排列。比方直播列表中的礼物排行榜,在线用户列表等
- 保障增删改查的速度:SkipList 跳跃表
- 跳跃表数据结构:set 汇合中每个元素任意生成层数,每次增删改查都从第一层开始查问,查问到两数之间的时候调至下一层持续放大范畴,晓得最初查问到具体数据或者最小区间
Redis 做异步音讯队列
- Redis 中的 list(列表)实现异步音讯队列,应用 rpush / lpush 操作插入队列音讯,应用 lpop 和 rpop 来出队音讯。应用场景:MQ 工作过多时,把工作存在 Redis,进行异步生产
-
队列空了怎么办?
- 队列空了,客户端一直 pop,因为没有数据会不停地进行 pop,这样的空轮询会占用 CPU 资源,redis 的 QPS 被拉高,Redis 慢查问可能会显著很多
- 解决方案:应用阻塞读:blpop,brpop。阻塞读在队列没有数据的时候会立刻进入休眠状态,一旦有数据会立刻醒来,生产提早根本为零。
- 下面的计划中,如果阻塞工夫过长,Redis 客户端连贯就成了闲置连贯,闲置过久服务器会被动断开连接,缩小闲置资源占用,这时候 blpop,brpop 就会抛出异样来。所以编写客户端消费者时须要留神捕获异样,减少重试机制
Redis 长久化
写时复制(copy on write)
在执行 bgsave 或 bgrewriteaof 命令时,Redis 都会须要创立以后服务器的子过程,采纳 COW 的形式来进步子过程应用效率。
原理
fork()出子过程共享父过程的物理空间,kernel 将父过程所有内存页设置为 read-only,当父过程 (或者子过程) 有内存写入操作时,read-only 内存页产生中断,将触发中断操作的内存页复制一份(linux 单位内存页 4KB),父子过程各自持有独立的一份(其余局部还是父子过程共享)
优缺点
- COW 技术缩小调配和复制大量资源时带来的霎时延时
- COW 技术可缩小不必要的资源分配。比方 fork 子过程时,父过程的代码段和只读数据段是不容许被批改的,就不须要复制
RDB 和 AOF
RDB
- Redis DataBase 快照
- Redis 默认长久化形式
- 依照肯定的工夫将内存的数据以二进制文件的格局,快照的形式保留在硬盘中
-
配置文件中 save 参数定义快照周期:
- save 900 1
- save 300 10
- save 60 10000
-
长处:
- 只有一个文件 dump.rdb 不便长久化
- 容灾性好,保留在磁盘
- 性能最大化,fork 子过程进行快照的写入操作,主过程持续解决命令,主过程不进行任何 I / O 操作,保障 Redis 高性能
- 数据集大时,启动效率比 AOF 高
-
毛病:
- 时效性不强,距离一段时间进行长久化,容易造成数据失落
AOF
- append-only file 追加文件
- 将 Redis 执行的每次写命令记录到独自的日志文件,重启 Redis 会从新从长久化的日志文件复原数据
- 两种长久化形式同时开启时,Redis 优先选择 AOF 进行数据恢复
-
appendfsyncs 设置写频率:
- appendfsync always
- appendfsync everysec(默认)
- appendfsync no
-
长处:
- 时效性强,数据安全
- 通过 append 模式写文件,数据失落少
- AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前,能够删除其中的某些命令
-
毛病:
- AOF 文件比 RDB 文件大,复原速度慢
- 数据集大时,启动效率比 RDB 低
Redis 线程模型
-
基于 Reactor 模式开发的文件事件处理器:
- 多个套接字
-
IO 多路复用程序(epoll、线程与 kernel 内存共享空间 mmap)
- 文件事件分派器
- 事件处理器
- 因为文件事件分派器队列生产是单线程,所以 Redis 被叫做单线程模型
- 利用 I / O 多路复用同时监听多个套接字,事件分派器依据套接字目前执行工作为套接字关联不同的事件处理器
- 因为应用 I / O 多路复用监听多个套接字,文件工夫处理器既实现了高性能的网络通信模型,又很好地与 Redis 服务器中其余同样以单线程形式运行的模块进行对接,放弃 Redis 外部单线程设计的简略性
Redis 内存淘汰机制
先说下 Redis 过期策略:定时删除和被动删除
- 定期删除:每过 100ms,Redis 会随机去查看一些设置了过期工夫的数据,如果过期就删除,之所以随机拜访局部数据的起因是:大部分数据都设置了过期工夫,如果每次都遍历去拜访,会占用 CPU,Redis 没法保障高性能
- 被动删除:拜访数据时会检查数据是否过期,如果过期就删除数据
然而如果有很多数据过期了,然而咱们又没有去拜访它,就会造成即便数据过期了,这些过期数据依然沉积在内存,导致内存耗尽。这时候就须要 Redis 内存淘汰机制。
内存淘汰机制包含:
- volatile-lru:从设置了过期工夫的数据集中筛选最近起码应用的数据淘汰
- volatile-ttl:从设置了过期工夫的数据集中筛选行将过期的数据淘汰
- volatile-random:从设置了过期工夫的数据集中随机抉择数据淘汰
- volatile-lfu:从设置了过期工夫的数据集中筛选起码应用的数据淘汰
- allkeys-lru:从所有数据集中筛选最近起码应用的数据淘汰
- allkeys-lfu:从所有数据集中筛选起码应用的数据淘汰
- allkeys-random:从所有数据集中随机抉择数据淘汰
- no-eviction:禁止驱赶数据,当内存不足以包容新写入的数据时,新写入操作报错
Redis 并发竞争问题
- Redis 自身是单线程模型,不存在并发问题,然而咱们应用 jedis 等客户端对 Redis 进行并发拜访的时候,就存在并发竞争的问题
- 应用 Redis 分布式锁。Redis 散布锁实现次要是利用 redis 的 SETNX。无论是哪种分布式锁基本原理都是不变的,都是通过数据携带一个标识,依据标识的状态来判断是否获取到锁
- 音讯队列。并发量过大的状况能够应用消息中间件,将 Redis.set 操作放在队列中一个一个执行
Redis 分布式锁
- 手写实现繁难的乐观锁实现(我的项目里有实现)
- 基于 Redisson 实现
- 基于 Zookeeper 实现
Redisson
redisson 具体执行加锁逻辑都是通过 lua 脚本实现的,lua 脚本可能保障原子性。
RLock lock = redisson.getLock(“anyLock”);
lock.lock();
lock.unlock();
- 可重入锁
- watchDog 原理:当线程 A 拿到锁,线程 A 锁超时开释,然而线程 A 的业务还没有执行完;这时候线程 B 拿到锁,执行本人的业务,这样分布式锁就没有意义了。Redisson 引入 watch dog 的概念,当线程拿到锁,会有一个后盾线程主动缩短锁的过期工夫,避免业务没执行完而锁过期的状况。
Zookeeper 分布式锁
- 线程每次在 zookeeper 中争抢锁,只有一个能取得锁
- 应用 Zookeeper 长期节点(session),长期节点当客户端连贯中断,存活工夫耗完就主动删除。这样能够避免取得锁的客户端呈现问题而导致死锁。
- sequence(序列节点) + zk 的 watch 机制:多个线程竞争时,按序列顺次 watch 前一个线程,最小序列取得锁,开释后给后一个节点发事件回调,后一个节点尝试取得锁。这样限度了 Redis 并发竞争的问题,资源耗费压力也小。
Redis 事务
-
Redis 事务通过 MULTI,WATCH,EXEC,DISCARD 四个原语实现
- WATCH 命令是一个乐观锁,能够监控一个或者多个 key,一旦其中一个 key 被批改(或删除),之后的事务不执行,监控始终继续 EXEC 命令被执行。
- MULTI 用于开启一个事务,总是返回 OK。MULTI 之后客户端能够向服务器发送任意多条命令放在一个队列中,当 EXEC 命令被调用时所有队列的命令能力执行。
- EXEC:执行所有事务块内的命令,命令按队列先后顺序排列。
- DISCARD:清空事务队列,退出事务。
- 事务隔离,多个线程进入,谁先执行 EXEC 命令就先执行谁,排他性。
- Redis 事务不反对回滚。有命令执行失败则继续执行之后的命令,保障 Redis 外部简略且疾速(性能为王)。
- Redis 命令有谬误,则所有命令都不执行;命令呈现运行谬误,继续执行之后的命令。
- Redis 满足 ACID 的一致性和隔离性(事务管理 ACID:Atomicity 原子性;Consistency 一致性;Isolation 隔离性;Durability 持久性)
Redis 主从复制
Redis 主从复制,一主多从,读写拆散。主节点负责写操作,并将数据复制到其余 slave 节点,从节点负责读。所有读申请全副走从节点,撑持读高并发。
复制模式
- 全量复制:Master 全副同步到 Slave
- 局部复制:Slave 数据失落进行备份
Redis 主从复制外围原理
- 当开启一个 slave node 时,它会发送一个 PSYNC 命令给 master node
- 如果是 slave node 首次连贯到 master node,则触发一次 全量复制(full resynchronization)。此时 master 会启动一个后盾线程,开始生成一份 RDB 快照文件,同时还会将从客户端 client 新收到的所有写命令缓存在内存中。
- RDB 文件生成结束后,master 会将这个 RDB 文件发送给 slave,slave 先写入本地磁盘,在从本地磁盘加载到内存中
- 而后 master 会将内存中缓存的写命令发送到 slave,slave 也会同步这些数据
- slave 节点如果和 master 节点有网络故障,断开连接后会主动重连,重连后 master 节点只会复制给 slave 局部短少的数据 ( 局部复制)
过程原理
- 当主从库建设 MS 关系后,slave 会向 master 发送 SYNC 命令
- master 库承受到 SYNC 命令后,就开始在后盾保留 RDB 快照,并将期间接管的写命令缓存起来
- 当快照实现,master 会将快照文件和所有缓存的写命令发送给 slave
- slave 接管到后,会载入快照文件并执行收到的缓存的写命令
- 之后,master 每当承受到写命令都会将命令发送给 slave,从而保证数据统一
主从配置不一样会有什么问题
- maxmemory 不统一:数据失落
- 优化参数不统一:内存不统一
毛病
所有 slave 节点数据的复制和同步都有 master 节点解决,master 节点压力过大。
哨兵模式
哨兵模式用于监控 Redis 主从节点,实现 redis 集群的高可用,自身也是分布式的,作为一个哨兵集群去运行,相互协同工作。
节点下线
- 主观下线:Sentinel 集群每一个节点会定时对 Redis 集群的所有节点发送心跳包检测节点是否失常,如果节点在 down-after-milliseconds 工夫内没有回复 Sentinel 节点的心跳包,则该 Sentinel 节点就会认为这个 Redis 节点下线
- 主观下线:所有 Sentinel 节点对 Redis 节点失败要达成共识,即超过 quorum 数量的 Sentinel 都认为该 Redis 节点主观下线,Redis 才真正被认为下线
Leader 选举
- 选举一个 Sentinel 作为 Leader:集群中至多要有三个 Sentinel 节点,但只有 Leader Sentinel 节点能力实现故障转移
-
选举流程
- 每个主观下线的 Sentinel 节点向其余 Sentinel 节点发送命令,要求设置本人为领导者
- 收到命令的 Sentinel 节点如果没有批准其余 Sentinel 节点发送的命令,则批准该申请,否则回绝
- 如果该 Sentinel 节点发现自己的票数曾经超过 Sentinel 汇合半数且超过 quorum,则它成为领导者
- 如果在这个过程中有多个 Sentinel 节点成为领导者,则期待一段时间再从新选举
故障转移
- 选举出的 Leader Sentinel 进行故障转移;Sentinel 集群运作过程中故障转移实现,所有 Sentinel 又会复原平等。Leader 仅仅是故障转移操作呈现的角色
-
转移流程
- Sentinel 选出一个适合的 Slave 节点作为新的 Master
- 向其余 Slave 发出通知,让它们成为新 Master 的 Slave
- 期待旧 Master 复活,并使之成为新 Master 的 Slave
- 向客户端告诉 Master 变动
-
抉择新 Master 节点的规定:
- 抉择 slave-priority 最高的节点
- 抉择复制偏移量最大的节点(同步数据最多)
- 抉择 runId 最小的节点
-
定时工作
- 每 1s 每个 Sentinel 对其余 Sentinel 和 Redis 节点执行 ping,进行心跳检测
- 每 2s 每个 Sentinel 通过 Master 的 Channel 替换信息(pub-sub)
- 每 10s 每个 Sentinel 对 Master 和 Slave 执行 info,目标是发现 Slave 节点,确定主从关系
-
毛病
- 主从服务器的数据要常常进行主从复制,性能受影响
- 当主服务器宕机,Slave 节点切换成 Master 节点那段时间,服务不可用
一致性 Hash
- 如果间接应用 Hash 算法 hash(key) % length,当缓存服务器变动时(宕机或新增节点),length 字段发送变动,就会导致所有缓存数据都要从新进行 Hash 运算,原来的数据就拜访不到了,大量数据申请容易导致服务器雪崩,因而,引入一致性 Hash
- 一致性哈希:通过对 2^32 取模来计算 Hash 值,保障增删缓存服务器时数据不变。
- 将 2^32 设想成一个环,一致性哈希算法通过将缓存服务器和被缓存对象都映射到 Hash 环上当前,从被缓存对象的地位登程,沿顺时针方向遇到的第一个服务器就是以后对象将被缓存的服务器,因为被缓存对象和服务器 Hash 后的值固定,所以在服务器不变的状况下,一个对象必然缓存在一个固定的服务器上。
- Hash 环偏斜问题:
将现有的物理节点通过虚构的办法复制进去,而复制进去的节点被称为 ”虚构节点“,让服务器尽量多的,平均的呈现在 Hash 环(留神 Hash 环是一个虚构进去的模型,不便了解,不是实在的)
- 一致性 Hash 解决容灾问题:
如果其中一个节点宕机了,其余服务器的对象依然能被命中,因为对象的映射到服务器的地位曾经固定了,不会因为服务器宕机而导致独享找不到,宕机的服务器上的对象会在下次容灾调配时,重新分配到就近的服务器上
缓存策略
更新策略
allkeys-lru、volatile-lru 最近起码
缓存最终一致性
- 读申请:先读缓存,缓存没有就读数据库,取出数据放入缓存,同时返回响应
- 写申请:先更新数据库,而后再删除缓存(间接删除缓存的起因:1. 是为了防止大量写而不常常读的状况,造成冷数据过多,导致缓存频繁更新 2. 两个线程同时进行更新操作,线程 A 先更新了数据库,线程 B 后更新数据库,然而因为一些起因导致线程 B 比线程 A 先更新了缓存,导致了脏数据。所以间接删除缓存是最好的)
* 利用间接写数据到数据库中
* 数据库更新 binlog 日志
* 利用 Canal 中间件读取 binlog,并借助限流组件按频率将数据发到 MQ
* 利用监控 MQ 通道,将 MQ 的数据更新到 Redis 缓存中
缓存穿透
- 当大量申请没有命中缓存(代码 bug,歹意攻打,高并发),间接申请到后端数据库,无奈利用缓存,流量过大而导致数据库压力过大
- 解决方案:采纳布隆过滤器,redis 继承了一个 bloomFilter
-
过滤原理:
- 筹备一个 bit 数组,用于寄存 Hash 值
- 筹备 n 个映射函数(输出:redis 的 key 值和 salt;输入:int 类型的 hash 值)
- 每次有新 key 进入 redis,都用这 n 个映射函数对 key 计算 hash 值,计算出来的值对应 bit 数组上的地位设置为 1
- 当有新的查问时,将须要查问的 key 通过这 n 个映射函数求取 hash 值,如果这些 hash 值对应 bit 数组的位数都为 1,则阐明这个 key”可能“ 在缓存中,让它先走缓存查找;如果有一个 hash 值对应位数为 0,则阐明这个 key 肯定不在缓存中,间接走数据库查找。
缓存雪崩
- 一时间大量缓存生效或者缓存解体,申请间接落在数据库上,很可能因为无奈接受大量的并发申请导致数据库解体
-
应答计划
- 事先:Redis 主从复制 + 哨兵模式,Redis Cluster,保障高可用防止崩盘
- 事中:减少本地 ehcache 缓存 +hystrix 限流 & 降级,防止数据库接受太多压力
- 预先:Redis 长久化(RDB,AOF),一旦重启,主动从磁盘上加载数据,疾速回复缓存数据
-
申请过程
- 用户申请的时候先拜访本地缓存,没有命中本地缓存再拜访 Redis,如果还有没有命中 Redis 缓存,再查询数据库,并把数据增加到 Redis 和本地缓存中
- 设置了限流,一段时间内超出的申请走降级解决(hystrix 反对本人定义降级办法,返回默认值或者给出情谊提醒就好了)