Redis总结

Redis面试总结

Redis 为什么这么快?

Redis 内部做了非常多的性能优化,比较重要的主要有下面 3 点:

  • Redis 基于内存,内存的访问速度是磁盘的上千倍;
  • Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型,主要是单线程事件循环和 IO 多路复用;
  • Redis 内置了多种优化过后的数据结构实现,性能非常高。

why-redis-so-fast.d3507ae8

Redis 常用的数据结构有哪些?

  • 5 种基础数据结构 :String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合)。
  • 3 种特殊数据结构 :HyperLogLogs(基数统计)、Bitmap (位存储)、Geospatial (地理位置)。

String 还是 Hash 存储对象数据更好呢?

  • String 存储的是序列化后的对象数据,存放的是整个对象,String 消耗的内存约是 Hash 的一半,存储具有多层嵌套的对象时也方便很多。
  • Hash 是对对象的每个字段单独存储,适用于部分数据经常变动的对象。

IO多路复用

IO多路复用

Redis 内存淘汰机制了解么?

相关问题:MySQL 里有 2000w 数据,Redis 中只存 20w 的数据,如何保证 Redis 中的数据都是热点数据?

Redis 提供 6 种数据淘汰策略:

  1. volatile-lru(least recently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
  2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  4. allkeys-lru(least recently used):当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)
  5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  6. no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!

4.0 版本后增加以下两种:

  1. volatile-lfu(least frequently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
  2. allkeys-lfu(least frequently used):当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key

Redis 持久化机制

什么是 RDB 持久化?

Redis 可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。类似MySQL的全量备份。

redis.conf 配置文件

1
2
3
4
5
6
save 900 1           #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发bgsave命令创建快照。

save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发bgsave命令创建快照。

save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发bgsave命令创建快照。

什么是 AOF 持久化?

与快照持久化相比,AOF 持久化的实时性更好,类似MySQL的binlog,redis.conf 配置文件

1
2
3
4
appendonly yes
appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
appendfsync everysec #每秒钟同步一次,显式地将多个写命令同步到硬盘
appendfsync no #让操作系统决定何时进行同步

通过aof-use-rdb-preamble开启RDB+AOF

Redis分布式锁

方案1

加锁与释放锁

1
2
3
4
5
6
7
8
9
# 加锁成功
127.0.0.1:6379> setnx lock 1
(integer) 1
# 加锁失败
127.0.0.1:6379> setnx lock 1
(integer) 0
# 释放锁
127.0.0.1:6379> del lock
(integer) 1

存在的问题

当客户端 1 拿到锁后,如果发生下面的场景,就会造成「死锁」:

  1. 程序处理业务逻辑异常,没及时释放锁
  2. 进程挂了,没机会释放锁

方案2

加锁的同时为锁设置有效期,避免因redis服务宕机而导致的死锁

1
2
3
4
5
6
# 加锁并设置有效期
127.0.0.1:6379> set lock 1 ex 10 nx
OK
# 释放锁
127.0.0.1:6379> del lock
(integer) 1

存在的问题

  1. 锁过期:客户端 1 操作共享资源耗时太久,导致锁被自动释放,之后被客户端 2 持有
  2. 释放别人的锁:客户端 1 操作共享资源完成后,却又释放了客户端 2 的锁

方案3

解决释放别人的锁的问题

1
2
3
# 加锁的时候为每个线程设置一个唯一标识或者业务标识,保证只有自己才能释放自己的锁
127.0.0.1:6379> SET lock $uuid EX 20 NX
OK

使用lua脚本保证获取锁和删除锁为原子性操作

1
2
3
4
5
6
7
// 判断锁是自己的,才释放
if redis.call("GET",KEYS[1]) == ARGV[1]
then
return redis.call("DEL",KEYS[1])
else
return 0
end

解决锁过期问题

使用Redisson的自动续期功能来避免锁过期

存在的问题

  1. 客户端 1 在主库上执行 SET 命令,加锁成功
  2. 此时,主库异常宕机,SET 命令还未同步到从库上(主从复制是异步的)
  3. 从库被哨兵提升为新主库,这个锁在新的主库上,丢失了!

方案4

使用RedLock,但也存在时钟问题,成本收益不成正比

Redlock 的方案基于 2 个前提:

  1. 不再需要部署从库哨兵实例,只部署主库
  2. 但主库要部署多个,官方推荐至少 5 个实例

也就是说,想用使用 Redlock,你至少要部署 5 个 Redis 实例,而且都是主库,它们之间没有任何关系,都是一个个孤立的实例。

注意:不是部署 Redis Cluster,就是部署 5 个简单的 Redis 实例。


Redis总结
http://example.com/2023/03/09/interview/Redis总结/
作者
UncleBryan
发布于
2023年3月9日
许可协议