截至当前日期:2023年8月28号,Linux
系统下最新版本的Redis
是7.2。
Linux
系统Redis
下载地址:https://redis.io/download
官方没有提供Windows
系统的Redis
,下载Windows
系统的Redis
:https://github.com/tporadowski/redis/releases
以下是来自GPT3.5的回答。
Redis
是将数据存储在内存中,而内存的读写速度远高于磁盘的读写速度。这使得Redis
能够快速地进行数据的读写操作。I/O
操作: Redis
使用了异步非阻塞的I/O
操作模型,通过使用事件循环机制(Event Loop
),在单个线程中管理多个客户端的请求。这种模型允许Redis
在同一时间处理多个并发请求,从而避免了线程切换的开销。Redis
提供了多种高效的数据结构,如字符串、哈希、列表、集合、有序集合等。这些数据结构在存储和操作数据时都经过了优化,使得Redis
在特定场景下能够以更少的操作完成更多的任务。Redis
的单线程模型避免了这些复杂性。这使得Redis
的代码更容易维护和调试,同时也减少了因为锁竞争而引起的性能问题。Redis
单线程模型避免了这种开销。虽然单线程模型本身也会存在性能瓶颈,但由于Redis
大部分操作是基于内存的,因此在绝大多数情况下,单线程模型的优势更大。Redis
提供了多种持久化方式,如RDB
快照和AOF
日志文件。虽然持久化操作会在某些情况下对性能产生影响,但用户可以根据自己的需求选择适合的持久化策略。需要注意的是,虽然 Redis
单线程模型在许多情况下表现出色,但也存在一些适用场景有限的情况。例如,当需要进行大量的计算密集型操作时,单线程模型可能会受到限制。此外, Redis
通过多实例的方式也可以在一定程度上实现并行处理多个请求。
以下是来自GPT3.5的回答。
Redis
的单线程模型避免了这些问题,使代码更加简洁,维护起来更容易。CPU
之间的平衡:Redis
主要用于高速数据存储和访问,而目前CPU
的速度已经远远超过了内存的速度。因此,在大多数情况下,Redis
遇到的瓶颈是在内存访问而不是CPU
计算上。通过使用单线程模型,Redis
能够充分利用CPU
的计算能力,而不会受到内存访问速度的限制。Redis
的设计目标之一是简单和可靠,选择单线程模型有助于降低系统的复杂性,使其更加稳定和易于维护。Redis
的单线程模型避免了这种开销,因为不需要频繁切换线程上下文。Redis
在单线程内使用异步非阻塞I/O
操作模型,可以在单个线程中同时处理多个客户端请求,从而提供了高并发能力。Redis
最初设计时是作为一个高性能的内存数据库和缓存系统,适用于数据存储、访问频繁、读写操作为主的场景。在这种场景下,单线程模型可以更好地发挥其优势。需要注意的是,尽管Redis
在很多情况下能够通过单线程模型实现高性能,但在某些特定场景,如大量的计算密集型操作,单线程模型可能会有一些限制。在这种情况下,可以考虑将计算部分从Redis
中分离出来,或者使用Redis
的多实例来实现并行处理。
以下是来自GPT3.5的回答。
Redis
的网络模型是基于事件驱动的、单线程的异步网络I/O
多路复用模型。以下是Redis
的网络模型的主要特点:
Redis
使用事件驱动架构来处理客户端请求和其他事件。这种架构通过一个事件循环(Event Loop
)来监听并处理不同类型的事件,如新连接、数据读取、数据写入等。Redis
主要运行在一个单独的线程中,负责处理所有的客户端请求和事件。这个线程不会被I/O
操作阻塞,而是通过非阻塞方式处理多个客户端的请求。Redis
使用多路复用技术来同时监听多个套接字(sockets
)。这种技术使得Redis能够在单个线程中同时处理多个客户端的连接和I/O
操作。Redis
的事件循环监听两种类型的事件。文件事件用于处理套接字的I/O
操作,例如连接、读取和写入。时间事件用于执行定时任务,例如定期执行某些操作。I/O
: Redis
的网络模型是非阻塞的,这意味着当一个客户端的请求需要进行I/O
操作时,Redis
不会阻塞整个线程,而是会继续处理其他客户端的请求,直到该I/O
操作完成。Redis
使用缓冲区来存储客户端发送的数据和要返回给客户端的数据。这种缓冲区机制有助于提高性能,避免频繁的数据复制操作。通过这种事件驱动的网络模型,Redis
能够在单线程下实现高并发和低延迟的数据处理能力。这种设计使得Redis
在处理高速数据存储和访问时表现出色,同时减少了多线程带来的复杂性和线程同步的问题。然而,需要注意的是,Redis
的单线程模型在某些特定的场景下可能会受到计算密集型任务的限制。
Redis
是Key-Value
的数据形式,所以Key
一般都是string
类型,主要是Value
分成5种数据类型。
Redis
中,以加速数据访问。例如,可以将数据库查询结果、计算结果或其他频繁访问的数据存储在Redis
中,以避免频繁查询数据库或执行复杂计算。string
类型可以用来实现计数器,例如用于统计网站的页面访问次数、用户点击次数等。通过使用Redis
的原子性操作(如INCR
和DECR
命令),可以实现高效的计数功能。string
类型来实现分布式锁。通过在Redis
中设置一个特定的键作为锁,并使用带有NX
参数的SET
命令来尝试获取锁,可以确保只有一个客户端能够获得锁。Web
应用中,可以使用string
类型来存储用户会话数据,例如用户登录信息、购物车内容等。这可以帮助实现快速的会话管理。string
类型可以用于实现简单的消息队列,通过将消息序列化为字符串并使用LPUSH
或RPUSH
命令将其添加到列表中,然后使用BRPOP
等命令进行消息的出队操作。Redis
中的string
类型可以存储任何类型的数据,包括序列化的对象。这在某些情况下可以用来存储应用程序状态或配置信息。Redis
提供了一些位操作命令,可以将string类型用作位图数据结构。这在一些应用场景中,如统计用户在线状态、活跃用户等,非常有用。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
: 获取键的值在指定偏移量上的位。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
中,字符数组的大小会比实际的字符串长度大,以便在需要进行追加操作时,不必频繁重新分配内存。
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
。
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
类型的命令。根据具体的应用场景和需求,可以选择适合的命令来操作和管理列表数据。
Redis
的list
类型是一个双向链表数据结构,用于存储有序的、可重复的元素列表。每个元素都可以在列表的头部或尾部进行插入、删除和访问操作。以下是list
类型的特点:
list
类型中的元素是有序的,每个元素都有一个相对位置。可以根据插入顺序来访问和操作元素。list
类型允许存储相同的元素,即使是重复的元素也会保留。list
类型实际上是一个双向链表,支持在列表的头部和尾部进行插入、删除操作。这使得头部和尾部操作的时间复杂度都为O(1)
。list
类型既可以作为队列使用(先进先出),也可以作为栈使用(先进后出)。list
类型底层实现:
Redis
的list
是一种基本的数据结构,它是一个有序的字符串元素集合。list
可以包含重复的元素,而且可以在list
的两端进行插入和删除操作。在底层,Redis
的list
是通过双向链表(doubly linked list)
和压缩列表(ziplist)
两种数据结构来实现的。
list
中的元素数量比较少且元素较小时,Redis
会使用压缩列表作为底层数据结构,这是一种紧凑的连续内存结构。O(1)
时间复杂度的尾部插入和弹出操作,但在中间插入或删除元素时可能会引发内存的重新分配和复制,因此效率较低。list
的元素数量较多或元素较大时,Redis
会选择使用双向链表作为底层数据结构,以便更好地处理插入、删除等操作。双向链表(doubly linked list)
和压缩列表(ziplist)
的切换Redis
会根据list
的当前大小和操作模式来自动选择使用压缩列表或双向链表。list
的元素数量增加时,Redis
可能会将压缩列表升级为双向链表,以便更好地处理大量元素。list
的元素数量减少时,Redis
可能会将双向链表降级为压缩列表,以节省内存。总结:
Redis
的list
底层实现原理涉及了压缩列表和双向链表这两种数据结构,根据元素数量和大小的不同,选择合适的数据结构来平衡性能和内存使用。这种灵活性使得Redis
的list
在不同场景下都能够高效地存储和处理数据。
Redis
的set
类型是一个无序的、不重复的数据集合。它可以存储多个不同的元素,但不允许重复。以下是一些适合使用Redis
的set
类型的场景:
set
,其中存储与该对象相关的标签。set
来存储用户之间的好友关系。每个用户对应一个set
,其中存储与该用户关联的好友。set
存储用户的兴趣爱好。比较两个用户的兴趣爱好集合,可以找到共同的兴趣爱好。set
中不允许重复的特性使得它适合用来存储唯一值,例如在线用户、IP
地址、手机号等。Redis
提供了一系列操作set
的命令,如求交集、并集、差集等。这些操作对于数据分析和数据处理非常有用。set
实现排他性操作,例如某个资源是否被占用,以及哪些用户正在使用它。ID
存储在一个set
中,通过SADD
和SREM
命令来维护用户的在线状态。IP
等)存储在set
中,用于实现黑名单或白名单功能。需要注意的是,虽然set
类型适合存储不重复的数据,但它不适合存储有序数据。如果需要有序性,可以考虑使用zset
(有序集合)类型。在选择数据类型时,需要根据具体的需求和场景来做出决策。
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]
: 随机移除并返回集合中一个或多个成员。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
这两种不同的数据结构来存储数据,以便根据元素类型和数量来选择合适的存储方式,以达到更好的内存效率和性能。
Redis
的有序集合(Zset
)是一种特殊的集合数据类型,它与普通集合相比,每个元素都有一个关联的分数(score
),并且元素按照分数的顺序排列。以下是一些适合使用Redis
的Zset
类型的场景:
Zset
可以用于实现排行榜,例如游戏中的玩家积分排名、音乐应用中的热门歌曲排名等。每个元素表示一个用户或歌曲,分数表示用户的积分或歌曲的播放次数。Zset
存储用户的关注关系,每个元素表示一个用户,分数表示关注时间。还可以使用Zset
存储用户的粉丝关系,分数表示粉丝关注时间。Zset
支持根据分数范围进行查询,这对于获取一定范围内的数据非常有用。例如,在时间轴上查询一段时间内的数据。Zset
来实现定时任务的调度。将任务的执行时间作为分数,任务内容作为成员,通过定时地获取分数小于当前时间的成员来执行任务。Zset
可以用于实现带有权重的投票系统,每个元素表示一个投票项,分数表示该投票项的权重。可以用来进行复杂的投票计算。Zset
的分数表示地理位置的坐标,可以实现基于距离的地理位置查询,比如查找附近的商店、用户等。Zset
的分数可以表示数据的过期时间,结合定时任务,可以实现一些带有过期时间的数据清理操作。需要注意的是,虽然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 ...]
: 计算给定的一个或多个有序集合的并集,并将结果存储在新的有序集合中。Redis
的有序集合(Zset
)是一种有序的、不重复的数据集合,每个元素都有一个与之关联的分数(score
),用于排序。以下是有序集合的特点:
O(log N)
,N
为元素数量。Zset
类型底层实现原理:
Redis
的Zset
底层实现实际上使用了两种数据结构来进行存储和索引:压缩列表(ziplist)
和跳跃表(skiplist)
。
Redis
使用压缩列表作为底层存储结构。压缩列表是一种紧凑的、高效的数据结构,适用于存储较小的数据。Redis
会使用跳跃表作为底层存储结构。跳跃表是一种有序数据结构,支持快速的插入、删除和范围查找操作,能够在较大数据量下保持良好的性能。这两种底层存储结构的选择是基于成员数量和成员值的大小来决定的,以便在不同场景下实现最佳的性能和内存占用。由于压缩列表适用于存储较小的数据,而跳跃表适用于存储较大的数据,因此Redis
会根据具体情况来做出选择。
参考1:Redis的数据结构之跳表
层高
节点的层高最小值为1,最大值是ZSKIPLIST_MAXLEVEL,Redis中节点层高的值为32。
zslRandomLevel函数
每次创建一个新跳跃表节点的时候,程序都会根据幂次定律(zslRandomLevel,越大的数出现的概率越小)随机生成一个介于1和32之间的值作为level数组的大小,这个大小就是层的“高度”。 节点层高确定之后便不会在修改。
Redis
的hash
类型是一种用于存储字段和值之间映射关系的数据结构。每个hash
可以存储多个字段和相应的值,类似于一个键值对的集合。以下是一些适合使用Redis
的hash
类型的场景:
hash
可以表示一个对象,字段表示对象的属性名称,值表示属性的值。例如,存储用户对象的属性。hash
类型可以用来存储缓存数据。每个hash
表示一个缓存项,字段表示缓存项的唯一标识,值表示缓存数据。hash
表示一个统计项,字段表示统计项的名称,值表示统计值。hash
中,字段表示配置项的名称,值表示配置项的值。hash
类型可以用于存储嵌套的数据结构,类似于JSON
。例如,可以存储文章的标题、内容、作者等信息。hash
表示一个用户的购物车,字段表示商品的ID
,值表示商品的数量。hash
类型可以用来存储表单字段和对应的值。每个hash
表示一个表单项,字段表示表单字段的名称,值表示表单字段的值。hash
类型可以用于存储计数器。每个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
进行增量式迭代,返回匹配给定模式的元素。Redis
的hash
类型是一种用于存储字段和值之间映射关系的数据结构。每个hash
可以存储多个字段和相应的值,类似于一个键值对的集合。以下是hash
类型的特点:
hash
类型用于建立字段和值之间的映射关系,类似于一个小型的散列表。hash
类型中可以存储多个字段,每个字段都有一个对应的值,不同hash
对象之间的字段数可以不同。hash
类型支持在常数时间内进行字段的查找、修改和删除操作。hash
类型适合存储对象的属性,每个字段表示对象的一个属性,值表示属性的值。hash
类型比使用多个hash
类型更经济、更高效。hash
类型底层实现原理:
Redis
的hash
类型底层是使用了两种不同的数据结构来实现:ziplist
(压缩列表)和hashtable
(哈希表)。
hash
类型的字段数量较少且字段名和值都是短字符串时,Redis
会使用ziplist
来存储数据。ziplist
是一种紧凑的、有序的数据结构,将字段和值按照一定的方式进行打包。ziplist
适用于小型的hash
,它可以节省内存。hash
类型的字段数量较多或字段名和值是长字符串时,Redis
会使用hashtable
来存储数据。hashtable
是一种典型的散列数据结构,用于支持更大规模的hash
对象。通过根据hash
对象的大小和特点选择合适的底层数据结构,Redis
在实现hash
类型时可以充分发挥不同数据结构的优势,以提供高效的性能和内存使用。
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
的性能和资源利用率。
O(1)
。O(1)
。在其他位置插入操作的时间复杂度是线性的,取决于列表的长度,所以是O(N)
。O(1)
。但是,最坏情况下可能是O(n)
,其中n
是集合中元素的数量,这是因为Redis
在集合元素数量增加时会进行扩容。O(1)
。但是在移除并返回分数最低的成员时是O(log N)
。O(1)
。但是,在最坏情况下,也就是一次性添加大量的字段到hash
时是O(n)
,其中n
是添加到哈希中字段的数量,这是因为Redis
在哈希字段数量增加时会进行扩容。需要注意的是,上述时间复杂度都是平均情况下的估算,实际的性能可能会受到多种因素的影响,如数据大小、服务器负载、内存管理策略等。在实际使用中,可以根据具体的应用场景和需求,选择合适的数据类型来存储数据,并综合考虑数据的读写性能。
参考1:redis数据类型(5种)和底层实现
参考2:redis数据类型底层实现
list
列表底层也有使用压缩列表的数据结构,它是通过紧密相连的节点来存储元素,来达到节约内存的目的,有序集合和hash
对象使用压缩列表实现的数据构造类似,都是相邻两个节点来存储一个元素的信息,只不过hash
存储的是键值对,相邻两个节点分别存储hash
对象的键和值,保存键的节点在前, 保存值的节点在后,两两一组。而zset
存储的是元素值和排序用的double
分数,数据结构图如下:
跳表全称为跳跃列表,是一个允许快速查询,插入和删除的有序数据链表。跳跃列表的平均查找和插入时间复杂度都是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;
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
出现故障时及时收到通知。这可以帮助快速采取行动,减少停机时间。Redis
数据是一种好的实践。如果发生宕机,可以使用备份数据来恢复Redis
的状态。确保备份是定期进行的,并测试备份的可恢复性。Redis
的高可用架构,如主从复制或Redis
集群。主从复制允许在主Redis
实例宕机时使用从实例继续提供服务。Redis
集群则可以将数据分布在多个节点上,提高可用性和性能Redis
提供了两种主要的持久化方式,分别是RDB
快照和AOF
日志。通过配置适当的持久化策略,可以在宕机发生时最大程度地减少数据丢失。Redis
集群,可以通过负载均衡将请求分发到不同的Redis
节点上。这有助于减轻单个节点宕机的影响。Redis
的性能和健康状况。这将能够预测问题并采取措施,以防止宕机。Redis
缓存不可用,应用程序应该能够从备用数据源获取数据或以某种方式继续运行,而不会导致严重故障。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
机制的触发时机有两个:手动触发、自动触发。
手动触发是通过向Redis
发送特定的命令来手动触发RDB
快照生成,比如使用save
或bgsave
命令。
但是使用save
命令会阻塞当前Redis
服务器,因为它会在生成RDB
文件时暂停处理其他命令请求,直到RDB
过程完成为止。如果存在旧的RDB
文件,一旦新的RDB
文件生成完毕,Redis
会用该文件覆盖旧的RDB
文件,以实现数据持久化。
连接Redis
服务器的客户端可能都是几万或者是几十万,这种方式会有性能问题,不推荐使用。
自动触发是通过配置文件来实现的。在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
:设置快照文件的存放路径,这个配置项一定是个目录,而不能是文件名。当Redis
的从节点与主节点进行初次同步(复制)时,主节点会生成一个RDB
快照,并将该快照发送给从节点。这有助于加速从节点的初始化同步过程。
save
:命令会阻塞当前Redis
服务器,因为它会在生成RDB
文件时暂停处理其他命令请求,直到RDB
过程完成为止。如果存在旧的RDB
文件,一旦新的RDB
文件生成完毕,Redis
会用该文件覆盖旧的RDB
文件,以实现数据持久化。bgsave
:执行该命令时,Redis
会在后台异步进行快照操作,快照同时还可以响应客户端请求。Redis
进程会执行fork
操作创建子进程,RDB
持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork
阶段,一般时间很短。基本上Redis
内部所有的RDB
操作都是采用bgsave
命令。优点:
RDB
文件是一个紧凑的二进制文件,包含了整个Redis
数据库的数据快照。这使得RDB
文件适用于备份和迁移数据,以及在恢复时占用较小的存储空间。RDB
文件是一个完整的数据快照,恢复速度通常比AOF
日志文件要快。这对于在灾难恢复情况下尤为重要。RDB
适合用于生成冷备份,可以在需要备份时手动触发RDB
生成。这有助于在数据库状态稳定的情况下进行备份,以避免备份过程中的数据变动。RDB
生成过程可以在Redis
子进程中进行,这有助于减轻主进程的负担,使其在高负载或性能较低的环境下更加稳定。缺点:
RDB
是基于时间或写入操作次数来触发的,如果在最近一次RDB
生成和Redis
宕机之间发生了数据写入,那么这部分数据将会丢失。RDB
适用于周期性的数据快照,而不适合需要实时持续更新的应用。由于生成RDB
文件会导致Redis
主进程在一段时间内阻塞,这可能会影响应用程序的性能。RDB
文件仅包含数据快照,如果Redis
在上次RDB
生成和宕机之间崩溃,那么AOF
日志文件将会是更可靠的数据恢复来源。RDB
文件会变得相对较大,因为它包含了所有数据的快照。这会影响备份和恢复的速度。AOF(Append-Only File)
是Redis
的另一种持久化机制,因为RDB
全量备份是比较耗时的,所以Redis
提供一种更加高效的方式AOF
,工作机制就是Redis
会将每一个收到的写命令都通过write
函数追加到日志文件中,以实现数据的持久化。AOF
机制相对于RDB
机制,更加持久,允许在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
文件的大小。Redis
会将每次执行的操作命令追加到AOF
文件中,从而确保每次操作都被持久化到磁盘。这是最高级别的数据保护,但也会产生显著的性能开销,因为每个操作都需要同步到磁盘。appendfsync always
Redis
会每秒执行一次,将已缓冲的操作命令同步追加到AOF
文件。这种方式在性能和持久性之间取得了平衡,一般来说,数据丢失的风险较小。appendfsync everysec
Redis
将操作命令追加到AOF
文件,但不会主动将数据同步到磁盘,而是由操作系统来处理同步。这提供了最高的性能,但也导致了数据丢失的风险,因为操作命令可能会在写入到磁盘之前存在于内存中。appendfsync no
Redis
还支持自定义的appendfsync
值,可以在配置文件中指定一个时间间隔,例如appendfsync 100
,表示每隔100
毫秒将缓冲的写入操作同步到磁盘。选择适当的AOF
触发机制取决于应用需求,需要根据性能和数据保护之间的权衡来进行选择。通常,everysec是一种常见的配置,因为它在大多数情况下可以提供良好的性能和可靠的持久性。 如果对性能要求非常高,并且可以容忍一些数据丢失风险,那么可以考虑使用no触发机制。
优点:
AOF
记录了每个写入操作,因此相对于RDB
持久化,数据恢复的能力更强,数据丢失的风险更低。AOF
文件可以被重新执行,使得在灾难恢复和故障转移的情况下更容易恢复数据。AOF
文件是一个文本文件,可以查看其内容,以了解Redis
的历史写入操作。appendfsync
的设置,可以在性能和持久性之间做出权衡。AOF
支持多种同步选项,可以根据应用需求调整性能和数据保护级别。缺点:
AOF
文件可能会变得很大,影响备份、迁移和恢复的速度。everysec
或no
模式下,数据可能会在一段时间内只存在于内存中,未持久化到磁盘,可能会导致数据丢失。appendfsync
设置下,AOF
持久化会对性能产生一定影响。AOF
文件可能会出现碎片问题,特别是在文件大小频繁增加和重写时,这可能会影响磁盘空间的利用率。AOF
恢复数据可能比使用RDB
恢复数据更慢,因为AOF
文件中的写入操作需要重新执行。总结:
AOF
持久化适用于需要高持久性和较小数据丢失风险的情况。如果更关注性能和较短的恢复时间,可以考虑在适当情况下使用RDB
持久化。在实际应用中,可以根据业务需求和性能要求来选择适合的持久化机制或结合使用两种机制以达到平衡。
选择的话,两者加一起才更好。因为两个持久化机制明白了,剩下的就是看自己的需求了,需求不同选择的也不一定,但是通常都是结合使用。有一张图可供总结:
参考1:https://www.cnblogs.com/FatalFlower/p/15952843.html
Redis
的主从复制允许将一个Redis
服务器(主节点)的数据复制到多个其他Redis
服务器(从节点)上,以实现数据的分发、备份和高可用性。主从复制在许多场景下都非常有用,例如高可用、提高读取性能、数据备份和灾难恢复。
Redis主从复制的一般工作原理和一些关键概念:
AOF
日志或RDB
文件中,然后将这些变更通过网络发送给从节点。总结:
主从复制为Redis
提供了高可用性、数据备份、负载均衡等优势。然而,需要注意的是,主从复制并不适用于所有场景,因为从节点仅能提供与主节点相同的数据,不能实现跨主节点的数据查询。
主从复制(Master-Slave Replication
)是Redis
中一种重要的数据复制机制,其目的是将一个Redis
服务器(主节点)的数据实时复制到其他多个Redis
服务器(从节点),以实现数据的分发、备份、高可用性和负载均衡等目标。主从复制的优点如下:
虽然主从复制具有许多优点,但也需要注意它的一些限制。比如:从节点的数据与主节点的数据相同,不能进行跨主节点的数据查询。 此外,主节点出现故障时,进行故障转移可能需要一些手动操作,具体取决于配置和部署。 综合考虑业务需求和系统架构,决定是否采用主从复制以及如何配置和管理它。
Redis
的主从复制分为以下几个阶段:
SYNC
命令给主节点,表示它希望进行全量同步。在Redis
主从复制中,全量同步(Full Resynchronization)是在从节点初始连接到主节点时所执行的过程,用于将主节点上的数据完整地传输到从节点,以确保从节点的数据与主节点的数据一致。以下是Redis
主从复制中全量同步的具体步骤:
SYNC
命令给主节点,表示它希望进行全量同步。SYNC
命令: 主节点收到从节点的SYNC
命令后,会准备进行全量同步。BGSAVE
(可选): 主节点在开始全量同步之前,可以执行一个后台持久化操作,即执行BGSAVE
命令生成RDB
快照文件。这是为了确保在全量同步期间主节点的数据不会发生大的变化。RDB
文件: 如果主节点执行了BGSAVE
,它会生成一个RDB
快照文件,其中包含了当前主节点的数据。RDB
文件和AOF
偏移量: 主节点将生成的RDB
文件发送给从节点,并发送当前的AOF
偏移量,表示数据变更在AOF
日志中的位置。RDB
文件: 从节点接收到RDB
文件后,会载入该文件,将其中的数据加载到自己的内存中。PSYNC
命令: 从节点完成RDB
文件的载入后,会向主节点发送PSYNC
命令,携带上次复制的偏移量信息,以告知主节点从哪个位置开始进行增量同步。PSYNC
命令后,根据携带的偏移量信息,开始将从该位置开始的写入操作发送给从节点。需要注意的是,全量同步是一个初始过程,仅在从节点刚连接到主节点时执行一次。之后,主节点会持续地将增量写入操作发送给从节点,以保持数据的同步。全量同步的过程确保了从节点的数据与主节点的数据一致性,并为之后的增量同步奠定了基础。
AOF
日志中,并将这些操作发送给从节点。从节点会持续地接收并执行这些写入操作,以保持数据一致性。AOF
日志中的位置。从节点会定期向主节点发送自己的复制偏移量,以确保从正确的位置进行增量同步。需要注意的是,尽管Redis
通过上述机制来维护主从数据的一致性,但在某些情况下,由于网络延迟、故障等原因,从节点的数据可能会稍微滞后于主节点。因此,在主从复制中,不同节点之间的数据同步是一个近似一致性的概念,而不是强一致性。如果需要更严格的一致性,可以考虑使用Redis
的哨兵机制或者集群模式。
主从复制中的AOF(Append-Only File)
持久化和主节点的AOF
持久化是两个不同的概念。在主从复制中,主节点的AOF
持久化仍然可以被启用,但在从节点上通常会禁用AOF
持久化。
为什么在从节点上不使用AOF
持久化:
AOF
文件来记录写入操作。AOF
文件。从节点并不直接处理客户端写入请求,因此不需要保留AOF
文件来确保数据的持久性和恢复能力。AOF
会增加写入操作的开销,可能会影响从节点的读取性能,并且可能引入额外的写入延迟。AOF
文件不会被用于数据恢复,因为它的数据是通过复制而来。因此,禁用从节点的AOF
可以节省存储空间。AOF
可以简化配置和管理,减少潜在的错误和混淆。虽然在从节点上禁用AOF
持久化是常见的做法,但这不是硬性规定。如果应用场景需要从节点也进行AOF
持久化,仍然可以在从节点上启用AOF
。然而,在主从复制的情况下,AOF
文件在从节点上通常不会被使用,因为数据同步是通过主节点发送写入操作来实现的。
Redis
哨兵(Sentinel
)是用于监控和管理Redis
主从复制和高可用性的系统。它可以自动检测主节点故障,进行故障转移,并重新配置从节点以提供高可用性的服务。
哨兵机制是Redis
高可用性架构的一部分,允许在主节点发生故障时实现自动故障转移,以确保服务的连续性。
哨兵节点本质上也是一个Redis
节点,但是和主节点和从节点不同,哨兵节点只是监视主节点和从节点,并不执行相关的业务操作。
Redis
的主节点和从节点发送心跳检测,以监测它们的状态。如果一个节点不再响应心跳,哨兵首先会将其标记为"主观下线"。SENTINEL is-masterdown-by-addr
指令获取其它哨兵节点对于当前主节点的判断情况,如果哨兵节点对于当前主节点的主观下线判断数量超过了在配置文件中定义的票数,那么该主节点就被判定为“ODOWN”(客观下线)
。然后与其他哨兵进行投票,以选择一个从节点升级为新的主节点。由于使用单个的哨兵来监视Redis
集群的节点不是完全可靠的,因为哨兵节点也有可能会出现故障,所以一般情况下会使用多个哨兵节点来监视整个Redis
集群,如下图所示:
当存在多个哨兵
节点时,在Redis
哨兵机制中,对于节点的下线也有区分:
SENTINEL is-master-down-by-addr
命令互相交流之后,作出Redis
节点下线的判断。一个哨兵节点可以通过向另一个哨兵节点发送SENTINEL is-master-down-by-addr
命令来询问对方是否认为给定的节点已经下线。需要注意的是,哨兵机制的目标是实现集群的高可用性,确保在主节点故障时仍然能够提供服务。但哨兵机制并不是完美的,因此在使用过程中需要正确配置和管理,以确保其能够正常工作。另外,考虑使用Redis
的集群模式也是实现高可用性的一种选择。
总结:
设置哨兵节点的数量需要权衡可靠性、资源消耗和管理复杂性。选择适当的奇数个哨兵节点,并确保它们分布在不同的位置,以实现高可用性、故障检测和自动故障转移的目标。
不是,哨兵在进行主节点故障转移时,选出新的主节点是经过一定的投票和共识过程的,以确保选出的节点是合适的、可用的,并且能够保持数据一致性。
在投票过程中,哨兵会考虑以下因素:
AOF
日志中的位置。复制偏移量较大的从节点可能更适合被选为新主节点,以确保数据的一致性。参考1:Redis的三大缓存异常原因分析和解决方案
Redis
的缓存击穿是一种性能问题,通常发生在具有高并发访问的系统中。它的产生原因是在缓存中存储了某个热点数据,但在某一时刻,大量并发请求同时访问该热点数据,而此时该热点数据的缓存数据刚好过期或被删除,导致每个请求都需要重新查询数据库或重新生成数据,从而导致数据库负载剧增,系统性能急剧下降。
Redis
的数万级别的高吞吐量可以很好的应对大量的并发请求。Redis
的缓存雪崩通常发生在大规模缓存中。它指在某一时刻,大量的缓存数据同时失效或被清除,导致大量请求同时落到数据库上,从而引起数据库负载急剧增加,甚至引发系统崩溃。
产生原因:产生原因一般有两种情况:
Redis
缓存实例发生故障,宕机了,导致大量请求积压到数据库。一般来说,一个Redis
实例可以支持数万级别的请求处理吞吐量,而单个数据库可能只能支持数千级别的请求处理吞吐量,它们两个的处理能力可能相差了近十倍。由于缓存雪崩,Redis
缓存失效,所以,数据库就可能要承受近十倍的请求压力,从而因为压力过大而崩溃。解决方案:
针对缓存中有大量的数据同时过期的情况,可以提供两种解决方案。
针对Redis缓存实例发生故障宕机的情况,同样也有两点建议。
Redis
缓存高可靠集群。如果Redis
缓存的主节点故障宕机了,从节点还可以切换成为主节点,继续提供缓存服务,避免了由于缓存实例宕机而导致的缓存雪崩问题。缓存穿透指的是要访问的数据既不在Redis
中,也不在数据库中,导致请求访问缓存缓缺失。这样一来应用无法从数据库中读取写入缓存,缓存成了摆设,同时给数据库和缓存都带来巨大的压力。
Redis
缓存中设置一个短期的空值或者缺省值,当应用发送后续请求进行查询的时候就可以从Redis
中读取到空值或者缺省值返回,避免大量请求数据库。跟缓存雪崩、缓存击穿这两类问题相比,缓存穿透的影响更大一些。从预防的角度来说,我们需要避免误删除数据库和缓存中的数据;从应对角度来说,我们可以在业务系统中使用缓存空值或缺省值、使用布隆过滤器,以及进行恶意请求检测等方法。
布隆过滤器(Bloom Filter
)是一种用于高效判断一个元素是否属于一个集合的数据结构,它可以快速检查一个元素是否可能在集合中,但具有一定的概率性和容错性。布隆过滤器在一些应用中能够有效地减少不必要的查询,节省计算资源。
优点:
缺点:
工作原理: 布隆过滤器的核心是一个二进制向量数组和一组哈希函数。
m
的位数组,所有位都初始化为0。使用场景:
Redis
缓存穿透问题。HBase/RocksDB/LevelDB
等数据库内置布隆过滤器,用于判断数据是否存在,可以减少数据库的IO
请求。Redis
集成布隆过滤器:
用Redis
可以集成布隆过滤器,版本推荐6.x,最低4.x版本,下载安装插件后在redis.config
配置文件中加入redisbloom.so
文件的地址并重启。(若是redis
集群则每个配置文件中都需要加入redisbloom.so
文件的地址)。
主要指令:
bf.add
:添加一个元素。bf.exists
:判断一个元素是否存在。bf.madd
:添加多个元素。bf.mexists
:判断多个元素是否存在。使用字符串(String):
使用哈希(Hash):
参考:Redis之缓存一致性
两种方式:
Redlock
算法;Redis
发送一个SET
命令,尝试在Redis
中设置一个特定的键(锁键)并赋予一个唯一的值,通常是客户端的标识符(如UUID
)。SET lock_key unique_value NX PX 30000
lock_key
:锁的名称,可以是任何唯一的标识符。unique_value
:是唯一的值,通常由客户端生成,用于标识这个锁的持有者。NX
:表示仅当lock_key
不存在时才设置成功,即获取锁的操作是原子的。PX 30000
:表示设置锁的超时时间为30秒,以防止锁被永久占用。DEL
命令来删除锁键,确保只有锁的持有者可以释放锁。DEL lock_key
可以在释放锁时检查锁的持有者是否是当前客户端,以确保不会释放其他客户端持有的锁。
这种基本的分布式锁实现有一些限制和注意事项:
Redlock
是一种用于分布式系统中的分布式锁算法,它是由Redis
的作者Antirez
在Redis
官方文档中提出的一种思路。虽然Redlock
提供了一种可行的分布式锁实现方法,但它仍然有一些局限性,需要谨慎使用。以下是使用Redlock
算法在Redis
中实现分布式锁的基本步骤:
Redis
节点,通常是奇数个(例如3、5、7个)。这些节点可以是Redis
的主节点或者具有持久性和同步机制的Redis
哨兵节点,作为锁服务器。Unix
时间戳或者其他合适的时间单位。key
和具有唯一性的value
(例如UUID
)获取锁,在每个Redis
实例上请求获取锁,使用SET
命令并设置锁的超时时间。SET lock_key unique_value NX PX 30000
lock_key
:锁的名称,可以是任何唯一的标识符。unique_value
:是唯一的值,通常由客户端生成,用于标识这个锁的持有者。NX
:表示仅当lock_key
不存在时才设置成功,即获取锁的操作是原子的。PX 30000
:表示设置锁的超时时间为30秒,以防止锁被永久占用。key
的真正有效时间等于有效时间减去获取锁所使用的时间。Redis
节点都取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功。DEL
命令在所有节点上根据key
和请求ID
删除锁。Redis
实例取到锁或者取锁时间已经超过了有效时间),客户端应该在所有的Redis
实例上进行解锁(即便某些Redis
实例根本就没有加锁成功,防止某些节点获取到锁但是客户端没有得到响应而导致接下来的一段时间不能被重新获取锁)。需要注意的是,Redlock
算法并不是绝对可靠的,它对网络分区等异常情况可能不够健壮。在使用Redlock
时,建议谨慎考虑以下因素:
Redlock
的性能相对较低,因为它需要在多个节点上进行多次尝试。参考1:分布式锁:概述篇
单机锁:解决的是进程内线程间的问题;
分布式锁:则是解决的进程间的一个问题;
参考1:Redis Setex 命令
Redis
的setex
命令为指定的key
设置值及其过期时间。如果key
已经存在, setex
命令将会替换旧的值。
SETNX key value
只有在key
不存在时设置key
的值。
示例:
redis 127.0.0.1:6379> SETEX KEY_NAME TIMEOUT VALUE
Redis
支持事务,事务允许将多个命令打包成一个单一的执行单元,要么全部执行成功,要么全部执行失败,保持了原子性。Redis
使用MULTI
、EXEC
、DISCARD
和WATCH
命令来实现事务。以下是关于Redis
事务的一些重要信息:
MULTI
命令来开启一个事务。一旦开启事务,后续的命令都会被放入事务队列中,但不会立即执行。MULTI
SET key1 value1
SET key2 value2
EXEC
命令来执行事务中的所有命令。一旦执行EXEC
,Redis
会按照事务队列中的顺序执行所有命令。EXEC
如果事务中的任何命令出现了错误,Redis
将执行失败,并返回一个包含错误信息的数组。否则,事务将被成功执行,返回每个命令的结果。
4. 取消事务:如果在执行事务之前需要取消事务,可以使用DISCARD
命令。DISCARD
命令会清空事务队列中的所有命令。
DISCARD
Redis
会继续执行后续的命令。因此,需要在客户端代码中检查每个命令的执行结果,以判断是否有错误。Redis
事务是原子性的,意味着在事务执行期间,其他客户端无法插入命令,也无法读取事务中的未执行命令。Redis
还提供了WATCH
命令,它用于监视一个或多个键。如果在事务执行前被监视的键发生了变化,事务将被取消。这可以用于实现乐观锁的机制。Redis
事务非常适合一次性执行多个命令,从而确保这些命令的原子性。然而,需要注意的是,Redis
事务不同于传统数据库的事务,它不提供隔离性,不支持回滚,且在一些情况下可能表现出预期之外的行为。因此,在使用Redis
事务时,需要充分了解其特性和限制,并根据具体需求进行设计和使用。
参考1:Redis:集群方案
常见的Redis
集群方案:
Redis Cluster
;不是,Redis
中的键过期后并不会立即被立刻删除。过期键的删除是通过Redis
的定期任务来进行的,具体的删除时间取决于Redis
的策略和配置。
Redis
使用两种主要策略来管理过期键的删除:
Redis
会定期检查一定数量的键,看它们是否过期,然后删除过期的键。这个检查过程不是立即发生,而是由Redis
配置中的hz
参数控制的,表示每秒执行检查的次数。默认情况下,hz
设置为10,即每秒检查10次。Redis
会首先检查该键是否过期。如果键过期了,Redis
会删除它,然后返回一个不存在的值。这意味着过期键只有在访问时才会被删除。总的来说,Redis
的过期键并不是立即删除的,而是在一定的时间内(取决于hz
参数和键的访问频率)被定期或惰性删除。这种策略可以有效减少删除操作的开销,同时保证了Redis
的高性能。
需要注意的是,过期键的删除是基于LRU
(最近最少使用)算法实现的,因此在某些情况下可能会有一些不确定性,例如可能出现一些已过期但尚未删除的键。但总体来说,Redis
会尽力确保过期键的及时删除。如果需要精确控制键的生命周期,可以使用EXPIRE
或PEXPIRE
命令来设置键的过期时间。
Redis
的删除操作通常是非阻塞的,当执行删除命令时,Redis
会立即返回,并在后台异步地执行删除操作,而不会阻塞其他操作的执行。
这意味着即使执行了删除操作,Redis
仍然可以同时响应其他读取和写入请求,不会因为删除操作而停止响应其他命令。这种非阻塞的特性有助于保持Redis
的高吞吐量和低延迟特性。
需要注意的是,虽然Redis
的删除操作是非阻塞的,但在删除大量数据时,删除操作可能会占用一定的CPU
和I/O
资源,从而对其他操作产生一定的影响。因此,在大规模删除操作时,需要谨慎考虑对系统性能的影响,可以采用分批次删除或者在低负载时段进行删除操作,以减轻影响。
此外,如果需要在删除某个键后立即获取该键的值,可以使用DEL
命令删除键,并立即访问该键,Redis
会在删除后返回"nil"
值。这可以用于原子地删除并获取键的值,但请注意,如果键不存在或已经过期,它也会返回"nil"
值。
setnx
的时候可以设置过期时间10,当请求的setnx
数量达到20时候即达到了限流效果。代码比较简单就不做展示了。这种做法的弊端有很多弊端,比如当统计1-10秒的时候,无法统计2-11秒之内,如果需要统计N秒内的M个请求,那么我们的Redis中需要保持N个key等等问题。
可以将请求构造成一个zset
数组,当每一次请求进来的时候,value
保持唯一,可以用UUID
或者时间戳,而score
可以用当前时间戳表示,因为score
我们可以用来计算当前时间戳之内有多少的请求数量。而zset
数据结构也提供了zrange
方法让我们可以很轻易的获取到2个时间戳内有多少请求。
通过上述代码可以做到滑动窗口的效果,并且能保证每N秒内至多M个请求,缺点就是zset
的数据结构会越来越大。实现方式相对也是比较简单的。
基于Redis
的令牌桶算法
提令牌桶算法涉及到输入速率和输出速率,当输出速率大于输入速率,那么就是超出流量限制了。也就是说我们每访问一次请求的时候,可以从Redis
中获取一个令牌,如果拿到令牌了,那就说明没超出限制,而如果拿不到,则结果相反。
根据这个思想,可以结合Redis
的list
数据结构很轻易的做到这样的代码,只是简单实现依靠list
的leftPop
来获取令牌。
Redis Streams
是Redis 5.0
引入的新数据结构,用于处理有序流式数据。它们提供了类似于消息队列的功能,但有更多的功能,允许轻松地添加、读取和处理事件流。Streams
主要用于日志处理、消息传递和事件驱动的应用程序中。
以下是有关Redis Streams
的关键概念和操作:
Stream
是一个有序的、不断增长的事件序列,它包含了一个或多个条目(entry
)。每个条目都有一个唯一的ID
来标识,可以是整数或时间戳。Stream
的特点是可以保留和查询历史数据。Stream
中的每个事件都称为一个entry
,它包含了一个关键字(field
)和一个值(value
)。通常,值是一个包含了事件数据的JSON
对象。Stream
,以便接收事件。Redis
允许多个消费者订阅相同的Stream
,并可以独立读取事件。XADD
命令用于向Stream
添加新的条目,它接收一个Stream
名称、一个条目ID
和一个包含字段值对的数据。条目ID
可以是自动生成的或者用户指定的。XREAD
命令用于从Stream
读取事件。可以根据条件读取事件,例如,读取未读取的事件、读取指定数量的事件等。XGROUP
命令用于创建和管理消费者组,以及将消费者加入组中。XACK
用于确认消费者已经成功处理了一个或多个事件,而XDEL
用于删除事件。XPENDING
用于查询未处理的事件以及与事件相关的信息,如消费者信息、未处理事件的数量等。Redis Streams
是一个强大的数据结构,可用于实现各种实时数据处理任务。它允许应用程序通过事件流进行通信,并且可以在流中保留历史数据以进行后续分析和查询。Streams
还支持多个消费者之间的负载均衡,以确保事件能够高效地处理。
设计和优化Redis
架构通常依赖于应用程序的需求和负载情况。Redis
是一个高性能的键值存储系统,可以用于多种用途,包括缓存、会话存储、计数器等。以下是设计和优化Redis
架构的一些常见考虑因素和策略:
Redis
的分片机制将数据分布到多个Redis
实例中,以减轻单个实例的负载。RDB
)和日志(AOF
)。Redis
的内存使用情况,确保不会超出可用内存。Redis
内置的LRU
算法或手动删除过期数据来管理内存。Redis
高可用性解决方案,如主从复制、哨兵或Redis
集群。Redis
实例的访问。Redis
的配置参数,如最大连接数、超时设置、并发客户端数等。Redis
的事务和管道功能,减少客户端与服务器之间的通信开销。LRU
或LFU
算法来淘汰不常用的缓存数据。Redis
的性能和健康状态。Redis
版本,以获取性能改进、安全性修复和新特性。在升级之前,确保进行充分的测试,以防止应用程序出现不兼容问题。Redis
查询和连接开销。Redis
作为分布式缓存,请考虑如何处理缓存失效、热点数据和缓存预热等问题。CPU
和存储。总结:
Redis
的设计和优化应该是根据具体的应用需求和负载情况来进行的。不同的应用可能需要不同的策略和配置。定期监控和性能调优是保持Redis
高性能和可用性的关键。同时,随着Redis
和应用的发展,也需要不断地进行容量规划和架构调整。