田超凡
20191206
转载请注明原作者
※目录※
1 Redis环境搭建和数据结构
2 Redis持久化和一致性
3 Redis内存淘汰、自动超时和事务处理
4 Redis实现分布式锁
5 Redis集群、主从复制和哨兵模式
6 Redis缓存安全问题
7 Redis Cluster去中心化分片集群
8 Redis Cluster集群扩容和缩容
9 Redis布隆过滤器、集群脑裂和最终一致性
Redis是一个分布式缓存中间件,是一个分布式、高性能非关系型数据库(nosql),主要用来做分布式缓存和分布式数据共享。
一般情况下,在项目中需要做分布式缓存来减轻数据库访问压力的时候,还有需要在多个项目中共享一些全局数据的时候,Redis是一个极佳的解决方案,并且Spring/SpringBoot均对Redis提供了良好的支持,配置方便,使用灵活,可以灵活存取对象数据,并且可以实现数据共享和计数器、定时策略的功能。
目前主流的非关系型数据库还有Memcached、tair等,但是在性能、高并发、实现高可用、轻量级等方面均不及Redis。
Redis数据类型有5种:String、Hash、Set、Sorted Set(ZSet)、List
String 字符串:使用key-value的形式存储任何字符串类型的数据,可以是图片路径、Token令牌等,这种数据类型最为常用
Hash哈希:使用key-
Set无序、唯一集合:使用key-
Sorted Set有序、唯一集合(ZSet):使用key-
List集合:使用key-
Redis主要用来做分布式缓存和分布式数据共享,一般会在以下几个场景使用Redis:
Redis线程模型实际上是基于NIO的多路IO复用实现的,使用单线程批量处理客户端发送的多个IO请求,核心实际上是NIO的选择器和缓冲区机制。
Redis官方只提供了Linux版本,没有Windows版本,这实际上就是因为选择器的原因。Redis在NIO实现多路IO复用的过程中,选择器使用的是Linux的Epoll选择器,Epoll选择器基于事件监听回调实现的,可以防止选择器空轮训,并且能够支持很好的TCP高并发,加上NIO的缓冲区机制,每次请求的数据都会先存放到缓冲区,当到达NIO进行单线程批量处理的条件时,会轮询缓冲区中的所有数据进行操作,所以Redis实际上是使用单线程来处理IO操作的,基于Linux的NIO多路IO复用机制,这也是为什么Redis能够使用单线程支持高并发的核心原因。
因为Redis是通过单线程来处理IO操作的,所以肯定是线程安全的。
基于JSON序列化机制存放对象数据
基于Redis内置序列化机制存放二进制对象数据(仅限Java操作)
redis.conf配置文件中需要修改
daemonized yes #表示使用后台启动,守护线程
# bind 0.0.0.0 127.0.0.1 需要注释掉,解除绑定本机IP,使用服务器IP
protected-mode no #表示外界可以访问当前Redis服务器
requirepass 123456 #设置Redis服务器访问密码为123456,配置可选
2.1 Redis宕机重启后,数据会丢失吗?
Redis采用数据持久化机制来防止数据丢失,所以Redis宕机重启后,数据不会丢失。
Redis数据持久化机制会在Redis主机宕机之前把Redis服务器中的数据存放到磁盘文件中,每次Redis启动都会先读取备份的数据文件,如果有数据就会直接恢复到Redis中,实现数据持久化。
2.2 Redis持久化机制是如何实现的?分别有什么特点?
Redis持久化机制有两种:RDB和AOF
Redis默认开启了RDB持久化模式
RDB是Redis默认使用的持久化模式,主要是基于全量同步实现数据持久化的。每隔一定周期就会把Redis服务器中的所有数据备份到dump.rdb文件。
RDB全量同步是基于fork子线程的方式实现的,不会影响写操作。
AOF是增量同步的持久化模式,采用定时策略实现数据同步,Redis服务器每隔1s或者每次客户端对Redis执行写操作,都会触发AOF增量同步,AOF增量同步会把Redis服务器最近一次备份的数据保存到appendenly文件。
AOF有两种方式实现增量同步:always和everysec,默认使用的是everysec每秒同步一次的方式实现。
AOF增量同步是基于执行命令和缓冲区方式实现的,不会影响写操作。
如果同时在Redis服务器中开启了RDB和AOF持久化,那么AOF持久化会优先执行,因为遵循局部优先的原则。
2.3 全量同步和增量同步的区别是什么?
全量同步:采用定时和周期的形式实现数据同步,每次都同步所有数据。
全量同步特点是数据同步效率高,但是容易丢失数据。
增量同步:每次对Redis写操作都会触发数据同步,每次只同步距上一次数据同步后发生变化的数据。
增量同步特点是:数据完整性高,数据同步效率低。
2.4 Redis如何和MySQL数据保持一致的?有哪些数据一致性解决方案?
Redis和MySQL数据一致性可以使用以下几种方式实现:
2.5 SpringBoot整合Redis如何使用注解实现缓存功能?
(1) 启动类使用@EnableCaching标注,开启缓存
(2) 对应的查询方法使用@Cachable标注,指定Redis缓存中的Key和缓存的持久层映射器方法,方法名需要加单引号。
3.1 Redis内存满了怎么办?
Redis最大占用内存可以在redis.conf配置文件中进行配置,当运行中的Redis服务器内存满了之后,会基于Redis内存淘汰策略优先清除一些Key,从而释放内存,节约Redis数据存储空间。
3.2 如何实现订单超时未支付自动取消?
基于Redis自动超时机制实现,订单下单时把订单Token存入Redis,设置超时时间
定义Redis Key超时失效监听器类,重写onMessage方法监听超时回调,当订单Token这个Key失效的时候,就可以执行这个回调方法,在这个回调方法中获取订单信息并设置订单为订单取消状态。
3.3 Redis内存淘汰策略有哪些?分别有什么特点?
naviction 默认的内存淘汰策略,表示内存满了之后,再申请内存直接报错
lru算法 优先清除不经常使用的Key
ttl算法 优先清除比当前Key过期时间更早的Key
3.4 Redis如何设置Key的超时时间?
expire key expireTimes实现,设置key和对应的过期时间(单位:毫秒)
在jedis客户端封装了对应的方法可以直接调用即可。
3.5 Redis支持事务吗?如果支持事务,那么是如何实现的?
Redis支持事务,Redis事务处理主要是基于四个命令实现:
watch、multi、exec、discard
watch用来监视一个或多个Key在事务提交之前是否发生了变化(因为Redis事务执行过程中,可能有多个线程同时操作事务中操作的Key),如果发生了变化,那么事务不会提交(即使显式指定提交命令exec也不会提交),如果没有发生变化则会进行提交。使用watch监视key是一种类似乐观锁的实现方式。
multi 开启事务
exec 提交事务
discard 事务取消
3.6 Redis事务如果要撤销,有提供事务回滚功能吗?
Redis不支持事务回滚,只支持事务取消。
Redis事务是不会对操作的数据上锁的,这也就导致事务中操作的数据可以被其他线程去修改的,Redis事务不保证隔离性和原子性。所以如果Redis事务需要撤销,会直接取消Redis事务。
事务回滚是针对关系型数据库而言的,事务回滚会先解除行锁,再取消事务。
简单来说就是,Redis事务取消只做一件事,就是直接取消当前事务。关系型数据库事务回滚做两件事,会先解除行锁,再取消当前事务。
3.7 Redis和MySQL事务的区别?
Redis实现事务操作对事务中的数据不会上锁,这也就导致事务进行中的数据是可以被其他线程访问和修改的,Redis不保证事务的隔离性和原子性。
MySQL实现事务操作的时候会对操作中的数据上行锁,除非事务最终提交或者回滚,否则事务中的数据会一直处于上锁状态,其他线程无法访问和操作锁住的行数据。
4.1 什么是线程锁?什么是分布式锁?二者有什么区别?
线程锁:保证同一个JVM中的多个线程并发执行时,同一时刻只有一个线程能够访问和执行某段代码,确保线程安全性,又叫线程并发锁。
分布式锁:在分布式项目中,保证在多个JVM并行时,同一时刻只能有一个JVM得到执行机会,保证多个JVM并行时的进程安全性,又叫进程并行锁。
线程锁和分布式锁的区别在于:
线程锁针对的是同一个JVM中的多个线程
分布式锁针对的是多个JVM中的线程
4.2 Redis如何实现分布式锁?
Redis实现分布式锁主要分为三个步骤:获取锁、释放锁、超时锁
获取锁:基于setnx命令操作key实现,只有获取到分布式锁的JVM才能得到执行机会。
释放锁:根据分布式锁的Key释放Redis中的分布式锁,确保谁创建的分布式锁,谁就可以释放这个分布式锁。
超时锁:某个JVM获取分布式锁后超时未释放,导致其他JVM无法获得抢占锁的机会。
Redis实现分布式锁主要是基于setnx命令操作key实现
4.3 分布式锁有哪些其他的方案实现?
(1) 基于数据库操作实现分布式锁
(2) 基于Redis的setnx命令操作key实现分布式锁
(3) 基于zookeeper的临时节点+事件通知实现分布式锁
(4) 基于Redis分布式锁框架redission实现分布式锁
4.4 setnx命令和set命令的区别是什么?
setnx命令和set命令都是用来对Redis执行写操作的,都是用来存入key-value
区别在于:
setnx命令对于操作的key分为两种情况:
set命令对于操作的key分为两种情况:
set命令不返回执行结果
4.5 分布式锁的应用场景有哪些?你在哪些地方用到了分布式锁?
(1) 分布式任务调度平台,任务的幂等性,同一时刻只有一个JVM能够执行
(2) 生成全局ID
(3) 服务集群中的定时任务,同一时刻能有一个JVM能够执行
5.1 什么是高可用?
高可用指的是服务的健壮性和容灾性强,高可用主要体现在当服务发生一些人为能够预料或者人为无法干预的极端恶劣情况下仍然能够实现很强的自我保护和故障排查解决机制,降低服务不可用发生的可能性,提高服务健壮性。
实现高可用则指的是通过一系列技术手段和配置方式尽可能提高服务的可用性,最终的目标是实现高可用。
5.2 什么是Redis集群?为什么要用Redis集群?
Redis集群指的是搭建多台Redis服务器来实现Redis服务高可用,集群中的Redis服务器分为主机和从机,主机只负责写,从机只负责读,Redis集群中的所有Redis服务器数据始终保持同步,当主机出现问题宕机之后,能够基于哨兵模式在剩下的存活的Redis从机中进行投票选举,产生新的Redis主机,不会影响服务的正常运作,从而实现Redis服务的高可用。
5.3 什么是主从复制?主从复制有哪些实现方式?
主从复制是搭建Redis集群的一种重要实现方式,主要目的是实现Redis主机和从机读写分离、数据同步。
主从复制实现方式常用的有:一主一从、一主多从、多主多从、树状主从
一主一从:一个Redis主机+一个Redis从机实现主从复制
一主多从:一个Redis主机+多个Redis从机实现主从复制
多主多从:多个Redis主机,每个Redis主机关联多个Redis从机
树状主从:优化一主多从模式中数据同步效率低的问题,采用类似二叉树的结构来实现一主多从,每个从机都可以作为下一级节点的主机,实现叶子节点形式的主从机关联关系(实际上在整个Redis集群中,真正的主机有且只有一个,就是二叉树的根节点)。
5.4 什么是哨兵模式?什么是哨兵集群?
哨兵模式用来解决Redis主从复制中主机宕机之后,从机只能读,不能写的问题。使用哨兵模式可以在Redis主机宕机之后,在剩下存活的Redis从机中进行投票选举,产生新的Redis主机来代替宕机的原Redis主机,实现Redis集群高可用。
哨兵模式的核心原理是每一个哨兵都单独作为一个服务监视Redis主机的生命周期,当Redis主机宕机之后,就会开始投票选举产生新的Redis主机。在投票选举过程中,Redis主机会出现不可执行写操作的情况,这是因为此时还不知道真正的Redis主机是谁。
哨兵集群指的是使用多个哨兵来监视Redis主机的变化情况,哨兵集群中的哨兵数量理论上来说应该和Redis集群中Redis服务器的数量保持一致,每个Redis服务启动之后都需要启动对应的哨兵服务。在使用哨兵模式实现Redis集群高可用的时候,一般都是用的是哨兵集群而不是单点哨兵,这样做的目的是:
5.5 什么是读写分离?为什么要做读写分离?
读写分离指的是在使用主从复制和哨兵模式实现Redis集群的时候,主机只负责写,从机只负责读,主从机数据共享。
读写分离的目的是尽可能做到每个Redis服务器功能职责的单一化,实现主从复制,最终实现Redis集群高可用。
6.1 什么是缓存穿透?如何解决缓存穿透?
缓存穿透:频繁使用一个Redis中不存在的Key进行查询,导致每次都绕过Redis服务器,直接去到数据库服务器中查询。缓存穿透破坏了Redis分布式缓存的意义,加大了对数据库的访问压力。
缓存穿透问题解决方案:
6.2 什么是缓存击穿?如何解决缓存击穿?
缓存击穿:Redis中的某一个热点Key在高并发访问的时候突然失效,导致失效后大量线程绕过Redis服务器,直接去数据库查询,使得数据库访问压力在一段时间内突然剧增。
缓存击穿问题解决方案:
6.3 什么是缓存雪崩?如何解决缓存雪崩?
缓存雪崩:Redis中的一部分Key在某一段时间内集中失效(可能是因为R超时时间设置的相同、Redis主机宕机、数据丢失等原因造成),导致Redis分布式缓存不可用。
缓存雪崩问题解决方案:
6.4 缓存穿透、缓存击穿、缓存雪崩之间是什么关系?
缓存穿透、缓存击穿、缓存雪崩都是导致Redis服务不可用的不良诱因,但是他们的表现形式在Redis服务器中来看是完全不同的:
缓存穿透:频繁使用不存在的Key访问,Redis中本来没有这个Key
缓存击穿:一个热点Key在高并发访问的时候突然失效,Redis中有这个Key
缓存雪崩:多个Key在一段时间内集中失效,Redis中有这些Key
7.1 什么是中心化集群?什么是去中心化集群?
传统的Redis集群就是中心化集群(主从复制+哨兵模式),这是因为在传统Redis集群中只有一个Redis主机,集群中的所有Redis从机都直接或间接关联这个Redis主机,也就是说整个集群只有一个中心,就是这个Redis主机。
Redis Cluster分片集群是去中心化分片集群(Hash卡槽动态迁移),这是因为在Redis Cluster分片集群中把整个集群划分为多个片区,每个片区都有一个Redis主机和多个Redis从机,是一种多主多从的实现方式,每个片区之间都是平级的,所以没有哪一个Redis主机是整个集群的中心,也就是去中心化。
7.2 中心化集群和去中心化集群分别有什么特点?
中心化集群:所有从机依赖一个主机
优点:可以使用Redis主机统一调度关联它的所有Redis从机,方便管理集群中的Redis服务器。
缺点:只能对一个Redis主机执行写操作,增加了Redis主机运行压力。另一方面,因为Redis集群中主机和从机的数据始终保持同步,如果从机多了之后,数据同步效率会降低,并且会存在大量冗余的同步数据,浪费内存空间。
7.3 Redis Cluster去中心化分片集群的实现原理是什么?
Redis Cluster是一个去中心化分片集群,主要实现原理是在集群中划分多个片区,使用Hash卡槽来确定每一个Key存放在哪个片区的Redis主机中,根据每个Redis主机分配的Hash卡槽范围不同,集群中各片区的Redis主机之间是可以相互转发数据的。
每个片区都使用一个Redis主机和多个Redis从机,是一种多主多从的实现方式,片区和片区之间是平级的,Redis数据可以在集群中各片区之间相互转发的,实现了去中心化。
Redis Cluster去中心化分片集群的特点是:可以实现Redis动态扩容和缩容,可以使用Redis Cluster集群中的多个Redis主机来分摊Key,降低缓存雪崩带来的数据丢失风险。
Redis Cluster集群在初始化的时候,默认会分配16384个Hash卡槽,默认根据Redis主机数量分摊这些Hash卡槽。对于每次客户端写入的Key会通过crc16(key)%16384公式计算Key的Hash卡槽值,根据Hash卡槽值的不同会自动把Key转发到卡槽范围包含Key的Hash卡槽值的Redis主机中存放。客户端获取Key的时候也是通过Key的Hash卡槽值在Redis集群中找到对应的Redis主机并读取值的。
7.4 什么是Hash卡槽?Hash卡槽的作用是什么?
Hash卡槽是用来在Redis Cluster去中心化分片集群中,唯一定位Key存放在哪个Redis主机的标志,根据客户端请求的Key计算出的Hash卡槽值的不同,会自动转发到卡槽范围包含这个Key的Hash卡槽值的Redis主机中进行存取。
Hash卡槽总数默认分配的是16384个,根据集群中Redis主机数量的不同会平均分摊这些Hash卡槽数到对应的Redis主机。
注意:
每个Hash卡槽可以存放一个或多个Key,只有主机会分配Hash卡槽,从机没有Hash卡槽。
7.5 写入一个Key,如何定位这个Key写入到哪个Redis主机?
(1) 根据公式crc16(key)%16384计算出Key对应的Hash卡槽值
(2) 在Redis Cluster集群中,会匹配Hash卡槽范围包含这个Key的Hash卡槽值的Redis主机中进行读写。如果当前连接的Redis主机中没有这个Key,也会自动转发到Hash卡槽范围包含这个Key的Hash卡槽值的Redis主机进行读写。
7.6 读取Key的时候,如果当前连接的Redis主机中不存在这个Key,那么还能取到吗?
能取到,会根据这个Key计算出的Hash卡槽值转发到对应的Redis主机中获取。
7.7 crc16算法是做什么的?Hash卡槽如何分配?默认卡槽数分配多少?
crc16是用来计算Key的Hash卡槽值的一个算法,Hash卡槽值的计算公式是:crc16(key)%16384
Redis Cluster去中心化分片集群初始化的时候,默认分配Hash卡槽数是16384
7.8 Redis Cluster集群中,Redis主机宕机了怎么办?
Redis Cluster集群中,如果某个片区的Redis主机宕机了,对应片区的Redis从机会基于哨兵模式重新进行投票选举,产生新的Redis主机,不会影响整个Redis Cluster去中心化分片集群的正常运作。
8.1 Redis Cluster集群扩容是什么意思?如何实现扩容?
Redis Cluster集群扩容指的是在集群中新增一个片区(包括片区内的Redis主机和关联的Redis从机)并重新分配Hash卡槽,减轻其他Redis主机Key的存储和访问压力。
Redis Cluster集群在扩容的时候,需要指定扩容的Redis主机、Redis从机和Redis主机最大分配卡槽数,在执行扩容的时候,会重新分配Redis Cluster集群中所有Redis主机的Hash卡槽数,扩容之后,每个Redis主机的Hash卡槽数都会减少,除扩容的Redis主机外的所有Redis主机减少的卡槽数都分配到了新的扩容后的Redis主机中,实现Redis集群分摊Key,根据每个Key的卡槽值把Key分配到对应的Redis主机中,这样一来在发生缓存雪崩时可以减少丢失数据的数量。
8.2 Redis Cluster集群缩容是什么意思?如何实现缩容?
Redis Cluster集群缩容指的是移除集群中某个片区Redis主机的卡槽数,把卡槽数还原到某个其他的Redis主机中的过程。
Redis Cluster集群在缩容的时候,需要指定从哪个Redis主机缩容,缩容多少Hash卡槽,缩容后的目标是哪个Redis主机(也就是指定Hash卡槽动态迁移的来源、目标和需要迁移的Hash卡槽总数)
在缩容完成后,缩容前的Redis主机会减少对应数量的Hash卡槽,而这些Hash卡槽会增加到迁移目标的Redis主机中
在扩容和缩容时指定的动态迁移的Hash卡槽总数一致的前提下,Redis Cluster集群扩容和缩容互为逆操作。
8.3 Redis Cluster集群在扩容和缩容的时候,数据会丢失吗?
不会丢失。Redis Cluster集群在扩容和缩容的时候,本质都是在动态迁移Hash卡槽值,只是实现的方式不同。Hash卡槽值在迁移的时候会连同Hash卡槽值中存放的Key一起迁移的,所以一般情况下在扩容和缩容的时候数据不会丢失,但是如果出现集群脑裂的极端情况可能会导致丢失部分数据,但是只要做好了集群的持久化和数据同步,实现了主从复制,一般不会出现这种极端情况。
9.1 什么是布隆过滤器?Redis布隆过滤器用来做什么?布隆过滤器有什么特点?
布隆过滤器本质是基于布隆算法实现的一种拦截过滤机制,主要是用来解决Redis缓存穿透问题。
布隆过滤器解决Redis缓存穿透问题的实现方式是:预先定义一个set集合,存放数据库中的数据,执行查询操作的时候,在Redis查询之前加入布隆过滤器作为首道拦截和过滤条件(原来是现在Redis中查询,没有的话再到数据库查询),那么客户端如果频繁发送不存在的Key,布隆过滤器就会校验到这些Key在set集合中不存在,所以会直接返回,不会访问Redis服务器和数据库服务器,从而解决缓存穿透问题。
布隆过滤器的特点是基于布隆算法实现,所以可以在set集合中存储大量的Key得数据,并且根据指定key检索这个key在set集合是否存在效率很高。
但是布隆过滤器存在一定的误判率,也就是说不能保证百分之百完全拦截不存在的Key,布隆过滤器的误判率越低,准确性越高。布隆过滤器的误判率和集合元素的内存占用率有着紧密的关联,也就是说集合元素的多少会影响布隆过滤器的误判率:
集合中存放的元素越多,误判率越低,准确性越高,但是内存空间占用多。
集合中存放的元素越少,误判率越高,准确性越低,但是节省内存空间。
综上所述,如果想保证布隆过滤器绝对的准确性,又想保证占用最少的内存空间,这种情况是无法实现的。
布隆过滤器的误判率可以这样理解:
9.2 什么是集群脑裂?集群脑裂如何避免?
集群脑裂指的是当发生一些人为无法干预的意外情况(如网络抖动、系统故障),最终出现Redis集群中,各Redis服务器数据丢失、数据不一致的严重问题。
集群脑裂最主要的一种表现形式就是在Redis主机宕机之后,哨兵模式进行投票选举产生新的Redis主机之前,客户端仍然不断发送写操作请求,此时由于客户端仍然连接的是宕机的Redis主机,所以会导致投票选举完成产生新的Redis主机之后,这个期间写入的数据丢失,最终导致数据不一致。
集群脑裂问题是一种导致Redis集群不可用的极端问题,一般情况下只要做好Redis集群中各节点的持久化、一致性、主从复制、哨兵模式,最好配置Redis集群中各Redis主机对于写操作的拦截机制,就可以降低Redis集群脑裂发生的可能性。
配置Redis主机对于写操作的拦截机制,可以拦截和阻止哨兵进行投票选举期间客户端发送的写操作请求。
解决Redis集群脑裂的一般思路是:宁愿让客户端知道写入失败,也不能让写入成功的数据在哨兵投票选举期间丢失。
9.3 强一致性、弱一致性、最终一致性是什么含义?区别是什么?
强一致性:数据同步之后,同步前和同步后的数据完全一致
弱一致性:数据同步之后,同步前和同步后的数据部分一致
最终一致性:整个数据同步过程是完整的,也就是说最终实现了数据同步,但是不保证同步前后的数据完全一致。
9.4 Redis集群中服务器之间的数据能确保始终是完全、同步、一致的吗?Redis使用哪一种数据一致性实现数据同步的?
Redis集群正常运行期间,可能会出现一些人为无法干预和百分之百完全避免的恶劣情形(比如网络抖动、系统故障),但是Redis集群中各Redis服务器的持久化、一致性、数据同步实现方式很多都是基于客户端发生的写操作和定时来触发的,所以不能保证Redis集群中所有Redis服务器数据始终是保持完全同步和一致的,也就是说,Redis只能保证最终一致性,不能保证强一致性。
转载请注明原作者