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查询文章&…...
机器人ROS学习:Ubuntu22.04安装ROS2和Moveit2实现运动规划
通过本篇文章学习,你可以收获以下内容: 学会在 Ubuntu22.04 上安装 Moveit2学会下载编译运行 Moveit2 样例程序学会使用样例程序进行运动规划等 版本平台 系统版本:ubuntu22.04ROS2 版本:humbleMoveit 版本:moveit2…...
树结构和数组之间的转化
1、树结构转为数组 treeToArray(treeData, returnValue []) { let newValue [...returnValue] treeData.map(item > { if (item.children) { const { children, ...treeObj } { ...item } newValue.push(treeObj) newValue this.treeToArray(children, newValue) } else…...
2024华东师范大学计算机复试上机真题
2024华东师范大学计算机复试机试真题 2023华东师范大学计算机复试机试真题 2022华东师范大学计算机复试机试真题 2024华东师范大学计算机复试上机真题 2023华东师范大学计算机复试上机真题 2022华东师范大学计算机复试上机真题 在线评测:传动门:pgcode…...
Blender-MCP服务源码3-插件开发
Blender-MCP服务源码3-插件开发 Blender-MCP服务源码解读-如何进行Blender插件开发 1-核心知识点 1)使用Blender开发框架学习如何进行Blender调试2)学习目标1-移除所有的Blender业务-了解如何MCP到底做了什么?3)学习目标2-模拟MC…...
C++复试笔记(三)
1.友元函数和友元类 1.1友元函数 友元函数的经典实例是重载 "<<" 和 ">>" ,去重载operator<<,然后发现没办法将operator<<重载成成员函数。因为cout的 输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默…...
【数学基础】概率与统计#1概率论与信息论初步
本系列内容介绍: 主要参考资料: 《深度学习》[美]伊恩古德菲洛 等 著 《机器人数学基础》吴福朝 张铃 著 文章为自学笔记,默认读者有一定的大学数学基础,仅供参考。 目录 随机变量概率分布离散型随机变量和概率质量函数连续型变量…...
掌握这些 UI 交互设计原则,提升产品易用性
在当今数字化时代,用户对于产品的体验要求越来越高,UI 交互设计成为决定产品成败的关键因素之一。一个易用的产品能够让用户轻松、高效地完成各种操作,而实现这一目标的核心在于遵循一系列科学合理的 UI 交互设计原则。本文将详细阐述简洁性、…...
工程化与框架系列(32)--前端测试实践指南
前端测试实践指南 🧪 引言 前端测试是保证应用质量的重要环节。本文将深入探讨前端测试的各个方面,包括单元测试、集成测试、端到端测试等,并提供实用的测试工具和最佳实践。 测试概述 前端测试主要包括以下类型: 单元测试&a…...
Python----计算机视觉处理(opencv:像素,RGB颜色,图像的存储,opencv安装,代码展示)
一、计算机眼中的图像 像素 像素是图像的基本单元,每个像素存储着图像的颜色、亮度和其他特征。一系列像素组合到一起就形成 了完整的图像,在计算机中,图像以像素的形式存在并采用二进制格式进行存储。根据图像的颜色不 同,每个像…...
表单 schema 配置化
一、前沿 基于 Ant Design Vue 组件库实现了表单的配置化生成,通过 schema 配置化的方式实现表单的动态渲染、数据绑定和更新等功能,而提交按钮及获取数据逻辑由使用方自行提供。通过 schema 对象来定义表单的结构和属性,modelData 对象存储…...
Java数据结构第二十三期:Map与Set的高效应用之道(二)
专栏:Java数据结构秘籍 个人主页:手握风云 目录 一、哈希表 1.1. 概念 1.2. 冲突 1.3. 避免冲突 1.4. 解决冲突 1.5. 实现 二、OJ练习 2.1. 只出现一次的数字 2.2. 随机链表的复制 2.3. 宝石与石头 一、哈希表 1.1. 概念 顺序结构以及平衡树中…...
unity生命周期
unity的生命周期 都是有序的1. 实例化与初始化阶段Awake()OnEnable() 2. 开始与更新阶段Start()FixedUpdate()Update()LateUpdate() 3. 渲染阶段OnPreCull()OnBecameVisible() 和 OnBecameInvisible()OnWillRenderObject()OnRenderObject()OnPostRender() 4. 销毁阶段OnDisable…...
对比学习(Contrastive Learning)
1. 概念 对比学习(Contrastive Learning)是一种自监督学习(Self-Supervised Learning)方法,其核心思想是通过相似样本靠近,不同样本远离的方式学习数据的潜在表示。它广泛用于无标签数据的特征提取&#x…...
C语言输入与输出:从零掌握数据的“对话”
手把手教你理解C语言中输入(Input)与输出(Output)的核心操作。 一、输入与输出是什么? C语言通过标准库函数实现程序与用户(或设备)的“对话”: 输出:程序将数据展示给…...
PyCharm 2019.1.3使用python3.9创建虚拟环境setuptools-40.8.0报错处理
目录 前置: 一劳永逸方法(缺最后一步,没有成行) step one: 下载高版本的pip、setuptools、virtualenv的tar.gz包 step two: 进入PyCharm安装目录的 helpers 目录下 step three: 下载并安装grep和sed命令,然后执行 …...
从0到1构建AI深度学习视频分析系统--基于YOLO 目标检测的动作序列检查系统:(2)消息队列与消息中间件
文章大纲 原始视频队列Python 内存视频缓存优化方案(4GB 以内)一、核心参数设计二、内存管理实现三、性能优化策略四、内存占用验证五、高级优化技巧六、部署建议检测结果队列YOLO检测结果队列技术方案一、技术选型矩阵二、核心实现代码三、性能优化策略四、可视化方案对比五…...
Redis基本命令手册——五大类型
目录 一:基本操作 二:字符串(String) 三:哈希(Hash) 四:列表(List) 五:集合(Set) 六:有序集合(Zset&…...
Java 大视界 -- Java 大数据在智能金融资产定价与风险管理中的应用(134)
💖亲爱的朋友们,热烈欢迎来到 青云交的博客!能与诸位在此相逢,我倍感荣幸。在这飞速更迭的时代,我们都渴望一方心灵净土,而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识,也…...
Linux 系统蓝牙音频服务实现分析
Linux 系统蓝牙音频服务实现分析 蓝牙音频设备连接管理Linux 系统中,蓝牙音频服务实现为系统音频服务 PulseAudio 的可加载模块,它用来以 PulseAudio 标准的方式描述蓝牙音频设备,将其嵌入 PulseAudio 的音频处理流水线,并呈现给用户,支持用户切换音频设备,如蓝牙耳机。 …...
PyTorch 深度学习实战(14):Deep Deterministic Policy Gradient (DDPG) 算法
在上一篇文章中,我们介绍了 Proximal Policy Optimization (PPO) 算法,并使用它解决了 CartPole 问题。本文将深入探讨 Deep Deterministic Policy Gradient (DDPG) 算法,这是一种用于连续动作空间的强化学习算法。我们将使用 PyTorch 实现 D…...
craftjs的示例landing项目改成APP路由
下载项目 项目地址是:https://github.com/prevwong/craft.js 示例项目在examples文件夹下面landing文件夹 修改 1.修改依赖包 由于craftjs使用的多包管理,示例项目中craftjs/core和craftjs/layers使用的是工作区路径,这里需要修改版本 …...
java -jar 执行基于Maven构建的Java应用的方法总结
一、Maven pom.xml文件未指定主类的情况 1、用Maven打包 mvn clean package -DskipTests 2、用java命令执行jar包 java -cp maven-allin-mainclass-demo-1.0-SNAPSHOT.jar org.example.Main 二、Maven pom.xml文件指定主类的情况 1、pom.xml文件指定主类,有两种…...
前端发布缓存导致白屏解决方案
解决发布H5后因为本地缓存白屏方案 一、 核心配置优化(前提是访问网站的请求能抵达服务器) 方案一:前端项目设置全局不缓存方案 运行逻辑:在H5服务器配置中增加Cache-Control: no-cache或max-age0响应头,禁用静态资…...
【后端】【django】Django 自带的用户系统与 RBAC 机制
Django 自带的用户系统与 RBAC 机制 Django 自带的用户系统(django.contrib.auth)提供了 身份验证(Authentication) 和 权限管理(Authorization),能够快速实现 用户管理、权限控制、管理员后台…...
SpringBoot MCP 入门使用
随着AI的火爆,最近发现MCP在未来确实大有可为,作为一名javaer怎么可以落后在历史洪流呢,根据官网和cursor也从零开始体验一下自定义mcp server。以后可以根据自己业务场景做出各种适合自身业务的工具。 至于什么是MCP 可以到https://modelcon…...
Java使用JDBC连接操作Sqlite 笔记250314
Java使用JDBC连接操作Sqlite 以下是使用 Java JDBC 连接和操作 SQLite 数据库的详细步骤: 1. 添加 SQLite JDBC 驱动 在项目中引入 SQLite JDBC 驱动依赖。 Maven 项目在 pom.xml 中添加:<dependency><groupId>org.xerial</groupId>…...
每日一题---腐烂的苹果(广度优先搜索)
腐烂的苹果 给定一个 nm nm 的网格,其中每个单元格中可能有三种值中的一个 0 , 1 , 2。 其中 0 表示这个格子为空、1 表示这个格子有一个完好的苹果,2 表示这个格子有一个腐烂的苹果。 腐烂的苹果每分钟会向上下左右四个方向的苹果传播一次病菌&…...
Visual Studio里的调试(debugging)功能介绍
参考 1- Introduction to Debugging | Basic Visual Studio Debugging(这是一位印度博主视频,我下面做到笔记也主要参考她的视频,但不得不说口音太重了,一股咖喱味) 目录 个人对调试浅显的认识和对调试的介绍逐行调…...
10.2linux内核定时器实验(详细编程)_csdn
我尽量讲的更详细,为了关注我的粉丝!!! 本章使用通过设置一个定时器来实现周期性的闪烁 LED 灯,因此本章例程就使用到了一个LED 灯。 这里我们以毫秒为单位,所以要用msecs_to_jiffies这个函数。 如果是2s就…...
机器学习——正则化、欠拟合、过拟合、学习曲线
过拟合(overfitting):模型只能拟合训练数据的状态。即过度训练。 避免过拟合的几种方法: ①增加全部训练数据的数量(最为有效的方式) ②使用简单的模型(简单的模型学不够,复杂的模型学的太多&am…...
Java多线程与高并发专题——阻塞和非阻塞队列的并发安全原理是什么?
引入 之前我们探究了常见的阻塞队列的特点,在本文我们就以 ArrayBlockingQueue 为例,首先分析 BlockingQueue ,也就是阻塞队列的线程安全原理,然后再看看它的兄弟——非阻塞队列的并发安全原理。 ArrayBlockingQueue 源码分析 …...
git 撤销某次提交的上交到远程服务器的commit提交,此提交后面的commit需要保留【deeepseek生成】
核心思路 使用 git rebase -i 重写提交历史,删除目标提交后强制推送到远程(需谨慎操作)。 操作步骤 1. 确认要删除的提交位置 # 查看提交历史(找到要删除的提交哈希,例如 a1b2c3d) git log --oneline查看提…...
docker composeyaml文件,什么是swap-space,内存不足硬盘来凑,--ipc=host,yaml文件、环境变量、容器报警健康检查
--swap-space 参数明确针对的是系统内存(RAM),与显存(GPU Memory)无关。以下是关键区分: 内存(RAM) vs 显存(GPU Memory) 类型内存(RAMÿ…...
tsfresh:时间序列特征自动提取与应用
tsfresh:时间序列特征自动提取与应用 本文系统介绍了 tsfresh 技术在 A 股市场数据分析与量化投资中的应用。从基础特征提取到高级策略开发,结合实战案例,详细讲解了如何利用 tsfresh 构建量化投资策略,并优化风险控制,…...
【A2DP】深入解读A2DP中通用访问配置文件(GAP)的互操作性要求
目录 一、模式支持要求 1.1 发现模式 1.2 连接模式 1.3 绑定模式 1.4 模式间依赖关系总结 1.5 注意事项 1.6 协议设计深层逻辑 二、安全机制(Security Aspects) 三、空闲模式操作(Idle Mode Procedures) 3.1 支持要求 …...
CUDA编程之内存
CUDA的内存类型有全局内存、共享内存、常量内存、纹理内存、本地内存、寄存器等。我们需要分别了解它们的特点和使用场景。在CUDA编程中,合理利用各种内存类型对性能优化至关重要。 1. 全局内存(Global Memory) 特点:设…...
【Agent实战】货物上架位置推荐助手(RAG方式+结构化prompt(CoT)+API工具结合ChatGPT4o能力Agent项目实践)
本文原创作者:姚瑞南 AI-agent 大模型运营专家,先后任职于美团、猎聘等中大厂AI训练专家和智能运营专家岗;多年人工智能行业智能产品运营及大模型落地经验,拥有AI外呼方向国家专利与PMP项目管理证书。(转载需经授权) 目录 结论 效果图示 1.prompt 2. API工具封…...
ffmpeg面试题整理
1. 基础概念 问题:FFmpeg 是什么?它的核心功能有哪些? 编解码:支持几乎所有音视频格式(如 H.264, AAC, MP3)。转换:在不同容器格式之间转换(如 MP4 → MKV)。流处理&…...
Idea运行项目报错:java.lang.OutOfMemoryError: Java heap space 解决方法
问题描述 Maven构建的时候,一直报错java.lang.OutOfMemoryError: Java heap space 尝试解决 找了几个JAVA高级小伙伴,一起去百度了各种可能,设置内存大小,发现都不行,还不断的重装了IDEA,以为是这个版本…...
解决 Linux /dev/mapper/ubuntu--vg-ubuntu--lv 磁盘空间不足的问题
解决 Linux /dev/mapper/ubuntu–vg-ubuntu–lv 磁盘空间不足的问题 https://blog.csdn.net/weixin_47908992/article/details/139882219 查看LVM卷组的信息 vgdisplay rootubuntu:~# vgdisplay--- Volume group ---VG Name ubuntu-vgSystem ID Fo…...
前端UI编程基础知识:基础三要素(结构→表现→行为)
以下是重新梳理的前端UI编程基础知识体系,结合最新技术趋势与实战要点,以更适合快速掌握的逻辑结构呈现: 一、基础三要素(结构→表现→行为) 1. HTML5 核心能力 • 语义化标签:<header>, <nav&g…...
Trae:与AI结伴,开启编程新体验
Trae:与AI结伴,开启编程新体验 在数字化时代,编程已经成为推动技术发展的核心力量。然而,随着项目复杂度的增加,开发者面临着诸多挑战,例如代码编写效率低下、代码质量难以把控等。如今,Trae作…...
如何用正则表达式爬取古诗文网中的数据(python爬虫)
一、了解正则表达式的基本内容: 什么是正则表达式 正则表达式(Regular Expression,简称 regex)是一种用于匹配字符串的模式。它通过特定的语法规则,可以高效地搜索、替换和提取文本中的特定内容。正则表达式广泛应用于…...
深度学习 Deep Learning 第1章 深度学习简介
第1章 深度学习简介 概述 本章介绍人工智能(AI)和深度学习领域,讨论其历史发展、关键概念和应用。解释深度学习如何从早期的AI和机器学习方法演变而来,以及如何有效解决之前方法无法应对的挑战。 关键概念 1. 人工智能的演变 …...
ByteByteGo学习笔记:通知系统设计
引言 在当今这个信息爆炸的时代,通知系统已经成为了现代应用程序中不可或缺的重要组成部分。无论是突发新闻的即时推送、产品更新的及时告知、促销活动的精准触达,还是用户交互的实时反馈,通知都扮演着至关重要的角色。一个高效、可靠、可扩…...
[设计模式]1_设计模式概览
摘要:设计模式原则、设计模式的划分与简要概括,怎么使用重构获得设计模式并改善代码的坏味道。 本篇作概览与检索用,后续结合源码进行具体模式深入学习。 目录 1、设计模式原理 核心原则(语言无关) 本质原理图 原…...
Python + Qt Designer构建多界面GUI应用程序:Python如何调用多个界面文件
引言 Qt Designer是一个用户友好的图形用户界面设计工具,它可以帮助开发人员通过拖放的方式快速创建界面。在实际开发中,往往需要设计多个界面文件,并在Python代码中进行统一管理和使用。本文将介绍如何在Python中使用Qt Designer设计好的多…...
AGI大模型(7):提示词应用
1 生成数据 LLM具有⽣成连贯⽂本的强⼤能⼒。使⽤有效的提示策略可以引导模型产⽣更好、更⼀致和更真实的响应。LLMs还可以特别有⽤地⽣成数据,这对于运⾏各种实验和评估⾮常有⽤。例如,我们可以使⽤它来为情感分类器⽣成快速样本,如下所示: 提示: ⽣成10个情感分析的范…...
【倒霉bug2025】找不到vc_runtimeMinimum_x64.msi
今天是倒霉的一天,当喉咙痛到无法出门玩耍的我打开steam准备开始玩《冰封世界》时,游戏启动直接报错 在选择安装之后弹出一个经典窗口 然后在C:\ProgramData\PackageCache中找msi到位置点击确定继续报错说msi版本不对 上网一搜,找不到vc_ru…...