概念: Redis是一个基于c语言开发的NoSQL数据库,与传统的关系型数据库不同,Redis将数据保存在内存中,这使得读写速度非常快。Redis支持多种数据结构,包括字符串、列表、集合、有序集合、哈希表等。Redis提供了持久化功能,可以将数据定期保存到磁盘,以防数据丢失。

适用场景:

  • 分布式锁:在分布式系统中,多个节点需要操作同一个资源的时会存在竞争问题。Redis通过原子操作命令SETNX实现分布式锁,SETNX命令仅在键不存在时设置键值,从而确保资源的唯一性。此外,结合EXPIRE命令可以设置锁的自动过期时间,避免死锁问题。Redis 分布式锁的实现保证了在高并发场景下对共享资源的安全访问。
  • 缓存:在应用程序中,一些存储于数据库的数据会被频繁访问。由于数据库通常位于磁盘上,其 I/O 性能相对较差,导致应用程序响应速度较慢。引入 Redis 作为缓存层,可以将频繁访问的数据存储在内存中,从而大幅提升应用程序的响应速度,降低数据库负载。通过设置适当的缓存策略(如 LRU、LFU),Redis 可以高效地管理缓存数据,保证数据的时效性和一致性。
  • 消息队列:支持发布/订阅(pub/sub)模式和基于列表的消息队列。发布/订阅模式适用于实时消息传递,而基于列表的消息队列则适用于任务队列、异步处理等场景。Redis 提供了简单高效的消息队列功能,支持多种消息模式,有助于构建高效的分布式系统。
  • 会话存储: 在无状态的服务器架构中,使用 Redis 存储会话数据可以在多个服务器之间共享用户状态信息。由于 Redis 存储于内存中,访问速度快,能够快速响应会话请求,提升用户体验。通过设置合适的过期时间,可以有效管理会话数据的生命周期,保证会话的及时失效和清理。Redis 的持久化机制还可以在服务器重启后恢复会话数据,增强系统的可靠性。

优点:

  • 数据存储在内存中,读取和写入速度极快,通常可以达到每秒数百万次读写操作。
  • 单线程操作,避免了频繁的上下文切换。
  • 使用非阻塞i/o多路复用机制。

缺点:

  • 容量受到物理内存的限制,不能用作海量数据的高性能读写,redis适合的场景局限在较小数据量的高性能操作和运算上
  • 在主从复制过程中,可能会存在数据不一致的情况,需要额外的机制来保证数据的一致性。
常见问题
  • 缓存和数据库双写一致性问题:添加缓存后,数据查询首先从 Redis 获取,如果缓存中存在数据则直接返回,不再访问数据库。这可能导致数据库中的数据与缓存中的数据不一致。
    解决方案:
  • 延时双删策略:在更新数据库数据后,先删除缓存,再更新数据库,最后再延时删除缓存。这样可以确保缓存与数据库的一致性。
  • 失效策略: 设置缓存过期时间,使缓存定期失效并从数据库中重新加载数据,减少缓存与数据库的不一致情况。
  • 缓存雪崩:缓存数据在同一时间内大面积失效,导致大量请求直接到数据库,造成数据库负载过重甚至宕机。
    解决方案:
  • 缓存数据过期时间错开:设置缓存时,为不同的数据设置不同的过期时间,避免大量缓存同时失效。
  • 缓存预热:在系统启动时先加载部分热点数据到缓存中,减少缓存失效时的瞬间压力。
  • 缓存击穿: 高并发下请求的某些热点数据缓存失效,导致所有请求都直接打到数据库,造成数据库压力过大。
    解决方案:
  • 热点数据缓存: 对于热点数据,可以提前缓存并设置较长的过期时间,避免频繁的缓存失效。
  • 设置默认值: 在缓存未命中且数据库查询前,可以设置一个默认值或占位符,减少对数据库的瞬时访问压力。
  • Redis过期键的删除策略:Redis使用过期删除+定期删除的策略
  • 定时删除:在设置键过期时间时,Redis会同时创建一个定时器,当过期时间到达时自动执行删除操作。这种方法可以保证及时清理过期键,但如果过期键很多,可能会导致较高的CPU负载。
  • 惰性删除:对于那些设置了过期时间但未及时访问的键,Redis不会主动删除,而是等到有操作访问这些键时再检查其过期状态。如果键已经过期,则会在访问时删除。这种方法的优点是对CPU的影响较小,但缺点是可能会积累大量的过期键,占用内存。
  • 定期删除:为了防止内存中积累过多的过期键,Redis会定期(默认每100毫秒)随机检查一部分键,并删除其中已经过期的键。检查的频率和每次检查的键数量可以通过配置进行调整。这种方法在减少内存使用和降低CPU负载之间取得了平衡。
  • Redis是单线程还是多线程?
  • Redis的核心操作(包括接收客户端请求、解析请求、进行数据读写操作、发送响应给客户端)是由一个单线程(主线程)完成的。这种设计简化了代码的复杂性,避免了多线程编程中的竞争条件和锁管理问题,从而提高了系统的稳定性和响应速度。
  • 从Redis 4.0开始,增加了lazyfree线程,用于异步释放内存。这解决了在删除大对象或大量对象时可能导致的主线程阻塞问题。通过将这些耗时的内存释放操作移到后台线程执行,Redis可以保持高效的请求处理能力。
  • 在Redis 6.0中,引入了I/O多线程功能。尽管Redis在处理命令时仍然是单线程,但在网络I/O操作(如读取请求和发送响应)中,可以使用多线程来提高性能。这样可以在高并发场景下更好地利用多核CPU资源,从而提升整体吞吐量。
  • Redis 采用单线程为什么还这么快?
  • 高效的内存操作:Redis 的大部分操作都在内存中完成,使用了高效的数据结构。因此,Redis 的瓶颈通常是机器的内存或网络带宽,而不是 CPU。既然 CPU 不是瓶颈,采用单线程就足够了。
  • 避免多线程竞争:采用单线程模型可以避免多线程之间的竞争,从而省去了线程切换带来的时间和性能开销。此外,单线程模型也避免了多线程可能导致的死锁问题。
  • I/O 多路复用机制:Redis 使用 I/O 多路复用机制来处理大量客户端的 Socket 请求。I/O 多路复用是指一个线程可以处理多个 I/O 流,通过 select、poll 或 epoll 等机制实现。在单线程情况下,这种机制允许内核同时监听多个 Socket(包括监听 Socket 和已连接的 Socket)。一旦有请求到达,内核会将其交给 Redis 线程处理,从而实现一个线程处理多个 I/O 流的效果。
  • Redis 持久化实现的方式,如何实现数据不丢失?
  • AOF日志:没执行一条写操作命令,就把该命令以追加的方式写入到一个文件中。
  • RDB快照:将某一时刻的内存数据,以二进制的方式写入磁盘,形成快照。
  • 混合持久化方式:Redis 4.0 之后引入了混合持久化,即在进行 RDB 快照时,同时将 AOF 日志中未持久化的命令一起写入到快照文件中。这样,在重启时可以通过加载 RDB 快照和 AOF 日志快速恢复数据。
  • Redis为什么用跳表实现有序集合?
  • 简单易实现:跳表相对于平衡树(如红黑树或 B+ 树)来说,实现起来更为简单。跳表的基本结构和操作(如插入、删除、查找)比较容易理解和实现,这使得代码维护和调试更加方便。红黑树和 B+ 树的实现相对复杂,需要维护额外的平衡和节点信息。
  • 空间占用小:跳表不需要存储额外的平衡信息,例如红黑树需要存储左右子节点信息和颜色信息,B+ 树需要存储更多的内部节点指针。因此,跳表在空间利用上更加高效。这对于 Redis 尤为重要,因为 Redis 通常处理大量数据,节省空间可以提高性能和效率。
  • 快速插入和删除:跳表支持 O(log n) 复杂度的插入、删除和查找操作,其性能与平衡树相近,同时实现起来更加简便。红黑树和 B+ 树的插入和删除操作需要复杂的平衡调整,而跳表则无需如此复杂的操作。
  • 顺序性:跳表天然支持范围查询和排序操作,这对于 Redis 的有序集合(Sorted Set)非常有用。跳表的多级链表结构使得范围查询和排序操作非常高效,特别是在处理需要范围查询的场景时。
  • 跳表:跳表使用多层级链表实现,支持 O(log n) 的插入、删除和查找操作。其结构简单,易于实现和维护,特别适合用于实现需要快速范围查询的有序集合。
  • 红黑树:红黑树是一种自平衡的二叉搜索树,通过旋转和重新着色操作来保持平衡,确保插入、删除和查找操作的时间复杂度为 O(log n)。然而,红黑树的实现较为复杂,需要维护左右子节点和颜色信息。
  • B+ 树:B+ 树是一种多路平衡搜索树,常用于数据库和文件系统中。它将所有值存储在叶子节点,并在叶子节点间建立链表,实现高效的范围查询。B+ 树的实现更为复杂,需要维护更多的内部节点和指针,但在磁盘存储和大规模数据处理中性能优秀。
基本数据结构
  • list:简单的字符串列表
  • 底层数据结构是由双向链表或压缩列表实现的,但是在 Redis 3.2 版本之后,List 数据类型底层数据结构就只由 quicklist 实现了。
  • 常用指令
# 将一个或多个值value插入到key列表的表头(最左边),最后的值在最前面
LPUSH key value [value ...] 
# 将一个或多个值value插入到key列表的表尾(最右边)
RPUSH key value [value ...]
# 移除并返回key列表的头元素
LPOP key     
# 移除并返回key列表的尾元素
RPOP key 

# 返回列表key中指定区间内的元素,区间以偏移量start和stop指定,从0开始
LRANGE key start stop

# 从key列表表头弹出一个元素,没有就阻塞timeout秒,如果timeout=0则一直阻塞
BLPOP key [key ...] timeout
# 从key列表表尾弹出一个元素,没有就阻塞timeout秒,如果timeout=0则一直阻塞
BRPOP key [key ...] timeout
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 应用场景:消息队列
  • string:最基本的 key-value 结构
  • 底层数据结构是SDS(简单动态字符串)。SDS不仅可以保存文本数据,还可以保存二进制数据、SDS获取字符串长度的时间复杂度是O(1)、Redis 的 SDS API 是安全的,拼接字符串不会造成缓冲区溢出。
  • 常用指令
# 设置 key-value 类型的值
> SET name lin
OK
# 根据 key 获得对应的 value
> GET name
"lin"
# 判断某个 key 是否存在
> EXISTS name
(integer) 1
# 返回 key 所储存的字符串值的长度
> STRLEN name
(integer) 3
# 删除某个 key 对应的值
> DEL name
(integer) 1
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 应用场景:缓存对象、常规计数、分布式锁、共享 Session 信息。
  • Hash:一个键值对(key - value)集合
  • 底层数据结构是由压缩列表或哈希表实现的,在 Redis 7.0 中,压缩列表数据结构已经废弃了,交由 listpack 数据结构来实现了:
  • 如果哈希类型元素个数小于 512 个(默认值,可由 hash-max-ziplist-entries 配置),所有值小于 64 字节(默认值,可由 hash-max-ziplist-value 配置)的话,Redis 会使用压缩列表作为 Hash 类型的底层数据结构;
  • 如果哈希类型元素不满足上面条件,Redis 会使用哈希表作为 Hash 类型的 底层数据结构。
  • 常用命令
# 存储一个哈希表key的键值
HSET key field value   
# 获取哈希表key对应的field键值
HGET key field

# 在一个哈希表key中存储多个键值对
HMSET key field value [field value...] 
# 批量获取哈希表key中多个field键值
HMGET key field [field ...]       
# 删除哈希表key中的field键值
HDEL key field [field ...]    

# 返回哈希表key中field的数量
HLEN key       
# 返回哈希表key中所有的键值
HGETALL key 

# 为哈希表key中field键的值加上增量n
HINCRBY key field n
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 应用场景:缓存对象、购物车
  • set:一个无序并唯一的键值集合
  • 底层数据结构是由哈希表或整数集合实现的:
  • 如果集合中的元素都是整数且元素个数小于 512 (默认值,set-maxintset-entries配置)个,Redis 会使用整数集合作为 Set 类型的底层数据结构;
  • 如果集合中的元素不满足上面条件,则 Redis 使用哈希表作为 Set 类型的底层数据结构。
  • 常用命令
# 往集合key中存入元素,元素存在则忽略,若key不存在则新建
SADD key member [member ...]
# 从集合key中删除元素
SREM key member [member ...] 
# 获取集合key中所有元素
SMEMBERS key
# 获取集合key中的元素个数
SCARD key

# 判断member元素是否存在于集合key中
SISMEMBER key member

# 从集合key中随机选出count个元素,元素不从key中删除
SRANDMEMBER key [count]
# 从集合key中随机选出count个元素,元素从key中删除
SPOP key [count]
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 应用场景:抽奖活动、共同关注、点赞
  • zset:有序集合类型,相比于 Set 类型多了一个排序属性 score(分值),对于有序集合 ZSet 来说,每个存储元素相当于有两个值组成的,一个是有序集合的元素值,一个是排序值。
  • 底层数据结构是由压缩列表或跳表实现的,在 Redis 7.0 中,压缩列表数据结构已经废弃了,交由 listpack 数据结构来实现了。:
  • 如果有序集合的元素个数小于 128 个,并且每个元素的值小于 64 字节时,Redis 会使用压缩列表作为 Zset 类型的底层数据结构;
  • 如果有序集合的元素不满足上面的条件,Redis 会使用跳表作为 Zset 类型的底层数据结构;
  • 常用命令
# 往有序集合key中加入带分值元素
ZADD key score member [[score member]...]   
# 往有序集合key中删除元素
ZREM key member [member...]                 
# 返回有序集合key中元素member的分值
ZSCORE key member
# 返回有序集合key中元素个数
ZCARD key 

# 为有序集合key中元素member的分值加上increment
ZINCRBY key increment member 

# 正序获取有序集合key从start下标到stop下标的元素
ZRANGE key start stop [WITHSCORES]
# 倒序获取有序集合key从start下标到stop下标的元素
ZREVRANGE key start stop [WITHSCORES]

# 返回有序集合中指定分数区间内的成员,分数由低到高排序。
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]

# 返回指定成员区间内的成员,按字典正序排列, 分数必须相同。
ZRANGEBYLEX key min max [LIMIT offset count]
# 返回指定成员区间内的成员,按字典倒序排列, 分数必须相同
ZREVRANGEBYLEX key max min [LIMIT offset count]
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 应用场景:排行榜、电话、姓名排序