Java面试八股—Redis篇
一、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是单线程,但是为什么还那么快?
相关文章:
Java面试八股—Redis篇
一、Redis的使用场景 (一)缓存 1.Redis使用场景缓存 场景:缓存热点数据(如用户信息、商品详情),减少数据库访问压力,提升响应速度。 2.缓存穿透 正常的访问是:根据ID查询文章&…...
计算矩阵边缘元素之和(信息学奥赛一本通-1121)
【题目描述】 输入一个整数矩阵,计算位于矩阵边缘的元素之和。所谓矩阵边缘的元素,就是第一行和最后一行的元素以及第一列和最后一列的元素。 【输入】 第一行分别为矩阵的行数m和列数n(m<100,n<100),…...
Web后端开发之Maven
Maven Mven是apache旗下的一个开源项目,用来管理和构建java项目的工具。 通过一小段描述信息来管理项目。 Maven的作用 1.依赖管理:方便快捷的管理项目依赖的资源(jar包),避免版本冲突问题 以前用某个jar包需要下载…...
哈希算法,蓝桥杯java备战中
哈希表的实现 核心思路 目标:实现一个基于开放寻址法(线性探测)的哈希表,支持插入元素 I x 和查询元素 Q x 两种操作。 核心逻辑: 哈希函数:将元素映射到固定范围的索引(哈希值)。…...
there are no enabled repos
我做了两个操作 第一个操作: 1.先在本地电脑,也就是在我们电脑的桌面上下载 https://repo.huaweicloud.com/repository/conf/CentOS-7-reg.repo 2.在CentOS 创建etc文件夹 3在etc文件夹内创建yum.repos.d文件夹 4.将下载好的repo 黏贴到yum.repos.d…...
OpenEuler-22.03-LTS上利用Ansible轻松部署MySQL 5.7
一、需求 使用ansible自动化部署mysql二进制部署mysql部署mysql并创建JDBC用户 二、环境信息 本文涉及的代码,配置文件地址: 链接:百度网盘 请输入提取码 提取码:1g6y 软件名称版本备注Ansible2.9.27All modules — Ansible Doc…...
前端无限滚动内容自动回收技术详解:原理、实现与优化
文章目录 一、核心需求与技术挑战1.1 无限滚动的问题症结1.2 自动回收的三大目标 二、技术实现原理2.1 虚拟滚动核心机制2.2 关键技术指标 三、完整实现方案3.1 基础HTML结构3.2 CSS关键样式3.3 JavaScript核心逻辑3.3.1 滚动控制器3.3.2 动态尺寸处理 四、性能优化策略4.1 内存…...
LeetCode hot 100 每日一题(9)——560. 和为 K 的子数组
这是一道难度为中等的题目,让我们来看看题目描述: 给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数 。 子数组是数组中元素的连续非空序列。 示例 1: 输入: nums [1,1,1], k 2 输…...
C++Primer学习(6.7 函数指针——难!)
6.7 函数指针 (这一章节比较难) 函数指针指向的是函数而非对象。和其他指针一样,函数指针指向某种特定类型。函数的类型由它的返回类型和形参类型共同决定,与函数名无关。例如: //比较两个 string 对象的长度 bool lengthCompare(const string &,co…...
单一责任原则在Java设计模式中的深度解析
在软件开发中,设计模式提供了一种解决特定问题的思路。在众多的设计原则中,单一责任原则(Single Responsibility Principle,SRP)是一个非常重要的概念。它主要强调一个类应该只有一个责任,也就是说…...
如何在Ubuntu上构建编译LLVM和ISPC,以及Ubuntu上ISPC的使用方法
之前一直在 Mac 上使用 ISPC,奈何核心/线程太少了。最近想在 Ubuntu 上搞搞,但是 snap 安装的 ISPC不知道为什么只能单核,很奇怪,就想着编译一下,需要 Clang 和 LLVM。但是 Ubuntu 很搞,他的很多软件版本是…...
学习计划:第四阶段(第十周)
目录 第四阶段:特殊方法与高级特性 第 10 周:综合复习与实践 周一 周二 周三 周四 周五 总结 一、项目设计与实现 二、问题与解决 三、学习成果 四、后续展望 第四阶段:特殊方法与高级特性 第 10 周:综合复习与实践 …...
如何查看redis的缓存时间
要查看 Redis 缓存的时间,有下列两种方式: 一、使用 TTL 命令来获取缓存剩余的时间 Redis提供了多个命令来查看缓存数据的时间戳,其中最常用的命令是ttl和pttl。 ttl它返回的是以秒为单位的时间,表示 key 距离过期的时间还有多久…...
每日学习Java之一万个为什么
JVM的加载过程 启动阶段:启动JVM实例,设置初始配置参数,加载核心类库如java.lang类加载器:自举加载器,扩展加载器,系统加载器,自定义加载器。分别负责- 1.核心类库rt.jar等 2.扩展目录下的类库…...
【MySQL】表的约束(上)
文章目录 表的约束什么是表的约束空属性默认值列描述(comment)零填充(zerofill)主键 总结 表的约束 什么是表的约束 表的约束(Constraints)是数据库表中的规则,用于限制存储的数据,…...
静态分析技术:Jadx-GUI高级用法与模式识别
1. 深度反编译策略 1.1 多层级反混淆方案 代码恢复流程: graph TD A[混淆代码] --> B{符号恢复} B -->|字典匹配| C[变量重命名] B -->|类型推导| D[参数重构] C --> E[控制流优化] D --> E E --> F[语义化输出] 反混淆脚本示例&…...
30天学习Java第六天——Object类
Object类 java.lang.Object时所有类的超类。Java中所有类都实现了这个类中的方法。 toString方法 将Java对象转换成字符串的表示形式。 public String toString() {return getClass().getName() "" Integer.toHexString(hashCode()); }默认实现是:完…...
【C语言】编译和链接详解
hi,各位,让我们开启今日份博客~ 小编个人主页点这里~ 目录 一、翻译环境和运行环境1、翻译环境1.1预处理(预编译)1.2编译1.2.1词法分析1.2.2语法分析1.2.3语义分析 1.3汇编1.4链接 2.运行环境 一、翻译环境和运行环境 在ANSI C…...
Secs/Gem第一讲(基于secs4net项目的ChatGpt介绍)
后续内容为基于github上secs4net项目源码的ChatGpt介绍 以该项目为主,从零开始介绍讲解secs/gem,更多的以面试口吻讲述形式。 主要为个人学习,提升使用 🎓 第一讲:SECS/GEM 协议是个什么东西? Ὄ…...
DataWhale 速通AI编程开发:(基础篇)第1章 环境下载、安装与配置
课程地址:Datawhale-学用 AI,从此开始 vscode 更新为最新版 目前绝大多数deepseek非官方渠道均兼容openai的api格式,这里以硅基流动为例进行演示,其他非官方渠道同理。 点击链接注册账号之后,点击“实名认证“完成实名࿰…...
本地知识库RAG总结
目录 RAG流程: 知识库的要求: 知识抽取: 知识存储: 向量化: 知识检索: 应用客户端: RAG智能问答应用几个痛点: 如何提升召回率改进思路: 如何提升回答专业性: RAG评测: 总结: 参考…...
torch_geometric 安装
环境监测: import torch print(torch.__version__) # 查看pytorch安装的版本号 print(torch.cuda.is_available()) # 查看cuda是否可用。True为可用,即是gpu版本pytorch print(torch.cuda.get_device_name(0)) # 返回GPU型号 …...
网页打印很简单!用web打印插件lodop轻松实现文件打印
最近,给客户发一个事件提醒软件,其中客户要求实现打印功能,因为是用asp.net mvc 开发首先考虑到用水晶报表来实现(crystalReport),以前开发c# winform程序,感觉水晶报表还是蛮好的,但…...
北京迅为iTOP-RK3568开发板OpenHarmony系统南向驱动开发实操-HDF驱动配置LED
瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工…...
C语言函数全解析 | 零基础入门指南
📚 C语言函数全解析 | 零基础入门指南 📑 目录 🌟 什么是函数?🔧 函数的定义与结构⚙️ 函数参数与返回值💡 函数声明与调用🏁 Main函数详解🚀 实战案例演示 1. 什么是函数 功能单…...
驻场运维服务方案书(Word文件)
目 录 第一章 背景分析 1.1. 项目背景 1.2. 项目目标 1.3. 系统现状 1.3.1. 网络系统 1.3.2. 设备清单梳理 1.3.3. 应用系统 第二章 需求分析及理解 2.1. 在重要日期能保障信息系统安全 2.2. 信息系统可长期安全、持续、稳定的运行 2.3. 提升发现安全问题、解决安全…...
【时时三省】(C语言基础)用printf函数输出数据2
山不在高,有仙则名。水不在深,有龙则灵。 ----CSDN 时时三省 格式字符 在输出时,对不同类型的数据要指定不同的格式声明,而格式声明中最重要的内容是格式字符。常用的有以下几种格式字符。 ( 1 ) d格式符。用来输出一个有符号的…...
django框架 [面试篇]
Django 是一个基于 Python 的web框架,遵循"快速开发,不重复造轮子(dont repeat yourself)"的原则,帮助用户构建web应用。 而 Django 它本身提供了一些全栈式的一些组件,包括了 ORM,模板引擎,表单…...
吴恩达机器学习笔记复盘(三)Jupyter NoteBook
Jupyter NoteBook Jupyter是一个开源的交互式计算环境: 特点 交互式编程:支持以单元格为单位编写和运行代码,用户可以实时看到代码的执行结果,便于逐步调试和理解代码逻辑。多语言支持:不仅支持Python,还…...
【Docker项目实战】使用Docker部署serverMmon青蛇探针(详细教程)
【Docker项目实战】使用Docker部署serverMmon青蛇探针 一、serverMmon介绍1.1 serverMmon 简介1.2 主要特点二、本次实践规划2.1 本地环境规划2.2 本次实践介绍三、本地环境检查3.1 检查Docker服务状态3.2 检查Docker版本3.3 检查docker compose 版本四、下载serverMmon镜像五、…...


