跟随黑马面Java-2,Redis篇
Joker2Yue第二篇:Redis篇
“求其上,得其中;求其中,得其下,求其下,必败”
开篇
引言
Redis作为目前后端开发中最常用的缓存中间件,几乎每个项目都会使用到Redis。因此,Redis在面试中占比是非常高的。
注意:此篇需要重点准备!
提问方式
-
Redis使用场景
问题 Redis的数据持久化策略有哪些 什么是缓存穿透,怎么解决 什么是布隆过滤器 什么是缓存击穿,怎么解决 什么是缓存雪崩,怎么解决 Redis双写问题 Redis分布式锁如何实现 Redis实现分布式锁如何合理的控制锁的有效时长 Redis的数据过期策略有哪些 Redis的数据淘汰策略有哪些 -
其他面试
这些一般不会和业务捆绑提问,而是会单独提问
问题 Redis集群有哪些方案,知道嘛 什么是Redis主从同步 你们使用Redis是单点还是集群?哪种集群 Redis分片集群中数据是怎么存储和读取的 redis集群脑裂 怎么保证redis的高并发高可用 你们用过Redis的事务吗?事务的命令有哪些 Redis是单线程的,但是为什么还那么快?
Redis使用场景面试题
注意点
使用场景需要结合实际项目
问题及回答
什么是缓存穿透,怎么解决
-
正常的缓存操作:
-
缓存穿透:查询一个不存在的数据,Mysql查询不到数据也不会直接写入缓存,就会导致每次请求都查数据库。
-
解决方案:
-
方案1:缓存空数据,查询返回的数据为空,仍将这个空结果进行缓存。
优点:简单
缺点:消耗内存,可能会发生缓存不一致的问题。
-
方案2:布隆过滤器
如果布隆过滤器中查询的到数据,就允许继续查询,否则直接返回。在缓存预热的时候,需要预热布隆过滤器。
优点:内存占用较少,没有多余的key
缺点:实现复杂,存在误判
-
什么是布隆过滤器
-
bitmap(位图):相当于是一个以
bit(位)
为单位的数组,数组中的每个单元只能存储二进制0或1 -
布隆过滤器作用:布隆过滤器可以用于检索一个元素是否在一个集合中。
-
布隆过滤器误判:
数组越小越容易误判,越大越消耗内存
-
布隆过滤器设置:
现在已经有了很多布隆过滤器的实现方案,比如Redission/Guava等,在初始化布隆过滤器时,能够对其进行误判率的设置。通常一般设置在5%以内,保证数据误判率的同时,不至于在高并发场景下压倒数据库。
什么是缓存击穿,怎么解决
-
缓存击穿:给某一个key设置了过期时间,当key过期的时候,恰好这时间点对这个key有大量的并发请求过来,这些并发的请求可能会瞬间把DB压垮
-
解决方案:
-
方案一:互斥锁
即全部等待,直到第一个进程查询完成数据并将数据写入缓存
-
方案二:逻辑过期
热点数据不设置过期时间(这里的不设置过期时间不是指【数据在缓存中永远不会过期】,而是指定期刷新数据,让此数据的过期时间反复重置。如果设置永久过期,你不知道现在缓存数据是不是最新的,你得有一个过期时间来触发去数据库刷新最新数据到缓存中)。
要实现此逻辑过期,存入Redis的数据还需要另外设置一个字段,这里以
expire
为例。在
expire
到期后,将触发刷新,重新从数据库中获取数据,重置缓存时间。
-
-
总结:
如果选择方案一,能够保证数据的强一致性,也就是保证缓存中数据总是最新的。同时,此方案性能较差,因为高并发下,另外的所有线程都需要等待缓存建立。
方案二能够保证数据的高可用性,能够使每次访问缓存都能立即返回结果。同时,性能较好。
什么是缓存雪崩,怎么解决
-
缓存雪崩:是指在同一时段大量的缓存key同时失效或者Redis服务岩机,导致大量请求到达数据库,带来巨大压力。
-
同时有大量缓存过期:
- 解决方案:给不同的key设置不同的过期时间(给不同的key的TTL添加随机值)
-
Redis宕机:
-
解决方案:搭建Redis高可用的集群来预防(哨兵模式/集群模式)
或者给缓存业务添加降级限流策略(nginx或者SpringCloudGateway)
或者给业务添加多级缓存(Guava或Caffeine作为一级缓存,Redis作为二级缓存)
-
Redis双写问题
-
redis做为缓存,mysql的数据如何与redis进行同步呢?
- 回答时一定要先设置前提,介绍自己的业务。
- 介绍自己简历上的业务,我们当时是把文章的热点数据存入到了缓存中,虽然是热点数据,但是实时要求性并没有那么高,所以,我们当时采用的是异步的方案同步的数据。
- 我们当时是把抢券的库存存入到了缓存中,这个需要实时的进行数据同步,为了保证数据的强一致,我们当时采用的是redisson提供的读写锁来保证数据的同步
-
什么是双写一致性:当修改了数据库的数据也要同时更新缓存的数据,缓存和数据库的数据应当要保持一致。
-
业务要求:一致性要求高
-
方案一:延迟双删:就是在修改数据时,先删除缓存,然后修改数据库,然后过一段时间再删除缓存。
-
一般的业务逻辑中,我们是删除缓存,然后修改数据库。那么就有下面一个问题:是先删除缓存还是先修改数据库?
-
先删除缓存还是先修改数据库?都可能出现脏数据
-
先删缓存:可能会出现脏数据。
-
正常情况是:线程1删除缓存,然后立即更新数据库。线程2在数据库更新完成后进行查询,未命中缓存,才查询数据库并重建缓存。
-
脏数据情况:线程1删除缓存,没来得及更新数据库,线程2就进行查询、缓存重建。这会导致后续进程读到缓存的脏数据。
-
-
先修改数据库:也可能导致脏数据
- 正常情况是:线程1查询缓存,未命中(可能是已经过期),于是查询数据库并重建缓存。线程2在缓存重建后命中缓存。
- 脏数据情况:线程1查询缓存,未命中,还没来得及重建缓存。线程2就进行更新数据同时删除缓存(缓存在查询时建立)。线程1重新切换回来并继续重建缓存。此时缓存中的数据不是经过2更新后的数据,而是脏数据。
-
-
方案二:互斥锁
-
分布式锁:
在每次读写数据的时候都加锁,防止其他线程读取。(性能低)
-
读写锁:
在读数据的时候加共享锁,允许其他线程读但不允许其写。在写数据的时候加排他锁,不允许其他线程读写。
-
-
-
-
业务要求:允许延迟
方案:异步通知
-
基于MQ的异步通知:使用MQ中间件,更新数据之后,通知删除缓存。
在进行Mysql的修改时,发一条消息给MQ,MQ将监听消息,并更新缓存。这种方案肯定是有延迟的,但是能够保证最终的一致性,这取决于MQ的可靠性。
-
基于的Canal异步通知:利用Canal中间件,不需要删除业务代码,伪装成Mysql的一个从节点,Canel通过读取BINLOG数据更新缓存。
Canal是通过监听数据库的变化来监听数据操作的。Mysql有个BINLOG,记录所有DDL和DML语句。当我们的表数据发生变化之后,Canal会检测到,并通知缓存服务以更新缓存。这种方式是0侵入的,因为它不需要修改业务代码。这种方案也是有延时的。
-
Redis的数据持久化策略有哪些
在Redis中提供了两种数据持久化的方式:RDB/AOF
-
RDB:Redis Database Backup File(Redis 数据备份文件),所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。
1
2
3
4
5[root@localhost ~]# redis-cli
127.0.0.1:6379> save #由Redis主进程来执行RDB,会阻塞所有命令
ok
127.0.0.1:6379> bgsave #开启子进程执行RDB,避免主进程受到影响
Background saving startedRedis内部有触发RDB的机制,可以在
redis.conf
文件中找到,格式如下:1
2
3
4#900秒内,如果至少有1个key被修改,则执行bgsave
save 900 1
save 300 10
save 60 10000-
执行原理
bgsave开始时会fork进程得到子进程,子进程共享主进程的内存数据。完成fork后读取内存数据并写入RDB文件。
在fork时,子进程会复制主进程的页表而不是整个内存单元。这样可以加快读取操作。
在bgsave备份数据的时候,主线程可以进行写操作,那么是否会冲突呢?
-
不会,因为fork采用的是copy-on-write技术。
- 当主进程执行读操作的时候,访问共享内存。
- 当主进程执行写操作的时候,子进程会拷贝一份此数据的块,主进程在副本上执行写操作。写完后,之后的读操作也会在此块上读。
-
-
-
AOF:Append Only File(追加文件)。Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件。
AOF默认是关闭的,需要修改
redis.conf
配置文件来开启AOF。1
2
3
4#是否开启AOF功能,默认是no
appendonly yes
#AOF文件的名称
appendfilename "appendonly.aof"-
当然,你也可以配置其命令记录频率。
配置项 刷盘时机 优点 缺点 Always 同步刷盘 可靠性高,几乎不丢失数据 性能影响大 everysec 每秒刷盘 性能适中 最多丢失1秒数据 no 操作系统控制 性能最好 可靠性较差,可能丢失大量数据 1
2
3
4
5
6#表每执行一次写命令,立即记录到AOF文件
appendfsync always
#写命令执行完先放入AOF缓冲区,然后表示每隔1秒将缓冲区数据写到AOF文件,是默认方案
appendfsync everysec
#写命令执行完先放入AOF缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
appendfsync no -
注意:因为是记录命令,AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作,但只有最后一次写操作才有意义。通过执行bgrewriteaof命令,可以让AOF文件执行重写功能,用最少的命令达到相同效果。
Redis也会在触发阈值的时候自动重写AOF文件。阈值可以在redis中配置
1
2
3
4#AOF文件比上次文件 增长超过多少百分比则触发重写
auto-aof-rewrite-percentage 100
#AOF文件体积最小多大以上才触发重写
auto-aof-rewrite-min-size 64mb
-
-
RDB和AOF对比
类型 RDB AOF 持久化方式 定时对整个内存做快照 记录每一次执行的命令 数据完整性 不完整,两次备份之间会丢失 相对完整,取决于刷盘策略 文件大小 会有压缩,文件体积小 记录命令,文件体积很大 宕机恢复速度 很快 慢 数据恢复优先级 低,因为数据完整性不如AOF 高,因为数据完整性更高 系统资源占用 高,大量CPU和内存消耗 低,主要是磁盘I0资源。但AOF重写时会占用大量CPU和内存资源 使用场景 可以容忍数分钟的数据丢失,追求更快的启动速度 对数据安全性要求较高常见
Redis的数据过期策略有哪些
Redis的key过期之后,会被立刻删除吗?
-
Redis对数据设置数据的有效时间,数据过期以后,就需要将数据从内存中删除掉。可以按照不同的规则进行删除,这种删除规则就被称之为数据的删除策略(数据过期策略)。
-
在Redis中提供了两种过期策略,惰性删除/定期删除。它们配合使用
-
惰性删除
-
设置该key过期时间之后,我们不去管他,当需要该key时,我们再去检查其是否过期,如果过期,删除之;否则返回此key。
-
优点:对CPU友好,只会在使用该key时才会进行过期检查,对于很多用不到的key不用浪费时间进行过期检查。缺点:对内存不友好,如果一个key已经过期,但是一直没有使用,那么该key就会一直存在内存中,内存永远不会释放。
-
-
定期删除
-
每隔一段时间,我们就对一定量的key进行检查,删除里面过期的key(从一定数量的数据库中取出一定数量的随机key进行检查,并删除其中的过期key)。
-
定期删除有两种模式:
- SLOW模式是定时任务,执行频率默认为10hz,每次不超过25ms,以通过修改配置文件redis.conf的hz选项来调整这个次数
- FAST模式执行频率不固定,但两次间隔不低于2ms,每次耗时不超过1ms
-
优点:可以通过限制删除操作执行的时长和频率来减少删除操作对CPU的影响。另外定期删除,也能有效释放过期键占用的内存。
缺点:难以确定删除操作执行的时长和频率。
-
Redis的数据淘汰策略有哪些
假如缓存过多,内存是有限的,内存被占满了怎么办?
-
数据的淘汰策略:当Redis中的内存不够用时,此时在向Redis中添加新的key,那么Redis就会按照某一种规则将内存中的数据删除掉,这种数据的删除规则被称之为内存的淘汰策略。
-
Redis支持8种不同策略来选择要删除的key:
策略 说明 noeviction 不淘汰任何key,但是内存满时不允许写入新数据。默认 volatile-ttl 对设置了TTL的key,比较它们的剩余TTL值,TTL小的优先淘汰。 allkeys-random 对全体key,随机进行淘汰 volatile-random 对设置了TTL的key,随机进行淘汰 allkeys-lru 对全体key,基于LRU算法进行淘汰 volatile-lru 对设置了TTL的key,基于LRU算法进行淘汰 allkeys-lfu 对全体key,基于LFU算法进行淘汰 volatile-lfu 对设置了TTl的key,基于LFU算法进行淘汰 LRU(Least Recently Used)最近最少使用。用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高。
LFU(Least Frequently Used)最少频率使用。会统计每个key的访问频率,值越小淘汰优先级越高。 -
使用建议
- 优先使用allkeys-lru策略。充分利用 LRU 算法的优势,把最近最常访问的数据留在缓存中。如果业务有明显的冷热数据区分,建议使用。
- 如果业务中数据访问频率差别不大,没有明显冷热数据区分,建议使用allkeys-random,随机选择淘汰。
- 如果业务中有置顶的需求,可以使用volatile-lru策略,同时置顶数据不设置过期时间,这些数据就一直不被删除会淘汰其他设置过期时间的数据。
- 如果业务中有短时高频访问的数据,可以使用 allkeys-lfu或volatile-lfu 策略。
-
其他问题
-
数据库有1000万数据,Redis只能缓存20w数据,如何保证Redis中的数据都是热点数据?
用allkeys-lru(挑选最近最少使用的数据淘汰)淘汰策略,留下来的都是经常访问的热点数据。
-
Redis的内存用完了会发生什么?
主要看数据淘汰策略是什么?如果是默认的配置(noeviction),会直接报错。
-
Redis分布式锁如何实现/Redis实现分布式锁如何合理的控制锁的有效时长
需要结合项目中的业务进行回答,通常情况下,分布式锁使用的场景:集群情况下的定时任务、抢单、幂等性场景。
-
抢票-加锁超卖
这种情况,如果是最后一张票(票:1)就可能出现超卖的情况(票:-1)
我们可以为其加锁来防止超卖,如下:
但是我们在项目中,一般会用到Nginx做反向代理,来实现服务的负载均衡:
这种情况下,为其加synchronize锁就无效了,因为此锁是JVM本地的锁,只对JVM本地有效,而多个微服务是处在不同的JVM中。
-
分布式锁
所有微服务的所有线程想要获取票,都需要先获取分布式互斥锁。
-
Redis分布式锁
Redis实现分布式锁主要利用Redis的
setnx
命令。setnx是SET if not exists(如果不存在,则 SET)的简写。获取锁:
1
2#添加锁,NX是互斥,EX是设置超时时间
SET key value NX EX 10 # 一定要加上这个失效时间,以免服务宕机导致死锁-
“set key value nx ex 10”,它是在设置键值对的同时设置了一个锁,如果锁已存在则不执行任何操作,同时设置了过期时间为 10 秒。
-
“setnx key value ex 10”,它是尝试设置一个带有过期时间的键值对,如果该键不存在则设置成功,否则不执行任何操作。
释放锁:
1
2# 释放锁,删除即可
DEL key-
如何合理的控制有效时长?
如果业务执行时间过长,那么锁可能自己释放。一般来说有两种方案:根据业务执行时间预估/给锁续期。但是第一种方案缺乏可靠性,我们一般采用第二种方案。另开一个线程,使其监督业务运行状体和锁的状态,及时给锁续期。
但是我们手工实现此方案过于复杂 ,其实市面上已经有基于redisson实现的分布式锁。
-
-
Redisson实现的分布式锁-执行流程
-
线程加锁成功后,将能够直接操作Redis。
-
加锁成功后,将会另开一个线程进行监控,一般称之为
watch dog
(看门狗)。watchdog将不断监听持有锁的线程,为其增加锁的持有时间(续期)。 -
续期是每隔
releaseTime/3
的时间做一次续期。这个releaseTime就是锁的过期时间,默认是30s。那么也就是每隔10s就会给持有锁的线程进行一次续期 ,将会重置锁的过期时间。比如锁的过期时间是30s,那么每隔10s就会重置锁的过期时间。 -
何时释放锁?手动释放锁(代码执行)。在手动释放后,需要通知watchdog停止对锁的监听,因为其key已经失效删除。
-
如果此时来了一个新的线程,它也会尝试去加锁,看看是否能够加锁成功。如果加锁成功,那么和上述流程一样。假如没有,它将循环尝试获取锁,在一定时间后,要么成功获取到锁,要么放弃循环。
-
代码如下:
-
首先获取到了Redis客户端,然后通过Redis客户端获取锁。
-
然后尝试上锁,需要注意的是
1
2
3
4
5// 第一个参数:循环等待锁的最大等待时间。第二个参数:时间单位
boolean isLock = lock.tryLock(10,TimeUnit.SECONDS);
// 第一个参数:循环等待锁的最大等待时间。第二个参数:这个锁的失效时间。第三个参数:时间单位
boolean isLock = lock.tryLock(10,30,TimeUnit.SECONDS);
// 设置了锁的失效时间时,将没有watchdog监听了,因为redisson认为你能够自己控制锁的失效时间,也就不自动续期了。如果你将其写为-1,那么watchdog仍能生效。这些加锁、设置过期时间等操作都是基于lua脚本完成的,其能够保证命令的原子性。lua脚本最大的作用是能够调用redis命令,能保证多条命令的原子性。
-
-
-
Redisson实现的分布式锁-可重入
-
我们在
add1()
方法中获取了一个锁,add2()
方法也获取此锁。在add1()
方法中调用add2()
,add2()
是可以成功执行的,这说明Redisson实现的分布式锁是可重入的。因为他们位于同一个线程。 -
在Redisson中,其实现如下:
使用Hash结构记录线程id和重入次数,其中,key可以通过业务进行命名,field表示当前持有锁的线程标识,value表示重入次数。
当执行
add1()
时,线程在Redisson中获取的结构如上所示。而当add2()
调用后,value将会增加1,如下:如果此线程有调用
lock.unlock()
,那么value将会-1。当value到0时,Redisson将会删除此锁信息。
-
-
Redisson实现的分布式锁-主从一致性
-
背景:
在使用Redis搭建的集群架构中,会有一个主节点和若干个从节点,如下:
主节点主要负责写操作,从节点主要负责读操作。当主节点发生了写操作之后,就要将数据同步给从节点,以保证数据一致性。
此时有一Java应用创建了一分布式锁,因为是获取锁,是写数据,所以需要先将数据写入到主节点中。而此时主节点尚未来得及将数据同步给从给点,便宕机了。依据Redis提供的哨兵模式,将会从剩下的从节点中选举一个节点作为主节点(这里以下方节点为例)。
若此时又有一Java应用创建相同分布式锁,由于新的主节点未从旧的主节点上同步数据,那么,此Java应用也会加锁成功。
这样,两个线程将同时拥有同一把锁,将会丧失锁的基本性质-互斥性,这样是万万不可的。
-
RedLock:
在Redis中,提供了另一种分布式锁RedLock,其特性是:不只在一个Redis实例上创建锁,而是在多(
n/2+1
)个实例上创建锁。其中n为redis节点数量。但是RedLock的缺点也很明显,实现复杂,性能差,运维繁琐。
Redis的思想是AP思想,优先保证可用性;如果实在要保证一致性,可以使用基于CP思想的Zookepper。
CAP:C(一致性),A(可用性),P(分区容错)
-
Redis其他面试问题
-
Redis集群有哪些方案
- 主从复制:主节点负责写操作,从节点复制主节点的数据,提供读操作。
- 哨兵模式:在主从复制基础上,增加哨兵节点监控主从节点状态,自动进行故障转移和通知。
- 分片集群:数据分布在多个节点上,每个节点负责一部分数据,通过哈希槽进行数据分片。
-
主从复制
单节点Redis的并发能力是有上限的,要搭建主从集群,实现读写分离。
-
主从全量同步
- 从节点执行
replicaof
命令,与主节点建立连接。 - 从节点请求数据同步。
- 主节点判断是否为首次同步。
- 若是首次,同步数据版本信息。
- 从节点保存版本信息。
- 主节点执行
bgsave
,生成RDB文件。 - 主节点发送RDB文件。
- 从节点清空本地数据并加载RDB文件。
- 主节点记录RDB期间的所有命令。
- 主节点发送
repl_backlog
中的命令。 - 从节点执行接收到的命令。
-
Replicationld:简称replid,是数据集的标记,id一致则说明是同一数据集。每一个master都有唯一的replid,slave则会继承master节点的replid
-
offset:偏移量,随着记录在repl_baklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset,说明slave数据落后于master,需要更新。
现在便有了两个问题:1,主节点是如何判断是否第一次同步呢?2,主节点和从节点之间是如何确保传输的就是需要的那部分数据呢?
-
在从节点发起数据同步的时候,就会把自己的replid发送给主节点。主节点将会根据从节点发送的replid与自己的进行比对,如果不一致,说明是第一次进行数据同步。然后,主节点将自己的replid发送给从节点,从节点就将此replid保存,此时,主从节点的replid一致。主节点于是开始执行bgsave,生成RDB。
-
主从节点的replid一致,主节点将会发送repl_baklog日志,这样比发送RDB文件要快。但是如何判断需要发送多少repl_baklog呢?这由offset偏移量决定。
假如主节点中的offset为80,从节点中的offset为50,说明相差的30数据没有同步完成,Redis就会对这30数据进行同步。
-
总结-3个大步骤
- 从节点给主节点发送请求想要同步数据,主节点master判断是否为第一次请求。如果是第一次请求,则全量同步。
- 主节点将会执行bgsave,生成一个RDB文件发送给从节点执行。
- 如果主节点在执行bgsave过程中执行了其他命令,将能够在repl_baklog中获取到,然后将此日志发送给从节点去执行。整个流程结束。
- 从节点执行
-
主从增量同步
主从增量同步(slave重启或主从数据变化)
-
从节点重启后,将会给主节点发送一个psync请求,其带了两个值,replid/offset
-
主节点Master判断其replied是否一致(不一致将会全量同步)
-
主节点会从repl_baklog中获取offset之后的数据,发送给从节点同步数据
-
-
-
哨兵模式
主从复制不能保证Redis的高可用,因为只要主节点宕机 ,那么redis就只能读不能写了。
-
Redis提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。哨兵的结构和作用如下:
-
监控:Sentinel 会不断检查您的master和slave是否按预期工作
-
自动故障恢复:如果master故障,Sentinel会将一个slave提升为master。 当故障实例恢复后也以新的master为主
-
通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端
-
-
Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令:
-
主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线
-
客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。
-
-
哨兵选主规则:
- 首先判断主与从节点断开时间长短,如超过指定值就排该从节点
- 然后判断从节点的slave-priority值,越小优先级越高
- 如果slave-priority一样,则判断slave节点的offset值,越大优先级越高
- 最后是判断slave节点的运行id大小,越小优先级越高
-
Redis集群(哨兵模式)脑裂
如果因为网络原因,内网中,主节点与所有从节点&哨兵都断开连接(或处于不同的网络分区),那么哨兵就会在从节点中选举一个主节点出来。而此时老的主节点并没有宕机,还和客户端保持着连接,客户端也在向老主节点写入数据。而内网的网络一旦恢复,哨兵会将老的主节点强制降为从节点,那么这个老的主节点(现在是从节点)将会把自己的数据全部清空,然后从新的主节点中复制数据。这样,这段脑裂过程中客户端写入的数据于是丢失了。
为防止集群脑裂,我们可以修改以下配置:
1
2min-replicas-to-write 1 # 表示最少的salve节点为1个,如果主节点连接的slave少于1,主节点将拒绝写入数据。
min-replicas-max-lag 5 # 表示数据复制和同步的延迟不能超过5秒,如果从节点的数据复制和同步延迟超过5秒,主节点将拒绝继续写入操作。
-
-
分片集群模式
-
主从和哨兵模式可以解决高可用、高并发读的问题,但是依然有两个问题:
-
海量数据存储问题
-
高并发写的问题
-
-
使用分片集群可以解决上述问题,分片集群特征:
- 集群中有多个Master,每个master保存不同的数据
- 每个master都可以有多个slave节点
- master之间通过ping检测彼此健康状态(哨兵模式)
- 客户端请求可以访问集群中任意一节点,最终都会被转发到正确节点(因为自动路由)
-
分片集群结构-数据读写
Redis 分片集群引入了哈希槽的概念,Redis集群有 16384 个哈希槽,每个key通过 CRC16校验后对16384 取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。
如果我们
set name itheima
,集群将会对其做CRC16计算name的hash,然后通过hash后的值,将name存到指定的节点中;如果要指定有效值,你可以set {valid}name key
-
Redis是单线程的,但是为什么还是这么快?
-
一般答到这里就可以了
-
Redis是纯内存操作,执行速度非常快
-
采用单线程,避免不必要的上下文切换可竞争条件,多线程还要考虑线程安全问题
-
使用I/O多路复用模型,非阻塞IO
-
-
I/O多路复用
Redis是纯内存操作,执行速度非常快,它的性能瓶颈是网络延迟而不是执行速度,I/O多路复用模型主要就是实现了高效的网络请求。
-
用户空间和内核空间
- Linux系统中一个进程使用的内存情况划分两部分:内核空间、用户空间
- 用户空间只能执行受限的命令(Ring3),而且不能直接调用系统资源,必须通过内核提供的接口来访问
- 内核空间可以执行特权命令(Ring0),调用一切系统资源
Linux系统为了提高IO效率,会在用户空间和内核空间都加入缓冲区:
-
写数据时,要把用户缓冲数据拷贝到内核缓冲区,然后写入设备
-
读数据时,要从设备读取数据到内核缓冲区,然后拷贝到用户缓冲区
-
阻塞IO/非阻塞IO/多路IO复用
-
阻塞IO:
-
非阻塞IO:
-
IO多路复用
-
总结
IO 模型 工作原理 优点 缺点 阻塞 IO IO 操作(读或写)会阻塞当前进程,直到操作完成 实现简单,编程模型直观 效率低下,无法处理高并发连接,每个 IO 操作都要等待 非阻塞 IO IO 操作立即返回,不会阻塞进程;如果操作未完成,会返回一个错误,程序继续执行其他任务,并轮询检查 可以同时处理多个 IO 请求,提高并发性能 需要不断轮询,编程复杂度高,效率低于 IO 多路复用 IO 多路复用 使用系统调用(如 select
、poll
、epoll
)同时监控多个文件描述符,当任何一个文件描述符准备好读或写时,通知程序进行操作高效处理大量并发连接,减少 CPU 资源浪费,编程模型相对简单 在某些场景下可能不如多线程或多进程模型高效,但总体性能最佳之一
-
-
Redis网络模型
-