Redis常见面试题

Redis(remote dictionary server)

远程字典服务

1、 db,每次都从数据库中查

2、 用一个List存储查的全部结果,然后再在List中过滤

3、 本地缓存,同一个Map,开始时是空的,则查询数据库,并存入Map,之后直接从Map中查

4、 如何解决分布式下缓存读写问题,每个微服务中有一个自己的Map,数据不一致

5、 从分布式中抽离出一个缓存,so 缓存中间件redis

干什么

内存存储、持久化,内存中是断电即失,所以持久化很重要(rdb、aof)

将一些热点数据存储到Redis中,要用的时候,直接从内存取,极大的提高了速度和节约了服务器的开销。

​ 1、会话缓存(最常用)

​ 2、消息队列(支付)

​ 3、活动排行榜或计数

​ 4、发布,订阅消息(消息通知)

​ 5、商品列表,评论列表

特性

多样的数据类型

持久化

集群

事务

1
2
3
4
5
6
7
8
select 3 #选择数据库
DBSIZE #查看大小
flushdb #清空当前数据库
FLUSHALL #清空全部数据库
EXISTS name #是否存在key
move name 1 #从0号移到1号
EXPIRE name 10 #10s过期
ttl name #查看还有多久过期                                                                                                                       

redis默认使用单线程,也支持多线程。多线程会产生cpu上下文切换

5大数据类型

string(字符串)的类型

string 是 redis 最基本的类型,你可以理解成与 Memcached 一模一样个 key 对应一个 value。 string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。 string 类型是 Redis 最基本的数据类型,string 类型的值最大能存储 512MB。

实例 redis 127.0.0.1:6379> SET runoob “菜鸟教程” OK redis 127.0.0.1:6379> GET runoob

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
APPEND #追加字符串
STRLEN  #字符串长度

incr #+1
decr #-1
INCRBY n#+n
DECRBY n#-n

GETRANGE Key 0 3 #截取字符串[0,3]
SETRANGE key 1 xx #替换指定位置的字符串

setex  #设置过期时间
setnx #不存在就设置成功,存在则设置失败

mset #同时设置多个值
mget #同时获取多个值
msetnx #是一个原子操作,要么一起成功,要么一起失败

#设置一个对象  user:{id}:{filed}
mset user:1:name zhangsan user:1:age 2
mset user:1:name user:1:age 

getset #先get再set

应用

  1. 计数器
  2. 统计多单位的数量
  3. 粉丝数
  4. 对象缓存存储

hash(哈希)

map

Redis hash 是一个键值(key=>value)对集合。 Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。

1
2
3
4
5
6
7
redis 127.0.0.1:6379> DEL runoob
redis 127.0.0.1:6379> HMSET runoob field1 "Hello" field2 "World"
"OK"
redis 127.0.0.1:6379> HGET runoob field1
"Hello"
redis 127.0.0.1:6379> HGET runoob field2
"World" 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
hset
hget
hmset
hmget
hgetall
hdel #删除
hlen #长度
hexists 
hkeys #所有key
hvals #所有value
hincrby
hsetnx

应用

hash更适合对象的存储

list(列表)

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
redis 127.0.0.1:6379> DEL runoob
redis 127.0.0.1:6379> lpush runoob redis
(integer) 1
redis 127.0.0.1:6379> lpush runoob mongodb
(integer) 2
redis 127.0.0.1:6379> lpush runoob rabbitmq
(integer) 3
redis 127.0.0.1:6379> lrange runoob 0 10
1) "rabbitmq"
2) "mongodb"
3) "redis"
redis 127.0.0.1:6379>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
LPUSH #将值插入列表头部
RPUSH #将值插入列表尾部

LPOP #移除列表的第一个元素
RPOP #移除列表的最后一个元素

lindex #通过下表获取列表的一个值
Llen #长度

lrange list 0 -1  #全部元素
lrem list 1 one  #移除list集合中指定个数的value
ltrim list 1 2 #通过下表截取指定的长度,list已经被改变了

rpoplpush #移除列表中最后一个元素,将它移动到新的列表中

linsert #将某个具体的value插入列中某个元素的前面或后面

应用

队列 lpush rpop 栈 lpush lpop

set(集合)

Redis 的 Set 是 string 类型的无序集合。 集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
redis 127.0.0.1:6379> DEL runoob
redis 127.0.0.1:6379> sadd runoob redis
(integer) 1
redis 127.0.0.1:6379> sadd runoob mongodb
(integer) 1
redis 127.0.0.1:6379> sadd runoob rabbitmq
(integer) 1
redis 127.0.0.1:6379> sadd runoob rabbitmq
(integer) 0
redis 127.0.0.1:6379> smembers runoob

1) "redis"
2) "rabbitmq"
3) "mongodb"
1
2
3
4
5
6
sismember  #判断一个值是否在集合中
scard  #获取元素个数
srem #移除元素
srandmember #随机抽选出一个元素
spop #随机删除集合中的元素
smove #将一个指定的值移动到另一个set集合中

应用

微博 b站共同关注(交集)

1
2
3
SDIFF  #差集
SINTER #交集
SUNION #并集

zset(sorted set:有序集合)

Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。 不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

zset的成员是唯一的,但分数(score)却可以重复。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
redis 127.0.0.1:6379> DEL runoob
redis 127.0.0.1:6379> zadd runoob 0 redis
(integer) 1
redis 127.0.0.1:6379> zadd runoob 0 mongodb
(integer) 1
redis 127.0.0.1:6379> zadd runoob 0 rabbitmq
(integer) 1
redis 127.0.0.1:6379> zadd runoob 0 rabbitmq
(integer) 0
redis 127.0.0.1:6379> ZRANGEBYSCORE runoob 0 1000
1) "mongodb"
2) "rabbitmq"
3) "redis"
1
2
3
4
5
ZRANGEBYSCORE #小到大
ZREVRANGEBYSCORE #大到小
zrange
zrem
zcard

应用

排行榜

普通消息,重要消息

三种特殊数据类型

geospatial 地理位置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#添加地理位置  下载城市数据 java程序一次性导入
经度(-180-180)纬度(-85-85)  名称
geoadd china:city 116.40 39.90 beijing
geoadd china:city 112.47 31.23 shanghai
geoadd china:city 106.50 29.53 chongqing 114.05 22.52 shengzhen
getpos china:city beijing #获取指定的经度和维度
getdist china:city beijing shanghai km #获取2地的直线距离
georadius china:city 110 30 1000 km withdist withcoord count 2 #找附近的人,获取指定数量的人
georadiusbymember china:city beijing 1000 km #找出指定元素周围的其他元素
geohash #将二维的经纬度转化为一维的字符串

底层原理是zset,可以用zset命令操作

zrange china:city 0 -1

hyperloglog

基数:不重复的元素

网页的UV(一个人访问一个网站多次,只算一个人)

传统:用set保存用户id ,保存了大量的id,但目的是计数

1
2
3
4
pfadd mykey1 a b c d e
pfadd mykey2 g h j k l e
pfcount mykey1 
pfmerge mykey3 mykey1 mykey2

优点:占用内存是一定的 12kb。0.81%的错误率!统计UV可以忽略不计

bitmaps

位存储

统计疫情感染人数: 0 1 0 1 0

统计用户信息(是否活跃) 用户打卡。两个状态的

1
2
3
4
5
6
7
8
9
setbit sign 0 1
setbit sign 1 1
setbit sign 2 1
setbit sign 3 1
setbit sign 4 1
setbit sign 5 1
setbit sign 6 0  #周一-周日的打卡
getbit sign 1 
bitcount sign #统计打卡记录

SpringBoot

springboot2.x后,原来使用的jedis被替换为了lettuce

jedis:采用的直连,多个线程操作的话,不安全,如果需要避免不安全,需要使用jedis pool连接;BIO模式

lettuce:采用netty,实例可以在多个线程中共享 NIO模式

1
2
3
4
5
6
7
RedisTemplate.opsForValue().set(key, value);

//封装一下
@Override
    public void set(String key, String value) {
        stringRedisTemplate.opsForValue().set(key, value);
    }

事务

单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。 事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。

Redis没有隔离级别的概念。

Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:

  • 批量操作在发送 EXEC 命令前被放入队列缓存。
  • 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
  • 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。

一个事务从开始到执行会经历以下三个阶段:

  • 开始事务。
  • 命令入队。
  • 执行事务。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
redis 127.0.0.1:6379> MULTI
OK

redis 127.0.0.1:6379> SET book-name "Mastering C++ in 21 days"
QUEUED

redis 127.0.0.1:6379> GET book-name
QUEUED

redis 127.0.0.1:6379> SADD tag "C++" "Programming" "Mastering Series"
QUEUED

redis 127.0.0.1:6379> SMEMBERS tag
QUEUED

redis 127.0.0.1:6379> EXEC
1) OK
2) "Mastering C++ in 21 days"
3) (integer) 3
4) 1) "Mastering Series"
   2) "C++"
   3) "Programming" ```

discard #取消事务
#命令写错了全不会执行,

## 消息通知
[消息通知](https://waterwang.blog.csdn.net/article/details/113768448)

## 管道
Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。这意味着通常情况下一个请求会遵循以下步骤:
* 客户端向服务端发送一个查询请求,并监听Socket返回,通常是以阻塞模式,等待服务端响应。
* 服务端处理命令,并将结果返回给客户端。

### Redis 管道技术
Redis 管道技术可以在服务端未响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的响应。
管道技术最显著的优势是提高了 redis 服务的性能。

$(echo -en “PING\r\n SET runoobkey redis\r\nGET runoobkey\r\nINCR visitor\r\nINCR visitor\r\nINCR visitor\r\n”; sleep 10) | nc localhost 6379

+PONG +OK redis :1 :2 :3``` 以上实例中我们通过使用 PING 命令查看redis服务是否可用, 之后我们设置了 runoobkey 的值为 redis,然后我们获取 runoobkey 的值并使得 visitor 自增 3 次。 在返回的结果中我们可以看到这些命令一次性向 redis 服务提交,并最终一次性读取所有服务端的响应

悲观锁、乐观锁version

使用watch 可以当做redis的乐观锁操作

持久化

redis是内存数据库,如果不将内存中的数据保存到硬盘,数据将丢失。

rdb(redis database)

config get dir 得到dump.rdb的存放位置/usr/local/bin 只需要放在此目录下,启动就会自动扫描其中的数据

快照形式是直接把内存中的数据保存到一个 dump 文件中,定时保存,保存策略。

当 Redis 需要做持久化时,Redis 会 fork 一个子进程,子进程将数据写到磁盘上一个临时 RDB 文件中。当子进程完成写临时文件后,将原来的 RDB 替换掉,这样的好处就是可以 copy-on-write。

Redis默认情况下,是快照 RDB 的持久化方式,将内存中的数据以快照的方式写入二进制文件中,默认的文件名是 dump.rdb 。当然我们也可以手动执行 save 或者 bgsave(异步)做快照。

RDB 的优点:

这种文件非常适合用于进行备份: 比如说,你可以在最近的 24 小时内,每小时备份一次 RDB 文件,并且在每个月的每一天,也备份一个 RDB 文件。 这样的话,即使遇上问题,也可以随时将数据集还原到不同的版本。RDB 非常适用于灾难恢复(disaster recovery)。

RDB 的缺点:

如果你需要尽量避免在服务器故障时丢失数据那么 RDB 不适合你。 虽然 Redis 允许你设置不同的保存点(save point)来控制保存 RDB 文件的频率, 但是, 因为RDB 文件需要保存整个数据集的状态, 所以它并不是一个轻松的操作。 因此你可能会至少 5 分钟才保存一次 RDB 文件。 在这种情况下, **一旦发生故障停机, 你就可能**会丢****失好几分钟的数据

触发机制:

  1. save的规则满足的情况下
  2. 执行flushall命令
  3. 退出redis时

aof(append only file)

使用 AOF 做持久化,每一个写命令都通过write函数追加到 appendonly.aof 中,配置方式:启动 AOF 持久化的方式

1
2
3
4
5
6
7
appendfsync yes   
appendfsync always     
#每次有数据修改发生时都会写入AOF文件。
appendfsync everysec   
#每秒钟同步一次,该策略为AOF的缺省策略。

redis-check-aof --fix appendonly.aof #修复文件

AOF 的优点

使用 AOF 持久化会让 Redis 变得非常耐久(much more durable):你可以设置不同的 fsync 策略,比如无 fsync ,每秒钟一次 fsync ,或者每次执行写入命令时 fsync 。 AOF 的默认策略为每秒钟 fsync 一次,在这种配置下,Redis 仍然可以保持良好的性能,并且就算发生故障停机,也最多只会丢失一秒钟的数据( fsync 会在后台线程执行,所以主线程可以继续努力地处理命令请求)。

AOF 的缺点

对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB在一般情况下, 每秒 fsync 的性能依然非常高, 而关 闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。 不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)。

如果aof文件大于64m,将fork一个新进程重写。

二者的区别

RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。

AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。

RDB 和 AOF ,我应该用哪一个?

  • 如果你非常关心你的数据,但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久。
  • AOF 将 Redis 执行的每一条命令追加到磁盘中,处理巨大的写入会降低 Redis 的性能,不知道你是否可以接受。

数据库备份和灾难恢复:定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快。

Redis 支持同时开启 RDB 和 AOF,系统重启后,Redis 会优先使用 AOF 来恢复数据,这样丢失的数据会最少。

Redis 发布订阅

image-20210610164654447

1
2
3
4
5
6
redis 127.0.0.1:6379**>** SUBSCRIBE runoobChat

Reading messages... **(**press Ctrl-C to quit**)**
1**)** "subscribe"
2**)** "redisChat"
3**)** **(**integer**)** 1
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
redis 127.0.0.1:6379> PUBLISH runoobChat "Redis PUBLISH test"

(integer) 1

redis 127.0.0.1:6379> PUBLISH runoobChat "Learn redis by runoob.com"

(integer) 1

\# 订阅者的客户端会显示如下消息
 1) "message"
2) "runoobChat"
3) "Redis PUBLISH test"
 1) "message"
2) "runoobChat"
3) "Learn redis by runoob.com"

Redis是使用C实现的,通过分析Redis源码里的pubsub.c文件,了解发布和订阅机制的底层实现,藉此加深对Redis的理解。

Redis通过publish、subscribe和psubscribe等命令实现发布和订阅功能。

微信:

通过subscribe命令订阅某频道后,redis-server里维护了一个字典,字典的键就是一个个频道,而字典的值则是一个链表,链表中保存了所有订阅这个channel的客户端。subscribe命令的关键,就是将客户端添加到给定channel的的订阅链表中。

image-20210610164844796

通过publish命令向订阅者发送消息,redis-server会使用给定的频道作为键,在它所维护的channel字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者。

Pub/Sub从字面上理解就是发布(Publish)与订阅(Subscribe),在Redis中,你可以设定对某一个key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能。

使用场景

1.实时消息系统

2.实时聊天(频道当作聊天室,将信息回显给所有人即可)

3.订阅,关注系统都是可以的

稍微复杂的场景我们就会使用消息中间件MQ。

Redis主从复制

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。master写,slave读。读写分离

作用:

数据冗余,热备份,持久化之外的一种数据冗余方式

故障恢复

负载均衡

高可用基石

1
2
3
info replication

slaveof  127.0.0.1 6379  #真实的应该在从机的conf文件里配置,这个方法重启这个从机就会重新变回主机

从机不能写

全量同步 Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份。

增量同步 Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。 增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。

Redis主从同步策略 主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。

注意点 如果多个Slave断线了,需要重启的时候,因为只要Slave启动,就会发送sync请求和主机全量同步,当多个同时出现的时候,可能会导致Master IO剧增宕机。

哨兵模式(sentinel)

哨兵是Redis的一种运行模式,它专注于对Redis实例(主节点、从节点)运行状态的监控,并能够在主节点发生故障时通过一系列的机制实现选主及主从切换,实现故障转移,确保整个Redis系统的可用性。结合Redis的官方文档,可以知道Redis哨兵具备的能力有如下几个:

  • 监控(Monitoring):持续监控Redis主节点、从节点是否处于预期的工作状态。

  • 通知(Notification):哨兵可以把Redis实例的运行故障信息通过API通知监控系统或者其他应用程序。

  • 自动故障恢复(Automatic failover):当主节点运行故障时,哨兵会启动自动故障恢复流程:某个从节点会升级为主节点,其他从节点会使用新的主节点进行主从复制,通知客户端使用新的主节点进行。

  • 配置中心(Configuration provider):哨兵可以作为客户端服务发现的授权源,客户端连接到哨兵请求给定服务的Redis主节点地址。如果发生故障转移,哨兵会通知新的地址。这里要注意:哨兵并不是Redis代理,只是为客户端提供了Redis主从节点的地址信息。

    image-20210610193505508

准备三个redis配置文件,对应Redis的一主二从,文件名称及内容如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# redis-6379.conf*
port 6379
daemonize yes
pidfile /var/run/redis-6379.pid
logfile "6379.log"
dir "/Users/eleme/raysonxin/docker/redis-docker/log"
dbfilename dump-6379.rdb

*# redis-6380.conf*
port 6380
daemonize yes
pidfile /var/run/redis-6380.pid
logfile "6380.log"
dir "/Users/eleme/raysonxin/docker/redis-docker/log"
dbfilename dump-6380.rdb
slaveof 127.0.0.1 6379

*# redis-6381.conf*
port 6381
daemonize yes
pidfile /var/run/redis-6381.pid
logfile "6381.log"
dir "/Users/eleme/raysonxin/docker/redis-docker/log"
dbfilename dump-6381.rdb
slaveof 127.0.0.1 6379

准备三个Redis Sentinel配置文件,作为三个监控节点,文件及内容如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# sentinel-26379.conf*
port 26379
daemonize yes
dir "/Users/eleme/raysonxin/docker/redis-docker/log"
logfile "26379.log"
*# 配置监听的主服务器,这里sentinel monitor代表监控,mymaster代表服务器的名称,可以自定义,192.168.11.128代表监控的主服务器,6379代表端口,2代表只有两个或两个以上的哨兵认为主服务器不可用的时候,才会进行failover操作。*
sentinel monitor mymaster 127.0.0.1 6379 2  
*# 判断主节点时间*
sentinel down-after-milliseconds mymaster 10000
sentinel parallel-syncs mymaster 1  
sentinel failover-timeout mymaster 180000

*# sentinel-26380.conf*
port 26380
daemonize yes
dir "/Users/eleme/raysonxin/docker/redis-docker/log"
logfile "26380.log"
*# 配置监听的主服务器,这里sentinel monitor代表监控,mymaster代表服务器的名称,可以自定义,192.168.11.128代表监控的主服务器,6379代表端口,2代表只有两个或两个以上的哨兵认为主服务器不可用的时候,才会进行failover操作。*
sentinel monitor mymaster 127.0.0.1 6379 2  
*# 判断主节点时间*
sentinel down-after-milliseconds mymaster 10000 
sentinel parallel-syncs mymaster 1  
sentinel failover-timeout mymaster 180000

*# sentinel-26381.conf*
port 26381
daemonize yes
dir "/Users/eleme/raysonxin/docker/redis-docker/log"
logfile "26381.log"
*# 配置监听的主服务器,这里sentinel monitor代表监控,mymaster代表服务器的名称,可以自定义,192.168.11.128代表监控的主服务器,6379代表端口,2代表只有两个或两个以上的哨兵认为主服务器不可用的时候,才会进行failover操作。*
sentinel monitor mymaster 127.0.0.1 6379 2  
*# 判断主节点时间*
sentinel down-after-milliseconds mymaster 10000 
sentinel parallel-syncs mymaster 1  
sentinel failover-timeout mymaster 180000

依次启动三个Redis Server,命令如下:

1
2
3
redis-server conf/redis-6379.conf
redis-server conf/redis-6380.conf
redis-server conf/redis-6381.conf

依次启动三个Redis Sentinel,命令如下:

1
2
3
redis-sentinel conf/redis-26379.conf
redis-sentinel conf/redis-26380.conf
redis-sentinel conf/redis-26381.conf

Redis缓存穿透和雪崩(服务器的高可用问题)

缓存穿透(查不到)

缓存穿透概念:key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。

缓存穿透方案:最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。

缓存击穿(查太多)

缓存击穿概念:key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

缓存击穿方案:

  • 用互斥锁(mutex key)

    加锁保证只有一个线程进去db

业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。

SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。x

缓存雪崩

缓存雪崩概念:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。缓存集体过期或redis宕机

双十一:停掉一些服务

缓存雪崩方案:缓存失效时的雪崩效应对底层系统的冲击非常可怕!大多数系统设计者考虑加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就时讲缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。