Redis
2020-09-04 10:21:11 0 举报
AI智能生成
Redis-知识体系梳理
作者其他创作
大纲/内容
缓存失效
缓存雪崩
现象:很多key同一时间失效,导致大量请求同时打到DB,造成DB负载过高
解决
1、设置不同的过期时间,让缓存失效的时间点尽量均匀
2、加锁排队,通过setnx去set一个mutex key,拿到锁后再操作数据库
3、缓存预热,通过reload机制,或者手动触发加载缓存
4、双层缓存,C1为原始缓存,C2为拷贝缓存,C1失效时,可以访问C2,C1失效时间设置为短期,C2失效时间设置为长期
5、定时刷新缓存
缓存击穿
现象:大量请求同时请求同一个key,这时key失效,请求同时打到DB,造成DB负载过高
解决
1、设置热点key永不失效,缺点是内存浪费,如果热点key很多,问题更大
2、加互斥锁,等待第一个线程查询到了数据,放到了缓存中,后续的线程就可以从缓存中取数据了
缓存穿透
现象:根据一个key到缓存中查询数据时,没有查询到,降级到数据库中查询,也没有查询到,如果这样的请求很多,就击穿了缓存,耗费了大量缓存和数据库的性能
解决:
1、对该key缓存空值,设置较短的过期时间,但是这样可能会耗费大量的内存空间
2、BloomFilter,以BitMap的思想在缓存之前加一层过滤器,先判断该key值在过滤器中有没有,没有的话就直接返回,有的话再去查缓存或者数据库,存在一定的误判率,可以自己指定容量的大小和误判率,通过三次hash放到位图中
整理思想
事前
Redis 高可用,主从+哨兵,Redis cluster,避免全盘崩溃
事中
本地 ehcache 缓存 + Hystrix 限流+降级,避免MySQL 被打死。
事后
Redis 持久化 RDB+AOF,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据
数据一致性
不能先写数据库再更新缓存的方式,容易造成ABA问题,而且造成性能浪费
先删缓存,再改数据库,延时双删,redis.del(key),db.updateData(data),Thread.sleep(1000),redis.del(key)
先删缓存,再更新数据,休眠一段时间,再删缓存,也会有数据不一致的情况
Cache-Aside pattern
失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中;
命中:应用程序从cache中取数据,取到后返回;
更新:先把数据存到数据库中,成功后,再让缓存失效;
删除缓存失败,可以把key放入队列中重试,启动一个订阅程序订阅mysql的binlog,获得需要操作的数据
查询快的原因
基于内存的,采用的是单进程单线程模型的 KV 数据库,官方提供的数据是可以达到100000+的QPS
完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。它的,数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的
采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗(引申:可以在单机开多个Redis实例)
使用多路I/O复用模型,非阻塞IO
使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制
数据结构
常用命令 https://juejin.im/post/5e12fd8a5188253ab60d99aa
String
String在redis内部存储默认就是一个字符串,被redisObject所引用,当遇到incr,decr等操作时会转成数值型进行计算,此时redisObject的encoding字段为int
Hash
Redis Hash对应Value内部实际就是一个HashMap,这里会有2种不同实现,这个Hash的成员比较少时Redis为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的HashMap结构,对应的value redisObject的encoding为zipmap,当成员数量增大时会自动转成真正的HashMap,此时encoding为ht
List
Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构
Set
set 的内部实现是一个 value永远为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的原因
SortedSet
Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单
HyperLogLog
Geo
Pub/Sub
Redis Mudule
BloomFilter
RedisSearch
Redis-ML
PiPLine
可以将多次IO往返的时间缩减为一次
分步式锁
使用setnx来争抢锁,再用expire给锁设置过期时间,防止锁忘了释放
如果在expire之前进程crash或者需要重启,可以通过set的参数设置,将setnx和expire合成一条指令来使用 key-with-expire-and-NX
通过Zookeeper实现,持久节点下创建临时顺序节点,排第一则获得锁,解锁时删除临时节点,崩溃也会自动删除
和Memcache比较
MC是异步IO方式,Redis是单线程方式处理请求
Redis 支持持久化,所以 Redis 不仅仅可以用作缓存,也可以用作 NoSQL 数据库
Redis 还有一个非常大的优势,就是除了 K-V 之外,还支持多种数据格式,例如 list、set、sorted set、hash 等
Redis 提供主从同步机制,以及 Cluster 集群部署能力,能够提供高可用服务
缓存类型
本次缓存
本地缓存就是在进程的内存中进行缓存,比如我们的 JVM 堆中,可以用 LRUMap 来实现,也可以使用 Ehcache 这样的工具来实现。
本地缓存是内存访问,没有远程交互开销,性能最好,但是受限于单机容量,一般缓存较小且无法扩展
分布式缓存
分布式缓存可以很好得解决扩展的问题
分布式缓存一般都具有良好的水平扩展能力,对较大数据量的场景也能应付自如。缺点就是需要进行远程请求,性能不如本地缓存
多级缓存
为了平衡这种情况,实际业务中一般采用多级缓存,本地缓存只保存访问频率最高的部分热点数据,其他的热点数据放在分布式缓存中。
在目前的一线大厂中,这也是最常用的缓存方案,单考单一的缓存方案往往难以撑住很多高并发的场景
集群
基于客户端
ShardedJedisPool
基于服务端
主从
第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将RDB文件全量同步到复制节点,复制节点接受完成后将RDB镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。后续的增量数据通过AOF日志同步即可,有点类似数据库的binlog
优点
同一个Master可以同步多个Slaves
支持主从复制
可以读写分离
缺点
不具备自动容错和恢复功能
哨兵
着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务
优点
基于主从模式的,所有主从的优点,哨兵模式同样具有
主从可以切换,故障可以转移,系统健壮性和可用性更好
缺点
较难支持在线扩容
配置复杂
主要功能
集群监控:负责监控 Redis master 和 slave 进程是否正常工作
消息通知:如果某个 Redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
故障转移:如果 master node 挂掉了,会自动转移到 slave node 上
配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址
数据同步
启动一台slave 的时候,他会发送一个psync命令给master ,如果是这个slave第一次连接到master,他会触发一个全量复制。master就会启动一个线程,生成RDB快照,还会把新的写请求都缓存在内存中,RDB文件生成后,master会将这个RDB发送给slave的,slave拿到之后做的第一件事情就是写进本地的磁盘,然后加载进内存,然后master会把内存里面缓存的那些新命名都发给slave
cluster
replication复制+主从架构+读写分离+哨兵集群+高可用
着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储
优点
部署简单
可扩展性好
缺点
升级时会影响业务
架构
多个master节点,每个master节点可以有多个slave节点
读写分离,写到master node,读从对应的slave node 读
高可用,如果master节点挂了,cluster机制会自动将某个slave节点转成master节点
cluster VS replication+sentinal
输入数据量很少,主要承载高并发的场景
replication 只有一个master节点,多个slave节点,所有节点数据一致,可提高请求吞吐量,可以通过sentinal集群保证高可用,但是扩容难
cluster可以有多个master节点去存储不同的数据,适合数据量特别大的场景,可以不用sentinal集群就保证高可用,直接集成了replication和sentinal的功能
数据分布算法
redis cluster机制
数据分片,分节点存储
内置高可用支持
6379 和 16379 端口,16379用来节点间通信,进行故障检测,配置更新,故障授权转移
hash算法
原理:计算hash值,节点数量取模,然后分布数据
缺点:如果某节点宕机,算法的范围会发生变化,导致取模结果变化,会导致大量的key瞬间失效
一致性hash算法(自动缓存迁移)
原理:将所有的主节点落在一个圆环里,计算hash值,将值落在距离自己最近的一个节点
优点:任何一个主节点宕机,只有影响之前分布在之上的数据,但是新的继续可用
缺点:1、也会导致前一个节点的数据丢失 2、缓存热点问题
一致性哈希(自动缓存迁移)+虚拟节点(自动负载均衡)
在一致性哈希的基础上增加虚拟节点方案,给每个主节点做一部分虚拟节点,将数据均匀分布在不同节点上
hash slot算法
16384个slot,每个key计算CR16值,,对16384取模,获取对应的hash slot
每个主节点都持有一部分 hash slot
每增加一个master,其他master的hash slot 就移动一部分给新加入的master
减少一个master,就将它的hash slot移动到其他master上去
移动hash slot的成本很低
客户端API可以指定hash tag 让数据走同一个hash slot
核心原理
节点内部通信
基础通信原理
采取gossip协议进行通信,每个节点相互之间都不停通信,保持所有节点的数据都是完整的
维护集群元数据
集中式
优点 :元数据的更新和读取,时效性非常好,一旦元数据出现了变更,立即就更新到集中式的存储中。
缺点 :所有的元数据的更新压力全部集中在一个地方,可能导致元数据的存储有压力。
gossip
优点 :元数据的更新比较分散,不是集中在同一个地方,更新请求会陆陆续续到达所有节点上去更新,有一定的延时,降低了压力。
缺点 :元数据更新有延时,可能会导致集群的一些操作会有一些滞后。
10000端口
每个节点都有一个专门用于节点间通信的端口号,就是自己提供服务的端口号+10000。每个节点每隔一段时间都会往另外几个节点发送ping消息,同时其他节点接收到ping之后会返回pong消息
交换信息的种类
包含故障信息,节点的增加和移除,hash slot信息等
gossip协议(流言协议)
ping: 每个节点都会频繁的给其他节点发送ping,其中包括自己的状态还有自己维护的集群元数据,互相通过ping交换元数据
很频繁,而且要携带一些元数据,所以会加重网络负担
每个节点每秒会执行10次ping,每次会选择5个最久没有通信的其他节点去通信
如发现某个节点通信时延达到了 cluster_node_timeout/2,那么立即发送ping
可以调节 cluster_node_timeout的值,如果调节比较大。就会降低ping发送的概率
每次ping都会带上自己的信息。还要带上1/10其他节点的信息,发送出去进行交换
至少包含3个其他节点的信息,最多包含总节点-2个其他节点的信息
meet:某个节点发送meet给新加入的节点,让其加入进群中,然后新节点就会开始与其他节点进行通信
pong:作为ping和meet的响应,包含自己的状态和其他信息,也可以用于信息广播和更新
fail:某个节点判断另一个节点fail之后,就会发送fail消息给其他节点,通知其他节点,指定的节点宕机了
面向集群的Jedis
基于重定向的客户端
redis-cli c,自动重定向
请求重定向
计算hash slot
hash slot查找
smart jedis
定义
工作原理
hash slot迁移和ask重定向
高可用与主备切换原理
判断节点宕机
如果一个节点认为另一个节点宕机,那么就是pfail,主观宕机
如果多个节点都认为另外一个 节点宕机了,那就是fail,客观宕机
在cluster-node-timeout内,某个节点一直没有返回pong,那么就认为pfail
如果一个节点认为某个节点pfail了,那么会在gossip ping消息中,发送给其他节点,如果超过半数的节点都认为pfail了,那就要变成fail
从节点过滤
对宕机的master node,从其所有的slave node中,选择一个切换成master node
检查每个slave node与master node断开连接的时机,如果超过了cluster-node-timeout * cluster-slave-validity-factor,那么这个节点就没有资格切换成 master node,直接被过滤
从节点选举
每个从节点,都根据自己对master复制数据的offset,来设置一个选举时间,offset越大的从节点,选举时间越靠前,优先进行选举
从节点执行主备切换,从节点切换为主节点
持久化
RDB
周期性的持久化,RDB耗时长,不够实时,停机时容易丢数据,适合冷备,恢复快
AOF
重启时,使用RDB构建内存,AOF做数据补齐,可以理解为全量的操作日志,对每条写入命令记录日志,以append-only的模式写入一个日志文件里,类似mysql的binlog,所需存储空间大,适合热备
可靠性保证,靠AOF的sync属性,每次写都sync耗性能,一般定时sync
RDB的原理
fork:通过创建子进程来进行RDB操作
cow:copy and write
延时队列实现
使用使用sortedset,时间戳作score,消息内容为key,调用zadd来生产消息,消费者用zrangebyscore获取之前的数据进行轮询处理
异步队列实现
list结构作为队列
rpush生产消息
lpop消费消息,当lpop没有消息的时候,要适当sleep一会再重试
如果不可以用sleep,用blpop,在没有消息的时候,它会阻塞住直到消息到来
ack,消费失败,回滚消息到待处理队列
生产一次消费多次
使用pub/sub主题订阅者模式,可以实现 1:N 的消息队列
缺点:在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如RocketMQ等
从海量key中找出指定key的集合
1、使用keys指令,Redis是单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复
2、可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端去重,但是整体所花费的时间会比直接用keys指令长。
删除策略及淘汰机制
删除策略
定时删除
按照添加key时设置的过期时间来删除
惰性删除
等查询的时候判断有没有过期,过期就删除
定期删除
默认定时(100S)就随机抽一些设置了过期时间的key,去检查是否过期,过期了就删了
淘汰机制
基础名词
FIFO 淘汰最早数据
LRU 剔除最近最少使用
LFU 剔除最近使用频率最低
noeviction:当内存使用达到阈值的时候,所有引起申请内存的命令会报错(大部分的写入指令,但DEL和几个例外)
allkeys-lru:在主键空间中,优先移除最近未使用的key
volatile-lru:在设置了过期时间的键空间中,优先移除最近未使用的key
allkeys-random:在主键空间中,随机移除某个key
volatile-random:在设置了过期时间的键空间中,随机移除某个key
volatile-ttl:在设置了过期时间的键空间中,具有更早过期时间的key优先移除
0 条评论
下一页