Redis一些基本问题整理

技术博客 (96) 2023-12-07 09:01:01

目录

1.单线程的Redis为什么快?

2.Redis支持的数据类型(必考)

3.Redis和memcached的区别

4.Redis在项目中的主要作用是是什么?怎么用的?(必考)

5.Redis实现分布式锁的思路方案(必考)

6.分析基于Redis的限流器实现

7.Redis和DB数据一致性处理(必考)

8.Redis的数据过期策略分析(必考)

9.Redis的LRU过期策略的具体实现(必考)

10.分析Redis缓存雪崩问题以及解决方案(必考)

11.分析Redis缓存穿透问题和解决方案(必考)

12.Redis的持久化机制分析(必考)

13.分析下Redis的管道pipeline

14.Redis集群容错机制是什么样的?(必考)

15.Redis集群中某个master节点挂了,后面流程怎么判断?(必考)

16.高并发情况下,对于Redis的更新操作有哪些注意事项?

17.高并发下,先更新数据库,再删除缓存,存在啥问题,如何解决呢?

18.高并发情况下,先删除缓存,再更新数据库,这样会有啥问题,解决方案是?

19.zset跳表的数据结构分析(必考)

20.展开说说你了解的跳表​​​​​​​

21.Redis使用跳表不用B+树的原因?

22.跳跃表和B+树结构上有啥区别

23.Redis的缓存优化方向有哪些?你们怎么理解的?对热点键的注意事项设计什么?

24.跳表(Skip List)三大问题举例

25.Zset底层的数据结构分析,如何体现的高效?(必考)

26.Redis 6.0以后的线程模型

27.Redis集群模式,节点怎么通信的?满足了CAP那两点?

28.Redis分布式锁实现上有啥问题缺陷?怎么解决?

29.如果是DB实现分布式锁的话,主要思路是?有啥问题?怎么解决?

30.Redis 分布式锁的问题缺以及优化思路

31.Redis 热点key 的问题和优化处理

32.Redis中有一批key瞬间过期,为什么其它key的读写效率会降低?

33.Redis的zset底层什么时候是hash,什么时候是跳表?

34.Redis 数据结构有哪些,底层实现都是什么?

35.Redis 热key 是什么,有什么问题,怎么发现,怎么解决?

36.Redis 中底层是跳表结构,那么插入和查询一个数的流程如何?如果是单位查询,流程又是如何实现的?

参考书籍、文献和资料


备注:针对基本问题做一些基本的总结,不是详细解答!

Redis是基于键值对(key-value)的NoSQL数据库,其中键都是字符串类型的,值由基本的5种数据结构组成,其中还包括了一些Redis基本的附加功能,它将所有的数据都存放在内存中,极大增加读写性能。

Redis受青睐的8大特性包括了:

  • ①速度快---所有数据都存放在内存 + 用C语言实现 + 单线程架构 + 源代码多次优化;
  • ②基于键值对的数据结构服务器---字符串 + 哈希 + 列表 + 集合 + 有序集合 + 位图 + HyperLogLog + GEO;
  • ③丰富的功能---添加了额外的8大辅助功能:支持事务、流水线、发布/订阅、消息队列等功能;
  • ④简单稳定---源码很少 + 单线程模型 + 无需依赖操作系统中的类库;
  • ⑤客户端语言多---简单的TCP通信协议 + Java、php、Python、Ruby、Lua、Node.js ;
  • ⑥持久化---RDB + AOF;
  • ⑦主从复制---主服务器(master)执行添加、修改、删除,从服务器执行查询;
  • ⑧高可用和分布式---Redis-Sentinel(v2.8)支持高可用 + Redis-Cluster(v3.0)支持分布式。

Redis基本使用场景为:

①缓存;②排行榜系统;③计数器系统;④社交网络;⑤消息队列系统。

Redis不可以做的事:

  •   数据规模角度看,Redis不支持大规模数据(每天有几亿用户行为),只支持小规模数据;
  •   数据冷热角度看,Redis支持热数据,例如对视频网站而言,视频信息属于热数据,用户观看记录为冷数据。

1.单线程的Redis为什么快?

Redis之所以被认为是快速的,主要有以下几个原因:

  1. 内存存储:Redis将数据存储在内存中,而不是磁盘。由于内存的读写速度比磁盘快得多,因此Redis能够提供非常快的读写性能。此外,Redis还使用了一些优化技术,如跳表(Skip List)和哈希表(Hash Table),以在内存中高效存储和检索数据。
  2. 单线程模型:Redis采用单线程的事件驱动模型,所有的请求按顺序执行,不需要处理多线程之间的竞争和同步问题。这样可以避免了多线程带来的上下文切换和锁冲突的开销,使得Redis的性能更高。此外,单线程模型还使得Redis的代码实现更加简单,易于维护和调试。
  3. 非阻塞IO:Redis使用了非阻塞IO多路复用机制,例如使用epoll或kqueue等。这使得Redis能够处理大量的并发连接,并在单个线程中高效地处理请求和响应。非阻塞IO避免了传统的阻塞式IO在等待IO操作完成时线程被阻塞的问题,提高了系统的并发能力和响应速度。
  4. 高效的网络协议:Redis使用自己定义的高效网络协议,协议简单且具有较小的数据传输量。这使得Redis在网络传输过程中的开销较小,减少了网络传输的延迟。

需要注意的是,Redis的单线程模型在某些情况下可能会受到性能瓶颈的影响。特别是当遇到大量计算密集型的操作或者某些操作需要较长时间完成时,会影响其他请求的响应时间。因此,在使用Redis时需要注意合理设计数据模型、避免长时间阻塞操作,以充分发挥Redis的性能优势。

2.Redis支持的数据类型(必考)

  • String字符串:字符串类型是 Redis 最基础的数据结构,首先键都是字符串类型,而且其他几种数据结构都是在字符串类型基础上构建的,我们常使用的 set key value 命令就是字符串。常用在缓存、计数、共享Session、限速等。
  • Hash哈希:在Redis中,哈希类型是指键值本身又是一个键值对结构,哈希可以用来存放用户信息,比如实现购物车
  • List列表(双向链表):列表(list)类型是用来存储多个有序的字符串。可以做简单的消息队列的功能
  • Set集合:集合(set)类型也是用来保存多个的字符串元素,但和列表类型不一 样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素利用 Set 的交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。
  • Sorted Set有序集合(跳表实现):Sorted Set 多了一个权重参数 Score,集合中的元素能够按 Score 进行排列可以做排行榜应用,取 TOP N 操作。

3.Redis和memcached的区别

Redis和Memcached是两种常见的内存缓存系统,它们有以下几个主要区别:

  1. 数据结构和功能:Redis支持更多的数据结构,如字符串、哈希、列表、集合、有序集合等,并提供了更丰富的功能,如发布/订阅、事务支持、持久化、复制和集群等。而Memcached仅支持键值对的存储,并提供了基本的缓存功能。
  2. 复杂性和灵活性:由于支持更多的数据结构和功能,Redis在使用上更为灵活,可以满足更多复杂的缓存需求。但也正因为如此,Redis相对于Memcached更加复杂,对于简单的键值缓存需求,Memcached可能更加轻量和易用。
  3. 数据持久化:Redis支持数据的持久化,可以将内存中的数据保存到磁盘上,以便在重启后恢复数据。它提供了两种持久化方式:快照(snapshotting)和AOF(Append-Only File)。而Memcached不支持数据持久化,数据仅存在于内存中,重启后数据将丢失。
  4. 内存管理:Redis采用了自己的内存管理机制,可以通过配置设置最大使用内存的大小,并提供了不同的淘汰策略来管理内存。而Memcached则是通过预先分配一块内存来存储数据,无法动态调整内存大小。
  5. 多线程支持:Redis在最新的版本中引入了多线程(Redis 6.0及以上版本),允许同时处理多个客户端请求,提高并发性能。而Memcached一直采用多线程模型,在处理并发请求时性能相对较高。

总的来说,Redis相对于Memcached更为功能丰富和灵活,适用于更广泛的应用场景,尤其在需要更复杂数据结构和功能的情况下。而Memcached则更加轻量和简单,适用于简单的键值缓存需求。选择使用哪个缓存系统应该根据具体的需求和应用场景来决定。

4.Redis在项目中的主要作用是是什么?怎么用的?(必考)

Redis在项目中的主要作用可以归纳为以下几个方面:

  1. 缓存Redis被广泛用作缓存系统,用于存储热门或频繁访问的数据,以提高系统的性能和响应速度。通过将数据存储在Redis内存中,可以大幅减少对后端数据库的访问压力,加快数据读取速度,并提供低延迟的响应。
  2. 分布式锁:Redis提供了分布式锁的实现方式,可以保证在分布式系统中对共享资源的并发访问控制。通过Redis的原子性操作和锁机制,可以实现线程安全的分布式锁,避免并发冲突和数据不一致问题
  3. 计数器和排行榜:Redis的原子性操作和高性能使其成为计数器和排行榜的理想选择。可以使用Redis的自增操作来实现实时计数器,如网站的访问次数统计。同时,Redis的有序集合(Sorted Set)数据结构可以用于实现排行榜功能,如热门文章、用户积分排名等。
  4. 发布/订阅Redis支持发布/订阅模式,可以实现消息的发布和订阅功能。这在实时通知、实时数据更新和消息队列等场景中非常有用,例如实时聊天应用、实时数据更新推送等。
  5. 地理位置和地理搜索:Redis提供了地理位置(Geospatial)数据类型和相应的操作,可以存储和处理地理位置信息。这在地理位置相关的应用中,如附近的人、位置服务等,提供了方便和高效的解决方案。

对于如何使用Redis,主要涉及以下几个方面:

  1. 连接和配置:需要在应用程序中使用Redis客户端库连接到Redis服务器,并进行相应的配置,如设置连接参数、认证、连接池等。
  2. 数据结构和操作:Redis提供了多种数据结构,如字符串(String)、哈希(Hash)、列表(List)、集合(Set)、有序集合(Sorted Set)等。根据具体的应用场景,选择合适的数据结构并使用相应的操作命令来存储和获取数据。
  3. 持久化和备份:根据需要,可以配置Redis进行数据持久化,以确保数据在重启或故障恢复后不丢失。Redis提供了两种持久化方式:RDB(快照)和AOF(日志),可以根据实际需求选择合适的方式。
  4. 安全和性能优化:需要考虑Redis的安全性,如访问控制、认证和数据加密等。同时,可以根据实际情况对Redis进行性能优化,包括使用合适的数据结构、合理设置过期时间、使用管道(Pipeline)操作、批量操作等方式来提高Redis的性能和吞吐量。

Redis的应用场景非常广泛,包括但不限于以下情况:

  1. 数据库缓存:将频繁访问的数据缓存到Redis中,减轻数据库的压力,提高系统的读取性能和响应速度。
  2. 会话缓存:用于存储用户会话信息,可以快速存取和更新会话状态,提高网站或应用的并发性能和可扩展性。
  3. 计数器和限流器:使用Redis的原子性操作和计数功能,实现实时计数器、访问频率限制等功能,如网站的PV/UV统计、API请求限流等。
  4. 消息队列:利用Redis的发布/订阅功能或列表数据结构,实现轻量级的消息队列,用于异步消息处理、任务调度等场景。
  5. 分布式锁:基于Redis的原子性操作和锁机制,实现分布式环境下的并发访问控制,保证共享资源的安全性和一致性。
  6. 实时排行榜:使用Redis的有序集合数据结构,存储和更新用户的分数或指标,实现实时排名、热门文章、热门商品等功能。
  7. 地理位置服务:利用Redis的地理位置数据类型和操作,存储和查询地理位置信息,用于附近的人、地点搜索等应用。

总而言之,Redis在项目中的主要作用是提供高性能的缓存、分布式锁、计数器、消息队列和排行榜等功能,以提升系统的性能、可扩展性和实时性。使用Redis需要根据具体的应用场景选择合适的数据结构和操作方式,并进行适当的配置和优化,以满足项目的需求。

5.Redis实现分布式锁的思路方案(必考)

Redis可以通过以下思路和方案实现分布式锁:

SETNX命令实现

  • 获取锁:使用SETNX命令在Redis中创建一个键作为锁,如果设置成功(返回1),表示获取到了锁。
  • 释放锁:使用DEL命令删除锁键,释放锁。

这种方式简单直接,但存在死锁和锁竞争问题。

SET命令带参数实现

  • 获取锁:使用SET命令设置锁键,并设置过期时间和参数,例如:SET lock_key value EX NX PX 10000。通过设置参数实现原子性操作和过期时间的设置。
  • 释放锁:使用DEL命令删除锁键,释放锁。

这种方式在获取锁时可以一次性完成设置过期时间和获取锁的操作,相对于SETNX命令更加安全。

Lua脚本实现

  • 获取锁:使用Lua脚本执行以下操作:

    检查锁是否存在,如果不存在,则创建锁并设置过期时间。

    如果锁已存在,则判断锁的持有者是否是当前请求的标识符,如果是,则更新锁的过期时间。

    如果锁已存在且持有者不是当前请求的标识符,则表示锁被其他进程持有。

  • 释放锁:使用Lua脚本执行以下操作:检查锁是否存在,如果存在且持有者是当前请求的标识符,则删除锁。

这种方式通过Lua脚本的原子性操作可以确保获取锁和释放锁的过程的原子性,避免了竞态条件和不一致性。

需要注意的是,以上方案仍然需要处理锁的超时和异常情况,如锁的续期、锁的释放、防止锁的误删除等。可以使用定时任务或者心跳机制来续期锁的过期时间,保证锁的可靠性。同时,在锁的释放时,需要确保只有持有锁的进程才能释放锁,避免误删其他进程的锁。

此外,还可以考虑使用Redlock算法、基于ZooKeeper的分布式锁等其他方案,根据具体的应用场景和需求选择适合的分布式锁实现方案。

分布式锁的实现方式可见:

Redis一些基本问题整理一个简单的Redis实现分布式锁基础方法:如果为空就插入值,返回true;如果不为空则不做操作,返回false,处理思路如下图:

//插入新值并返回旧值
getAndSet(String key, String value)

Redis一些基本问题整理 (https://mushiming.com/) 技术博客 第1张

6.分析基于Redis的限流器实现

基于Redis的限流器可以用来限制系统在一定时间窗口内的请求频率,以防止系统被过多的请求压力压垮。下面是一种基于Redis的令牌桶算法的限流器实现:

  1. 为每个接口或者请求设置一个唯一的标识符作为限流器的键名,例如接口的URL或者请求的IP地址。
  2. 定义一个固定大小的令牌桶,用来存放令牌,每个令牌代表一个允许通过的请求
  3. 使用Redis的计数器数据结构(例如Redis的INCR命令)记录当前令牌桶中的令牌数量
  4. 设置一个固定的时间窗口,例如每秒钟处理的请求数。
  5. 在处理请求之前,首先检查令牌桶中的令牌数量是否超过设定的阈值
    • 如果令牌数量大于阈值,则表示仍有令牌可用,允许请求通过,并将令牌数量减1。
    • 如果令牌数量小于等于阈值,则表示令牌已经用尽,不允许请求通过,可以返回一个限流提示或者拒绝请求。
  6. 使用Redis的过期时间设置令牌桶的自动重置
    • 在每个时间窗口的开始时,将令牌桶的令牌数量重置为最大值,以便接受新的请求。
    • 可以使用Redis的EXPIRE命令设置令牌桶的过期时间,以确保在时间窗口结束后自动清空令牌桶。

这种基于Redis的令牌桶算法的限流器可以有效地控制请求的频率,并保护系统免受过多的请求负荷。同时,由于Redis的高性能和原子性操作的特点,可以保证限流操作的效率和准确性。

需要注意的是,令牌桶算法的具体实现可以根据实际需求进行调整,如令牌的产生速率、令牌桶的大小等参数可以根据系统的负载情况进行优化。

7.Redis和DB数据一致性处理(必考)

确保Redis和数据库之间的数据一致性是一个重要的问题。由于Redis是内存中的缓存,而数据库是数据的持久化存储,两者之间存在数据不一致的风险。下面是一些处理数据一致性的常见方法:

  1. 读写双写(Read-Write Through)
    在更新数据库之前,首先更新Redis缓存。这样可以确保Redis中的数据与数据库保持同步。在读取数据时,首先从Redis中获取,如果缓存不存在,则从数据库中读取,并将读取到的数据放入Redis缓存中。
    这种方法简单直接,但在写入操作上会存在一定的性能开销,因为每次更新操作都需要同时更新Redis和数据库。
  2. 写后更新(Write-Behind)
    在更新数据库后,异步更新Redis缓存。即使Redis缓存的更新稍有延迟,但在大多数情况下不会造成严重的数据不一致。在读取数据时,先从Redis中获取,如果缓存不存在,则从数据库中读取。
    这种方法在写入操作上性能较高,因为只需更新数据库。但在读取数据时,如果缓存未及时更新,可能会导致读取到旧数据。
  3. 定时刷新(Scheduled Refresh)
    定期刷新Redis缓存,以确保与数据库中的数据保持一致。定时任务可以在后台更新Redis缓存,例如每隔一段时间或每天执行一次全量或增量更新操作。
    这种方法可以减少对数据库的读取压力,并在更新时提供一定的数据一致性保证。但定时刷新可能会造成缓存与数据库之间的数据延迟。
  4. 发布/订阅(Publish/Subscribe)
    使用Redis的发布/订阅功能,订阅数据库中的更新事件,并在更新发生时通知Redis缓存进行相应的更新。
    这种方法可以实现实时的数据同步,但需要在应用程序中处理订阅和更新的逻辑,并增加了复杂性。

需要根据具体的业务需求和性能要求选择适合的数据一致性处理方法。在某些情况下,可以结合多种方法来实现更好的数据一致性和性能。此外,还可以使用事务(Transaction)来确保更新操作的原子性,并在数据一致性方面提供更强的保证。

8.Redis的数据过期策略分析(必考)

Redis提供了多种数据过期策略,可以根据不同的业务需求选择合适的策略。以下是对Redis的数据过期策略的分析:

  1. 定时过期(TTL):Redis中最常用的过期策略是给键设置一个过期时间(Time to Live,TTL),在到达过期时间后自动删除键。这种策略适用于那些在指定时间后不再需要的数据。TTL可以为键提供自动失效的功能,减少了手动删除键的工作量。
  2. 惰性过期(Lazy Expiration):Redis还采用了惰性过期策略。它不会主动检查键的过期时间,而是在访问键时才会进行检查。如果键已过期,则在访问时将其删除。这种策略能够节省CPU资源,因为只有在需要访问键时才会进行过期检查。但是,如果长时间未访问某个键,它可能会一直留在内存中,占用空间。
  3. LRU过期策略(Least Recently Used):LRU过期策略是通过跟踪键的访问时间来确定哪些键应该被淘汰。当内存空间不足时,Redis会优先淘汰最久未被访问的键。这种策略适用于那些经常被访问的键需要保留在内存中,而不经常被访问的键可以被淘汰。
  4. 具体过期时间(Expires):Redis还提供了通过具体过期时间来设置键的过期策略。可以使用EXPIREAT命令设置键的到期时间戳,或使用EXPIRE命令设置相对于当前时间的过期时间。这种策略适用于需要在特定的时间点过期的数据。

需要根据具体的业务需求和数据访问模式来选择适合的过期策略。有时也可以结合多种过期策略,根据不同类型的数据选择不同的过期方式,以达到更好的性能和空间利用率。

9.Redis的LRU过期策略的具体实现(必考)

Redis中的LRU(Least Recently Used,最近最少使用)过期策略是通过跟踪键的访问时间来确定哪些键应该被淘汰。下面是Redis中LRU过期策略的具体实现步骤:

  1. 每个键都有一个字段来记录其最后一次访问的时间戳(timestamp)。
  2. 当一个键被访问时,Redis会更新该键的时间戳为当前时间。
  3. 当Redis需要淘汰键时,它会选择最久未被访问的键作为候选。
  4. Redis会比较候选键的时间戳与当前时间的差值,选择最久未被访问的键进行淘汰。
  5. 如果有多个键的时间戳相同,Redis会根据一个先进先出(FIFO)的规则进行选择。

需要注意的是,Redis并不是每次访问键都会立即更新时间戳。为了提高性能,Redis会通过一定的策略来控制时间戳的更新频率。例如,可以设置一个定时任务来定期更新键的时间戳,或者在内存使用达到一定阈值时进行更新。

另外,Redis还提供了其他过期策略,如定时过期和惰性过期。定时过期是通过设置键的过期时间,在到达过期时间后将键删除。惰性过期是在访问键时检查键是否过期,如果过期则删除键。这些过期策略可以根据具体的业务需求进行选择和配置。

10.分析Redis缓存雪崩问题以及解决方案(必考)

Redis缓存雪崩问题是指在缓存中大量的键同时过期或失效,导致大量请求直接落到后端数据库上,引起数据库负载剧增,甚至导致数据库崩溃的情况。

下面是对Redis缓存雪崩问题的分析以及常见的解决方案:

问题分析:

  1. 缓存过期时间一致:如果大量的缓存键设置了相同的过期时间,那么它们很可能在同一时间段内同时过期,从而引发雪崩效应。
  2. 缓存同时失效:当Redis服务器重启或发生故障时,所有的缓存数据会同时失效,导致请求全部落到后端系统上,造成压力过大。

解决方案:

  1. 设置随机的过期时间:可以通过在缓存键的过期时间上添加一个随机值来避免大量缓存同时过期。例如,可以在原有的过期时间基础上增加一个随机的秒数或毫秒数,使得过期时间分散在不同的时间段内。
  2. 使用平滑过期:可以通过在缓存键的过期时间上添加一个较短的随机值,并使用定期刷新的方式保持缓存的有效性。例如,可以在缓存过期时间快到达时,异步刷新缓存,避免大量缓存同时失效。
  3. 搭建多级缓存:引入多级缓存架构,将缓存分为不同层次,例如本地缓存、分布式缓存(如Redis),甚至CDN等。这样即使某一级缓存发生失效或雪崩,仍然可以从其他层级的缓存中获取数据,减轻数据库的负载。
  4. 实时监控和报警:通过监控Redis缓存的命中率、过期情况等指标,及时发现异常情况,并设置报警机制,以便及时采取措施应对。

综上所述,通过合理设置过期时间、引入缓存多级架构以及实时监控和报警等措施,可以有效地解决Redis缓存雪崩问题,提高系统的可用性和稳定性。

11.分析Redis缓存穿透问题和解决方案(必考)

Redis缓存穿透问题是指恶意或异常的请求通过缓存层直接访问后端系统,绕过缓存,导致大量请求直接落到后端数据库上,造成数据库负载过大甚至崩溃的情况。

下面是对Redis缓存穿透问题的分析以及常见的解决方案:

问题分析:

  1. 不存在的键请求:当恶意请求或异常请求访问一个不存在的键时,由于缓存中无法命中,每个请求都会直接落到后端系统上,导致性能下降。
  2. 高基数查询:当查询条件的基数非常高(如大量不同的用户ID),而缓存中没有相关数据时,每个请求都会落到后端系统上执行,引发性能问题。

解决方案:

  1. 布隆过滤器(Bloom Filter):使用布隆过滤器可以在缓存层进行快速的键存在性检查。布隆过滤器是一种概率型数据结构,可以判断一个键是否可能存在于缓存中,可以有效过滤掉一部分不存在的键请求,从而减轻后端负载。如果布隆过滤器判断键可能存在,再去查询实际的缓存或后端系统。(针对不存在的键)
  2. 缓存空值(Cache Null Values):对于查询结果为空的请求,可以将空值也缓存起来,即将键设置为对应的空值(如NULL),并设置适当的过期时间。这样当相同的请求再次到达时,可以直接从缓存中获取空值,避免对后端系统的重复查询。(针对不存在的键)
  3. 数据预加载(Cache Preloading):在系统启动时,可以预先加载热门或常用的数据到缓存中,以减少对后端系统的直接查询。通过数据预加载,可以提前将数据加载到缓存中,避免因为首次查询导致缓存穿透问题。(针对高基数查询)
  4. 限流和防护措施:在缓存层和后端系统之间设置合适的限流和防护措施,如请求频率控制、IP黑名单、访问频次限制等,以过滤掉恶意或异常请求。(针对高基数查询)

综上所述,通过使用布隆过滤器、缓存空值、数据预加载和设置合适的限流和防护措施等解决方案,可以有效地应对Redis缓存穿透问题,保护后端系统免受过多无效请求的影响,提高系统的稳定性和性能。

12.Redis的持久化机制分析(必考)

Redis提供了两种主要的持久化机制,分别是RDB(Redis Database)和AOF(Append-Only File)。

RDB(Redis Database)持久化

RDB持久化是将Redis的内存数据以快照的方式写入磁盘文件。该机制会在指定的时间间隔或者达到一定的数据变化量时,将当前数据库的数据集合保存到磁盘上的一个二进制文件中。RDB持久化适用于数据备份和恢复,以及冷启动时快速加载数据。
优点:

  • RDB持久化对于数据的备份和恢复速度非常快,适用于大规模数据集的恢复。
  • 生成的RDB文件紧凑,可以节约磁盘空间。

缺点:

  • RDB持久化会在指定的时间间隔或者数据变化量达到一定程度时进行数据写入,因此在持久化间隔期间发生故障时,可能会丢失最近的数据。
  • 由于RDB文件是快照方式保存数据,恢复大规模的RDB文件可能会需要较长的时间和较大的内存。

AOF(Append-Only File)持久化

AOF持久化是将Redis的操作命令追加到一个只追加文件中。通过记录所有的写操作命令,AOF文件可以重建整个数据集。AOF持久化适用于数据的持久性和故障恢复。
优点:

  • AOF持久化可以提供更高的数据安全性,因为它记录了每个写操作命令,可以更好地保护数据的完整性。
  • AOF文件可以按照一定的策略进行重写,可以压缩文件大小,节约磁盘空间。

缺点:

  • AOF持久化相对于RDB持久化来说,对于数据的备份和恢复速度较慢。
  • AOF文件的大小通常会比RDB文件大,因为它记录了每个写操作命令。

在Redis中,可以同时开启RDB和AOF持久化,这样可以在发生故障时,先使用AOF文件进行数据恢复,再使用RDB文件进行快速加载。此外,还可以根据实际需求进行持久化的配置,如设置自动触发持久化的条件、定期执行持久化操作、设置AOF重写的策略等。

需要根据业务需求和对数据安全性、恢复速度以及磁盘空间的要求来选择适合的持久化机制或它们的组合方式。

13.分析下Redis的管道pipeline

Redis的管道(Pipeline)是一种用于提高命令执行效率的技术。通过将多个命令打包成一个批量操作一次性发送给Redis服务器,可以减少客户端和服务器之间的通信开销,从而提高性能。以下是对Redis管道的分析:

  1. 减少网络开销:在普通的Redis操作中,每个命令都需要通过网络独立发送给服务器,并等待服务器的响应。这会产生额外的网络开销。而使用管道技术,多个命令可以打包在一起发送,减少了网络通信的次数,从而降低了网络开销。
  2. 提高吞吐量:由于管道可以一次性发送多个命令,Redis服务器可以在收到所有命令后一次性执行它们,而不需要等待每个命令的响应。这样可以显著提高命令的执行效率和吞吐量。
  3. 原子性操作:在管道中的一组命令会作为一个原子操作执行。这意味着在执行管道期间,其他客户端不会插入命令,从而确保了命令的原子性。这在某些需要保持事务性和原子性的场景下非常有用。
  4. 减少客户端与服务器之间的往返时间:管道技术可以将多个命令一次性发送给服务器,然后在服务器端执行,最后将结果一次性返回给客户端。这减少了客户端与服务器之间的往返时间,尤其在网络延迟较高的情况下效果更为明显。

需要注意的是,使用管道并不适用于所有场景。由于管道是批量发送命令,如果其中某个命令执行失败,后续命令可能无法正确执行。因此,在一些需要保证数据一致性和完整性的场景下,可能需要使用事务或其他更强的一致性保证机制。

总而言之,Redis管道是一种有效提高性能和吞吐量的技术,适用于批量命令的情况,可以减少网络开销和往返时间,提高系统的响应速度。

14.Redis集群容错机制是什么样的?(必考)

主要详细内容可见:

Redis一些基本问题整理Redis集群提供了一些容错机制,旨在保障集群的可用性和数据的一致性。下面是Redis集群的主要容错机制:

  1. 数据分片和复制:Redis集群将数据分散存储在多个节点上,采用哈希槽(Hash Slot)的方式将数据划分为不同的槽。每个节点负责处理一部分哈希槽的数据。同时,为了提高数据的冗余性和容错性,每个槽通常会有多个副本存储在不同的节点上,以防止节点故障导致数据丢失。
  2. 主从复制:Redis集群中的每个主节点都会有一个或多个从节点。主节点负责处理客户端请求和写操作,而从节点则复制主节点的数据,并处理只读请求。当主节点发生故障时,从节点可以自动切换为主节点,保证集群的可用性。
  3. 故障检测和自动故障转移:Redis集群会定期进行故障检测,监测节点的健康状态。如果主节点发生故障或无法正常访问,集群会自动执行故障转移操作,将一个从节点晋升为新的主节点,并进行相应的重新分配哈希槽。
  4. Gossip协议:Redis集群使用Gossip协议进行节点间的信息交换和状态同步。节点通过互相交换信息,了解其他节点的状态和拓扑信息,以便做出合适的决策和调整。
  5. 客户端路由和重定向:Redis集群使用客户端分区(Client Sharding)来将不同的命令路由到正确的节点。当客户端请求到达一个错误的节点时,节点会发送重定向响应,告知客户端正确的节点地址,使客户端能够重新发送请求。

需要注意的是,Redis集群的容错机制是自动化的,可以在节点故障或网络分区等情况下自动进行故障转移和数据重分配,从而保证集群的可用性和数据一致性。然而,集群的配置、监控和维护仍然需要管理员的关注和管理,以确保集群的稳定运行。

15.Redis集群中某个master节点挂了,后面流程怎么判断?(必考)

更为具体的见:Redis一些基本问题整理

可以从以下三个方面分析:

redis集群如何判断一个主节点挂了:

  • 集群中的每个节点都会顶起地向集群中的其他节点发送PING消息
  • 默认每隔1s中发送一次PING
  •  默认随机选择5个集群中的其他主节点,规则为最长时间未PING以及PONG时间超过timeout/2
  • 如果发送PING的主节点1,在timeout时间内没收到主节点2的PONG消息,那么主节点1会将主节点2的状态标记为pfail
  • 主节点1在后续发送PING消息时,会带上主节点2的pfail状态,收到这个消息的主节点会在clusterNode里下线报告fail_reports中将主节点2标记为pfail
  • 当某个主节点被标记为pfail的个数大于集群总主节点个数的一半时,会被标记为fail, 并向整个集群广播一条PONG消息,说明该主节点下线。
  • 其他主节点收到广播消息后,将该主节点状态标记为fail,
  • 集群进入故障转移阶段

故障转移阶段流程:

  • 当一个从节点发现自己正在复制的主节点进入已下线状态时,从节点将开始对下线主节点进行故障转移
  • 复制下线主节点的所有从节点里面,会有一个从节点被选为新的主节点
  • 新的主节点从撤销所有已下线主节点的槽位指派,并将这些槽位指给自己
  • 新的主节点会向集群广播一条PONG消息,告知其他节点自己已由从节点转为主节点,并接管相应的槽位
  • 故障转移完成

选举新的主节点

  • 集群的纪元时一个自增的计数器,初始值为0
  • 当集群里的某个节点开始一次故障转移操作时,集群的纪元会加一
  • 对于每个纪元,集群里每个复制处理槽位的主节点都有一次投票机会,而第一次向主节点要求投票的从节点将获得主节点的投票
  • 什么时候开始投票? 当从节点发现自己正在复制的主节点进入已下线状态,从节点会向集群广播一条CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST消息,收到该消息的主节点可以开始投票
  • 主节点投票后返回CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息
  • 当某个从节点的票数大于投票总节点数的一半时,被选为新的主节点
  • 若每个获得足够的票数,进入下一轮投票

16.高并发情况下,对于Redis的更新操作有哪些注意事项?

在高并发情况下,对于Redis的更新操作,有以下几个注意事项:

  1. 使用乐观锁或分布式锁:在高并发环境下,多个客户端可能同时对同一个键进行更新操作,为了保证数据的一致性,可以使用乐观锁或者分布式锁来控制并发访问。乐观锁通过在更新操作前检查版本号或时间戳等标识来判断是否可以进行更新,而分布式锁可以在更新操作期间保证只有一个客户端能够获取到锁执行更新。
  2. 考虑并发冲突和竞争条件:在高并发环境下,多个客户端同时对同一个键进行更新操作可能会引发并发冲突和竞争条件。需要仔细分析业务场景和数据访问模式,设计合理的并发控制策略,例如使用CAS(Compare and Swap)操作、乐观锁或分布式锁等。
  3. 使用批量操作:在更新操作需要批量处理多个键时,可以使用Redis提供的批量操作命令(如MSET、HMSET等)来减少与服务器之间的通信开销。通过将多个更新操作打包成一个批量操作一次性发送给Redis服务器,可以减少网络开销和提高性能。
  4. 合理设置超时时间和重试机制:在高并发环境下,Redis服务器可能会面临请求过载或网络延迟等情况。为了保证操作的可靠性,可以设置合理的超时时间,并根据业务需要设计适当的重试机制,以处理因请求超时或失败而导致的异常情况。
  5. 使用Redis事务:Redis事务提供了一种将多个命令打包成一个原子操作的机制。在更新操作涉及多个键或需要保证原子性的情况下,可以使用Redis事务来执行这些操作,从而避免并发冲突和数据不一致的问题。
  6. 监控和调优:在高并发情况下,对Redis的更新操作进行监控和调优是非常重要的。可以使用Redis的监控工具、性能分析工具或者第三方监控工具来监控关键指标,如QPS(每秒查询数)、响应时间等,并进行性能调优,例如优化命令的执行顺序、合理设置连接池大小、调整并发线程数等。

综上所述,对于Redis的更新操作,在高并发环境下需要注意并发冲突和竞争条件、使用乐观锁或分布式锁、考虑批量操作、设置合理的超时时间和重试机制、使用Redis事务、进行监控和调优等措施,以确保更新操作的并发安全性、可靠性和性能优化。根据具体的业务需求和场景,可能还需要进一步考虑其他相关的注意事项和策略。

17.高并发下,先更新数据库,再删除缓存,存在啥问题,如何解决呢?

在高并发环境下,先更新数据库再删除缓存可能会存在以下问题:

  1. 数据不一致性:如果先更新了数据库而后删除缓存,那么在这两个操作之间可能会有其他并发请求读取到旧的缓存数据,导致数据不一致的问题。
  2. 缓存脏读:如果在更新数据库后立即删除缓存,而后续的并发请求很快又读取了这个缓存数据,那么它们可能会读取到已经删除的缓存,从而导致缓存脏读的问题。

为了解决这些问题,可以采用以下几种策略:

  1. 先删除缓存再更新数据库:在进行数据更新操作之前,先删除相关的缓存,然后再执行数据库更新操作。这样可以保证在数据库更新完成之前,不会有其他请求读取到旧的缓存数据,从而避免了数据不一致性和缓存脏读的问题。
  2. 引入锁机制:在高并发情况下,可以使用锁机制来控制并发操作的顺序。例如,在更新数据库和删除缓存的操作之间引入一个全局锁或分布式锁,确保这两个操作的原子性。先获取锁,然后按顺序执行更新数据库和删除缓存的操作,最后释放锁。这样可以避免并发操作导致的数据不一致性和缓存脏读问题。
  3. 使用事务:如果数据库支持事务,可以将更新数据库和删除缓存的操作放在一个事务中。在事务提交时,先更新数据库,然后再删除缓存。使用事务可以确保这两个操作的原子性,保证数据的一致性。
  4. 使用缓存失效策略:在更新数据库之后,可以采用缓存失效策略,即在缓存的过期时间内不再访问数据库,而是直接返回旧的缓存数据。这样可以避免并发请求读取到旧的缓存数据,直到缓存过期后再重新从数据库加载最新数据。

综上所述,为了解决先更新数据库再删除缓存可能导致的数据不一致性和缓存脏读问题,可以先删除缓存再更新数据库、引入锁机制、使用事务或采用缓存失效策略等措施。具体选择哪种策略取决于业务需求和系统架构的要求。

18.高并发情况下,先删除缓存,再更新数据库,这样会有啥问题,解决方案是?

在高并发环境下,先删除缓存再更新数据库可能会引起以下问题:

  1. 缓存击穿:如果某个缓存失效时,有大量的并发请求同时访问数据库来获取数据,这会导致数据库负载过高,影响系统的稳定性和性能。
  2. 数据不一致性:在删除缓存后,如果有并发请求立即访问数据库来获取数据,此时数据库可能还未完成更新操作,导致并发请求读取到旧的数据,产生数据不一致性的问题。

为了解决这些问题,可以采用以下策略:

  1. 设置短暂的缓存过期时间:在删除缓存后,为了避免缓存击穿,可以设置一个较短的缓存过期时间(比如几秒钟),让后续的并发请求在这个时间段内访问缓存而不是直接访问数据库。这样可以有效降低数据库负载。
  2. 使用互斥锁(Mutex Lock):在删除缓存和更新数据库的操作之间引入互斥锁机制,保证同一时间只有一个线程可以进行这两个操作。先获取互斥锁,然后按顺序执行删除缓存和更新数据库的操作,最后释放锁。这样可以避免并发问题导致的数据不一致性。
  3. 使用消息队列:可以将删除缓存和更新数据库的操作放入消息队列中,让消费者逐个处理这些操作。这样可以保证在更新数据库之前先删除缓存,并且可以控制并发量和顺序,减轻数据库负载。
  4. 延迟双删(Lazy Double-Check):在删除缓存后,先让后续请求等待一段时间,然后再进行数据库更新操作。这样可以留出一段时间供缓存被重建,如果有其他请求访问同一数据,则在等待期间直接从缓存中获取数据,避免了直接访问数据库。

总之,在高并发环境下,先删除缓存再更新数据库时,可以采用设置短暂的缓存过期时间、使用互斥锁、使用消息队列或采用延迟双删等策略来解决缓存击穿和数据不一致性的问题。具体的解决方案应根据系统的需求和架构进行选择和优化。

19.zset跳表的数据结构分析(必考)

在Redis中,有序集合(Sorted Set)的实现基于一种称为跳表(Skip List)的数据结构。跳表是一种平衡的数据结构,可以快速地插入、删除和查找有序数据。

跳表的基本思想是通过在数据层之上建立多层索引来加速查找操作每一层都是一个有序链表,最底层包含所有的数据,而上层则包含一小部分数据的引用。每一层的节点通过指针连接,形成一个向右和向下的结构。顶层的链表只有两个节点,分别指向最小值和最大值,作为边界节点。

通过使用多层索引,跳表可以在平均情况下实现对数时间复杂度的插入、删除和查找操作。当需要查找或操作一个元素时,可以从顶层链表开始逐层向右移动,直到找到对应元素或者找到一个小于目标元素的节点。然后,在下一层链表中继续向右移动,直到达到目标元素或者下一个大于目标元素的节点。这样就能够快速定位到目标元素所在的位置。

在Redis中,有序集合使用跳表来存储元素,并通过一个字典结构来维护元素和对应的分值之间的映射关系。跳表允许元素的分值可以重复,但是元素本身必须是唯一的。

跳表的优点是简单、易于实现,并且在某些情况下比平衡二叉树的性能更好。它不需要像平衡二叉树那样频繁地进行平衡操作,因此对于读多写少的场景,跳表是一种高效的数据结构选择。但是相对于红黑树等其他平衡树结构,跳表的实现稍微复杂一些。

需要注意的是,Redis的跳表实现是为了在有序集合中提供高效的操作,并不是通用的跳表实现。因此,在其他应用场景下,可能需要根据具体需求进行跳表的实现和优化。

20.展开说说你了解的跳表

如何理解跳表? 

对于单链表来说,我们查找某个数据,只能从头到尾遍历链表,此时时间复杂度是 ○(n)。

Redis一些基本问题整理 (https://mushiming.com/) 技术博客 第2张

提高单链表的查找效率呢?对链表建立一级索引,每两个节点提取一个结点到上一级,被抽出来的这级叫做索引或索引层。 所以要找到13,就不需要将16前的结点全遍历一遍,只需要遍历索引,找到13,然后发现下一个结点是17,那么16一定是在 [13,17] 之间的,此时在13位置下降到原始链表层,找到16,加上一层索引后,查找一个结点需要遍历的结点个数减少了,也就是说查找效率提高了。

Redis一些基本问题整理 (https://mushiming.com/) 技术博客 第3张

建立一级索引的方式相似,我们在第一级索引的基础上,每两个结点就抽出一个结点到第二级索引。此时再查找16,只需要遍历 6 个结点了,需要遍历的结点数量又减少了。 

Redis一些基本问题整理 (https://mushiming.com/) 技术博客 第4张

当结点数量多的时候,这种添加索引的方式,会使查询效率提高的非常明显,这种链表加多级索引的结构,就是跳表。

Redis一些基本问题整理 (https://mushiming.com/) 技术博客 第5张

用跳表查询到底有多快 

在一个单链表中,查询某个数据的时间复杂度是 ○(n),那在一个具有多级索引的跳表中,查询某个数据的时间复杂度就是 ○(㏒n) 。

Redis一些基本问题整理 (https://mushiming.com/) 技术博客 第6张

根据上图得知,每级遍历 3 个结点即可,而跳表的高度为 h ,所以每次查找一个结点时,需要遍历的结点数为 3*跳表高度 ,所以忽略低阶项和系数后的时间复杂度就是 ○(㏒n) 。

跳表是不是很浪费内存? 

来分析一下跳表的空间复杂度为O(n)。实际上,在实际开发中,我们不需要太在意索引占据的额外空间,在学习数据结构与算法时,我们习惯的将待处理数据看成整数,但是实际开发中,原始链表中存储的很可能是很大的对象,而索引结点只需要存储关键值(用来比较的值)和几个指针(找到下级索引的指针),并不需要存储原始链表中完整的对象所以当对象比索引结点大很多时,那索引占用的额外空间就可以忽略了。 

高效的动态插入和删除 

跳表这个动态数据结构,不仅支持查找操作,还支持动态的插入、删除操作,而且插入、删除操作的时间复杂度也是 ○(㏒n)。 

对于单纯的单链表,需要遍历每个结点来找到插入的位置。但是对于跳表来说,因为其查找某个结点的时间复杂度是 ○(㏒n),所以这里查找某个数据应该插入的位置,时间复杂度也是 ○(㏒n)。 

跳表索引动态更新 

当我们不停的往跳表中插入数据时,如果我们不更新索引,就可能出现某 2 个索引结点之间数据非常多的情况。极端情况下,跳表会退化成单链表。 

Redis一些基本问题整理 (https://mushiming.com/) 技术博客 第7张

作为一种动态数据结构,我们需要某种手段来维护索引与原始链表大小之间的平滑,也就是说如果链表中结点多了,索引结点就相应地增加一些,避免复杂度退化,以及查找、插入、删除操作性能下降。

跳表是通过随机函数来维护前面提到的平衡性。 

我们往跳表中插入数据的时候,可以选择同时将这个数据插入到第几级索引中,比如随机函数生成了值 K,那我们就将这个结点添加到第一级到第 K 级这 K 级索引中。 随机函数可以保证跳表的索引大小和数据大小的平衡性,不至于性能过度退化。

Redis一些基本问题整理 (https://mushiming.com/) 技术博客 第8张

21.Redis使用跳表不用B+树的原因?

Redis选择使用跳表而不是B+树的原因主要有以下几点:

  1. 简单性和易于实现:跳表相对于B+树来说,实现起来更加简单。它的基本操作包括插入、删除和查找,而B+树涉及到更复杂的平衡操作,如旋转和合并。跳表的实现逻辑相对简单,容易理解和调试。
  2. 跳表适合小规模数据集:Redis通常用于缓存场景,对于相对较小的数据集而言,跳表能够提供较好的性能。相比之下,B+树在大规模数据集的情况下才能体现其优势,而对于小规模数据集,跳表的性能表现更好。
  3. 跳表的查找效率与B+树接近:在平均情况下,跳表的插入、删除和查找操作的时间复杂度为O(log n),与B+树相当。尽管在最坏情况下,跳表的时间复杂度可能达到O(n),但在实际使用中,由于数据分布的随机性,最坏情况出现的概率较低。
  4. 空间占用更小:相比于B+树,跳表的数据结构相对简单,不需要存储额外的指针和元数据信息,因此在存储空间上更加紧凑。

需要注意的是,选择数据结构要根据具体的应用场景和需求来决定。B+树适用于更大规模的数据集和范围查询等场景,而跳表则在小规模数据集和有序集合的操作上更加高效。Redis作为一个高性能的内存数据库和缓存系统,选择跳表作为有序集合的实现,是基于对应用场景和性能需求的综合考虑。

22.跳跃表和B+树结构上有啥区别

跳跃表(Skip List)和B+树是两种不同的数据结构,它们在实现和性能特征上有一些区别。

  1. 结构特点:
    • 跳跃表是一种基于链表的数据结构,通过多层索引来加速查找。每个节点包含一个键值对和指向下一层节点的指针。
    • B+树是一种基于树的数据结构,具有层次结构和有序性。每个节点可以存储多个键值对,并且存在指向子节点的指针。
  2. 插入和删除操作:
    • 跳跃表的插入和删除操作相对简单,只需要在相应的层级上进行插入或删除节点即可。平均情况下,时间复杂度为O(log n)。
    • B+树的插入和删除操作相对复杂,需要进行平衡操作以维护树的平衡性。平均情况下,时间复杂度也为O(log n),但由于树的平衡性能够保持,最坏情况下的时间复杂度更可控。
  3. 查找操作:
    • 跳跃表的查找操作通过多层索引,可以在平均情况下实现对数时间复杂度的查找。但在最坏情况下,时间复杂度为O(n)。
    • B+树的查找操作通过树的层次结构,可以在平均和最坏情况下都实现对数时间复杂度的查找。
  4. 空间占用:
    • 跳跃表相对于B+树来说,在存储上更为紧凑,因为它不需要存储额外的指针和元数据信息。
    • B+树需要存储每个节点的子节点指针和额外的元数据信息,因此在存储空间上相对占用更多。

综上所述,跳跃表适用于较小规模的数据集和简单的有序集合操作,实现简单且性能良好。而B+树适用于更大规模的数据集和范围查询等场景,对于维护有序性和平衡性有更好的支持。选择使用哪种数据结构应该根据具体的应用需求和性能要求进行权衡。

23.Redis的缓存优化方向有哪些?你们怎么理解的?对热点键的注意事项设计什么?

缓存的主要优势和成本:

  • 缓存收益:加速读写、降低后端存储负载;
  • 缓存成本:缓存和存储数据不一致性、代码维护成本、运维成本;

优化方向:

  • Redis 的数据过期策略采用定期删除+惰性删除策略结合起来,以及采用淘汰策略来兜底。
  • 缓存粒度控制:全部属性更好or部分重要属性更好?
  • 缓存穿透优化:缓存空对象 + 布隆过滤器拦截(通过很小的内存来实现对数据的过滤。)
  • 缓存雪崩优化:保证缓存高可用性,例如 Redis Cluster、Redis Sentinel、VIP;依赖隔离组件为后端限流;提前演练,例如压力测试。
  • 无底洞问题优化:命令本身优化,例如慢查询 keys、hgetall bigkey;减少网络通信次数;降低接入成本,例如客户端使用长连接/连接池、NIO 等 。
  • 热点key优化避免 bigkey热键不要用 hash_tag,因为 hash_tag 会落到一个节点上;如果真有热点 key 而且业务对一致性要求不高时,可以用本地缓存 + MQ 解决
  • 热点key重建优化互斥锁(mutex key),查询数据源 -> 重建缓存 这个过程加互斥锁;永不过期缓存层面不设置过期时间(没有用 expire),功能层面为每个 value 添加逻辑过期时间,但发现超过逻辑过期时间后,会使用单独的线程去构建缓存

对热点键的注意事项,如上热点key优化和热点key重建优化。

24.跳表(Skip List)三大问题举例

跳表是一种基于链表的数据结构,它通过增加多级索引来提高查找效率,类似于平衡树的效果。在有序链表中,查找某个元素的时间复杂度是O(n),而在跳表中,可以通过多级索引进行跳跃查找,平均查找时间复杂度为O(log n)。

跳表存在的三个问题

  • a. 空间复杂度:跳表中需要维护多级索引,导致占用更多的内存空间。
  • b. 维护成本:当有新元素插入或删除时,需要更新相应的索引结构,导致维护成本较高。
  • c. 实现复杂度:相对于其他数据结构(如红黑树),跳表的实现比较复杂。

需要注意的是,跳表是Redis中有序集合(Sorted Set)的底层实现之一,Redis使用跳表来实现有序集合数据结构,以提供高效的范围查找操作。跳表在Redis中的应用体现了其在高性能数据库中的价值。

25.Zset底层的数据结构分析,如何体现的高效?(必考)

在Redis中,有序集合(Sorted Set)使用的底层数据结构是跳表(Skip List)和哈希表(Hash Table)的结合。

跳表是一种有序的链表数据结构,通过在每一层链表上建立索引节点,可以在搜索和插入操作中实现较高的效率。跳表允许快速的元素查找,时间复杂度为O(log N),其中N为元素个数。跳表的高效性主要体现在以下几个方面:

  1. 查找效率高:跳表通过建立多级索引,可以快速定位到目标元素,而不需要像普通链表那样逐个遍历。因此,在有序集合中进行元素的查找操作非常高效。
  2. 插入和删除效率高:跳表的插入和删除操作相对简单。由于跳表的结构特性,可以通过调整索引节点的指针来实现元素的插入和删除,而不需要对整个数据结构进行重组。
  3. 空间效率:跳表相对于其他平衡树结构(如红黑树)来说,具有更低的空间占用。跳表的索引层级可以根据需求进行灵活调整,可以在平衡性和空间效率之间进行权衡。
  4. 支持范围查询:跳表可以支持范围查询,例如获取某个范围内的元素或者按排名获取指定排名范围内的元素。这对于有序集合来说非常有用。

总体而言,跳表作为有序集合的底层数据结构,在元素的查找、插入和删除操作上具有较高的效率。它既能保持有序性,又能通过索引层级的建立和调整来提高操作的效率。这使得有序集合在Redis中的使用非常高效和灵活。

26.Redis 6.0以后的线程模型

Redis 6.0及其后续版本引入了多线程模型,以提高Redis在多核系统上的性能。在Redis 6.0之前,Redis采用的是单线程的模型。

Redis 6.0之后的线程模型被称为”Redis主从线程模型”,它基于多线程框架,包括I/O 线程、工作线程和管理线程。

  1. I/O 线程:I/O 线程主要负责处理网络 I/O 操作,包括接收客户端连接、读取和写入数据。它们专注于处理网络相关的操作,可以利用多核处理器的并行能力。
  2. 工作线程:工作线程主要用于处理非阻塞的计算密集型任务,例如对数据库的读写操作和键空间的操作。它们负责执行命令请求、执行 Lua 脚本、持久化操作和复制操作等。
  3. 管理线程:管理线程主要负责协调整个系统的状态和管理,包括处理线程间的同步、处理命令请求的调度和处理等。

这种多线程模型的引入使得Redis能够同时利用多个CPU核心来处理并发请求,从而提高了系统的整体性能。通过将不同类型的任务分配给不同的线程,Redis能够在多核系统上实现更好的负载平衡和并行处理能力。

需要注意的是,Redis的多线程模型是通过多个线程并发地处理请求,但在任何给定的时刻只有一个线程访问数据,从而避免了多线程访问共享数据的并发冲突。这种方式确保了Redis的数据一致性和线程安全性。

27.Redis集群模式,节点怎么通信的?满足了CAP那两点?

在 Redis 集群模式中,节点之间使用节点间通信(Node-to-Node Communication)来进行数据的交互和同步。下面是 Redis 集群模式节点通信的一般原理:

  1. 节点间通信协议:Redis 集群使用了自定义的二进制协议(Redis Cluster Bus Protocol)作为节点间通信的协议。该协议基于 TCP/IP 进行通信,并使用二进制数据格式进行数据传输。
  2. Gossip 协议:Redis 集群使用 Gossip 协议进行节点之间的信息交换和状态同步。每个节点通过周期性地向其他节点发送消息来获取集群中其他节点的信息,如节点的存活状态、拥有的槽位分布等。
  3. 握手过程:当一个新节点加入集群时,它首先需要与集群中的其他节点进行握手。通过 Gossip 协议,新节点可以获取集群的拓扑结构和节点信息,并进行相应的初始化操作。
  4. 数据迁移:当集群中的某个节点失效或者新节点加入时,数据需要进行迁移以保证集群的数据一致性。Redis 集群使用分片的方式将数据划分为多个槽位,每个节点负责处理其中的一部分槽位。数据迁移过程中,源节点将属于目标节点槽位的数据传输给目标节点,以实现数据的平衡和复制。

关于 CAP(一致性、可用性、分区容忍性)原则,Redis 集群满足了其中的两个方面:

  • 分区容忍性(Partition Tolerance):Redis 集群使用数据分片的方式将数据分散存储在不同的节点上,每个节点负责处理其中的一部分数据槽位。这样即使某个节点或者部分节点失效,集群仍然可以继续工作,保证了分区容忍性。
  • 可用性(Availability):Redis 集群通过主从复制的方式实现数据的高可用性。每个主节点都有多个从节点进行数据的备份,当主节点失效时,从节点可以接管主节点的工作,并继续提供服务,保证了系统的可用性。

但是,Redis 集群在一致性(Consistency)方面采取了最终一致性的策略。在节点失效或者数据迁移的过程中,可能会出现数据的不一致状况,但随后会通过后续的数据同步和修复操作来保证数据最终的一致性。因此,Redis 集群提供了分布式系统中的分区容忍性和可用性,并提供了近似一致性的数据保证。

28.Redis分布式锁实现上有啥问题缺陷?怎么解决?

Redis分布式锁是一种常见的实现方式,但也存在一些问题和缺陷,包括:

  1. 网络通信延迟:由于Redis是一个远程服务器,获取和释放锁都需要通过网络通信,可能会引入较高的延迟。这可能会影响系统的性能和响应时间。
  2. 锁的可重入性:Redis分布式锁默认不支持锁的可重入性,即同一个线程或进程在持有锁期间无法再次获取该锁。这可能导致死锁的情况。
  3. 锁的过期问题:如果获取锁的客户端在某些情况下崩溃或网络中断,锁可能永远不会被释放,导致其他客户端无法获取到锁,造成死锁。
  4. 时钟的不同步:在分布式环境中,各个节点的时钟可能存在不同步的情况。这可能导致在不同节点上获取和释放锁时的时间判断不准确,从而引发并发冲突或锁的错误释放。

为了解决这些问题和缺陷,可以考虑以下方法:

  1. 设置合理的超时时间:在获取锁时,可以设置一个合理的超时时间。如果超过该时间仍未获取到锁,则放弃或重试。这样可以避免锁被长时间占用。
  2. 实现锁的可重入性:通过在锁的数据结构中维护持有锁的客户端信息,可以实现锁的可重入性。每个客户端可以跟踪自己的锁持有次数,并在释放锁时相应地减少计数。
  3. 使用红锁或互斥体:考虑使用更复杂的锁机制,如Redlock算法或基于互斥体的分布式锁,以增强锁的可靠性和正确性。
  4. NTP时间同步:确保各个节点的时钟同步,可以减少时钟不同步导致的问题。使用NTP(网络时间协议)或其他时间同步机制可以帮助节点保持一致的时钟。
  5. 锁的主动续期:在持有锁的客户端可以周期性地更新锁的过期时间,确保锁在客户端执行的时间内不会过期,从而避免锁的错误释放。

综合考虑具体的应用场景和需求,选择合适的解决方案和策略,以确保分布式锁的正确性、可靠性和性能。

29.如果是DB实现分布式锁的话,主要思路是?有啥问题?怎么解决?

如果使用数据库(DB)来实现分布式锁,可以采用以下主要思路:

  1. 创建唯一约束:在数据库表中创建一个带有唯一约束的列,用于表示锁的状态。当某个客户端成功插入一行时,表示该客户端获取到了锁。
  2. 事务操作:通过数据库事务来确保对锁的操作的原子性和一致性。在获取锁时,通过事务尝试插入一行,若插入成功则获取到了锁;在释放锁时,通过事务删除相应的行。
  3. 锁的过期时间:可以在锁的记录中添加一个过期时间字段,表示锁的有效期。客户端在获取锁时,需要检查是否存在过期的锁,若存在则认为锁已过期并可以获取。
  4. 心跳机制:客户端在持有锁期间可以定期更新锁的过期时间或执行心跳操作,以防止锁过期或其他客户端获取锁。

尽管使用数据库实现分布式锁可以解决一些问题,但也存在一些问题和挑战,包括:

  1. 单点故障:如果数据库成为单点故障,整个分布式锁的可用性和可靠性会受到影响。当数据库不可用时,无法获取或释放锁。
  2. 性能开销:数据库操作通常比较耗时,因此频繁的锁获取和释放可能会导致性能瓶颈。数据库的并发性能限制了分布式锁的吞吐量和响应时间。
  3. 死锁风险:在高并发环境下,可能存在死锁的风险。例如,一个客户端在持有锁期间崩溃或执行耗时的操作,导致其他客户端无法获取锁,从而产生死锁。

为了解决这些问题,可以采取以下措施:

  1. 数据库高可用:确保数据库的高可用性,使用主从复制、数据库集群或分片等机制,减少单点故障的风险。
  2. 优化数据库操作:通过合理的索引设计、批量操作和缓存优化等手段,优化数据库的性能,减少数据库操作对分布式锁性能的影响。
  3. 死锁检测与处理:实现死锁检测和解决机制,例如设置超时时间、定期释放未及时释放的锁,或使用死锁检测算法来解决死锁问题。
  4. 基于缓存的锁:结合使用数据库和缓存,将锁的状态存储在缓存中,利用缓存的高性能和原子操作。当获取锁时,先在缓存中检查锁的状态,如果缓存中不存在或已过期,则尝试获取数据库锁。在释放锁时,先删除缓存中的锁状态,然后释放数据库锁。这样可以减轻数据库负载,提高性能和并发能力。

综合考虑具体应用场景和需求,选择适合的数据库锁实现方式,并结合缓存、高可用性和性能优化等措施,可以提高分布式锁的可靠性、性能和并发性。

30.Redis 分布式锁的问题缺以及优化思路

Redis分布式锁在实现分布式系统中的锁机制时非常有用,但也存在一些问题和潜在的缺点。下面是一些常见的问题和相应的优化思路:

  1. 竞争条件(Race Condition):多个客户端同时尝试获取锁时可能会出现竞争条件,导致多个客户端都成功获取到了锁。这会导致数据不一致或资源冲突的问题。优化思路:可以使用Redlock算法或者基于Redis的Redsync库来实现更可靠的分布式锁。这些算法通过在多个Redis实例之间进行协调,增加了锁的可靠性和一致性。
  2. 死锁和锁过期如果持有锁的客户端在处理过程中发生崩溃或锁过期未及时释放,可能会导致其他客户端无法获取锁,从而产生死锁情况优化思路:引入锁的超时机制,即为锁设置一个合理的过期时间。客户端在获取锁时,可以使用带有超时参数的命令(如SETNX和EXPIRE)来确保锁在一定时间内自动释放。另外,可以采用心跳机制,定期续约锁的过期时间,避免因为持有锁的客户端失联而导致锁无法释放。
  3. 性能问题:在高并发场景下,大量的锁请求和锁释放可能导致Redis的性能瓶颈,降低系统的吞吐量优化思路:可以考虑使用Redis Cluster来扩展Redis的性能和容量,以支持更大规模的锁并发请求。另外,可以采用细粒度锁(如分片锁)来减少锁的竞争范围,提高并发性能。
  4. 客户端故障处理如果获取锁的客户端在处理过程中崩溃或网络中断,锁可能永远不会被释放,导致其他客户端无法获取锁优化思路:引入锁的拥有者标识,客户端在获取锁时将自身的唯一标识存储在锁的值中,并在释放锁时进行校验。当其他客户端尝试获取锁时,可以检查锁的拥有者标识,如果标识不匹配,则可以强制释放锁。

需要注意的是,分布式锁的实现非常复杂,需要综合考虑业务场景、并发量、容错性等因素。

31.Redis 热点key 的问题和优化处理

Redis热点key问题指的是在高并发场景下,某些特定的key被频繁地读取或写入,导致这些key成为系统的瓶颈,影响性能和可伸缩性。以下是一些常见的问题和优化处理方法:

问题:

  1. 频繁的读写操作导致Redis性能瓶颈:如果某个热点key被大量客户端频繁读写,会导致该key所在的Redis节点成为性能瓶颈,造成延迟增加和吞吐量下降。
  2. 热点key过期导致的缓存击穿:如果热点key过期后被大量请求同时命中,导致请求直接访问后端存储,增加了数据库负载和延迟。

优化处理:

  1. 数据分片:将热点key分散到不同的Redis节点上,可以通过一致性哈希等算法将相同类型的key映射到不同的节点上。这样可以将请求负载均衡到多个节点,减轻单个节点的压力
  2. 缓存预热:在系统启动或负载较低的时候,提前加载热点数据到Redis中,避免在高并发时才进行热点数据的加载,减少请求对热点key的直接访问。
  3. 设置合理的过期时间:根据业务需求,设置合适的热点key的过期时间,避免热点key过期后大量请求同时命中后端存储。可以考虑使用LRU(最近最少使用)等策略进行过期淘汰
  4. 增加缓存层在Redis之前增加一层缓存,如使用CDN、分布式缓存(如Memcached)等,将部分请求分流到缓存层,减轻Redis的压力。
  5. 使用分布式锁避免缓存击穿:当热点key过期使用分布式锁来保护后续请求,只允许一个请求去加载数据并更新缓存,其他请求等待并读取已加载的数据。
  6. 数据异步更新:对于热点key的写操作,可以考虑使用异步更新的方式,将写操作放入队列中,由后台线程异步处理,减少实时写入对Redis的压力。

以上是一些常见的优化处理方法,根据具体的业务场景和需求,可以采用不同的方法或结合多种方法来解决Redis热点key的问题。

32.Redis中有一批key瞬间过期,为什么其它key的读写效率会降低?

当Redis中有一批key瞬间过期时,可能会对其他key的读写效率产生影响,原因如下:

  1. 过期键删除操作:当过期键触发过期时,Redis会执行键的删除操作。删除操作可能会涉及到内存回收和释放,这会占用一定的系统资源和处理时间。如果同时有大量键过期并被删除,这些删除操作会增加系统的负载和消耗,从而导致其他键的读写效率降低。
  2. 内存碎片化:过期键被删除后,Redis的内存可能会产生碎片化。删除操作会释放被占用的内存空间,但这些空间可能不连续,形成碎片化。当后续进行写操作时,Redis可能需要进行内存碎片整理和重新分配,导致额外的开销和延迟。
  3. 缓存失效重建:当其他键在过期键被删除后再次被访问时,可能需要重新从数据源加载或计算缓存值,这可能会耗费额外的时间和资源,导致读写效率降低。

为了减少过期键对其他键的影响,可以考虑以下措施:

  1. 合理设置过期时间:根据业务需求和数据访问模式,合理设置键的过期时间,避免过多键集中在相同时间过期,从而减少过期键瞬间删除的压力。
  2. 分布式删除:如果系统中有大量过期键需要删除,可以考虑分布式删除策略,将删除操作分散到不同的节点上,以减轻单个节点的负载。
  3. 内存优化:对于内存碎片化问题,可以使用Redis提供的内存优化策略,如开启内存碎片整理、使用内存分配器等,以减少碎片化对性能的影响。
  4. 缓存预热和异步加载:对于重要的缓存数据,可以在过期之前进行预热,以避免缓存失效时的性能损失。另外,对于耗时的计算或数据加载操作,可以考虑使用异步方式进行,以减少对读写操作的影响。

综上所述,过期键对其他键的读写效率会产生影响,主要是因为删除操作、内存碎片化和缓存失效重建等原因。通过合理的配置和优化策略,可以减少这种影响并提高Redis的整体性能。

33.Redis的zset底层什么时候是hash,什么时候是跳表?

Redis中的有序集合(Sorted Set)使用了两种数据结构来实现,具体使用哪种取决于有序集合的大小和配置:

  1. 当有序集合的元素数量较小且满足一定条件时,Redis使用哈希表(Hash Table)来实现有序集合。这些条件包括哈希表的负载因子低于0.5,且有序集合的元素数量小于等于64个。
  2. 当有序集合的元素数量较大或不满足上述条件时,Redis会使用跳表(Skip List)来实现有序集合。跳表是一种有序的数据结构,可以在有序集合中高效地进行插入、删除和按排名查找等操作。

哈希表在存储和查找单个元素时具有O(1)的平均时间复杂度,但不适合按照分数范围或按照排名进行范围查询。跳表可以支持更高效的范围查询,但对于单个元素的查找和插入操作的平均时间复杂度为O(log n)。

因此,当有序集合的元素数量较少时,使用哈希表可以获得更好的性能。而当有序集合的元素数量较大时,使用跳表可以提供更高效的范围查询能力。

需要注意的是,Redis在内部自动切换哈希表和跳表的实现方式,对外表现为有序集合的一致接口。这样的设计可以根据实际情况自动选择合适的数据结构,以在不同场景下提供高性能和高效的有序集合操作。

34.Redis 数据结构有哪些,底层实现都是什么?

Redis支持多种数据结构,以下是一些常见的Redis数据结构及其底层实现:

  1. 字符串(String):存储一个字符串值。底层使用简单动态字符串(SDS)实现,SDS允许在O(1)时间复杂度内进行常数时间的追加、删除和查找操作。
  2. 列表(List):由多个字符串值组成的有序集合,允许在列表的头部或尾部快速地进行添加或删除操作。底层使用双向链表实现。
  3. 哈希表(Hash):存储字段和值的映射关系,类似于字典或关联数组。底层使用哈希表实现,哈希表是一种数组和链表的组合数据结构,可以在O(1)时间复杂度内进行添加、删除和查找操作。
  4. 集合(Set):由多个不重复元素组成的无序集合,支持对元素进行添加、删除和判断是否存在操作。底层使用哈希表或有序整数数组实现
  5. 有序集合(Sorted Set):类似于集合,但每个元素都有一个分数值,根据分数值对元素进行排序。底层使用跳跃表和哈希表实现,跳跃表用于实现有序性,哈希表用于实现快速查找。
  6. 地理位置(Geospatial):用于存储地理位置信息的数据结构,支持根据经纬度查询附近的位置。底层使用基于跳跃表和哈希表的数据结构

总的来说,Redis的数据结构都经过精心设计和优化,以满足不同类型的应用需求,并提供高性能的数据存储和访问功能。

35.Redis 热key 是什么,有什么问题,怎么发现,怎么解决?

Redis热key是指在Redis中频繁被访问的键(key)。当某个键成为热key时,会引发一些问题,例如性能下降、内存占用增加等。以下是热key可能导致的问题以及如何发现和解决的一些建议:

问题:

  1. 性能瓶颈: 热key可能成为访问热点,导致Redis负载不均衡,造成性能瓶颈。
  2. 内存占用增加: 热key频繁被访问,占用较多的内存空间,可能导致其他数据的LRU(最近最少使用)淘汰,造成数据丢失。

发现:

  1. 监控工具: 使用Redis监控工具(如Redis的MONITOR命令或第三方监控工具)来实时监控Redis中的访问情况。
  2. 日志分析: 分析Redis的访问日志,查看哪些键被频繁访问。

解决:

  1. 数据分片: 对于热key,可以考虑将其数据进行分片存储,将负载均衡到多个Redis节点上,从而避免单个节点过载。
  2. 缓存预热: 在系统启动时或者低峰期,提前加载热门数据到Redis,避免冷启动时突然出现大量访问导致的性能下降。
  3. 缓存失效策略: 对于热key,可以设置较短的过期时间,避免长时间占用内存。
  4. 数据淘汰策略: 使用合适的数据淘汰策略(如LRU、LFU等),避免热key长期占用内存,导致其他数据被频繁淘汰。

请注意,解决Redis热key问题需要综合考虑业务需求和系统实际情况。不同的场景可能需要不同的优化策略。因此,建议在解决问题时谨慎评估,避免引入新的问题。同时,合理设计数据结构和缓存策略,能够有效地减轻Redis热key带来的问题。

36.Redis 中底层是跳表结构,那么插入和查询一个数的流程如何?如果是单位查询,流程又是如何实现的?

在Redis中,有序集合(Sorted Set)底层使用跳表(Skip List)结构来实现。跳表是一种有序数据结构,可以提供快速的插入、查询和删除操作。

插入一个数的流程:

  1. 生成一个新的跳表节点,包含要插入的数值。
  2. 在跳表中查找合适的位置,使得插入的数值按照有序排列。
  3. 在每一层插入新节点,并根据一定的概率随机决定是否将新节点插入更高层的索引,从而构建跳表的多层索引结构。

查询一个数的流程:

  1. 从跳表的顶层索引(最高层)开始,沿着索引层级逐步向下搜索。
  2. 在每一层级中,从当前节点向右移动,直到找到目标数值,或者遇到比目标数值大的节点。
  3. 如果找到目标数值,返回该节点;否则,继续下一层级的搜索,直到找到目标节点或者搜索到底层。

单位查询-如果是单位查询,即查询某个范围内的数值,流程如下:

  1. 根据查询条件确定查询范围的起始节点和结束节点。
  2. 从跳表的顶层索引开始,向下逐层搜索,直到找到起始节点。
  3. 从起始节点开始,沿着每一层级的右侧向右移动,直到找到结束节点或者遇到比结束节点大的节点。
  4. 返回范围内的节点集合,即满足条件的数值。

跳表的结构允许Redis在有序集合中快速进行插入和查询操作,同时也提供了高效的范围查询功能。

参考书籍、文献和资料

1.Redis在项目中如何使用及相关知识?_关注我不迷路 带你上高速的博客-CSDN博客

2.Redis在项目中的运用总结

3.Redis实现分布式锁_秦霜的博客-CSDN博客_redis setifabsent

4.基于Redis的限流器的实现_秦霜的博客-CSDN博客

5.redis数据结构-跳跃表_D·罗杰的博客-CSDN博客_redis 条约表

6.聊聊Mysql索引和redis跳表 ---redis的有序集合zset数据结构底层采用了跳表原理 时间复杂度O(logn)(阿里) - aspirant - 博客园

7.node.js - Redis持久化机制 - 个人文章 - SegmentFault 思否

8.分布式缓存Redis之Pipeline(管道)_BugFree_张瑞的博客-CSDN博客_redis的pipe提交失败

9.Redis一些基本问题整理

10.redis 集群容错策略_pl在之心的博客-CSDN博客_redis容错机制

11.Redis 性能调优——缓存设计优化_一叶知秋V的博客-CSDN博客

THE END

发表回复