1. 数据类型
Redis作为key-value型数据库,支持以下5种常见数据类型,即value的类型
- String
字符串数据类型,最大存储512M,二进制安全(可包含任何二进制数据,包含jpg对象等)
set key "value"
get key
- Hash
String元素组成的字典,适合存储对象
hmset Object id "xxxxx" name "Object"
hmget Object id
hmget Object name
- List
列表,按照String元素插入顺序排序,顺序为后进先出,类似栈。可以实现类似“最新消息排行榜”功能
lpush key value1
lpush key value2
lrange key 0 10
- Set
String 元素组成的无序集合,通过哈希表实现(增删改查时间复杂度为 O(1)),不允许重复。在获取的时候顺序跟插入顺序无关
sadd testset a
sadd testset b
smembers testset
- SortedSet
顺序集合,通过成员的值进行排序
zadd testset 1 a
zadd testset 2 b
zrangebyscore testset 0 10
2. 持久化机制
Redis将内存中数据持久存储,保证数据的完整性,不受断电等因素影响
2.1. RDB持久化
快照持久化,会每隔某一个时间保存全量数据的快照。在存储上是非常紧凑的二进制内存数据。
Redis是单线程的,所以在快照持久化的时候回调操作系统的rdbSave函数(SAVE和BGSAVE两个指令,前者为阻塞,后者为非阻塞)fork一个子进程将内存中数据序列化。为使父进程不受子进程快照操作阻塞影响,采用COW(Copy-On-Write)技术:将数据段分为N个数据页(数量取决热数据的多少),当主进程操作一个数据页中数据时,把页面从共享内存中复制一份,父进程指向新的页进行数据修改,此时子进程还是指向老的页面。
缺点:全量同步数据量大,性能下降、会丢失宕机到最近一次快照的数据
优点:全量快照,文件小,恢复快
2.2. AOF持久化
保存每次Redis的写状态记录数据库,将写的信息以增量形式保存在AOF文件中
缺点:随着写不断增加,AOF文件越来越大。例如递增100次一个值,AOF保存100个记录,而RDB只保存一个,即最终结果100
优点:可读性高,适合保存增量数据,数据不易丢失
2.3. RDB-AOF混合持久化
先全量数据写入RDB文件,在下一次快照期间使用AOF方式持久化。将增量数据追加在RDB数据后面,在下一次全量快照时将AOF数据重新以RDB形式写入文件
3. 集群模式
3.1. 主从模式
一个Master节点作为主数据库提供读写能力,Slave数据库作为从数据库提供读的能力。数据写入主节点,通过主从同步将新的数据同步到其他的从数据库中。这种模式适用于数据请求量不大的场景。
主从复制的过程如下:参考
- 当建立一个从服务器时, 从服务器都将向主服务器发送一个 SYNC 命令。
- 接到 SYNC 命令的主服务器将开始执行 BGSAVE , 并在保存操作执行期间, 将所有新执行的写入命令都保存到一个缓冲区里面。
- 当 BGSAVE 执行完毕后, 主服务器将执行保存操作所得的
.rdb
文件发送给从服务器, 从服务器接收这个.rdb
文件, 并将文件中的数据载入到内存中。
- 之后主服务器会以 Redis 命令协议的格式, 将写命令缓冲区中积累的所有内容都发送给从服务器。
3.2. 哨兵模式
相较于Redis主从模式增加了哨兵机制提升可用性
哨兵节点具有如下功能:
- 监控(Monitoring)主从节点的健康情况,哨兵作为一个独立进程,通过定期发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
- 通知(Notification),数据库发生故障时通过API通知管理员或其他应用程序
- 自动故障迁移(Automatic failover)将故障节点的从节点升级为新主节点并被其他从节点复制
哨兵之间通过使用流言协议(gossip protocols)来接收关于主服务器是否下线的信息, 并使用投票协议(agreement protocols)来决定是否执行自动故障迁移, 以及选择哪个从服务器作为新的主服务器。
3.3. Redis-cluster
4. 同步机制
5. 删除机制
5.1. 定期删除&惰性删除
对于key有过期时间的数据,redis采用定期+惰性删除的方式将过期数据从内存中移除。
定期删除:redis维护一个字典,里面存放着所有有过期时间的key,每隔100ms会扫描一次字典删除过期的key。删除的策略也是选择一种贪心策略:
- 从字典中随机获取20个key
- 删除选中key中过期的key
- 如果超过1/4的key被删除,重复1
惰性删除:如果客户端请求的key已经过期,那么立即删除这个key,并不返回任何值给客户端
- 为什么不定时删除?
给每个key配一个定时器,过期则自动删除。对CPU消耗很大,当并发量大的时候,需要更多去处理请求,而不是删除数据
5.2. 内存淘汰
当Redis内存满的时候,会采用以下策略中的一种,对内存中数据进行淘汰:
noeviction(默认策略):对于写请求不再提供服务,直接返回错误(DEL请求和部分特殊请求除外)
allkeys-lru:从所有key中使用LRU算法进行淘汰
volatile-lru:从设置了过期时间的key中使用LRU算法进行淘汰
allkeys-random:从所有key中随机淘汰数据
volatile-random:从设置了过期时间的key中随机淘汰
volatile-ttl:在设置了过期时间的key中,根据key的过期时间进行淘汰,越早过期的越优先被淘汰
LRU(Least Recently Used)最近最少使用,缓存置换算法。Redis中采用近似LRU的算法进行老的数据的淘汰。Redis给每个key增加了一个额外增加了一个24bit的字段,用来存储该key最后一次被访问的时间,然后每次随机从5个key淘汰其中一个最后访问的key。
3.0后优化了算法,每次随机取的key会放入候选池中(大小16),每次选取key只有访问时间小于池中最小时间才会进入池,直到池满。后续有key要放入时,将访问时间最大(最近被访问)的key从池中移除。需要淘汰时,将池中选时间最小的(最久未被访问)的key淘汰。相当于为淘汰加了个缓存层。
6. 缓存问题
6.1. 缓存击穿
- 针对某一个不存在key的高并发访问,由于数据库不存在这个数据因此缓存中也不会有,这时候针对这个key的所有访问请求都落在了数据库上,缓存相当于不存在一样
- 针对某一个热点key,由于key都有过期时间,如果这个key是个热点数据,当过期的时候,有大量的请求到了缓存发现没有,就会全部请求到数据库上,大并发可能把数据库压垮
解决方案
- 对于第一种情况,可以把不存在的key对应的值在缓存中随便设置一个,例如-1,然后快速返回给上层业务一个空的对象。防止一个key的请求频繁访问数据库,这个也是现在pc查询保障服务用户信息所用方法,空key的过期时间为5分钟
- 对于第二种情况,就是只让一个线程构建缓存,其他线程等待构建缓存的线程执行完,重新从缓存获取数据就可以了,参考
6.2. 缓存穿透
缓存击穿一般是针对一个key的访问产生的,而缓存穿透是指当有大量不同的不存在的key的请求过来,由于数据不存在,则跳过缓存直接访问数据库,数据库可能直接挂了,并且缓存失去了意义
解决方案
布隆过滤器,对数据库存在的数据映射到bitmap上,对于不存在的数据可以直接被拦截掉。布隆过滤器可以放在缓存和数据库间,对不存在的数据进行过滤
6.3. 缓存雪崩
缓存设置的过期时间可能相同,导致同一时间全都过期,然后访问全部都转发到DB上,瞬间的压力导致数据库雪崩
解决方案
- 设置不同的过期时间,避免在同一时间全部过期
- 加锁或队列,保证数据库大量写入
7. 应用场景
除了当作缓存存储热点数据外,Redis还有以下应用场景
7.1. 分布式锁
对于多线程的共享数据,我们会通过java api提供的锁机制锁住堆内存上的资源,防止同一时间多个线程操作堆上数据出错。但是在分布式系统中,可能多个进程访问共享资源,不同进程甚至不在一个机器上,单机部署下锁机制就失去了意义,因此引入分布式锁来实现跨JVM互斥地访问共享数据
特点
互斥性:任意时刻只有一个客户端获取到锁
安全性:锁只能由持有他的客户端删除,不能由其他人删除
防死锁:获取到锁的客户端因为宕机无法释放锁导致其他客户端无法获取锁,
高可用:某个Redis节点宕机时,客户端依然可以获取/释放锁
7.2. 异步队列
List可以作为队列,使用Rpush生产消息,LPOP消费消息(BLPOP阻塞直到队列有消息或超时,防止没有消息消费)
7.3. 主题发布/订阅
8. 问题
8.1. 为什么Redis选择单线程模型?
Redis4.0之后的版本抛弃了单线程这一设计,选择使用多线程模型
原来的Redis作为内存服务器,使用IO多路复用(单线程监听网络多个文件描述符FD,可读可写就绪则线程进行处理)处理请求;4.0后会使用主处理线程外其他线程进行非阻塞的删除操作
选择单线程模型的原因:
- 可维护性高,方便开发/调试:多线程引入的程序执行顺序不确定性,单线程不需要并发控制;减少切换线程的开销(保存线程1的执行上下文&加载线程2的执行上下文)
- 并发处理,通过IO复用处理多个并发请求,扩展可以通过分片扩展
- CPU资源不是Redis服务器的性能瓶颈,内存和网络才是
多线程引入原因:
- 删除超大的键值对,单线程会阻塞待处理任务一段时间用于释放内存,删除操作可以通过其他线程处理
Redis 选择使用单线程模型处理客户端的请求主要还是因为 CPU 不是 Redis 服务器的瓶颈,所以使用多线程模型带来的性能提升并不能抵消它带来的开发成本和维护成本,系统的性能瓶颈也主要在网络 I/O 操作上;而 Redis 引入多线程操作也是出于性能上的考虑,对于一些大键值对的删除操作,通过多线程非阻塞地释放内存空间也能减少对 Redis 主线程阻塞的时间,提高执行的效率。
8.2. 常见面试问题
参考:编码砖家 - 博客园
1、如何保证Redis高可用和高并发?
Redis主从架构,一主多从,可以满足高可用和高并发。出现实例宕机自动进行主备切换,配置读写分离缓解Master读写压力。
2、Redis高可用方案具体怎么实施?
使用官方推荐的哨兵(sentinel)机制就能实现,当主节点出现故障时,由Sentinel自动完成故障发现和转移,并通知应用方,实现高可用性。
它有四个主要功能:
- 集群监控,负责监控redis master和slave进程是否正常工作。
- 消息通知,如果某个redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
- 故障转移,如果master node挂掉了,会自动转移到slave node上。
- 配置中心,如果故障转移发生了,通知client客户端新的master地址。
3、你能说说Redis哨兵机制的原理吗?
通过sentinel模式启动Redis后,自动监控master/slave的运行状态,基本原理是:心跳机制+投票裁决。
每个sentinel会向其它sentinal、master、slave定时发送消息,以确认对方是否活着,如果发现对方在指定时间内未回应,则暂时认为对方宕机。
若哨兵群中的多数sentinel都报告某一master没响应,系统才认为该master真正宕机,通过Raft投票算法,从剩下的slave节点中,选一台提升为master,然后自动修改相关配置。
4、部署Redis哨兵要注意哪些问题?
哨兵至少需要3个实例,来保证自己的健壮性。哨兵的详细教程及与Spring Boot如何集成请关注公众号Java技术栈进行阅读。
5、Redis主从架构数据会丢失吗,为什么?
有两种数据丢失的情况:
1)异步复制导致的数据丢失:因为master -> slave的复制是异步的,所以可能有部分数据还没复制到slave,master就宕机了,此时这些部分数据就丢失了。
2)脑裂导致的数据丢失:某个master所在机器突然脱离了正常的网络,跟其他slave机器不能连接,但是实际上master还运行着,此时哨兵可能就会认为master宕机了,然后开启选举,将其他slave切换成了master。这个时候,集群里就会有两个master,也就是所谓的脑裂。此时虽然某个slave被切换成了master,但是可能client还没来得及切换到新的master,还继续写向旧master的数据可能也丢失了。因此旧master再次恢复的时候,会被作为一个slave挂到新的master上去,自己的数据会清空,重新从新的master复制数据。
6、Redis主从复制的工作原理?
1)一个Slave实例,无论是第一次连接还是重连到Master,它都会发出一个SYNC命令;
2)当Master收到SYNC命令之后,会做两件事:(a) Master执行BGSAVE,即在后台保存数据到磁盘(rdb快照文件);(b) Master同时将新收到的写入和修改数据集的命令存入缓冲区(非查询类);
3)当Master在后台把数据保存到快照文件完成之后,Master会把这个快照文件传送给Slave,而Slave则把内存清空后,加载该文件到内存中;
4)而Master也会把此前收集到缓冲区中的命令,通过Reids命令协议形式转发给Slave,Slave执行这些命令,实现和Master的同步;
5)Master/Slave此后会不断通过异步方式进行命令的同步,达到最终数据的同步一致;
7、由于主从延迟导致读取到过期数据怎么处理?
1)通过scan命令扫库:当Redis中的key被scan的时候,相当于访问了该key,同样也会做过期检测,充分发挥Redis惰性删除的策略。这个方法能大大降低了脏数据读取的概率,但缺点也比较明显,会造成一定的数据库压力,否则影响线上业务的效率。
2)Redis加入了一个新特性来解决主从不一致导致读取到过期数据问题,增加了key是否过期以及对主从库的判断,如果key已过期,当前访问的master则返回null;当前访问的是从库,且执行的是只读命令也返回null。
8、Redis Key的过期策略有哪些?
1)惰性删除:当读/写一个已经过期的key时,会触发惰性删除策略,直接删除掉这个过期key,很明显,这是被动的。
2)定期删除:由于惰性删除策略无法保证冷数据被及时删掉,所以 Redis 会定期主动淘汰一批已过期的key。
3)主动删除:当前已用内存超过maxMemory限定时,触发主动清理策略。主动设置的前提是设置了maxMemory的值。