Redis常见面试题及解答
- 20.2 delete是阻塞还是非阻塞,或者Redis有没有非阻塞删除的方式
- 20.3 假如Redis有一个大键,这个键存储了很多数据,我们想把这个键删除,你会一般是用什么删
- 20.4 Redis怎么发现这种大key问题?
- 21 Redis限流的几种方式
- 22 Redis的stream是否用过
- 23 Redis架构如何设计及优化
- 24 排行榜,有一个积分,在相同的数值时要按照那个时间进行排序,是怎么处理?
- 25 Redis缓存的命中率问题:
- 26 Redis是怎么检查出哪些key过期了?
- 27 Redis的ZSet往跳表里面插一条数据,然后插一个节点,它具体的过程是啥?就比如说它层高怎么确定的,然后怎么找到它那原来那个位置的。
- 28 Redis的高可用有哪些方式?
- 29 Redis在使用的过程中,可能会有哪些问题,或者注意事项有那些?
Redis的版本
截至当前日期:2023年8月28号,Linux
系统下最新版本的Redis
是7.2。
Linux
系统Redis
下载地址:https://redis.io/download
官方没有提供Windows
系统的Redis
,下载Windows
系统的Redis
:https://github.com/tporadowski/redis/releases
1 Redis单线程的速度快的原因
以下是来自GPT3.5的回答。
- 内存存储:
Redis
是将数据存储在内存中,而内存的读写速度远高于磁盘的读写速度。这使得Redis
能够快速地进行数据的读写操作。 - 非阻塞
I/O
操作:Redis
使用了异步非阻塞的I/O
操作模型,通过使用事件循环机制(Event Loop
),在单个线程中管理多个客户端的请求。这种模型允许Redis
在同一时间处理多个并发请求,从而避免了线程切换的开销。 - 高效的数据结构:
Redis
提供了多种高效的数据结构,如字符串、哈希、列表、集合、有序集合等。这些数据结构在存储和操作数据时都经过了优化,使得Redis
在特定场景下能够以更少的操作完成更多的任务。 - 单线程避免了锁竞争: 多线程程序需要处理锁竞争和线程间同步的问题,而
Redis
的单线程模型避免了这些复杂性。这使得Redis
的代码更容易维护和调试,同时也减少了因为锁竞争而引起的性能问题。 - 避免上下文切换: 多线程程序在进行上下文切换时会有一定的开销,而
Redis
单线程模型避免了这种开销。虽然单线程模型本身也会存在性能瓶颈,但由于Redis
大部分操作是基于内存的,因此在绝大多数情况下,单线程模型的优势更大。 - 持久化策略的选择:
Redis
提供了多种持久化方式,如RDB
快照和AOF
日志文件。虽然持久化操作会在某些情况下对性能产生影响,但用户可以根据自己的需求选择适合的持久化策略。
需要注意的是,虽然 Redis
单线程模型在许多情况下表现出色,但也存在一些适用场景有限的情况。例如,当需要进行大量的计算密集型操作时,单线程模型可能会受到限制。此外, Redis
通过多实例的方式也可以在一定程度上实现并行处理多个请求。
2 Redis为什么从单线程变成多线程
Redis
从4.0之后开始多线程。Redis
从单线程变成多线程的决定并不常见,因为Redis
一直以来都以其单线程执行模型而著称,这个模型具有低延迟、高性能和原子性操作的优点。然而,有时在某些情况下,人们可能会考虑在Redis
之上构建多线程或多进程的解决方案来满足特定需求。以下是一些可能导致人们选择这种方式的情况:
- 利用多核处理器:虽然
Redis
是单线程的,但在具有多核处理器的服务器上,可以通过在多个Redis
实例之间分割负载来利用多个核心。这种方式被称为"Redis
多实例",每个实例都运行在单独的进程中,以充分利用硬件资源。 - 多线程客户端:虽然
Redis
本身是单线程的,但客户端可以选择使用多线程或多进程来同时与Redis
服务器进行通信。这样可以提高客户端程序的并发性,但Redis
服务器本身仍然是单线程的。 - 多进程/多容器方案:有时,在容器化环境中,人们可能会运行多个
Redis
容器,每个容器负责不同的数据分片,从而实现某种程度的多线程或多进程执行。
虽然Redis
本身的核心代码是单线程的,但可以通过使用多实例、多线程客户端或容器化技术来实现分布式和并行处理。但这些方法通常更复杂,需要更多的管理和协调,因此需要权衡利弊,根据特定的用例和需求来选择合适的方法。不过需要明确指出,Redis
官方版本仍然是单线程的,多线程版本通常是第三方的扩展或变种。
2.1 Redis为什么能保持原子性?
Redis(Remote Dictionary Server)
能够保持原子性的主要原因在于其单线程执行模型和内置的原子操作。以下是一些原因解释为什么Redis能够保持原子性:
- 单线程执行模型:
Redis
是单线程的内存数据库,所有的命令都是顺序执行的,不会发生并发竞争问题。这使得Redis
能够避免多线程并发操作中可能出现的竞争条件。 - 原子操作:
Redis
提供了多个原子操作,这些操作可以保证在单个命令中执行,而不会被其他操作中断。一些常见的原子操作包括INCR
(增加一个值)、DECR
(减少一个值)、SETNX
(设置一个键的值,如果它不存在的话)等。这些操作是原子性的,不会被其他客户端的操作干扰。 - 事务支持:
Redis
提供了事务(Transaction)支持,通过MULTI
、EXEC
、DISCARD
和WATCH
等命令,可以将一系列命令包装在一个事务中,然后原子性地执行这个事务。如果在事务执行期间有其他客户端尝试修改被监视的键(使用WATCH
命令),事务会被中断。 - 锁定机制:
Redis
还提供了分布式锁机制,通过SET
命令设置一个键值对,并使用EX(过期时间)参数来实现锁的自动释放,这有助于防止多个客户端同时修改相同的数据。
总之,Redis
能够保持原子性的关键在于其单线程执行模型、内置的原子操作、事务支持和锁定机制。这些功能使Redis
成为一个强大的工具,用于处理分布式系统中的并发和竞争条件问题。但需要注意的是,虽然Redis
可以保持原子性,但在复杂的分布式系统中,仍然需要谨慎地设计和编写应用程序来处理各种并发情况和故障恢复。
3 Redis的网络模型
以下是来自GPT3.5的回答。
Redis
的网络模型是基于事件驱动的、单线程的异步网络I/O
多路复用模型。以下是Redis
的网络模型的主要特点:
- 事件驱动架构:
Redis
使用事件驱动架构来处理客户端请求和其他事件。这种架构通过一个事件循环(Event Loop
)来监听并处理不同类型的事件,如新连接、数据读取、数据写入等。 - 单线程模型:
Redis
主要运行在一个单独的线程中,负责处理所有的客户端请求和事件。这个线程不会被I/O
操作阻塞,而是通过非阻塞方式处理多个客户端的请求。 - 多路复用(Multiplexing):
Redis
使用多路复用技术来同时监听多个套接字(sockets
)。这种技术使得Redis能够在单个线程中同时处理多个客户端的连接和I/O
操作。 - 文件事件和时间事件:
Redis
的事件循环监听两种类型的事件。文件事件用于处理套接字的I/O
操作,例如连接、读取和写入。时间事件用于执行定时任务,例如定期执行某些操作。 - 非阻塞
I/O
:Redis
的网络模型是非阻塞的,这意味着当一个客户端的请求需要进行I/O
操作时,Redis
不会阻塞整个线程,而是会继续处理其他客户端的请求,直到该I/O
操作完成。 - 缓冲区:
Redis
使用缓冲区来存储客户端发送的数据和要返回给客户端的数据。这种缓冲区机制有助于提高性能,避免频繁的数据复制操作。
通过这种事件驱动的网络模型,Redis
能够在单线程下实现高并发和低延迟的数据处理能力。这种设计使得Redis
在处理高速数据存储和访问时表现出色,同时减少了多线程带来的复杂性和线程同步的问题。然而,需要注意的是,Redis
的单线程模型在某些特定的场景下可能会受到计算密集型任务的限制。
4 string(字符串)
Redis
是Key-Value
的数据形式,所以Key
一般都是string
类型,主要是Value
分成5种数据类型。
4.1 string使用场景
- 单值缓存: 最常见的使用场景之一是将数据缓存在
Redis
中,以加速数据访问。例如,可以将数据库查询结果、计算结果或其他频繁访问的数据存储在Redis
中,以避免频繁查询数据库或执行复杂计算。 - 计数器:
string
类型可以用来实现计数器,例如用于统计网站的页面访问次数、用户点击次数等。通过使用Redis
的原子性操作(如INCR
和DECR
命令),可以实现高效的计数功能。 - 分布式锁: 可以使用
string
类型来实现分布式锁。通过在Redis
中设置一个特定的键作为锁,并使用带有NX
参数的SET
命令来尝试获取锁,可以确保只有一个客户端能够获得锁。 - 会话管理: 在
Web
应用中,可以使用string
类型来存储用户会话数据,例如用户登录信息、购物车内容等。这可以帮助实现快速的会话管理。 - 消息队列:
string
类型可以用于实现简单的消息队列,通过将消息序列化为字符串并使用LPUSH
或RPUSH
命令将其添加到列表中,然后使用BRPOP
等命令进行消息的出队操作。 - 数据序列化:
Redis
中的string
类型可以存储任何类型的数据,包括序列化的对象。这在某些情况下可以用来存储应用程序状态或配置信息。 - 位图:
Redis
提供了一些位操作命令,可以将string类型用作位图数据结构。这在一些应用场景中,如统计用户在线状态、活跃用户等,非常有用。 - 缓存验证: 有时,需要将缓存与后端数据同步,以确保缓存数据的一致性。可以将数据存储为
string
类型,并在存储时添加一个版本号或时间戳。应用程序在使用缓存数据之前,可以检查版本号或时间戳,以验证缓存的有效性。
4.2 string常用命令
SET key value [EX seconds] [PX milliseconds] [NX|XX]
: 设置键的值。可以选择设置键的过期时间,以及设置条件(NX表示只在键不存在时设置,XX表示只在键已经存在时设置)。GET key
: 获取键的值。DEL key [key ...]
: 删除一个或多个键。INCR key
: 将键的值加一,如果键不存在则创建并设置值为1。DECR key
: 将键的值减一,如果键不存在则创建并设置值为-1。INCRBY key increment
: 将键的值增加指定的整数。DECRBY key decrement
: 将键的值减少指定的整数。APPEND key value
: 在键的值后追加字符串。STRLEN key
: 返回键的值的长度。GETRANGE key start end
: 返回键的值从start
到end
的子字符串。SETRANGE key offset value
: 用指定字符串替换键值从指定偏移量开始的部分。MSET key value [key value ...]
: 设置多个键的值。MGET key [key ...]
: 获取多个键的值。SETNX key value
: 将键的值设置为指定的值,仅当键不存在时。GETSET key value
: 设置键的值,并返回之前的值。MSETNX key value [key value ...]
: 同时设置多个键的值,仅当所有键都不存在时。STRLEN key
: 返回键的值的长度。SETEX key seconds value
: 设置键的值,并指定过期时间(以秒为单位)。PSETEX key milliseconds value
: 设置键的值,并指定过期时间(以毫秒为单位)。SETBIT key offset value
: 设置或清除键的值在指定偏移量上的位。GETBIT key offset
: 获取键的值在指定偏移量上的位。
4.3 string特点及底层实现
Redis
的string
类型是最基本的数据类型之一,用于存储字符串数据。它具有以下特点:
- 存储任意数据: 虽然称为
string
,但实际上可以存储任何类型的字符串数据,包括文本、二进制数据等。 - 简单的键值对结构:
string
类型采用键值对的方式存储数据,每个键都对应一个字符串值。 - 高效的操作:
Redis
的string
类型支持丰富的操作,如获取、设置、追加、增加、减少等,这些操作都能在常数时间内完成,具有高效性能。 - 自动内存管理:
Redis
会自动进行内存管理,根据数据的大小和情况,选择性地使用不同的内存优化策略,如使用RAW编码、INT编码等。 - 高速缓存功能:
string
类型是存储缓存数据的常见选择。通过将数据存储在string
类型中,可以在不需要时轻松地清除、替换或更新缓存。 - 过期时间:
string
类型可以设置过期时间,用于实现数据的自动过期和失效。过期时间到达后,数据会被自动删除。
string类型底层实现:
Redis
的string
类型的底层数据实际上使用一个叫做SDS
(Simple Dynamic String
)的数据结构来进行存储。SDS
是一种可动态调整大小的字符串表示,它允许字符串的长度根据实际存储的内容进行变化,从而避免了频繁的内存重新分配操作。SDS
还会记录字符串的长度、可用空间以及字符数组。
SDS的结构:
struct sdshdr {
int len; // 字符串的已使用长度
int free; // 未使用的空间长度
char buf[]; // 字节数组,存储实际数据
};
在SDS
的结构中,len
表示已使用的字符串长度,free
表示未使用的空间长度,而buf
是一个字符数组,存储实际的字符串数据。SDS
可以根据字符串的长度进行动态扩展和收缩,从而在保证高效内存使用的同时,提供了快速的字符串操作。
SDS的特点:
- 动态性:
SDS
可以根据字符串的长度动态地调整内存的大小,避免了固定大小带来的内存浪费问题。 - 二进制安全:
SDS
可以存储任意二进制数据,因此在Redis
中,字符串不仅仅限于存储文本数据,还可以存储图片、音频等任何二进制数据。 - 惰性空间释放: 当
SDS
的字符串被修改时,不会立即释放被修改前的内存,而是在必要的时候才进行释放,这可以避免频繁的内存分配和释放操作。 - 预分配空间:
SDS
会根据字符串的长度预分配额外的空间,以减少频繁的内存重新分配。 - 快速获取长度: 由于
SDS
在内部维护了字符串的长度信息,所以可以在O(1)
的时间复杂度内获取字符串的长度。 - 常数时间复杂度操作:
SDS
支持在字符串的末尾追加、删除和修改字符,这些操作的时间复杂度都是O(1)
。
- 当存储短字符串时,
Redis
可能会使用int
编码来存储,这会将字符串转换为整数,节省内存空间。而在需要的情况下,Redis
还会使用embstr
编码,将短字符串直接嵌入到数据对象中,避免了分配和释放额外的内存。 - 对于长字符串,
Redis
会使用raw
编码,这是一种典型的字符串存储方式,其格式包括字符串长度和字符数组。在SDS
中,字符数组的大小会比实际的字符串长度大,以便在需要进行追加操作时,不必频繁重新分配内存。
5 list(列表)
5.1 list使用场景
Redis
的list
类型是一个双向链表数据结构,它支持在列表的两端执行插入、删除、获取操作。以下是一些适合使用Redis
的list
类型的场景:
- 消息队列:
Redis
的list
可以用作简单的消息队列。生产者可以使用LPUSH
命令将消息添加到列表的头部,消费者可以使用BRPOP
或BLPOP
命令从列表的尾部阻塞式地获取消息。这种方式实现了一种基本的发布-订阅模式。 - 任务队列: 类似于消息队列,
list
也可以用于构建任务队列。生产者将需要执行的任务放入列表,消费者从列表中获取任务并执行。 - 实时数据流: 可以将实时产生的数据放入
list
中,供客户端按需获取。这在实时分析、监控等场景中非常有用。 - 活动日志: 将用户的活动日志按时间顺序存储在
list
中,以便后续分析和查看用户的活动历史。 - 历史记录: 用于存储应用程序的历史操作记录,比如聊天应用中的消息历史记录。
- 排行榜: 可以将用户分数和
ID
存储在list
中,用于实现排行榜功能。通过使用ZADD
命令,还可以将成员按照分数有序地存储。 - 数据缓存: 通过
list
存储一些热门数据,例如近期热门文章、商品等。可以使用LPUSH
、RPUSH
、LPOP
等命令来更新缓存数据。 - 任务调度: 将需要定时执行的任务存储在
list
中,并使用BLPOP
命令在合适的时间获取并执行这些任务。 - 聊天应用:
list
可以用于存储聊天消息。通过LPUSH
将新消息添加到聊天历史中,然后使用LRANGE
获取聊天记录。
需要注意的是,虽然list
是一个有用的数据类型,但在处理大型列表时,需要考虑性能和内存的问题。过长的链表可能会影响操作的速度和内存使用。此外,如果需要更复杂的数据结构或功能,可能需要考虑使用其他数据类型,如hash
或set
。
5.2 list常用命令
LPUSH key value [value ...]
: 将一个或多个值插入到列表的左侧(头部)。RPUSH key value [value ...]
: 将一个或多个值插入到列表的右侧(尾部)。LPOP key
: 移除并返回列表的左侧(头部)的值。RPOP key
: 移除并返回列表的右侧(尾部)的值。LINDEX key index
: 返回列表中指定索引位置的元素。LLEN key
: 返回列表的长度。LRANGE key start stop
: 返回列表中指定范围的元素。范围是闭区间,包括start
和stop
位置的元素。LTRIM key start stop
: 对列表进行修剪,只保留指定范围内的元素。范围是闭区间。LINSERT key BEFORE|AFTER pivot value
: 在列表中找到指定值pivot
,然后在其前面或后面插入新的值。LREM key count value
: 从列表中删除指定数量的与值匹配的元素。BLPOP key [key ...] timeout
: 阻塞式地从列表的左侧弹出一个或多个元素,如果列表为空则阻塞等待,直到有元素可弹出或超时。BRPOP key [key ...] timeout
: 类似于BLPOP
,但从列表的右侧弹出元素。RPOPLPUSH source destination
: 弹出source
列表的最后一个元素,并将它添加到destination
列表的头部。LSET key index value
: 将列表中指定索引位置的元素设置为新值。LREM key count value
: 从列表中删除指定数量的与值匹配的元素。RPOPLPUSH source destination
: 弹出source
列表的最后一个元素,并将它添加到destination
列表的头部。
这些是一些常用于处理Redis
中list
类型的命令。根据具体的应用场景和需求,可以选择适合的命令来操作和管理列表数据。
5.3 list特点及底层实现
Redis
的list
类型是一个双向链表数据结构,用于存储有序的、可重复的元素列表。每个元素都可以在列表的头部或尾部进行插入、删除和访问操作。以下是list
类型的特点:
- 有序存储:
list
类型中的元素是有序的,每个元素都有一个相对位置。可以根据插入顺序来访问和操作元素。 - 可重复元素:
list
类型允许存储相同的元素,即使是重复的元素也会保留。 - 双向链表:
list
类型实际上是一个双向链表,支持在列表的头部和尾部进行插入、删除操作。这使得头部和尾部操作的时间复杂度都为O(1)
。 - 支持索引访问: 可以通过索引位置来访问list中的元素,这使得可以在常数时间内访问任意位置的元素。
- 支持范围查询: 可以通过索引范围来获取一段连续的元素子列表。
- 用于队列和栈:
list
类型既可以作为队列使用(先进先出),也可以作为栈使用(先进后出)。
list
类型底层实现:
Redis
的list
是一种基本的数据结构,它是一个有序的字符串元素集合。list
可以包含重复的元素,而且可以在list
的两端进行插入和删除操作。在底层,Redis
的list
是通过双向链表(doubly linked list)
和压缩列表(ziplist)
两种数据结构来实现的。
- 压缩列表(ziplist):
- 当
list
中的元素数量比较少且元素较小时,Redis
会使用压缩列表作为底层数据结构,这是一种紧凑的连续内存结构。 - 压缩列表通过将多个元素紧密地存储在一块内存中,减少了指针等开销,从而节省了内存空间。
- 它适用于类似于字符串、整数等较小数据类型的元素。
- 压缩列表支持
O(1)
时间复杂度的尾部插入和弹出操作,但在中间插入或删除元素时可能会引发内存的重新分配和复制,因此效率较低。
- 双向链表(doubly linked list):
- 当
list
的元素数量较多或元素较大时,Redis
会选择使用双向链表作为底层数据结构,以便更好地处理插入、删除等操作。 - 双向链表通过指针将元素连接起来,支持快速的插入和删除操作,但相比压缩列表会占用更多的内存空间。
- 它适用于任何大小的元素,尤其是在需要频繁地在中间插入或删除元素的情况下。
双向链表(doubly linked list)
和压缩列表(ziplist)
的切换
Redis
会根据list
的当前大小和操作模式来自动选择使用压缩列表或双向链表。- 当
list
的元素数量增加时,Redis
可能会将压缩列表升级为双向链表,以便更好地处理大量元素。 - 反之,当
list
的元素数量减少时,Redis
可能会将双向链表降级为压缩列表,以节省内存。
总结:
Redis
的list
底层实现原理涉及了压缩列表和双向链表这两种数据结构,根据元素数量和大小的不同,选择合适的数据结构来平衡性能和内存使用。这种灵活性使得Redis
的list
在不同场景下都能够高效地存储和处理数据。
6 Set(集合)
6.1 Set使用场景
Redis
的set
类型是一个无序的、不重复的数据集合。它可以存储多个不同的元素,但不允许重复。以下是一些适合使用Redis
的set
类型的场景:
- 标签和关键字存储: 适用于存储文章、商品、图片等对象的标签、关键字或标识。每个对象可以对应一个
set
,其中存储与该对象相关的标签。 - 好友关系: 可以使用
set
来存储用户之间的好友关系。每个用户对应一个set
,其中存储与该用户关联的好友。 - 共同兴趣爱好: 类似于好友关系,可以使用
set
存储用户的兴趣爱好。比较两个用户的兴趣爱好集合,可以找到共同的兴趣爱好。 - 唯一值存储:
set
中不允许重复的特性使得它适合用来存储唯一值,例如在线用户、IP
地址、手机号等。 - 投票系统: 用于存储用户的投票选项,确保每个用户只能投一次票。
- 集合操作:
Redis
提供了一系列操作set
的命令,如求交集、并集、差集等。这些操作对于数据分析和数据处理非常有用。 - 排他性操作: 可以使用
set
实现排他性操作,例如某个资源是否被占用,以及哪些用户正在使用它。 - 用户在线状态: 将在线用户的
ID
存储在一个set
中,通过SADD
和SREM
命令来维护用户的在线状态。 - 黑名单和白名单: 可以将需要屏蔽或允许的对象(用户、
IP
等)存储在set
中,用于实现黑名单或白名单功能。
需要注意的是,虽然set
类型适合存储不重复的数据,但它不适合存储有序数据。如果需要有序性,可以考虑使用zset
(有序集合)类型。在选择数据类型时,需要根据具体的需求和场景来做出决策。
6.2 Set常用命令
SADD key member [member ...]
: 将一个或多个成员添加到集合中。SREM key member [member ...]
: 从集合中移除一个或多个成员。SISMEMBER key member
: 检查成员是否存在于集合中,返回布尔值。SCARD key
: 返回集合中成员的数量。SMEMBERS key
: 返回集合中所有成员。SRANDMEMBER key [count]
: 随机返回集合中一个或多个成员,如果指定了count
参数,则返回不重复的成员。SPOP key [count]
: 随机移除并返回集合中一个或多个成员。SDIFF key [key ...]
: 返回给定集合之间的差集。SINTER key [key ...]
: 返回给定集合的交集。SUNION key [key ...]
: 返回给定集合的并集。SDIFFSTORE destination key [key ...]
: 将给定集合之间的差集存储到另一个集合中。SINTERSTORE destination key [key ...]
: 将给定集合的交集存储到另一个集合中。SUNIONSTORE destination key [key ...]
: 将给定集合的并集存储到另一个集合中。SMOVE source destination member
: 将成员从一个集合移动到另一个集合。SPOP key [count]
: 随机移除并返回集合中一个或多个成员。
6.3 Set特点及底层实现
Redis
的set
类型是一种无序的、不重复的数据集合。它具有以下特点:
- 无序性:
set
类型中的元素是无序的,没有特定的排列顺序。 - 不重复性:
set
类型保证其中不会存在重复的元素,每个元素只能出现一次。 - 高效的成员判定:
set
类型支持高效地判断一个成员是否存在,操作的时间复杂度为O(1)
。 - 集合操作:
set
类型支持集合操作,如求交集、并集、差集等,这些操作对于数据处理和分析非常有用。 - 适合存储唯一值:由于不允许重复元素,
set
类型适合用来存储唯一的标识、ID
、用户名等数据。 - 无索引:
set
类型不支持通过索引来访问元素,只能通过成员判定操作来检查元素是否存在。 - 自动内存管理:
Redis
会根据集合中元素的数量,自动选择使用intset
(整数集合)或hashtable
(哈希表)来存储数据,以便节省内存。
Set
类型底层实现:
Redis
的set
类型的底层实现有两种方式:intset
和hashtable
。
intset
(整数集合):当set
中的所有元素都可以表示为整数时,Redis
会使用intset
来存储数据。intset
是一种紧凑的、有序的数据结构,使用连续的内存块来存储整数数据。它可以节省内存,同时支持高效的成员判定操作。hashtable
(哈希表):当set
中的元素不能全都表示为整数,或者元素数量较大时,Redis
会使用hashtable
来存储数据。hashtable
是一种常见的散列数据结构,可以用来存储非整数类型的元素。虽然会占用更多的内存,但它提供了更大的灵活性。
总结:
Redis
的set
类型在底层使用intset
和hashtable
这两种不同的数据结构来存储数据,以便根据元素类型和数量来选择合适的存储方式,以达到更好的内存效率和性能。
7 Zset(有序集合)
7.1 Zset使用场景
Redis
的有序集合(Zset
)是一种特殊的集合数据类型,它与普通集合相比,每个元素都有一个关联的分数(score
),并且元素按照分数的顺序排列。以下是一些适合使用Redis
的Zset
类型的场景:
- 排行榜和计分系统:
Zset
可以用于实现排行榜,例如游戏中的玩家积分排名、音乐应用中的热门歌曲排名等。每个元素表示一个用户或歌曲,分数表示用户的积分或歌曲的播放次数。 - 实时热门数据: 用于存储实时热门数据,例如热门文章、热门商品等。根据分数来判断热门程度,可以轻松地获取热门数据。
- 社交网络关注和粉丝关系: 可以使用
Zset
存储用户的关注关系,每个元素表示一个用户,分数表示关注时间。还可以使用Zset
存储用户的粉丝关系,分数表示粉丝关注时间。 - 范围查询:
Zset
支持根据分数范围进行查询,这对于获取一定范围内的数据非常有用。例如,在时间轴上查询一段时间内的数据。 - 定时任务调度: 可以使用
Zset
来实现定时任务的调度。将任务的执行时间作为分数,任务内容作为成员,通过定时地获取分数小于当前时间的成员来执行任务。 - 带有权重的投票系统:
Zset
可以用于实现带有权重的投票系统,每个元素表示一个投票项,分数表示该投票项的权重。可以用来进行复杂的投票计算。 - 基于距离的地理位置查询: 如果使用
Zset
的分数表示地理位置的坐标,可以实现基于距离的地理位置查询,比如查找附近的商店、用户等。 - 带有过期时间的数据:
Zset
的分数可以表示数据的过期时间,结合定时任务,可以实现一些带有过期时间的数据清理操作。
需要注意的是,虽然Zset
提供了有序性和分数的特性,但由于其内部实现是基于跳跃表和散列表,所以在性能方面要综合考虑。在选择数据类型时,需要根据具体的需求和场景来做出决策。
7.2 Zset常用命令
ZADD key score member [score member ...]
: 将一个或多个成员添加到有序集合中,每个成员关联一个分数。ZREM key member [member ...]
: 从有序集合中移除一个或多个成员。ZSCORE key member
: 获取成员在有序集合中的分数。ZINCRBY key increment member
: 增加成员的分数,可以指定增量。ZCARD key
: 获取有序集合中成员的数量。ZRANK key member
: 获取成员在有序集合中的排名(从小到大)。ZREVRANK key member
: 获取成员在有序集合中的排名(从大到小)。ZRANGE key start stop [WITHSCORES]
: 返回有序集合中指定范围的成员。通过WITHSCORES
选项,可以同时返回成员的分数。ZREVRANGE key start stop [WITHSCORES]
: 类似于ZRANGE
,但是结果从大到小排列。ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
: 根据分数范围返回成员。可以通过WITHSCORES
选项返回分数,通过LIMIT
选项进行分页。ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
: 类似于ZRANGEBYSCORE
,但是结果从大到小排列。ZCOUNT key min max
: 计算有序集合中分数在给定范围内的成员数量。ZREMRANGEBYRANK key start stop
: 移除有序集合中指定排名范围内的成员。ZREMRANGEBYSCORE key min max
: 移除有序集合中分数在给定范围内的成员。ZINTERSTORE destination numkeys key [key ...]
: 计算给定的一个或多个有序集合的交集,并将结果存储在新的有序集合中。ZUNIONSTORE destination numkeys key [key ...]
: 计算给定的一个或多个有序集合的并集,并将结果存储在新的有序集合中。
7.3 Zset特点及底层实现
Redis
的有序集合(Zset
)是一种有序的、不重复的数据集合,每个元素都有一个与之关联的分数(score
),用于排序。以下是有序集合的特点:
- 有序性:有序集合中的元素是按照分数进行排序的,可以根据分数从小到大或从大到小进行遍历。
- 不重复性:有序集合中不允许有重复的元素,每个元素是唯一的。
- 支持范围查询:可以根据分数范围来获取一段分数内的元素。
- 支持排名:可以获取元素在有序集合中的排名,从小到大或从大到小。
- 高效的插入和删除:有序集合支持高效地插入和删除元素,时间复杂度为
O(log N)
,N
为元素数量。 - 适合用于排行榜:有序集合适用于实现排行榜,例如用户积分排名、热门内容排名等。
Zset
类型底层实现原理:
Redis
的Zset
底层实现实际上使用了两种数据结构来进行存储和索引:压缩列表(ziplist)
和跳跃表(skiplist)
。
- 压缩列表(ziplist):当有序集合中的成员数量较少,且成员的值较短时,
Redis
使用压缩列表作为底层存储结构。压缩列表是一种紧凑的、高效的数据结构,适用于存储较小的数据。 - 跳跃表(skiplist):当有序集合中的成员数量较多,或者成员的值较长时,
Redis
会使用跳跃表作为底层存储结构。跳跃表是一种有序数据结构,支持快速的插入、删除和范围查找操作,能够在较大数据量下保持良好的性能。
这两种底层存储结构的选择是基于成员数量和成员值的大小来决定的,以便在不同场景下实现最佳的性能和内存占用。由于压缩列表适用于存储较小的数据,而跳跃表适用于存储较大的数据,因此Redis
会根据具体情况来做出选择。
7.4 跳表中的层高是怎么决定
参考1:Redis的数据结构之跳表
层高
节点的层高最小值为1,最大值是ZSKIPLIST_MAXLEVEL,Redis中节点层高的值为32。
zslRandomLevel函数
每次创建一个新跳跃表节点的时候,程序都会根据幂次定律(zslRandomLevel,越大的数出现的概率越小)随机生成一个介于1和32之间的值作为level数组的大小,这个大小就是层的“高度”。 节点层高确定之后便不会在修改。
8 hash(字典)
8.1 hash使用场景
Redis
的hash
类型是一种用于存储字段和值之间映射关系的数据结构。每个hash
可以存储多个字段和相应的值,类似于一个键值对的集合。以下是一些适合使用Redis
的hash
类型的场景:
- 存储对象属性:适用于存储对象的属性。每个
hash
可以表示一个对象,字段表示对象的属性名称,值表示属性的值。例如,存储用户对象的属性。 - 缓存:当需要缓存一个复杂的数据结构时,
hash
类型可以用来存储缓存数据。每个hash
表示一个缓存项,字段表示缓存项的唯一标识,值表示缓存数据。 - 实时统计:适用于存储实时统计数据。例如,存储网站的页面浏览次数、用户在线时长等信息,每个
hash
表示一个统计项,字段表示统计项的名称,值表示统计值。 - 配置存储:可以将应用程序的配置信息存储在
hash
中,字段表示配置项的名称,值表示配置项的值。 - 存储嵌套数据:
hash
类型可以用于存储嵌套的数据结构,类似于JSON
。例如,可以存储文章的标题、内容、作者等信息。 - 存储用户购物车:适用于存储用户的购物车信息。每个
hash
表示一个用户的购物车,字段表示商品的ID
,值表示商品的数量。 - 存储表单数据: 当需要存储表单数据时,
hash
类型可以用来存储表单字段和对应的值。每个hash
表示一个表单项,字段表示表单字段的名称,值表示表单字段的值。 - 存储计数器:
hash
类型可以用于存储计数器。每个hash
表示一个计数项,字段表示计数项的名称,值表示计数值。
8.2 hash常用命令
HSET key field value
: 设置指定hash
中字段的值。HGET key field
: 获取指定hash
中字段的值。HDEL key field [field ...]
: 删除指定hash
中的一个或多个字段。HEXISTS key field
: 检查指定hash
中是否存在指定的字段。HLEN key
: 获取指定hash
中字段的数量。HKEYS key
: 获取指定hash
中所有字段的列表。HVALS key
: 获取指定hash
中所有字段的值的列表。HMSET key field value [field value ...]
: 设置指定hash
中多个字段的值。HMGET key field [field ...]
: 获取指定hash
中多个字段的值。HINCRBY key field increment
: 将指定hash
中字段的值增加指定的整数。HINCRBYFLOAT key field increment
: 将指定hash
中字段的值增加指定的浮点数。HSETNX key field value
: 当指定hash
字段不存在时,设置字段的值。HGETALL key
: 获取指定hash
中所有字段和值的列表,返回一个关联数组。HSCAN key cursor [MATCH pattern] [COUNT count]
: 对hash
进行增量式迭代,返回匹配给定模式的元素。
8.3 hash特点及底层实现
Redis
的hash
类型是一种用于存储字段和值之间映射关系的数据结构。每个hash
可以存储多个字段和相应的值,类似于一个键值对的集合。以下是hash
类型的特点:
- 字段-值映射:
hash
类型用于建立字段和值之间的映射关系,类似于一个小型的散列表。 - 灵活的字段数:
hash
类型中可以存储多个字段,每个字段都有一个对应的值,不同hash
对象之间的字段数可以不同。 - 高效的查找和修改:由于采用了散列表的结构,
hash
类型支持在常数时间内进行字段的查找、修改和删除操作。 - 适合存储对象属性:
hash
类型适合存储对象的属性,每个字段表示对象的一个属性,值表示属性的值。 - 适合存储小数据集:当存储的字段数较少时,
hash
类型比使用多个hash
类型更经济、更高效。
hash
类型底层实现原理:
Redis
的hash
类型底层是使用了两种不同的数据结构来实现:ziplist
(压缩列表)和hashtable
(哈希表)。
- ziplist(压缩列表):当
hash
类型的字段数量较少且字段名和值都是短字符串时,Redis
会使用ziplist
来存储数据。ziplist
是一种紧凑的、有序的数据结构,将字段和值按照一定的方式进行打包。ziplist
适用于小型的hash
,它可以节省内存。 - hashtable(哈希表):当
hash
类型的字段数量较多或字段名和值是长字符串时,Redis
会使用hashtable
来存储数据。hashtable
是一种典型的散列数据结构,用于支持更大规模的hash
对象。
通过根据hash
对象的大小和特点选择合适的底层数据结构,Redis
在实现hash
类型时可以充分发挥不同数据结构的优势,以提供高效的性能和内存使用。
8.4 Hash存数据时大Key的问题
Redis
中的"大Key
"问题是指存储在Redis
中的单个键(Key
)所关联的数据非常大,导致一些性能和管理方面的问题。这种情况可能会对Redis
的性能、内存使用和数据管理产生负面影响。以下是大Key
问题可能带来的一些问题和解决方法:
产生原因:
- 内存消耗: 大
Key
会占用大量的内存空间,如果大量的内存被用于存储少量的大Key
,会导致内存资源的浪费。 - 影响性能: 对于大
Key
的读写操作可能会影响Redis
的性能,因为读写一个大Key
可能会耗费更多的时间。 - 数据迁移问题: 在
Redis
的集群环境中,数据可能会在节点之间进行迁移。如果大Key
的迁移频繁发生,会增加网络开销和延迟。
解决方法:
- 分割大
Redis
: 将一个大Redis
拆分成多个小Redis
,每个小Redis
存储其中的一部分数据。例如,如果要存储一个大的JSON
对象,可以将其拆分为多个字段存储在不同的小Key
中。 - 压缩数据: 对于存储在大
Key
中的数据,可以考虑对数据进行压缩,以减少内存占用。 - 分片: 如果大
Key
无法避免,可以考虑将数据分散存储在多个小Key
中,以减少单个Key
的大小。 - 使用数据结构: 根据数据的特点,选择适当的数据结构来存储,例如将大数据拆分成多个小的有序集合(
Zset
)或列表(List
)来存储。 - 设置合适的过期时间: 如果大
Key
是临时性的数据,可以设置适当的过期时间,确保数据不会长时间占用内存。 - 定期清理: 对于不再需要的大
Key
,应该及时进行删除操作,以释放内存。
总结:
避免大Key
问题可以通过合理的数据设计、数据拆分、数据压缩以及定期的数据管理来实现。在设计数据存储方案时,需要综合考虑数据的大小、访问频率和生命周期等因素,以便有效地管理数据并确保Redis
的性能和资源利用率。
9 几种数据类型总结
9.1 写入数据的时间复杂度
- String(字符串):写入和更新字符串数据的时间复杂度都是常数时间
O(1)
。 - List(列表):在列表的头部或尾部添加元素的时间复杂度是常数时间
O(1)
。在其他位置插入操作的时间复杂度是线性的,取决于列表的长度,所以是O(N)
。 - Set(集合):平均情况下,添加元素到集合的时间复杂度是常数时间
O(1)
。但是,最坏情况下可能是O(n)
,其中n
是集合中元素的数量,这是因为Redis
在集合元素数量增加时会进行扩容。 - Sorted Set(有序集合):平均情况下,添加和删除元素的时间复杂度是常数时间
O(1)
。但是在移除并返回分数最低的成员时是O(log N)
。 - Hash(哈希):平均情况下,添加或更新字段到哈希的时间复杂度是常数时间
O(1)
。但是,在最坏情况下,也就是一次性添加大量的字段到hash
时是O(n)
,其中n
是添加到哈希中字段的数量,这是因为Redis
在哈希字段数量增加时会进行扩容。
需要注意的是,上述时间复杂度都是平均情况下的估算,实际的性能可能会受到多种因素的影响,如数据大小、服务器负载、内存管理策略等。在实际使用中,可以根据具体的应用场景和需求,选择合适的数据类型来存储数据,并综合考虑数据的读写性能。
参考1:redis数据类型(5种)和底层实现
参考2:redis数据类型底层实现
9.2 ziplist(压缩列表)
list
列表底层也有使用压缩列表的数据结构,它是通过紧密相连的节点来存储元素,来达到节约内存的目的,有序集合和hash
对象使用压缩列表实现的数据构造类似,都是相邻两个节点来存储一个元素的信息,只不过hash
存储的是键值对,相邻两个节点分别存储hash
对象的键和值,保存键的节点在前, 保存值的节点在后,两两一组。而zset
存储的是元素值和排序用的double
分数,数据结构图如下:
9.3 skiplist(跳表)
跳表全称为跳跃列表,是一个允许快速查询,插入和删除的有序数据链表。跳跃列表的平均查找和插入时间复杂度都是O(logn)
。快速查询是通过维护一个多层次的链,通过有限次范围查找来实现的,它的效率和红黑树不相上下,但是实现原理相对于红黑树来说简单很多。
跳跃表节点定义:
typedef struct zskiplistNode {
//成员对象
robj *robj;
//分值
double score;
//后退指针
struct zskiplistNode *backward;
//层级信息数组,用来实现跳跃,对应于下图的L1、L2、L3…每个节点所拥有的层级信息是随机的
struct zskiplistLevel {
/* 对应level的下一个节点 */
struct zskiplistNode *forward;
/* 从当前节点到下一个节点的跨度 */
unsigned int span;
} level[];
} zskiplistNode
跳表定义:
typedef struct zskiplist {
//跳跃表的头结点和尾节点,通过这两字段+zskiplistNode的backward和zskiplistLevel的forward可以实现正向和反向遍历
struct zskiplistNode *header, *tail;
//当前跳跃表的长度,保留这个字段的主要目的是可以以O(1)复杂度内获取跳跃表的长度
unsigned long length;
/* 跳跃表的节点中level的最大值。但是不包括头结点,头结点包含所有的层级信息—ZSKIPLIST_MAXLEVEL = 32。level的值随着跳跃表中节点的插入和删除随时动态调整 */
int level;
} zskiplist;
结构图如下:
Redis
使用跳表存储集合元素和分值的同时,还使用字典来保持元素和分值的映射关系,利用字典的特性使zscord()
的实现只有O(1)
复杂度。其定义如下:
typedef struct zset {
//数据字典:用来存储元素和分数的对应关系
dict *dict;
//跳表:用来存储集合元素及元素直接的跳跃关系
zskiplist *zsl;
} zset;
9.4 hashtable(字典)
Redis
中的hashtable
跟Java
中的HashMap
类似,都是通过"数组+链表"的实现方式解决部分的哈希冲突。
字典的定义
typedef struct dict {
//类型特定函数,可以根据所存数据类型的不同,指向不同的数据操作实现
dicType **type;
/私有数据,配合type使用
void *privdate;
//hash表
dictht ht[2];
//rehash索引,当不进行rehash时值伟-1
int trehashidx;
}dict;
hash表:
typedef struct dictht {
//哈希表数组
dicEntry **table;
//哈希表大小
unsigned long size;
//已有节点数量
unsigned long used;
}dictht;
hash表节点:
typedef struct dicEntry {
//键
void *key
//值
union{
void *val;
}v;
//下一个节点指向
struct dicEntry *next;
}dicEntry;
Redis
的Hash
的字典的结构图:
10 Redis的AOF和RDB
10.1 Redis缓存宕机如何处理
- 监控和警报系统:设置监控和警报系统,以便在
Redis
出现故障时及时收到通知。这可以帮助快速采取行动,减少停机时间。 - 备份和恢复:定期备份
Redis
数据是一种好的实践。如果发生宕机,可以使用备份数据来恢复Redis
的状态。确保备份是定期进行的,并测试备份的可恢复性。 - 高可用架构:考虑使用
Redis
的高可用架构,如主从复制或Redis
集群。主从复制允许在主Redis
实例宕机时使用从实例继续提供服务。Redis
集群则可以将数据分布在多个节点上,提高可用性和性能 - 持久化策略:
Redis
提供了两种主要的持久化方式,分别是RDB
快照和AOF
日志。通过配置适当的持久化策略,可以在宕机发生时最大程度地减少数据丢失。 - 故障转移:如果使用主从复制,当主节点宕机时,可以手动或自动地将从节点提升为新的主节点。这将允许继续提供服务,同时可以修复原始主节点。
- 负载均衡:如果使用
Redis
集群,可以通过负载均衡将请求分发到不同的Redis
节点上。这有助于减轻单个节点宕机的影响。 - 监控和分析:使用监控工具来实时监测
Redis
的性能和健康状况。这将能够预测问题并采取措施,以防止宕机。 - 容错处理:在应用程序代码中实现适当的容错处理。如果
Redis
缓存不可用,应用程序应该能够从备用数据源获取数据或以某种方式继续运行,而不会导致严重故障。
10.2 RDB机制
RDB(Redis Database Backup)
是Redis
默认的持久化方式,它是在指定的时间间隔内把数据以快照的形式保存在磁盘上,实际是写入到二进制文件中,默认的文件名为dump.rdb
。
- 可以在配置文件中设置触发
RDB
快照的条件,例如每隔一段时间或在达到一定的写入操作次数后。 - 当触发条件满足时,
Redis
会fork
一个子进程。这个子进程负责生成RDB
文件,这个RDB
文件是一个二进制文件,包含了当前内存中的数据快照。 - 在子进程生成
RDB
文件的过程中,Redis
主进程仍然继续处理命令请求。 - 一旦
RDB
文件生成完毕,Redis
会用新的RDB
文件覆盖旧的RDB
文件,以实现数据持久化。 - 在
Redis
重启时,它会尝试加载最新的RDB
文件,将数据恢复到内存中。
在安装了
Redis
之后,所有的配置都是在redis.conf
文件中,里面保存了RDB
和AOF
两种持久化机制的各种配置。
RDB
机制的触发时机有两个:手动触发、自动触发。
10.2.1 手动触发
手动触发是通过向Redis
发送特定的命令来手动触发RDB
快照生成,比如使用save
或bgsave
命令。
但是使用save
命令会阻塞当前Redis
服务器,因为它会在生成RDB
文件时暂停处理其他命令请求,直到RDB
过程完成为止。如果存在旧的RDB
文件,一旦新的RDB
文件生成完毕,Redis
会用该文件覆盖旧的RDB
文件,以实现数据持久化。
连接Redis
服务器的客户端可能都是几万或者是几十万,这种方式会有性能问题,不推荐使用。
10.2.2 自动触发
自动触发是通过配置文件来实现的。在redis.conf
配置文件中,里面有触发save
命令的配置,可以去设置。
比如“save m n
”。表示m
秒内数据集存在n
次修改时,自动触发RDB
快照。
默认如下配置:
save 900 1
:表示900秒内如果至少有1个key
的值变化;save 300 10
:表示300秒内如果至少有10个key
的值变化;save 60 10000
:表示60秒内如果至少有10000个key
的值变化;
可以同时配置多个。如果不需要自动持久化,可以注释掉所有的save
行来停用保存功能。
其他参数:
stop-writes-on-bgsave-error
:默认值为yes
。当启用了RDB
且最后一次后台保存数据失败,Redis
是否停止接收数据。这会让用户意识到数据没有正确持久化到磁盘上,否则没有人会注意到灾难(disaster
)发生了。如果Redis
重启了,那么又可以重新开始接收数据了。rdbcompression
:默认值是yes
。对于存储到磁盘中的快照,可以设置是否进行压缩存储。rdbchecksum
:默认值是yes
。在存储快照后,我们还可以让Redis
使用CRC64
算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。dbfilename
:设置快照的文件名,默认是dump.rdb
。dir
:设置快照文件的存放路径,这个配置项一定是个目录,而不能是文件名。
10.2.3 主从复制刚开始时触发
当Redis
的从节点与主节点进行初次同步(复制)时,主节点会生成一个RDB
快照,并将该快照发送给从节点。这有助于加速从节点的初始化同步过程。
10.2.4 save和bgsave命令的区别
save
:命令会阻塞当前Redis
服务器,因为它会在生成RDB
文件时暂停处理其他命令请求,直到RDB
过程完成为止。如果存在旧的RDB
文件,一旦新的RDB
文件生成完毕,Redis
会用该文件覆盖旧的RDB
文件,以实现数据持久化。bgsave
:执行该命令时,Redis
会在后台异步进行快照操作,快照同时还可以响应客户端请求。
具体操作是Redis
进程会执行fork
操作创建子进程,RDB
持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork
阶段,一般时间很短。基本上Redis
内部所有的RDB
操作都是采用bgsave
命令。
10.2.5 RDB的优缺点
优点:
- 紧凑的文件格式:
RDB
文件是一个紧凑的二进制文件,包含了整个Redis
数据库的数据快照。这使得RDB
文件适用于备份和迁移数据,以及在恢复时占用较小的存储空间。 - 恢复速度快:由于
RDB
文件是一个完整的数据快照,恢复速度通常比AOF
日志文件要快。这对于在灾难恢复情况下尤为重要。 - 适合冷备份:
RDB
适合用于生成冷备份,可以在需要备份时手动触发RDB
生成。这有助于在数据库状态稳定的情况下进行备份,以避免备份过程中的数据变动。 - 性能较低的环境下更友好:
RDB
生成过程可以在Redis
子进程中进行,这有助于减轻主进程的负担,使其在高负载或性能较低的环境下更加稳定。
缺点:
- 数据丢失风险:由于
RDB
是基于时间或写入操作次数来触发的,如果在最近一次RDB
生成和Redis
宕机之间发生了数据写入,那么这部分数据将会丢失。 - 不适合持续更新:
RDB
适用于周期性的数据快照,而不适合需要实时持续更新的应用。由于生成RDB
文件会导致Redis
主进程在一段时间内阻塞,这可能会影响应用程序的性能。 - RDB适应性差:
RDB
文件仅包含数据快照,如果Redis
在上次RDB
生成和宕机之间崩溃,那么AOF
日志文件将会是更可靠的数据恢复来源。 - 文件变大:随着时间的推移,
RDB
文件会变得相对较大,因为它包含了所有数据的快照。这会影响备份和恢复的速度。
10.3 AOF机制
AOF(Append-Only File)
是Redis
的另一种持久化机制,因为RDB
全量备份是比较耗时的,所以Redis
提供一种更加高效的方式AOF
,工作机制就是Redis
会将每一个收到的写命令都通过write
函数追加到日志文件中,以实现数据的持久化。AOF
机制相对于RDB
机制,更加持久,允许在Redis
重启时通过重新执行写入操作来恢复数据。
10.3.1 文件重写原理(针对AOF文件比较大的情况,Redis做的优化)
AOF
的方式也同时带来了另一个问题。持久化文件会变的越来越大。为了压缩AOF
的持久化文件。Redis
提供了bgrewriteaof
命令。将内存中的数据以命令的方式保存到临时文件中,同时会fork
出一条新进程来将文件重写。
具体优化:
AOF
重写:Redis
引入了AOF
重写机制,它允许在不中断服务的情况下创建一个新的AOF
文件,其中只包含从数据库重启后的写入操作。这可以通过运行一个后台进程来完成,该进程分析现有AOF
文件,然后根据数据库的当前状态重写新的AOF
文件。这样可以减小AOF
文件的大小并且不会影响性能。AOF
重写触发: 可以通过配置auto-aof-rewrite-percentage
和auto-aof-rewrite-min-size
参数来触发AOF
重写。当AOF
文件的大小达到auto-aof-rewrite-min-size
并且文件增长量超过了当前文件大小的auto-aof-rewrite-percentage
时,Redis
会自动触发AOF
重写。AOF
重写后合并: 在AOF
重写过程中,如果发现有多条命令对同一个键执行了多次操作,那么只保留最终状态的写入操作,从而避免重复写入导致的数据冗余。- 压缩指令: 在
AOF
文件中,Redis
可以使用一些优化技术来减小指令的存储空间,从而降低AOF
文件的大小。
10.3.2 AOF也有三种触发机制
- always(始终同步):
Redis
会将每次执行的操作命令追加到AOF
文件中,从而确保每次操作都被持久化到磁盘。这是最高级别的数据保护,但也会产生显著的性能开销,因为每个操作都需要同步到磁盘。
在配置文件中配置方式:appendfsync always
- everysec(每秒同步):
Redis
会每秒执行一次,将已缓冲的操作命令同步追加到AOF
文件。这种方式在性能和持久性之间取得了平衡,一般来说,数据丢失的风险较小。
在配置文件中配置方式:appendfsync everysec
- no(异步):
Redis
将操作命令追加到AOF
文件,但不会主动将数据同步到磁盘,而是由操作系统来处理同步。这提供了最高的性能,但也导致了数据丢失的风险,因为操作命令可能会在写入到磁盘之前存在于内存中。
在配置文件中配置方式:appendfsync no
Redis
还支持自定义的appendfsync
值,可以在配置文件中指定一个时间间隔,例如appendfsync 100
,表示每隔100
毫秒将缓冲的写入操作同步到磁盘。
选择适当的AOF
触发机制取决于应用需求,需要根据性能和数据保护之间的权衡来进行选择。通常,everysec是一种常见的配置,因为它在大多数情况下可以提供良好的性能和可靠的持久性。 如果对性能要求非常高,并且可以容忍一些数据丢失风险,那么可以考虑使用no触发机制。
10.3.3 AOF的优缺点
优点:
- 高持久性:
AOF
记录了每个写入操作,因此相对于RDB
持久化,数据恢复的能力更强,数据丢失的风险更低。 - 数据重放:
AOF
文件可以被重新执行,使得在灾难恢复和故障转移的情况下更容易恢复数据。 - 可读性:
AOF
文件是一个文本文件,可以查看其内容,以了解Redis
的历史写入操作。 - 部分持久性: 根据
appendfsync
的设置,可以在性能和持久性之间做出权衡。 - 灵活性:
AOF
支持多种同步选项,可以根据应用需求调整性能和数据保护级别。
缺点:
- 文件大小: 随着时间的推移,
AOF
文件可能会变得很大,影响备份、迁移和恢复的速度。 - 写入延迟: 在
everysec
或no
模式下,数据可能会在一段时间内只存在于内存中,未持久化到磁盘,可能会导致数据丢失。 - 性能开销: 在较高的
appendfsync
设置下,AOF
持久化会对性能产生一定影响。 - 文件碎片:
AOF
文件可能会出现碎片问题,特别是在文件大小频繁增加和重写时,这可能会影响磁盘空间的利用率。 - 恢复速度: 在某些情况下,使用
AOF
恢复数据可能比使用RDB
恢复数据更慢,因为AOF
文件中的写入操作需要重新执行。
总结:
AOF
持久化适用于需要高持久性和较小数据丢失风险的情况。如果更关注性能和较短的恢复时间,可以考虑在适当情况下使用RDB
持久化。在实际应用中,可以根据业务需求和性能要求来选择适合的持久化机制或结合使用两种机制以达到平衡。
10.4 RDB和AOF的选择
选择的话,两者加一起才更好。因为两个持久化机制明白了,剩下的就是看自己的需求了,需求不同选择的也不一定,但是通常都是结合使用。有一张图可供总结:
RDB和AOF对比图:
11 Redis的主从复制
参考1:https://www.cnblogs.com/FatalFlower/p/15952843.html
11.1 主从复制介绍
Redis
的主从复制允许将一个Redis
服务器(主节点)的数据复制到多个其他Redis
服务器(从节点)上,以实现数据的分发、备份和高可用性。主从复制在许多场景下都非常有用,例如高可用、提高读取性能、数据备份和灾难恢复。
Redis主从复制的一般工作原理和一些关键概念:
- 主节点(Master):主节点是数据源,它负责接收客户端的写入操作,并将这些操作应用于自身的数据集。主节点将自己的数据变更记录在
AOF
日志或RDB
文件中,然后将这些变更通过网络发送给从节点。 - 从节点(Slave):从节点是主节点的复制品,它通过连接到主节点并接收主节点发送的写入操作变更,来保持自身的数据与主节点同步。从节点不能进行写入操作,只能处理读取请求,从而分担主节点的读取压力。
- 数据同步:从节点在初始连接时会执行全量复制(全量同步),获取主节点上的所有数据。之后,从节点会持续地从主节点获取增量变更(增量同步),以保持自身数据的一致性。
- 复制的类型:
- 全量复制(Full Resynchronization):在初始连接或数据丢失的情况下,从节点会进行全量复制,获取主节点上的所有数据。
- 增量复制(Incremental Replication):在全量复制之后,从节点会持续接收主节点的写入操作,保持数据的同步。
- 自动重连:如果从节点与主节点的连接中断,从节点会自动尝试重新连接,并从最近的数据点开始增量复制,以保持数据的一致性。
- 读写分离:由于从节点能够处理读取请求,主从复制也可以用于实现读写分离架构,将读请求分发到从节点,从而减轻主节点的负载。
- 高可用性:如果主节点发生故障,可以将其中一个从节点提升为新的主节点,实现故障转移,以确保服务的可用性。
总结:
主从复制为Redis
提供了高可用性、数据备份、负载均衡等优势。然而,需要注意的是,主从复制并不适用于所有场景,因为从节点仅能提供与主节点相同的数据,不能实现跨主节点的数据查询。
11.2 主从复制的目的及优点
主从复制(Master-Slave Replication
)是Redis
中一种重要的数据复制机制,其目的是将一个Redis
服务器(主节点)的数据实时复制到其他多个Redis
服务器(从节点),以实现数据的分发、备份、高可用性和负载均衡等目标。主从复制的优点如下:
- 高可用性:主从复制可以提高系统的可用性。当主节点出现故障时,可以将一个从节点升级为新的主节点,使系统能够继续提供服务,减少停机时间。
- 数据备份:从节点保存了主节点的数据的副本,因此可以在数据丢失、破坏或误删除时用作备份。这对于数据的恢复和业务连续性非常重要。
- 读写分离:从节点可以用于处理读取请求,从而分担主节点的负载。这有助于提高主节点的写入性能,并实现负载均衡。
- 数据分发:主从复制可以将数据在多个节点之间分发,这在分布式系统中非常有用。从而,各个节点可以提供就近的服务,减少网络延迟。
- 灾难恢复:如果主节点遭受硬件故障、数据损坏或其他灾难,从节点可以用作快速的灾难恢复手段。
- 升级和维护:当需要对主节点进行升级、维护或配置更改时,可以先将其降级为从节点,然后升级从节点。这样可以避免服务中断。
- 实时数据复制:主从复制可以实现实时的数据同步,从而保持从节点的数据与主节点的数据一致。
- 实验和分析:可以使用从节点来进行实验、分析和性能测试,而不影响主节点的生产环境。
虽然主从复制具有许多优点,但也需要注意它的一些限制。比如:从节点的数据与主节点的数据相同,不能进行跨主节点的数据查询。 此外,主节点出现故障时,进行故障转移可能需要一些手动操作,具体取决于配置和部署。 综合考虑业务需求和系统架构,决定是否采用主从复制以及如何配置和管理它。
11.3 主从复制的实现过程
Redis
的主从复制分为以下几个阶段:
- 连接阶段:从节点首先与主节点建立连接。从节点发送一个
SYNC
命令给主节点,表示它希望进行全量同步。 - 全量同步阶段:在连接建立后,主节点开始执行全量同步过程。
- 增量同步阶段: 一旦全量同步完成,从节点进入增量同步阶段。在此阶段,主节点会持续地将写入操作发送给从节点,从节点接收并执行这些操作,以保持数据的实时一致性。
- 持续同步和保持连接: 从节点持续地向主节点发送心跳信号,以保持连接活跃。如果连接断开,从节点会尝试重新连接,然后继续进行增量同步。
- 故障转移阶段(可选): 如果主节点发生故障,从节点可以被提升为新的主节点,以实现故障转移和高可用性。
11.3.1 全量同步具体步骤
在Redis
主从复制中,全量同步(Full Resynchronization)是在从节点初始连接到主节点时所执行的过程,用于将主节点上的数据完整地传输到从节点,以确保从节点的数据与主节点的数据一致。以下是Redis
主从复制中全量同步的具体步骤:
- 从节点连接到主节点: 当从节点启动或重新连接时,它会发送一个
SYNC
命令给主节点,表示它希望进行全量同步。 - 主节点接受
SYNC
命令: 主节点收到从节点的SYNC
命令后,会准备进行全量同步。 - 主节点执行
BGSAVE
(可选): 主节点在开始全量同步之前,可以执行一个后台持久化操作,即执行BGSAVE
命令生成RDB
快照文件。这是为了确保在全量同步期间主节点的数据不会发生大的变化。 - 生成
RDB
文件: 如果主节点执行了BGSAVE
,它会生成一个RDB
快照文件,其中包含了当前主节点的数据。 - 发送
RDB
文件和AOF
偏移量: 主节点将生成的RDB
文件发送给从节点,并发送当前的AOF
偏移量,表示数据变更在AOF
日志中的位置。 - 从节点载入
RDB
文件: 从节点接收到RDB
文件后,会载入该文件,将其中的数据加载到自己的内存中。 - 发送
PSYNC
命令: 从节点完成RDB
文件的载入后,会向主节点发送PSYNC
命令,携带上次复制的偏移量信息,以告知主节点从哪个位置开始进行增量同步。 - 主节点进行增量同步: 主节点接收到从节点的
PSYNC
命令后,根据携带的偏移量信息,开始将从该位置开始的写入操作发送给从节点。 - 从节点应用增量操作: 从节点接收并应用主节点发送的增量操作,以使从节点的数据与主节点的数据保持一致。
- 完成同步: 一旦从节点赶上主节点的数据,全量同步完成。从节点现在是主节点的复制品,并可以处理读取请求。
需要注意的是,全量同步是一个初始过程,仅在从节点刚连接到主节点时执行一次。之后,主节点会持续地将增量写入操作发送给从节点,以保持数据的同步。全量同步的过程确保了从节点的数据与主节点的数据一致性,并为之后的增量同步奠定了基础。
11.4 Redis如何保证主从的一致性
- 全量同步:在主从复制刚开始时,从节点会进行全量同步。主节点会将自己的整个数据集发送给从节点,确保从节点的数据与主节点一致。
- 增量同步:全量同步完成后,主节点会将写入操作记录在
AOF
日志中,并将这些操作发送给从节点。从节点会持续地接收并执行这些写入操作,以保持数据一致性。 - 命令复制:从节点接收到主节点的写入操作后,会将这些操作执行到自己的数据集中。这意味着从节点上的数据会按照主节点的操作顺序进行更新,从而保持一致性。
- 复制偏移量:主节点会记录每个写入操作的偏移量,表示该操作在
AOF
日志中的位置。从节点会定期向主节点发送自己的复制偏移量,以确保从正确的位置进行增量同步。 - 断点续传:如果从节点与主节点的连接中断,从节点会尝试重新连接并从断点位置继续同步。这避免了重复同步已经应用的数据。
- 异步复制:主从复制默认是异步的,主节点不会等待从节点应用写入操作。这可能导致从节点的数据略有滞后,但不影响一致性。
需要注意的是,尽管Redis
通过上述机制来维护主从数据的一致性,但在某些情况下,由于网络延迟、故障等原因,从节点的数据可能会稍微滞后于主节点。因此,在主从复制中,不同节点之间的数据同步是一个近似一致性的概念,而不是强一致性。如果需要更严格的一致性,可以考虑使用Redis
的哨兵机制或者集群模式。
11.5 Redis主从复制为什么不使用AOF?
主从复制中的AOF(Append-Only File)
持久化和主节点的AOF
持久化是两个不同的概念。在主从复制中,主节点的AOF
持久化仍然可以被启用,但在从节点上通常会禁用AOF
持久化。
为什么在从节点上不使用AOF
持久化:
- 数据同步的方式:主从复制的主要目的是将主节点的数据同步到从节点,以实现数据的分发、备份和高可用性。从节点通过执行与主节点一样的写入操作来实现数据同步,因此不需要
AOF
文件来记录写入操作。 - 数据保护和持久化:从节点的数据是通过执行主节点的写入操作来同步的,而不是通过
AOF
文件。从节点并不直接处理客户端写入请求,因此不需要保留AOF
文件来确保数据的持久性和恢复能力。 - 性能和延迟:从节点的主要任务是处理读取请求,它需要尽可能快速地响应这些请求。启用
AOF
会增加写入操作的开销,可能会影响从节点的读取性能,并且可能引入额外的写入延迟。 - 节省空间:从节点的
AOF
文件不会被用于数据恢复,因为它的数据是通过复制而来。因此,禁用从节点的AOF
可以节省存储空间。 - 简化配置和管理:禁用从节点的
AOF
可以简化配置和管理,减少潜在的错误和混淆。
虽然在从节点上禁用AOF
持久化是常见的做法,但这不是硬性规定。如果应用场景需要从节点也进行AOF
持久化,仍然可以在从节点上启用AOF
。然而,在主从复制的情况下,AOF
文件在从节点上通常不会被使用,因为数据同步是通过主节点发送写入操作来实现的。
12 Redis哨兵机制
Redis
哨兵(Sentinel
)是用于监控和管理Redis
主从复制和高可用性的系统。它可以自动检测主节点故障,进行故障转移,并重新配置从节点以提供高可用性的服务。
哨兵机制是Redis
高可用性架构的一部分,允许在主节点发生故障时实现自动故障转移,以确保服务的连续性。
哨兵节点本质上也是一个Redis
节点,但是和主节点和从节点不同,哨兵节点只是监视主节点和从节点,并不执行相关的业务操作。
12.1 哨兵机制的工作原理
- 故障检测:哨兵会定期向
Redis
的主节点和从节点发送心跳检测,以监测它们的状态。如果一个节点不再响应心跳,哨兵首先会将其标记为"主观下线"。 - 选举新主节点: 当主节点被标记为"主观下线"后,哨兵会通过
SENTINEL is-masterdown-by-addr
指令获取其它哨兵节点对于当前主节点的判断情况,如果哨兵节点对于当前主节点的主观下线判断数量超过了在配置文件中定义的票数,那么该主节点就被判定为“ODOWN”(客观下线)
。然后与其他哨兵进行投票,以选择一个从节点升级为新的主节点。 - 自动故障转移: 一旦新主节点被选出,哨兵会执行自动故障转移过程。主要为以下步骤:
- 哨兵会向其他从节点发送命令,让它们将复制目标从旧主节点切换到新主节点。
- 哨兵会更新客户端的配置,将连接指向新的主节点。
- 哨兵会更新所有相关节点的配置,以确保节点间的通信正确地指向新主节点。
- 数据同步: 一旦新主节点选举出来,从节点会向新主节点进行全量同步,以获取丢失的数据并保持一致性。
- 故障恢复: 当故障转移完成后,整个集群会恢复正常运行。新的主节点将负责处理客户端的写入请求,而从节点将继续处理读取请求。
- 监控和报警: 哨兵会持续监控集群的状态,如内存使用情况、连接数等,它还可以配置报警,在发生故障转移或其他问题时触发报警,以便在出现问题时通知管理员。
- 多哨兵支持: 在大规模的系统中,可以部署多个哨兵实例来提高监控和决策的可靠性。
由于使用单个的哨兵来监视Redis
集群的节点不是完全可靠的,因为哨兵节点也有可能会出现故障,所以一般情况下会使用多个哨兵节点来监视整个Redis
集群,如下图所示:
当存在多个哨兵
节点时,在Redis
哨兵机制中,对于节点的下线也有区分:
- 主观下线(Subjectively Down,即 SDOWN):指单个哨兵节点对集群中的节点作出主观下线判断。
- 客观下线(Objectively Down,即 ODOWN):指多个哨兵节点对集群中的节点作出主观下线判断,并且通过
SENTINEL is-master-down-by-addr
命令互相交流之后,作出Redis
节点下线的判断。一个哨兵节点可以通过向另一个哨兵节点发送SENTINEL is-master-down-by-addr
命令来询问对方是否认为给定的节点已经下线。
需要注意的是,哨兵机制的目标是实现集群的高可用性,确保在主节点故障时仍然能够提供服务。但哨兵机制并不是完美的,因此在使用过程中需要正确配置和管理,以确保其能够正常工作。另外,考虑使用Redis
的集群模式也是实现高可用性的一种选择。
12.2 Redis哨兵机制中哨兵节点个数设置原则
- 奇数个节点:哨兵节点的数量通常选择奇数个,以确保在进行选举和决策时能够达成多数共识。奇数个节点能够更好地处理节点故障、拆分等问题,避免出现僵局情况。
- 至少三个节点:最少应该配置三个哨兵节点,以确保能够实现故障检测和自动故障转移。两个节点可能会导致选举过程中出现不确定性。
- 节点分布:哨兵节点应该分布在不同的物理机器或虚拟机上,以确保即使一部分节点出现故障,仍然能够保持足够的监控和故障检测能力。
- 偶数节点不推荐:避免使用偶数个哨兵节点,因为在故障情况下可能出现投票平局,导致选举无法达成多数共识。
- 多于三个节点:在生产环境中,通常建议配置多于三个哨兵节点,例如五个或七个。多个哨兵节点可以提高监控的可靠性,减少误判和误操作。
- 考虑资源和维护:哨兵节点也会消耗资源,包括内存和网络带宽。在设置节点数量时,考虑可用的资源和维护成本。
总结:
设置哨兵节点的数量需要权衡可靠性、资源消耗和管理复杂性。选择适当的奇数个哨兵节点,并确保它们分布在不同的位置,以实现高可用性、故障检测和自动故障转移的目标。
12.3 在从节点中选出新的主节点是随机的吗
不是,哨兵在进行主节点故障转移时,选出新的主节点是经过一定的投票和共识过程的,以确保选出的节点是合适的、可用的,并且能够保持数据一致性。
在投票过程中,哨兵会考虑以下因素:
- 健康状态:哨兵会选择一个被标记为"主观上线"(即正常状态)的从节点作为新的主节点。被标记为"主观下线"的节点不会参与选举。
- 复制偏移量:哨兵会考虑每个从节点的复制偏移量,即从节点在
AOF
日志中的位置。复制偏移量较大的从节点可能更适合被选为新主节点,以确保数据的一致性。 - 投票数:哨兵会向其他哨兵发送投票请求,然后会根据收到的投票数来决定是否选举某个从节点为新主节点。
- 优先级:在一些情况下,可以为从节点配置不同的优先级。优先级高的从节点可能更有可能被选为新的主节点。
- 其他因素:哨兵还可以考虑其他因素,如节点的配置、性能等。
13 缓存击穿、缓存雪崩、缓存穿透区别及解决方案
13.1 缓存击穿
Redis
的缓存击穿是一种性能问题,通常发生在具有高并发访问的系统中。它的产生原因是在缓存中存储了某个热点数据,但在某一时刻,大量并发请求同时访问该热点数据,而此时该热点数据的缓存数据刚好过期或被删除,导致每个请求都需要重新查询数据库或重新生成数据,从而导致数据库负载剧增,系统性能急剧下降。
- 产生原因:缓存击穿的情况,经常是发生在热点数据过期失效的情况。
- 解决方案:
- 对于访问很频繁的热点数据,就不需要设置过期时间了。这样对热点数据的访问可以直接在缓存中进行处理,
Redis
的数万级别的高吞吐量可以很好的应对大量的并发请求。 - 使用布隆过滤器(Bloom Filter):布隆过滤器可以用来判断某个数据是否存在于缓存中,可以在查询缓存之前快速排除不存在于缓存中的数据,减少对数据库的请求。
- 使用缓存预热(Cache Warming):在系统启动或数据更新之前,可以通过预先加载常用数据到缓存中,以减少冷启动时的缓存击穿风险。
13.2 缓存雪崩
Redis
的缓存雪崩通常发生在大规模缓存中。它指在某一时刻,大量的缓存数据同时失效或被清除,导致大量请求同时落到数据库上,从而引起数据库负载急剧增加,甚至引发系统崩溃。
- 产生原因:产生原因一般有两种情况:
- 缓存中有大量的数据同时过期,导致请求无法得到处理。当多个缓存键的失效时间(过期时间)设置得非常接近时,它们可能在同一时刻失效,导致大量请求同时落到数据库上。
Redis
缓存实例发生故障,宕机了,导致大量请求积压到数据库。一般来说,一个Redis
实例可以支持数万级别的请求处理吞吐量,而单个数据库可能只能支持数千级别的请求处理吞吐量,它们两个的处理能力可能相差了近十倍。由于缓存雪崩,Redis
缓存失效,所以,数据库就可能要承受近十倍的请求压力,从而因为压力过大而崩溃。
- 解决方案:
针对缓存中有大量的数据同时过期的情况,可以提供两种解决方案。
- 随机失效时间:给数据的到期时间增加一个较小的随机数,避免给大量的数据设置相同的过期的时间。
- 使用缓存预热(Cache Warming):在系统启动或数据更新之前,可以通过预先加载常用数据到缓存中,以减少冷启动时的缓存击穿风险。
- 服务降级:当发生缓存雪崩时,针对不同的数据采取不同的处理方式。
① 非核心数据:暂时停止从缓存中查询这些数据,而是直接返回预定义信息、空值或者是错误信息。
② 核心数据:缓存数据丢失时通过数据库读取。
使用服务降级的方式,只有部分的数据请求会被发送到数据库,则数据库的压力就没有那么大了。 - 监控和报警:实施监控系统,定期检查缓存的状态,当发现缓存出现异常或过期集中时,及时发出警报并采取措施,以防止缓存雪崩。
针对Redis缓存实例发生故障宕机的情况,同样也有两点建议。
- 在业务系统中实现服务熔断或者请求限流机制:
① 服务熔断:在发生缓存雪崩时,为了防止引发连锁的数据库雪崩,甚至是整个系统的崩溃,可以暂停业务应用对缓存系统的接口访问。
② 请求限流:在请求入口前端只允许每秒进入系统的请求数为1000个,再多的请求就会在入口前端被直接拒绝服务。 - 提前预防:通过主从节点的方式构建
Redis
缓存高可靠集群。如果Redis
缓存的主节点故障宕机了,从节点还可以切换成为主节点,继续提供缓存服务,避免了由于缓存实例宕机而导致的缓存雪崩问题。
13.3 缓存穿透
缓存穿透指的是要访问的数据既不在Redis
中,也不在数据库中,导致请求访问缓存缓缺失。这样一来应用无法从数据库中读取写入缓存,缓存成了摆设,同时给数据库和缓存都带来巨大的压力。
- 产生原因:
- 业务层误操作:缓存中的数据和数据库中的数据被误删除了,所以缓存中和数据库中都没有数据。
- 恶意攻击:恶意用户或攻击者可能会故意发送查询不存在的数据的请求,以耗尽系统资源或尝试破坏应用程序。
- 解决方案:
- 缓存空值或者缺省值:一旦发生缓存穿透,可针对查询的数据在
Redis
缓存中设置一个短期的空值或者缺省值,当应用发送后续请求进行查询的时候就可以从Redis
中读取到空值或者缺省值返回,避免大量请求数据库。 - 使用布隆过滤器快速判断数据是否存在,避免从数据库中查询数据,减轻数据库压力:通过查询布隆过滤器快速判断数据是否存在,如果不存在就不需要再去数据库中查询了。
- 在请求入口的前端进行请求检测:缓存穿透很大一部分原因是有大量的恶意请求访问不存在的数据,所以对业务系统接收到的请求进行合法性检测,把恶意的请求直接过滤掉,不让它们访问后端缓存和数据库。
- 使用缓存预热(Cache Warming):在系统启动或数据更新之前,可以通过预先加载常用数据到缓存中,以减少冷启动时的缓存击穿风险。
- 监控和报警:实施监控系统,定期检查缓存的状态,当发现缓存出现异常或过期集中时,及时发出警报并采取措施,以防止缓存穿透。
跟缓存雪崩、缓存击穿这两类问题相比,缓存穿透的影响更大一些。从预防的角度来说,我们需要避免误删除数据库和缓存中的数据;从应对角度来说,我们可以在业务系统中使用缓存空值或缺省值、使用布隆过滤器,以及进行恶意请求检测等方法。
13.4 布隆过滤器
布隆过滤器(Bloom Filter
)是一种用于高效判断一个元素是否属于一个集合的数据结构,它可以快速检查一个元素是否可能在集合中,但具有一定的概率性和容错性。布隆过滤器在一些应用中能够有效地减少不必要的查询,节省计算资源。
优点:
- 空间效率高:布隆过滤器使用位数组,相对于存储实际元素,它的空间消耗很小。
- 查询效率高:布隆过滤器的查询操作非常快速,只需计算哈希并检查位值。
- 支持大规模数据集:布隆过滤器适用于处理大规模的数据集,因为它的空间和时间复杂度都与数据集大小无关。
- 保密性强:因为布隆过滤器不存储元素本身。
缺点:
- 存在误判:布隆过滤器可以产生误判,即它可能认为元素在集合中但实际上不在,但不会产生漏判(如果它认为元素不在集合中,那么一定不在)。
- 无法删除元素:布隆过滤器中一旦将位设置为1,就无法删除元素。如果需要删除元素,通常需要使用其他技巧,如定时重建布隆过滤器。
- 无法获取元素本身。
工作原理: 布隆过滤器的核心是一个二进制向量数组和一组哈希函数。
- 初始化:创建一个长度为
m
的位数组,所有位都初始化为0。 - 添加元素:将待添加的元素通过多个哈希函数映射为位数组中的多个位置,并将这些位置的对应位设置为1。
- 查询元素:对待查询的元素使用相同的哈希函数,检查对应位置的位值。如果所有位置的位值都为1,说明元素可能存在于集合中;如果有任何一个位置的位值为0,说明元素一定不存在于集合中。
使用场景:
- 解决
Redis
缓存穿透问题。 - 做邮件的黑名单过滤。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Go语言工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Go语言全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Golang知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Go)
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
器不存储元素本身。
缺点:
- 存在误判:布隆过滤器可以产生误判,即它可能认为元素在集合中但实际上不在,但不会产生漏判(如果它认为元素不在集合中,那么一定不在)。
- 无法删除元素:布隆过滤器中一旦将位设置为1,就无法删除元素。如果需要删除元素,通常需要使用其他技巧,如定时重建布隆过滤器。
- 无法获取元素本身。
工作原理: 布隆过滤器的核心是一个二进制向量数组和一组哈希函数。
- 初始化:创建一个长度为
m
的位数组,所有位都初始化为0。 - 添加元素:将待添加的元素通过多个哈希函数映射为位数组中的多个位置,并将这些位置的对应位设置为1。
- 查询元素:对待查询的元素使用相同的哈希函数,检查对应位置的位值。如果所有位置的位值都为1,说明元素可能存在于集合中;如果有任何一个位置的位值为0,说明元素一定不存在于集合中。
使用场景:
- 解决
Redis
缓存穿透问题。 - 做邮件的黑名单过滤。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Go语言工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Go语言全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-KZZOQJCf-1712911563382)]
[外链图片转存中…(img-isU6hPNd-1712911563383)]
[外链图片转存中…(img-E6dGSduG-1712911563384)]
[外链图片转存中…(img-9j5Pk92l-1712911563384)]
[外链图片转存中…(img-VrmQW5ZE-1712911563385)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Golang知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Go)
[外链图片转存中…(img-ddwT3f8I-1712911563385)]
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!