一、Redis的使用场景
(一)缓存
1.Redis使用场景缓存
场景:缓存热点数据(如用户信息、商品详情),减少数据库访问压力,提升响应速度。
2.缓存穿透
正常的访问是:根据ID查询文章,先查Redis,如果Redis中命中,返回结果;Redis查不到,查DB,DB查询到结构,返回(返回之前数据存储到Redis)。
缓存穿透是什么?
缓存穿透是大量请求访问缓存和数据库中都不存在的数据(如非法ID或随机攻击),导致请求穿透缓存直接打到数据库,mysql查询不到数据也不会直接写入缓存,每次请求都查数据库,引发数据库压力简单回答:查询不存在的数据,mysql查询不到数据也不会直接写入缓存,导致请求穿透缓存直接打到数据库。
3.缓存穿透解决方案
(1)方案一:缓存空值。
①缓存空数据,查询返回的数据为空,仍把这个空结果进行缓存,设置较短的过期时间。
②优点:简单。
③缺点:消耗内存,可能会发生不一致的问题。
(2)方案二:布隆过滤器
①缓存预热时,需要将布隆过滤器给初始化。例如有一批热点数据,先将这些热点数据批量添加到缓存中,与此同时,要将这些热点数据添加到布隆过滤器中。
②布隆过滤器依靠位图(bitmap):相当于是一个以(bit)位为单位的数组,数组中每个单元只能存储二进制数0或1。
③布隆过滤器作用:可以用于检索一个元素是否在一个集合中。
④布隆过滤器存储数据和查询数据,初始的bitmap中数组中都是0。
存储数据时:假设存储id=1的数据,通过多个hash函数获取hash值,根据hash计算数组的对应位置,将对应位置0改为1。
查询数据时:使用相同hash函数获取hash值,判断对应位置是否都为1。
⑤布隆过滤器存在误判。查询数据时,根据hash函数获取hash值时,可能出现重叠,也就是误判,明明不存在的数据,被误判存在。
误判率:数组越小误判率越大;数组越大误判率越小,但是同时带来了更多的内存消耗。误判不可能不存在,一般设置误判率为0.05(5%)。
⑥优点:内存占用较少,没有多余的key
⑦缺点:实现复杂,存在误判。
⑧布隆过滤器实现方案:Redisson或Guava。
4.缓存击穿
缓存击穿定义:当某一个key设置了过期时间,当key过期的时候,恰好这时间点对这个key有大量的并发请求过来,这些请求发现缓存过期,一般都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把 DB 压垮。
5.缓存击穿解决方案
(1)方案一:互斥锁
假设有两个线程。
线程1查询缓存,未命中,缓存中没有查询需要的数据。接着获取互斥锁成功,线程1查询数据库,查询之后返回数据给缓存,重建缓存数据。将返回的数据写入缓存,最后释放锁。
线程2在线程1查询过程中也发起查询缓存,发现未命中,线程2尝试获取互斥锁但是失败。(线程1目前正获取互斥锁)线程2休眠一会儿后,再返回到发起查询缓存,进行不断的重试,直到线程1释放互斥锁,那么线程2就可以在查询缓存中,缓存命中,返回数据。
第一,可以使用互斥锁:当缓存失效时,不立即去load db,先使用如 Redis 的 SETNX 去设置一个互斥锁。当操作成功返回时,再进行 load db的操作并回设缓存,否则重试get缓存的方法。
特点:确保数据的强一致性,但性能低,且有可能产生死锁的问题。
(2)方案二:逻辑过期
第二种方案是设置当前key逻辑过期,大概思路如下:1) 在设置key的时候,设置一个过期时间字段一块存入缓存中,不给当前key设置过期时间;2) 当查询的时候,从redis取出数据后判断时间是否过期;3) 如果过期,则开通另外一个线程进行数据同步,当前线程正常返回数据,这个数据可能不是最新的。
特点:具有高可用性,性能比较高,但数据同步无法做到强一致,不能保证数据绝对一致。
6.缓存雪崩
缓存雪崩是指在同一时段大量的缓存key同时失效或者Reids服务宕机,导致大量请求到达数据库,带来巨大压力。与缓存击穿的区别是:雪崩是很多key,而击穿是某一个key缓存。
7.缓存雪崩的解决方案
(1)方案一:给不同的key的TTL(失效时间)添加随机值
让不同的key的过期时间是不一样的就可以,例如可以在原有的失效时间上添加随机值。这样,每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
(2)方案二:搭建Redis集群提高服务的可用性,例如哨兵模式、集群模式。
(3)方案三:给缓存业务添加降级限流策略,例如ngxin或spring cloud gateway。
(4)方案四:给业务添加多级缓存,例如Guava或Caffeine
8.双写一致性
一定要设置前提,比如项目中要求一致性要求高,还是允许延迟一致,两者是不同的。
(1)定义
双写一致性:当修改了数据库的数据也要同时更新缓存的数据,缓存和数据库的数据要保持一致。由于数据库和缓存的读写操作可能并发执行,导致缓存与数据库不一致,Redis采取不同策略来解决该问题。
读操作:缓存命中,直接返回数据;缓存未命中查询数据库,返回之前将数据写入缓存,设定该数据的过期时间。
(2)双写一致性问题原因
①先删除缓存,再操作数据库
并发读写冲突:
当更新操作(如先删缓存再更新数据库)和查询操作并发执行时,查询可能读取到旧数据并重新写入缓存,导致缓存与数据库不一致。
时序问题:更新操作删除缓存后,数据库尚未完成更新,此时查询将旧数据重新加载到缓存。
主从同步延迟:数据库主从同步需要时间,从库可能短暂存在旧数据,导致查询结果不一致。
正常情况下:
假设初始缓存=10,数据库=10;
线程1任务:对数据库进行更新操作,将数据库改为20。
线程2任务:查询数据。
线程1先删除缓存中存放的数据,现在缓存=空。然后线程1再去更新数据库,将其改为20;目前数据库=20,缓存=空。
在线程1更新完数据库后。线程2现在要进行查询操作。线程2查询缓存,未命中,再查询数据库,查到20,将20写入缓存。目前数据库=20,缓存=20,两者一致。
冲突情况下:
假设初始缓存=10,数据库=10;
线程1任务:对数据库进行更新操作,将数据库改为20。
线程2任务:查询数据。
线程1先去删除缓存中的数据。数据库还没有进行更改。目前数据库=10,缓存=空。等线程1要去进行下一步更新数据库时。线程2来了。
线程2查询缓存未命中,缓存=空,接着线程2去查询数据库,发现数据库=10,线程2就将该数据写入缓存。目前数据库=10,缓存=10。
线程2执行完查询操作后,线程1接着来进行更新数据库操作,将数据库改为20。目前数据库=20,缓存=10。数据不一致了,无法满足双写一致性,出现了脏读。
②先操作数据库,再删除缓存。
正常情况下:
假设初始缓存=10,数据库=10;
线程1任务:对数据库进行更新操作,将数据库改为20。
线程2任务:查询数据。
此时线程1先进行数据库操作,将数据库改为20,再删除缓存。目前数据库=20,缓存=空。
接着线程2在线程1删除缓存之后开始查询数据,查询缓存,未命中,去查询数据库得到20,接着写入缓存。目前数据库=20,缓存=20。两者一致。
冲突情况下:
正常情况下:
假设初始缓存=10,数据库=10;
线程1任务:对数据库进行更新操作,将数据库改为20。
线程2任务:查询数据。
线程2先查询数据,查询缓存,但是并发情况下,可能该数据过期了,线程2缓存未命中,将去查询数据库,获得数据库=10;接着线程1开始更新数据库,数据库=20,再删除缓存,目前缓存=空;线程1执行完毕,线程2将查询到的数据库10写入缓存,那么此时缓存=10,数据库=20,双写不一致。
先删缓存 vs 先更新数据库:无论哪种顺序,在高并发场景下都可能因线程切换导致脏数据残留。
(3)双写一致性问题解决方案1—延迟双删
在写操作采用延迟双删
①延迟双删原理
先删除缓存,然后进行更新操作,修改数据库,更新操作完成后,延迟一定时间再次删除缓存。
②为什么要删除两次缓存?
先删除缓存,之后再删除一次缓存,就是为了降低脏数据出现。
③为什么要延时呢?
因为数据库主从同步需要时间,一般情况下数据库是主从模式的,是读写分离的,所以需要延时,让主节点把数据同步到从节点。
④延迟双删的实现方式
定时任务:通过 ScheduledExecutorService 的 schedule() 实现,指定时间延迟删除Redis中的缓存。
消息队列:在更新操作之后,设置一条删除Redis中指定缓存的延迟消息,通过发送该延迟消息触发二次删除。
⑤延迟双删的特点
优点:
1)性能高,由于读和写是并发的,性能很高。
2)实现简单:定时任务和消息队列实现都比较简单。
3)保证了数据最终一致性,最终数据是一致的。
缺点:
1)无法保证数据的强一致性,且延迟时间需根据业务调整:由于延迟删除缓存的时刻可能与数据更新完毕(主从同步之后)的时刻间隔了不少时间,在这期间数据的一致性无法保障。
⑥延迟双删使用的场景
允许短暂不一致但对性能要求较高的场景(如文章浏览量统计)。
(4)双写一致性问题解决方案2—读写锁机制
①读写锁原理
通过Redission提供的共享锁(读锁)和排他锁(写锁)控制并发。
②实现过程。
共享锁:对于删除缓存操作即读操作,加共享锁,允许多线程读,但阻塞写操作。直到删除缓存执行完之后,释放锁。
排他锁:对于更新操作即写操作,加排他锁,阻塞其他读写操作,直到更新缓存之后,解锁。
③特点
优点:
1)强一致性保障。
缺点:
1)性能较低。
④使用场景
适用于对一致性要求极高但并发量适中的场景。
(5)异步消息队列(MQ/Canal)
①MQ异步通知原理
数据库更新完数据后,发送消息到MQ,而消费者也就是缓存服务器监听到消息后,更新缓存。
MQ异步通知特点:
优点:解耦数据库和缓存操作,支持最终一致性。
缺点:存在短暂延迟,高并发下会出现数据不一致的情况。
②基于Canal的异步通知原理
Canal是基于MySQL的主从同步来实现的。
MySQL进行更新操作后,数据库发生变化,将这种变化记录于BinLog文件中,Canal监听mysql的Binlog,然后解析binlog,发送消息给缓存,将数据变更情况告知给缓存服务器,接着再更新缓存。
Canal特点:
无代码入侵,适用于复杂系统。
二进制日志(BinLog)记录了所有的DDL(数据定义语言)语句和DML(数据操纵语言)语句,但不包括数据查询(Select、show)语句。
9.数据持久化策略
(1)RDB
①定义
RDB全称Redis Database Backup file(Redis数据备份文件),也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。
save和bgsave是人工手动备份
Redis内部有触发RDB的机制,可以在redis.conf文件中找到,格式如下:
②RDB执行原理
简单理解:
bgsave开始时,主进程通过调用fork()函数创建子进程,子进程共享主进程的内存数据,子进程是异步的,对主进程几乎没有阻塞。完成fork后,子进程读取内存数据并写入RDB文件,此时内存为只读。fork采用copy-on-write技术,当主进程执行读操作时,访问共享内存;当主进程执行写操作时,拷贝一份内存数据,得到数据副本,在数据副本上执行写操作。
详细解析:
bgsave开始时,主进程通过调用fork()函数创建子进程,子进程共享主进程的内存数据,子进程是异步的,对主进程几乎没有阻塞。
有一个进程,Redis的主进程,要去实现对redis的读写操作,要在内存中去操作。但是在linux系统中,所有的进程都没有办法直接操作物理内存,操作系统给每一个进程都分配了一个虚拟内存,主进程只能操作虚拟内存。操作系统会维护虚拟内存和物理内存之间的映射关系表,这个表被称为页表。主进程操作虚拟内存,虚拟内存基于页表的映射,关联到物理内存真正的存储数据的位置,这样就可以对物理内存进行读和写操作。
子进程会拷贝主进程中的页表,也就是映射关系,子进程在操作自己的虚拟内存的时候,也会关联到物理内存。那这就实现了子进程和主进程的内存空间共享。
子进程读取到物理内存中的数据,就可以将数据写入磁盘中,生成新的RDB文件,替换旧的RDB文件。
子进程在写新的RDB文件时,主进程可能接收到写请求,会产生冲突。为避免这个冲突,fork采用了copy-on-write技术,写时复制。也就是将物理内存中的数据变为只读,不可以写。如果主进程接收到写请求,主进程将物理内存中的数据拷贝一份,得到数据副本,在副本上进行写操作。
页表:记录虚拟地址和物理地址的映射关系。
(2)AOF
①定义
AOF全称为Append Only File(追加文件)。Redis处理的每一个写命令(修改数据的命令,如set)都会存储在AOF文件,可以看作是命令日志文件。
②详细解析AOF
AOF默认是关闭的,需要修改redis.conf配置文件来开启AOF
AOF的命令记录的频率也可以通过redis.conf文件来配
一般在项目中,我们都会采用everysec。
因为是记录命令,AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作,但只有最后一次写操作才有意义。通过执行bgrewriteaof
命令,可以让AOF文件执行重写功能,用最少的命令达到相同的效果。
(3)RDB和AOF的对比
RDB和AOF各有自己的优缺点,如果对数据安全性要求较高,在实际开发中往往会结合两者来使用。
10.数据过期策略
(1)数据过期定义
Redis对数据设置数据的有效时间,数据过期以后,就需要将数据从内存中删除掉。可以按照不同的规则进行删除,这种删除规则就被称之为数据的删除策略(数据过期策略)。有两种策略,惰性删除和定期删除。
Redis的过期删除策略是惰性删除+定期删除两种策略配合使用
(2)惰性删除
①定义
惰性删除:设置该key过期时间后,我们不去管它,当需要该key时,我们再检查其是否过期,如果过期,我们就删掉它,反之就返回该key。
②优缺点
优点:对CPU友好,只会在使用该key时才会进行过期检查,对于很多用不到的key不用浪费时间进行过期检查。
缺点:对内存不友好,如果一个key已经过期,但是一直没有使用,那么该key就会一直存在内存中,内存永远不会释放。
(3)定期删除
①定义
定期删除:每隔一段时间,我们就会对一些key进行检查,删除里面过期的key(从一定数量的数据库中取出一定数量的随机key进行检查,并删除其中的过期key)。不会存在已经过期的key还没有被删除的可能。
②清理模式
1)SLOW模式:是定时任务,执行频率默认为10hz(每秒执行10次,每个执行周期是100毫秒),每次不超过25毫秒【时间这么短是因为在清理过程中,要尽可能少的去影响主进程操作】,可以通过修改配置文件redis.conf的hz选项来调整这个次数。
2)FAST模式:执行频率不固定,但两次间隔不低于2毫秒,每次耗时不超过1毫秒。【尽可能少的去影响主进程操作】
③优缺点
优点:可以通过限制删除操作执行的时长和频率来减少删除操作对CPU的影响。另外定期删除,也能有效释放过期key占用的内存。
缺点:难以确定删除操作执行的时长和频率。
11.数据淘汰策略
加入缓存过多,内存是有限的,内存被占满了怎么办?
问的就是数据淘汰策略。
(1)数据淘汰策略定义
当Redis中的内存不够用时,此时在向Redis中添加新的key,那么Redis就会按照某一种规则将内存中的数据删除掉,这种数据的删除规则被称之为内存的淘汰策略。
(2)八种淘汰策略
-
noeviction
: 不淘汰任何key,但是内存满时不允许写入新数据,默认就是这种策略。
-
volatile-ttl
: 对设置了TTL的key,比较key的剩余TTL值,TTL越小越先被淘汰 -
allkeys-random
:对全体key ,随机进行淘汰。 -
volatile-random
:对设置了TTL的key ,随机进行淘汰。 -
allkeys-lru
: 对全体key,基于LRU算法进行淘汰
LRU:
LFU:
volatile-lru
: 对设置了TTL的key,基于LRU算法进行淘汰allkeys-lfu
: 对全体key,基于LFU算法进行淘汰volatile-lfu
: 对设置了TTL的key,基于LFU算法进行淘汰
(3)淘汰策略使用建议
1)优先使用allkeys-lru策略,充分利用LRU算法的优势,把最近最常访问的数据留在缓存中。如果业务有明显的冷热数据区分,建议使用。
2)如果业务中数据访问频率差别不大,没有明显冷热数据区分,建议使用allkeys-random,随机选择淘汰。
3)如果业务中有置顶的需求,可以使用volatile-lru策略,同时置顶数据不设置过期时间,这些数据就一直不被删除,会淘汰其他设置过期时间的数据。
4)如果业务中有短时高频访问的数据,可以使用allkeys-lfu或volatile-flu策略。
(二)分布式锁
Redis分布式锁,是如何实现的。
需要结合项目中的业务进行回答,通常情况下,分布式锁使用的场景:集群情况下的定时任务、抢单、幂等性场景。
1.抢券场景
正常情况下:
线程1执行完之后,线程2开始执行,查询优惠券,不会出现问题。
冲突情况:
假设此时优惠券库存=1,线程1查询到优惠券库存=1,而此时线程2也查询到优惠券库存=1,接着线程1扣减库存,库存=0,线程1执行结束。线程2也扣减库存,那么此时,库存=-1,出现了超卖现象。
解决方案:
加锁。但是这种解决方案只适合单体项目,并且只开启一台服务。
但是我们的项目为了支撑更多的并发请求,往往将服务做成集群部署,同一份代码,部署在多个tomcat中.
当用户请求的时候,使用nginx做反向代理,负载均衡到各个请求去访问各个服务。那么此时就不适合加锁了。
此时使用的是本地的锁,只能解决同一个jvm下线程的互斥,解决不了多个jvm下线程的互斥。所以在集群的情况下,就不能使用本地的锁来解决,需要使用外部的锁来解决,也就是分布式锁。
使用了分布式锁以后,针对集群部署的项目,在服务器8080下,线程1加锁之后,分布式锁中就会记录服务器8080的线程1持有锁。那么服务器8081的线程1试图获取锁会失败,别的线程都会获取锁失败,只有等8080的线程1释放锁以后才可以获取到锁。
2.分布式锁的原理
(1)定义
Redis实现分布式锁主要利用Redis的setnx
命令。setnx是 SET if not exists(如果不存在,则SET)的简写。
其中EX表示key的过期时间,在一条命令中设置可以保证原子性,不设置EX可能会导致死锁的问题。
(2)redis分布式锁控制锁的有效时长
Redis实现分布式锁如何合理地控制锁的有效时长?
方案一:根据业务执行时间预估。但是可能出现网络不稳定服务宕机的情况,也会自动释放锁,导致业务的原子性无法得到满足,业务不能很好地被执行。【该方案有欠缺】
方案二:给锁续期
。单独开一个线程监控业务完成情况,如果业务不够时间完成,则延长加锁时间。【也有欠缺,很浪费】
最好的解决方案:
redisson
实现的分布式锁
(3)redisson实现的分布式锁执行流程
线程1获取锁,加锁成功之后可以直接操作Redis执行业务,同时加锁成功之后会增加一个watch dog
看门狗进行监听【每隔(releaseTime/3)的时间做一次续期】,看门狗会不断地去监听持有锁的线程,releaseTime就是锁的过期时间(默认30秒),也就是每隔10s,看门狗将锁的过期时间重新设置为30s。当业务执行完成以后,手动地释放锁,并且告诉看门狗不需要再进行监听了。
线程2也尝试获取锁,看是否加锁成功,但是线程1正在持有锁。在线程2中设置了一个while循环,不断尝试获取锁。如果线程1在很短的时间内释放了锁,那线程2可以获得加锁。在这个while循环设置了一个阈值,如果线程2尝试获取锁达到这个阈值,那么线程2会获取锁失败。
一般情况下,业务执行非常快,所以线程2不需要等待线程1很久。加入了这个while循环等待机制,在高并发的情况下,可以很大程度上增加分布式锁的使用性能。【这就是redisson的重试机制】
加锁、设置过期时间等操作都是基于lua脚本完成。
(4)Redisson实现的分布式锁—可重入
add1方法调用add2方法,其中add2方法是可以获取锁成功的,redisson实现的分布式锁是可以重入的。add1和add2方法都是同一个线程,每个线程在执行的时候,都有一个唯一的线程ID作为标识,加锁的时候根据这个线程ID来判断,如果是同一个线程,那么可以获取锁成功。如果不是一个线程,则获取不成功。
可重入的好处:当业务比较复杂的时候,锁的粒度比较细的时候,就可以用到重入。可以避免多个锁之间产生死锁的问题。
重入的实现:在存储锁数据的时候,采用hash结构,利用hash结构记录线程id和重入次数。这个key根据自己的业务进行命名,也就是锁的命名,field存储的是持有锁线程的唯一标识(线程ID),value存储的是当前线程重入的次数。
上面的代码中,加锁之后,存入到field中,将线程标识,然后add1中加锁,value=1,之后调用add2,先去field查看线程ID是否一致,一致的话,add2加锁成功,value=2。add2执行完业务之后,释放锁value-1=1。之后,add1执行完业务之后,释放锁value-1=0;
(5)Redisson实现的分布式锁—主从一致性
Redis的主从集群架构,有主节点和从节点,主节点主要负责写操作(更新操作),从节点负责对外的读操作,当主节点发生了写操作之后,就要将数据同步到从节点,因为要保证主从数据的同步。目前有java应用创建了一个分布式锁,因为是写操作,先找到主节点,将数据写入到主节点中,正常的话就是主节点同步到从节点,但是还没来得及同步数据,主节点宕机了。Redis提供的哨兵模式
会在两个从节点中,选择一个从节点充当主节点,那么新的线程来了以后,会直接请求新的主节点,尝试获取锁,会加锁成功。那么这两个线程会同时持有同一把锁,那么此时就丧失了锁的互斥性,可能会出现脏数据的问题。
解决方案:
redisson提供了RedLock
红锁:不能只在一个redis实例上创建锁,应该是在多个redis实例上创建锁(n/2+1),避免在一个redis实例上加锁。(n代表的是redis节点的数量)
下面有3个节点,那么3/2+1=2.5,至少要创建大于等于2个锁。
红锁也是有缺陷的,在实际项目中很少使用,主要是加了红锁之后,实现起来很复杂,尤其是在高并发的情况下,性能变得很差,因为需要提供多个独立的redis节点,运维繁琐。不是很支持用这个。
Redis集群的思想是AP思想,优先保证高可用性,可以做到最终一致。如果要做到强一致性,则考虑使用zookeeper的CP思想
(三)计数器
(四)保存token
(五)消息队列
(六)延迟队列
二、其他面试题
(一)集群
Redis集群有哪些方案?知道嘛
三种:主从复制、哨兵模式、分片集群。D
1.主从复制
(1)定义
主从复制:单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写分离。
主节点复制写操作,从节点复制读操作,因为Redis一般是读多写少,多个从节点,增加了并发能力。主节点需要将数据同步给从节点。
(2)主从数据同步原理
①主从全量同步
执行流程:
- 从节点执行
replicaof
命令,建立连接。 - 接着从节点向主节点请求数据同步。
- 主节点接收到之后,会判断该从节点是否是第一次同步。
- 如果该从节点是第一次和主节点建立连接,那么将返回主节点的数据版本信息给从节点。
- 从节点保持版本信息,主从版本保持一致。
- 接着主节点会执行bgsave,生成RDB文件。
- RDB文件生成之后,主节点向从节点发送RDB文件。
- 从节点接收到RDB文件后,会清空本地数据,加载RDB文件。
- 那么主节点在生成RDB文件时,可能接收到其他更改请求,那么就会导致主从数据不一致的问题。为了解决这个问题,主节点会记录RDB期间的所有命令,生成一个日志文件repl_baklog。
- 主节点 再将这个日志文件发送给从节点,发送repl_baklog中的命令给从节点。
- 从节点接收到该日志文件之后,会执行接收到的命令,那么就可以实现主从数据的完全同步。
问题解决:
1)主节点是怎么判断从节点是否是第一次同步呢?
从节点发起连接请求时,将自己的replid发送给主节点,主节点通过判断该从节点的replid和自己的replid是否一致,如果不一样,说明这个从节点是第一次进行连接。
如果一致,表明该从节点之前已经建立过连接。那么主节点就不会再生成RDB文件,直接执行repl-baklog的命令。
2)不是第一次建立连接(第二次、第三次同步),那么该执行多少这个日志文件中的命令呢?
根据offset,从节点发送自己的offset给主节点,主节点判断从节点的offset和自己是否一致,不一致的话,将不一致的部分发送给同节点进行同步。
②主从增量同步(salve重启或后期数据变化)
从节点重启之后,向主节点发起同步请求,附带有replid和offset两个值,主节点master判断请求replid是否一致,如果是第一次,则返回主节点replid和offset给从节点。如果不一致,不是第一次连接,回复continue,主节点去repl_baklog中获取offset后的数据,发送offset后的命令给从节点,从节点执行命令。
主从复制保证不了redis的高可用,因为一旦主节点宕机之后,就无法执行写操作。
2.哨兵模式
(1)哨兵的作用
Redis提供了哨兵机制来实现主从集群的自动故障恢复。哨兵也是redis节点,也由多个节点组成了集群,一般情况下至少要部署三台哨兵。
- 监控:哨兵会不断检查主节点和从节点是否按预期工作,监控集群状态。
- 自动故障恢复:如果主节点故障,哨兵会将一个从节点提升为主节点。当故障实例恢复后,也以新的主节点为主。
- 通知:哨兵充当Redis客户端的服务发现来源,当集群发生故障转移时,哨兵会将最新信息推送给Redis的客户端。例如,主节点宕机,更换新的主节点之后,哨兵会将新的主节点告诉给Redis的客户端,Redis的客户端会自动连接上新的主节点进行工作。
极大可能保持了Redis的高可用性。
(2)服务状态监控
哨兵(Sentinel)基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令:
其中指定数量quorum也是可以设置的,最好超过哨兵实例数量的一半。
哨兵选主规则:
(3)Redis集群(哨兵模式)脑裂
如果主节点和哨兵处于不同的网络分区,那哨兵只能去监测从节点,无法监测到主节点,那么哨兵就会在从节点当中根据选主规则,选择新的主节点。但是原本的主节点还存在,只是网络出现问题,客户端还可以正常连接。那这样子,就会出现两个master主节点,就像大脑分裂了一样。这个就是脑裂。
脑裂带来的问题:
由于原本的主节点还存在,客户端仍然在向原来的主节点写入数据,但是其他节点无法同步数据,因为网络异常。
如果网络恢复之后,哨兵会将原来的主节点强制降为从节点,依附新的主节点,那么这个从节点就会从新的主节点同步数据,就会将自身原本的数据清空,那脑裂之前,客户端写入的数据就丢失了。脑裂带来数据丢失。
解决方案:
主节点必须要有最少一个从节点,才可以接收客户端的数据,否则直接拒绝请求。
redis中有两个配置参数:
min-replicas-to-write 1 表示最少的salve节点为1个
min-replicas-max-lag 5 表示数据复制和同步的延迟不能超过5秒
3.分片集群
主从和哨兵可以解决高可用、高并发读的问题【主从复制解决高并发读的问题,哨兵模式解决高可用问题】。但是依然有两个问题没有解决:
海量数据存储问题
高并发写的问题。(采用分片集群解决)
(1)分片集群特征
使用分片集群可以解决上述问题,分片集群特征:
1.集群中有多个master,每个master保存不同数据
2.每个master都可以有多个slave节点
3.master之间通过ping监测彼此健康状态。每个master互相之间起到哨兵作用。
4.客户端请求可以访问集群任意节点,最终都会被转发到正确节点
(2)数据读写
Redis 分片集群引入了哈希槽的概念,Redis 集群有 16384 个哈希槽,每个 key通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分 hash 槽。
可以设置key的有效部分,按照一定的规则,key选择存储到哪一个节点中。有相同的业务数据,都想进入到redis的同一个节点下,就可以设置相同的有效部分来存储。
(二)事务
(三)Redis为什么这么快
1.用户空间和内核空间
Linux系统中一个进程使用的内存情况划分两部分:内核空间、用户空间。
用户空间只能执行受限的命令(Ring3),而且不能直接调用系统资源,必须通过内核提供的接口来访问。
内核空间可以执行特权命令(Ring0),调用一切系统资源。
需要想办法减少等待时间,以及减少用户空间和内核空间之间数据的拷贝。
2.阻塞IO
阻塞IO就是两个阶段都必须阻塞等待。比较耗时,性能不高。
3.非阻塞IO
4.IO多路复用(Redis底层使用的就是这个)
IO多路复用:是利用单个线程来同时监听多个Socket ,并在某个Socket可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。
这个Socket指的是客户端的连接。
用户进程先调用select函数,可以监听一个Socket集合,里面包含了多个Sockets
IO多路复用是利用单个线程来同时监听多个Socket ,并在某个Socket可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。不过监听Socket的方式、通知的方式又有多种实现,常见的有:
select
poll
epoll。
5.Redis网络模型
单线程的
IO多路复用监听每个客户端的连接,每个连接可能处理不同的事件,IO多路复用只是针对已经就绪的连接,将这些事件进行派发。Redis中提供了多个事件处理器,这些事件处理器分别用于实现不同的网络通信请求。比如连接应答处理器,可以处理客户端请求的应答;命令回复处理器,处理客户端响应的;命令请求处理器,接收客户端的参数,接收请求数据,将数据转为Redis命令,选择并执行命令,把结果写入缓冲队列,放入缓冲区。这个是单线程的。
影响性能的永远是IO。也就是网路的读写,为了解决这个问题,引入了多线程。命令回复处理器也就是往外写,也加入了多线程。加入了多线程以后,大大提高了Redis对客户端的速度,主要是减少了网络IO导致的性能变慢的影响。
1.Redis的数据持久化策略有哪些?
2.什么是缓存穿透,怎么解决?
3.什么是布隆过滤器?
4.什么是缓存击穿,怎么解决?
5.什么是缓存雪崩,怎么解决?
6.Redis双写问题是什么?
7.Redis分布式锁如何实现?
8.Redis实现分布式锁如何合理的控制锁的有效时长?
9.Reids的数据过期策略有哪些?
10.Redis的数据淘汰策略有哪些?
11.Redis集群有哪些方案,知道吗?
12.什么是Redis主从同步?
13.你们使用Redis是单点还是集群?哪种集群?
14.Redis分片集群中数据是怎么存储和读取的?
15.Redis集群脑裂是什么?
16.怎么保证Redis的高并发高可用?
17.你们用过Redis的事务吗?事务的命令有哪些?
18.Redis是单线程,但是为什么还那么快?