Redis
与Memcache
的区别?redis支持更丰富的数据类型
(支持更复杂的应用场景):Redis
不仅仅支持简单的k/v
类型的数据,同时还提供list
,set
,zset
,hash
等数据结构的存储。memcache
支持简单的数据类型,String
。Redis支持数据的持久化
,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而Memecache
把数据全部存在内存之中。集群模式
:memcached
没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是redis
目前是原生支持cluster
模式的.Redis使用单线程
:Memcached
是多线程,非阻塞IO
复用的网络模型;Redis
使用单线程的多路IO
复用模型。Redis
的单线程问题面试官:Redis
采用单线程,如何保证高并发?
面试话术:
Redis
快的主要原因是:
I/O
复用模型,充分利用CPU
资源面试官:这样做的好处是什么?
面试话术:
单线程优势有下面几点:
CPU
切换,充分利用CPU
资源Redis
的持久化方案由哪些?相关资料:
1)RDB
持久化
RDB
持久化可以使用save
或bgsave
,为了不阻塞主进程业务,一般都使用bgsave
,流程:
Redis
进程会fork
出一个子进程(与父进程内存数据一致)。RDB
文件中。RDB
文件会被新的RDB
文件替换掉。下面是一些和RDB
持久化相关的配置:
save 60 10000
:如果在60
秒内有10000
个key
发生改变,那就执行RDB
持久化。stop-writes-on-bgsave-error yes
:如果Redis
执行RDB
持久化失败(常见于操作系统内存不足),那么Redis
将不再接受client
写入数据的请求。rdbcompression yes
:当生成RDB
文件时,同时进行压缩。dbfilename dump.rdb
:将RDB
文件命名为dump.rdb
。dir /var/lib/redis
:将RDB
文件保存在/var/lib/redis
目录下。当然在实践中,我们通常会将stop-writes-on-bgsave-error
设置为false
,同时让监控系统在Redis
执行RDB
持久化失败时发送告警,以便人工介入解决,而不是粗暴地拒绝client
的写入请求。
RDB
持久化的优点:
RDB
持久化文件小,Redis
数据恢复时速度快fork
时采用copy-on-write
方式,大多数情况下,没有太多的内存消耗,效率比较好。RDB
持久化的缺点:
fork
时采用copy-on-write
方式,如果Redis
此时写操作较多,可能导致额外的内存占用,甚至内存溢出RDB
文件压缩会减小文件体积,但通过时会对CPU
有额外的消耗durability
),那么不应该采用RDB
持久化。譬如说,如果Redis
每 5 分钟执行一次RDB
持久化,要是Redis
意外奔溃了,那么最多会丢失 5 分钟的数据。2)AOF
持久化
可以使用appendonly yes
配置项来开启AOF
持久化。Redis
执行AOF
持久化时,会将接收到的写命令追加到AOF
文件的末尾,因此Redis
只要对AOF
文件中的命令进行回放,就可以将数据库还原到原先的状态。
与RDB
持久化相比,AOF
持久化的一个明显优势就是,它可以提高数据的持久性 (durability
)。因为在AOF
模式下,Redis
每次接收到client
的写命令,就会将命令write()
到AOF
文件末尾。
然而,在Linux
中,将数据write()
到文件后,数据并不会立即刷新到磁盘,而会先暂存在OS
的文件系统缓冲区。在合适的时机,OS
才会将缓冲区的数据刷新到磁盘(如果需要将文件内容刷新到磁盘,可以调用fsync()
或fdatasync()
)。
通过appendfsync
配置项,可以控制Redis
将命令同步到磁盘的频率:
always
:每次Redis
将命令write()
到AOF
文件时,都会调用fsync()
,将命令刷新到磁盘。这可以保证最好的数据持久性,但却会给系统带来极大的开销。no
:Redis
只将命令write()
到AOF
文件。这会让OS
决定何时将命令刷新到磁盘。everysec
:除了将命令write()
到AOF
文件,Redis
还会每秒执行一次fsync()
。在实践中,推荐使用这种设置,一定程度上可以保证数据持久性,又不会明显降低Redis
性能。然而,AOF
持久化并不是没有缺点的:Redis
会不断将接收到的写命令追加到AOF
文件中,导致AOF
文件越来越大。过大的AOF
文件会消耗磁盘空间,并且导致Redis
重启时更加缓慢。为了解决这个问题,在适当情况下,Redis
会对AOF
文件进行重写,去除文件中冗余的命令,以减小AOF
文件的体积。在重写AOF
文件期间,Redis
会启动一个子进程,由子进程负责对AOF
文件进行重写。
可以通过下面两个配置项,控制Redis
重写AOF
文件的频率:
auto-aof-rewrite-min-size 64mb
auto-aof-rewrite-percentage 100
上面两个配置的作用:当AOF
文件的体积大于64MB
,并且AOF
文件的体积比上一次重写之后的体积大了至少一倍,那么Redis
就会执行AOF
重写。
优点:
CPU
消耗缺点:
面试话术:
Redis
提供了两种数据持久化的方式,一种是RDB
,另一种是AOF
。默认情况下,Redis
使用的是RDB
持久化。
RDB
持久化文件体积较小,但是保存数据的频率一般较低,可靠性差,容易丢失数据。另外RDB
写数据时会采用Fork
函数拷贝主进程,可能有额外的内存消耗,文件压缩也会有额外的CPU
消耗。
AOF
持久化可以做到每秒钟持久化一次,可靠性高。但是持久化文件体积较大,导致数据恢复时读取文件时间较长,效率略低
Redis
的集群方式有哪些?面试话术:
Redis
集群可以分为主从集群和分片集群两类。
主从集群一般一主多从,主库用来写数据,从库用来读数据。结合哨兵,可以再主库宕机时从新选主,目的是保证Redis
的高可用。
分片集群是数据分片,我们会让多个Redis
节点组成集群,并将16383个插槽分到不同的节点上。存储数据时利用对key
做hash
运算,得到插槽值后存储到对应的节点即可。因为存储数据面向的是插槽而非节点本身,因此可以做到集群动态伸缩。目的是让Redis
能存储更多数据。
1)主从集群
主从集群,也是读写分离集群。一般都是一主多从方式。
Redis
的复制(replication
)功能允许用户根据一个Redis
服务器来创建任意多个该服务器的复制品,其中被复制的服务器为主服务器(master
),而通过复制创建出来的服务器复制品则为从服务器(slave
)。
只要主从服务器之间的网络连接正常,主从服务器两者会具有相同的数据,主服务器就会一直将发生在自己身上的数据更新同步 给从服务器,从而一直保证主从服务器的数据相同。
哨兵节点
,当master
宕机时,哨兵会从salve
节点选出一个新的主。主从集群分两种:
带有哨兵的集群:
2)分片集群
主从集群中,每个节点都要保存所有信息,容易形成木桶效应。并且当数据量较大时,单个机器无法满足需求。此时我们就要使用分片集群了。
集群特征:
每个节点都保存不同数据;
所有的redis
节点彼此互联(PING-PONG
机制),内部使用二进制协议优化传输速度和带宽;
节点的fail
是通过集群中超过半数的节点检测失效时才生效;
客户端与redis
节点直连,不需要中间proxy
层连接集群中任何一个可用节点都可以访问到数据;
redis-cluster
把所有的物理节点映射到[0-16383] slot
(插槽)上,实现动态伸缩。
为了保证Redis
中每个节点的高可用,我们还可以给每个节点创建replication
(slave
节点),如图:
出现故障时,主从可以及时切换:
Redis
的常用数据类型有哪些?支持多种类型的数据结构,主要区别是value
存储的数据格式不同:
string
:最基本的数据类型,二进制安全的字符串,最大512M
。
list
:按照添加顺序保持顺序的字符串列表。
set
:无序的字符串集合,不存在重复的元素。
sorted set
:已排序的字符串集合。
hash
:key-value
对格式
Redis
事务机制?相关资料:
参考:http://redisdoc.com/topic/transaction.html
Redis
事务功能是通过MULTI
、EXEC
、DISCARD
和WATCH
四个原语实现的。Redis
会将一个事务中的所有命令序列化,然后按顺序执行。但是Redis
事务不支持回滚操作,命令运行出错后,正确的命令会继续执行。
MULTI
: 用于开启一个事务,它总是返回OK
。 MULTI
执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个待执行命令队列中EXEC
:按顺序执行命令队列内的所有命令。返回所有命令的返回值。事务执行过程中,Redis不会执行其它事务的命令。DISCARD
:清空命令队列,并放弃执行事务, 并且客户端会从事务状态中退出WATCH
:Redis
的乐观锁机制,利用compare-and-set
(CAS
)原理,可以监控一个或多个键,一旦其中有一个键被修改,之后的事务就不会执行使用事务时可能会遇上以下两种错误:
EXEC
之前,入队的命令可能会出错。比如说,命令可能会产生语法错误(参数数量错误,参数名错误,等等),或者其他更严重的错误,比如内存不足(如果服务器使用 maxmemory
设置了最大内存限制的话)。
Redis
2.6.5
开始,服务器会对命令入队失败的情况进行记录,并在客户端调用EXEC
命令时,拒绝执行并自动放弃这个事务。EXEC
调用之后失败。举个例子,事务中的命令可能处理了错误类型的键,比如将列表命令用在了字符串键上面,诸如此类。
为什么Redis
不支持回滚(roll back
)?
以下是这种做法的优点:
Redis
命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。Redis
的内部可以保持简单且快速。鉴于没有任何机制能避免程序员自己造成的错误, 并且这类错误通常不会在生产环境中出现, 所以Redis
选择了更简单、更快速的无回滚方式来处理事务。
面试话术:
Redis
事务其实是把一系列Redis
命令放入队列,然后批量执行,执行过程中不会有其它事务来打断。不过与关系型数据库的事务不同,Redis
事务不支持回滚操作,事务中某个命令执行失败,其它命令依然会执行。
为了弥补不能回滚的问题,Redis
会在事务入队时就检查命令,如果命令异常则会放弃整个事务。
因此,只要程序员编程是正确的,理论上说Redis
会正确执行所有事务,无需回滚。
面试官:如果事务执行一半的时候Redis
宕机怎么办?
Redis
有持久化机制,因为可靠性问题,我们一般使用AOF
持久化。事务的所有命令也会写入AOF
文件,但是如果在执行EXEC
命令之前,Redis
已经宕机,则AOF
文件中事务不完整。使用 redis-check-aof
程序可以移除 AOF
文件中不完整事务的信息,确保服务器可以顺利启动。
Redis
的Key
过期策略?Redis
中,set
指令可以指定key
的过期时间,当过期时间到达以后,key
就失效了;Redis
是基于内存操作的,所有的数据都是保存在内存中,一台机器的内存是有限且很宝贵的。基于以上两点,为了保证Redis
能继续提供可靠的服务,Redis
需要一种机制清理掉不常用的、无效的、多余的数据,失效后的数据需要及时清理,这就需要内存回收了。
Redis
的内存回收主要分为过期删除策略和内存淘汰策略两部分。
删除达到过期时间的key
。
对于每一个设置了过期时间的key
都会创建一个定时器,一旦到达过期时间就立即删除。该策略可以立即清除过期的数据,对内存较友好,但是缺点是占用了大量的CPU
资源去处理过期的数据,会影响Redis
的吞吐量和响应时间。
当访问一个key
时,才判断该key
是否过期,过期则删除。该策略能最大限度地节省CPU
资源,但是对内存却十分不友好。有一种极端的情况是可能出现大量的过期key
没有被再次访问,因此不会被清除,导致占用了大量的内存。
在计算机科学中,懒惰删除(英文:
lazy deletion
)指的是从一个散列表(也称哈希表)中删除元素的一种方法。在这个方法中,删除仅仅是指标记一个元素被删除,而不是整个清除它。被删除的位点在插入时被当作空元素,在搜索之时被当作已占据。
每隔一段时间,扫描Redis
中过期key
字典,并清除部分过期的key
。该策略是前两者的一个折中方案,还可以通过调整定时扫描的时间间隔和每次扫描的限定耗时,在不同情况下使得CPU
和内存资源达到最优的平衡效果。
在Redis
中,同时使用了定期删除和惰性删除
。不过Redis
定期删除采用的是随机抽取的方式删除部分Key
,因此不能保证过期key
100%的删除。
Redis
结合了定期删除和惰性删除,基本上能很好的处理过期数据的清理,但是实际上还是有点问题的,如果过期key
较多,定期删除漏掉了一部分,而且也没有及时去查,即没有走惰性删除,那么就会有大量的过期key
堆积在内存中,导致redis
内存耗尽,当内存耗尽之后,有新的key到来会发生什么事呢?是直接抛弃还是其他措施呢?有什么办法可以接受更多的key
?
Redis
的内存淘汰策略,是指内存达到maxmemory
极限时,使用某种算法来决定清理掉哪些数据,以保证新数据的存入。
Redis
的内存淘汰机制包括:
noeviction
: 当内存不足以容纳新写入数据时,新写入操作会报错。allkeys-lru
:当内存不足以容纳新写入数据时,在键空间(server.db[i].dict
)中,移除最近最少使用的key
(这个是最常用的)。allkeys-random
:当内存不足以容纳新写入数据时,在键空间(server.db[i].dict
)中,随机移除某个key
。volatile-lru
:当内存不足以容纳新写入数据时,在设置了过期时间的键空间(server.db[i].expires
)中,移除最近最少使用的key
。volatile-random
:当内存不足以容纳新写入数据时,在设置了过期时间的键空间(server.db[i].expires
)中,随机移除某个key
。volatile-ttl
:当内存不足以容纳新写入数据时,在设置了过期时间的键空间(server.db[i].expires
)中,有更早过期时间的key
优先移除。在配置文件中,通过
maxmemory-policy
可以配置要使用哪一个淘汰机制。
什么时候会进行淘汰?
Redis
会在每一次处理命令的时候(processCommand
函数调用freeMemoryIfNeeded
)判断当前redis
是否达到了内存的最大限制,如果达到限制,则使用对应的算法去处理需要删除的key
。
在淘汰key
时,Redis
默认最常用的是LRU
算法(Latest Recently Used
)。Redis
通过在每一个redisObject
保存lru
属性来保存key
最近的访问时间,在实现LRU
算法时直接读取key
的lru
属性。
具体实现时,Redis
遍历每一个db
,从每一个db
中随机抽取一批样本key
,默认是3个key
,再从这3个key
中,删除最近最少使用的key
。
Redis
过期策略包含定期删除和惰性删除两部分。定期删除是在Redis
内部有一个定时任务,会定期删除一些过期的key
。惰性删除是当用户查询某个Key
时,会检查这个Key
是否已经过期,如果没过期则返回用户,如果过期则删除。
但是这两个策略都无法保证过期key
一定删除,漏网之鱼越来越多,还可能导致内存溢出。当发生内存不足问题时,Redis
还会做内存回收。内存回收采用LRU
策略,就是最近最少使用。其原理就是记录每个Key
的最近使用时间,内存回收时,随机抽取一些Key
,比较其使用时间,把最老的几个删除。
Redis
的逻辑是:最近使用过的,很可能再次被使用
Redis
在项目中的哪些地方有用到?(1)共享session
在分布式系统下,服务会部署在不同的tomcat
,因此多个tomcat
的session
无法共享,以前存储在session
中的数据无法实现共享,可以用redis
代替session
,解决分布式系统间数据共享问题。
(2)数据缓存
Redis
采用内存存储,读写效率较高。我们可以把数据库的访问频率高的热点数据存储到redis
中,这样用户请求时优先从redis
中读取,减少数据库压力,提高并发能力。
(3)异步队列
Reids
在内存存储引擎领域的一大优点是提供list
和set
操作,这使得Redis
能作为一个很好的消息队列平台来使用。而且Redis
中还有pub/sub
这样的专用结构,用于1对N的消息通信模式。
(4)分布式锁
Redis
中的乐观锁机制,可以帮助我们实现分布式锁的效果,用于解决分布式系统下的多线程安全问题
Redis
的缓存击穿、缓存雪崩、缓存穿透参考资料:
什么是缓存穿透
穿透带来的问题
解决办法
key
。从而导致每次查询都到数据库去了。那么我们就可以为这些key
对应的值设置为null
丢到缓存里面去。后面再出现查询这个key
的请求的时候,直接返回null
。这样,就不用在到数据库中去走一圈了,但是别忘了设置过期时间。BloomFilter
(布隆过滤):将所有可能存在的数据哈希到一个足够大的bitmap
中,一个一定不存在的数据会被 这个bitmap
拦截掉,从而避免了对底层存储系统的查询压力。在缓存之前在加一层 BloomFilter
,在查询的时候先去BloomFilter
去查询key
是否存在,如果不存在就直接返回,存在再走查缓存 -> 查DB
。话术:
缓存穿透有两种解决方案:其一是把不存在的key
设置null
值到缓存中。其二是使用布隆过滤器,在查询缓存前先通过布隆过滤器判断key
是否存在,存在再去查询缓存。
设置null
值可能被恶意针对,攻击者使用大量不存在的不重复key
,那么方案一就会缓存大量不存在key
数据。此时我们还可以对Key
规定格式模板,然后对不存在的key
做正则规范匹配,如果完全不符合就不用存null
值到redis
,而是直接返回错误。
相关资料:
key
可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题。
当这个key
在失效的瞬间,redis
查询失败,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
mutex key
):mutex
,就是互斥。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db
,而是先使用Redis
的SETNX
去set
一个互斥key
,当操作返回成功时,再进行load db
的操作并回设缓存;否则,就重试整个get
缓存的方法。SETNX
,是「SET if Not eXists
」的缩写,也就是只有不存在的时候才设置,可以利用它来实现互斥的效果。redis
提供的过期时间,而是业务层在数据中存储过期时间信息。查询时由业务程序判断是否过期,如果数据即将过期时,将缓存的时效延长,程序可以派遣一个线程去数据库中获取最新的数据,其他线程这时看到延长了的过期时间,就会继续使用旧数据,等派遣的线程获取最新数据后再更新缓存。推荐使用互斥锁,因为软过期会有业务逻辑侵入和额外的判断。
面试话术:
缓存击穿主要担心的是某个Key
过期,更新缓存时引起对数据库的突发高并发访问。因此我们可以在更新缓存时采用互斥锁控制,只允许一个线程去更新缓存,其它线程等待并重新读取缓存。例如Redis
的setnx
命令就能实现互斥效果。
相关资料:
缓存雪崩,是指在某一个时间段,缓存集中过期失效。对这批数据的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。
解决方案:
redis
节点宕机引起雪崩,搭建主从集群,保证高可用面试话术:
解决缓存雪崩问题的关键是让缓存Key
的过期时间分散。因此我们可以把数据按照业务分类,然后设置不同过期时间。相同业务类型的key
,设置固定时长加随机数。尽可能保证每个Key
的过期时间都不相同。
另外,Redis
宕机也可能导致缓存雪崩,因此我们还要搭建Redis
主从集群及哨兵监控,保证Redis
的高可用。
背景资料:
Redis
使用的是内存存储,当需要海量数据存储时,成本非常高。
经过调研发现,当前主流DDR3
内存和主流SATA SSD
的单位成本价格差距大概在20倍左右,为了优化redis
机器综合成本,我们考虑实现基于热度统计 的数据分级存储及数据在RAM/FLASH
之间的动态交换,从而大幅度降低成本,达到性能与成本的高平衡。
基本思路:基于key
访问次数(LFU
)的热度统计算法识别出热点数据,并将热点数据保留在redis
中,对于无访问/访问次数少的数据则转存到SSD
上,如果SSD
上的key
再次变热,则重新将其加载到redis
内存中。
目前流行的高性能磁盘存储,并且遵循Redis
协议的方案包括:
SSDB
:http://ssdb.io/zh_cn/RocksDB
:https://rocksdb.org.cn/因此,我们就需要在应用程序与缓存服务之间引入代理,实现Redis
和SSD
之间的切换,如图:
Redis
实现分布式锁分布式锁要满足的条件:
利用Redis
的setnx
命令,这个命令的特征时如果多次执行,只有第一次执行会成功,可以实现互斥
的效果。但是为了保证服务宕机时也可以释放锁,需要利用expire
命令给锁设置一个有效期
setnx lock thread-01 # 尝试获取锁
expire lock 10 # 设置有效期
面试官问题1:如果expire
之前服务宕机怎么办?
要保证setnx
和expire
命令的原子性。redis
的set
命令可以满足:
set key value [NX] [EX time]
需要添加nx
和ex
的选项:
NX
:与setnx
一致,第一次执行成功EX
:设置过期时间面试官问题2:释放锁的时候,如果自己的锁已经过期了,此时会出现安全漏洞,如何解决?
在锁中存储当前进程和线程标识,释放锁时对锁的标识判断,如果是自己的则删除,不是则放弃操作。
但是这两步操作要保证原子性,需要通过Lua
脚本来实现。
if redis.call("get",KEYS[1]) == ARGV[1] then
redis.call("del",KEYS[1])
end
如果有重入的需求,则除了在锁中记录进程标识,还要记录重试次数,流程如下:
下面我们假设锁的key
为“lock
”,hashKey
是当前线程的id
:“threadId
”,锁自动释放时间假设为20
获取锁的步骤:
lock
是否存在 EXISTS lock
id
作为hashKey
是否存在:HEXISTS lock threadId
end
HINCRBY lock threadId 1
,去到步骤3HSET key threadId 1
EXPIRE lock 20
释放锁的步骤:
id
作为hashKey
是否存在:HEXISTS lock threadId
HINCRBY lock threadId -1
,获取新的重入次数key
:DEL lock
EXPIRE lock 20
对应的Lua
脚本如下:
首先是获取锁:
local key = KEYS[1]; -- 锁的key
local threadId = ARGV[1]; -- 线程唯一标识
local releaseTime = ARGV[2]; -- 锁的自动释放时间
if(redis.call('exists', key) == 0) then -- 判断是否存在
redis.call('hset', key, threadId, '1'); -- 不存在, 获取锁
redis.call('expire', key, releaseTime); -- 设置有效期
return 1; -- 返回结果
end;
if(redis.call('hexists', key, threadId) == 1) then -- 锁已经存在,判断threadId是否是自己
redis.call('hincrby', key, threadId, '1'); -- 不存在, 获取锁,重入次数+1
redis.call('expire', key, releaseTime); -- 设置有效期
return 1; -- 返回结果
end;
return 0; -- 代码走到这里,说明获取锁的不是自己,获取锁失败
然后是释放锁:
local key = KEYS[1]; -- 锁的key
local threadId = ARGV[1]; -- 线程唯一标识
local releaseTime = ARGV[2]; -- 锁的自动释放时间
if (redis.call('HEXISTS', key, threadId) == 0) then -- 判断当前锁是否还是被自己持有
return nil; -- 如果已经不是自己,则直接返回
end;
local count = redis.call('HINCRBY', key, threadId, -1); -- 是自己的锁,则重入次数-1
if (count > 0) then -- 判断是否重入次数是否已经为0
redis.call('EXPIRE', key, releaseTime); -- 大于0说明不能释放锁,重置有效期然后返回
return nil;
else
redis.call('DEL', key); -- 等于0说明可以释放锁,直接删除
return nil;
end;
面试官问题
:redis
分布式锁依赖与redis
,如果redis
宕机则锁失效。如何解决?
此时大多数同学会回答说:搭建主从集群,做数据备份。
这样就进入了陷阱,因为面试官的下一个问题就来了:
面试官问题
:如果搭建主从集群做数据备份时,进程A获取锁,master
还没有把数据备份到slave
,master
宕机,slave
升级为master
,此时原来锁失效,其它进程也可以获取锁,出现安全问题。如何解决?
关于这个问题,Redis
官网给出了解决方案,使用RedLock
思路可以解决:
在
Redis
的分布式环境中,我们假设有N
个Redis master
。这些节点完全互相独立,不存在主从复制或者其他集群协调机制。之前我们已经描述了在Redis
单实例下怎么安全地获取和释放锁。我们确保将在每(N
)个实例上使用此方法获取和释放锁。在这个样例中,我们假设有5个Redis master
节点,这是一个比较合理的设置,所以我们需要在5台机器上面或者5台虚拟机上面运行这些实例,这样保证他们不会同时都宕掉。为了取到锁,客户端应该执行以下操作:
- 获取当前
Unix
时间,以毫秒为单位。- 依次尝试从
N
个实例,使用相同的key和随机值获取锁。在步骤2,当向Redis
设置锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间。例如你的锁自动失效时间为10秒,则超时时间应该在5-50毫秒之间。这样可以避免服务器端Redis
已经挂掉的情况下,客户端还在死死地等待响应结果。如果服务器端没有在规定时间内响应,客户端应该尽快尝试另外一个Redis
实例。- 客户端使用当前时间减去开始获取锁时间(步骤1记录的时间)就得到获取锁使用的时间。当且仅当从大多数(这里是3个节点)的
Redis
节点都取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功。- 如果取到了锁,
key
的真正有效时间等于有效时间减去获取锁所使用的时间(步骤3计算的结果)。- 如果因为某些原因,获取锁失败(没有在至少
N/2+1
个Redis
实例取到锁或者取锁时间已经超过了有效时间),客户端应该在所有的Redis
实例上进行解锁(即便某些Redis
实例根本就没有加锁成功)。
面试话术:
实现方案有下面几种:
Redis
的修改逻辑,保证一致。A
调用了服务B
,并对查询结果缓存。服务B数据库修改,可以通过MQ
通知服务A
,服务A
修改Redis
缓存数据Canal
框架,伪装成MySQL
的salve
节点,监听MySQL
的binLog
变化,然后修改Redis
缓存数据