Redis 是一种高性能、基于内存的开源键值存储数据库系统,它主要用于缓存、会话管理和实时分析等用途。
关键特点
键值存储:Redis 以键值对的形式存储数据。每个键都是一个唯一的标识符,与一个值相关联。
基于内存存储:Redis 所有的数据存储在内存中,这意味着它提供极高的读写速度,使得它非常适合用作缓存层,能够快速响应读取请求。
持久性:Redis 支持数据持久性,可以将数据保存到磁盘上,以便在重启后恢复数据。
多数据结构支持:Redis 支持各种数据结构,包括如字符串(string)、列表(list)、集合(set)、有序集合(sorted set)、哈希表(hash)、位图(bitmap)、超日志(hyperloglog)和地理空间索引(geospatial index)。等。这些数据结构可以用于不同的应用场景。
发布-订阅模式:Redis 支持发布-订阅消息模式,允许客户端订阅特定的频道,从而实现高效的消息通信。这对于实现实时通信和事件驱动应用程序非常有用。
事务:Redis 支持事务,可以一次执行多个命令,并保证这些命令在执行期间不会受到其他客户端的干扰。
分布式和高可用性:通过使用哨兵(Sentinel)和集群,Redis 可以提供高可用性。哨兵用于监控 Redis 服务器的健康状况并自动执行故障转移。集群则提供数据分片和自动分区,以支持更大规模的数据存储。
分片:Redis 支持数据分片,可以水平扩展存储容量和吞吐量。
Lua脚本:Redis 允许使用 Lua 脚本执行高级操作,使得在服务器端可以进行复杂的逻辑,减少网络往返次数。
社区支持:Redis 社区活跃,提供了许多扩展和插件,使其适用于各种不同的用例。
数据类型
- 字符串(String)
- 结构:字符串是Redis最基本的数据类型。Redis中的字符串是二进制安全的,这意味着它们可以包含任何数据,比如JPEG图像或序列化对象。
- 实现:字符串使用简单的动态字符串(SDS,Simple Dynamic String)实现,允许快速追加操作,与Java中的
StringBuffer或StringBuilder类似,都支持动态修改,如追加、修改等操作,而不需要每次操作都创建一个新的字符串实例。
- 列表(List)
- 结构:列表是字符串元素的集合,按插入顺序排序。
- 实现:Redis列表用双向链表或压缩列表(ziplist)实现。较小的列表通常使用压缩列表以节省空间,而较大的列表则使用双向链表。
- 集合(Set)
- 结构:集合是无序的字符串集合,每个元素都是唯一的。
- 实现:小的集合使用压缩列表(ziplist)实现,较大的集合使用散列(hash table)实现。
- 有序集合(Sorted Set)
- 结构:有序集合类似于集合,但每个元素都关联一个分数。这些元素按分数有序排列。
- 实现:有序集合通过跳跃表(skiplist)和散列结构组合实现。跳跃表用于按分数排序和范围查询,而散列用于快速访问。
- 哈希(Hash)
- 结构:哈希是键值对的集合,类似于Java中的HashMap或Python中的字典。
- 实现:小的哈希使用压缩列表实现,大的哈希使用散列结构。
- 位图(Bitmap)
- 结构:位图不是独立的数据类型,而是在字符串上操作单个位(binary digit)。
- 实现:通过对字符串类型的特殊操作实现,允许设置和查询字符串值的特定位。
- HyperLogLog
- 结构:用于高效地执行基数计数(比如计算一个集合中不同元素的数量)。
- 实现:使用近似算法,牺牲了精度以换取极高的空间效率。
- 地理空间索引(Geospatial)
- 结构:用于存储地理位置信息,并进行各种地理相关的计算,如两点之间的距离。
- 实现:基于有序集合,利用Z-order曲线在一维值中编码二维经纬度。
应用场景
- 缓存系统:
- 场景:减少数据库的负载,加速数据检索。
- 实现:将经常查询的数据,如用户信息、商品详情等存储在 Redis 中。当数据被请求时,首先查询 Redis,如果找不到,再查询数据库,并将结果存回 Redis。
- 会话存储(Session Store):
- 场景:用于 Web 应用的用户会话管理。
- 实现:将用户的会话信息存储在 Redis 中,由于 Redis 的读写速度快,可以快速处理大量并发的会话数据。
- 消息队列:
- 场景:应用程序之间的消息传递和异步处理。
- 实现:使用 Redis 的发布/订阅功能或列表结构实现消息队列,支持生产者-消费者模型,实现数据的异步处理。
- 排行榜/计数器:
- 场景:用于实现社交网络、游戏等应用的排行榜功能。
- 实现:利用 Redis 的有序集合(sorted set),可以快速添加、更新和获取排行榜数据。
- 实时分析:
- 场景:网站的实时访问数据统计。
- 实现:使用Redis的计数器功能,例如HyperLogLog来估计唯一访问者数量。
- 地理空间数据处理:
- 场景:例如实现基于位置的服务,如查找附近的商店或用户。
- 实现:使用Redis的地理空间索引功能,可以存储地理位置信息,并进行范围查询和距离计算。
- 分布式锁:
- 场景:在分布式系统中同步不同进程或服务器之间的操作。
- 实现:通过Redis的SETNX命令实现锁的机制,确保同一时间只有一个进程能执行特定的代码段。
- 数据过期处理:
- 场景:自动删除过期的数据,如临时令牌或验证码。
- 实现:利用Redis的键过期功能,可以为存储的数据设置生存时间。
持久化机制
Redis 支持两种持久化机制,分别是RDB(Redis DataBase)和 AOF(Append Only File)。这两种机制可以单独使用,也可以同时使用,以便在不同的场景下平衡性能和数据安全性。
RDB(默认持久化机制)
介绍:
RDB 持久化是通过创建数据集的快照来实现的。工作原理:
- 在指定的时间间隔内,Redis自动创建当前数据的快照,并将其保存在一个紧凑的二进制文件中(默认为
dump.rdb)。Redis 在默认情况下只有一个 dump.rdb 文件,意味着每次创建新的RDB快照时,都会覆盖现有的dump.rdb文件。快照的创建可以通过自动或手动触发。自动触发基于配置的时间间隔和数据变化的次数。如果因为服务器宕机死机重启,那么内存中的数据就没了,但是他会从rdb中进行恢复。 - 如果只开启了 RDB 持久化:
- 那么每次启动或重启 Redis 时,它都会寻找
dump.rdb文件。 - 如果找到了,就会加载该文件,将数据恢复到内存中。这个过程会阻塞服务器,直到加载完成。
- 如果没有找到
dump.rdb文件,Redis 就会作为一个空实例启动。
- 那么每次启动或重启 Redis 时,它都会寻找
- 如果同时开启了 RDB 和 AOF 两种持久化:
- 在这种情况下,当 Redis 启动时,它会优先加载 AOF 文件 (
appendonly.aof) 来恢复数据,因为 AOF 文件通常保存了比 RDB 文件更完整、更新的数据。 - 此时,即使
dump.rdb文件存在,它也会被忽略,Redis 不会加载它。
- 在这种情况下,当 Redis 启动时,它会优先加载 AOF 文件 (
- 在指定的时间间隔内,Redis自动创建当前数据的快照,并将其保存在一个紧凑的二进制文件中(默认为
优点:
- 高效性能:RDB是一个非常高效的方式来保存大量数据的快照。
- 灾难恢复:由于RDB文件是压缩的二进制文件,适用于需要
定期备份数据的情况,非常适合灾难恢复。
缺点:
- 数据丢失风险:如果Redis崩溃,自上次快照以来的所有数据都可能丢失。
- 性能开销:在大数据集的情况下,保存快照可能会对性能产生短暂的影响。
在 RDB 持久性机制下,Redis 确实提供了两种快照(snapshot)保存方式:SAVE和BFSAVE。这两种命令都用于生成当前 Redis 数据库状态的快照,但它们在执行方式上有显著的不同。
在 Redis 的配置文件redis.conf中,有类似这样的save配置项(默认开启):
save 900 1
save 300 10
save 60 10000
当 Redis 因为满足了上述 save 配置的条件而自动触发持久化时,它**执行的操作等同于 ****BGSAVE**,而不是阻塞的 SAVE。Redis 会在后台创建一个子进程来完成快照的生成,以避免阻塞主服务。
- SAVE
- 执行方式:
SAVE命令会创建一个快照并将其保存在磁盘上,但这个过程是同步进行的。这意味着在SAVE命令执行期间,Redis将停止处理其他命令。 - 使用场景:由于
SAVE会堵塞所有其他客户端请求,它通常不推荐在生产环境中使用。它更适用于低流量的时段或维护期间,例如,当需要确保数据完全同步到磁盘时。
- BGSAVE( Redis 自动持久化时默认采用的机制)
- 执行方式:
BGSAVE命令会在后台创建一个快照。具体来说,Redis会先创建一个子进程,然后子进程负责将快照写入磁盘,而父进程(即原始的Redis服务器进程)可以继续处理客户端请求。 - 使用场景:由于
BGSAVE不会堵塞主服务进程,它更适合生产环境中使用,尤其是在需要定期快照但又不希望影响服务性能的场合。
AOF
介绍:
AOF(Append-Only File)是一种将Redis操作命令以追加方式写入日志文件的机制,是追加式备份。它以文本格式记录Redis的写操作(新增、修改、删除),都会记录在这个AOF日志里。
需要注意的是,redis是先执行写操作指令,随后再把指令追加进AOF文件中。工作原理:
- 每个写操作命令都会追加到AOF文件的末尾。
- 类似于记录日志,把所有的写操作追加到文件。追加的形式是append,逐个命令追加,不是修改。
- 比如说
set key1 abc,set key1 123,虽然两次设置key1的值,但不会合并,而是追加命令。 - redis恢复的时候先恢复AOF,如果AOF有问题(比如破损),则再恢复RDB。
- redis恢复的时候是读取AOF中的命令,从头到尾读一遍,然后数据恢复。
- 比如说
- Redis启动时,通过重新执行这些命令来重建原始数据。
- AOF 是通过Redis主线程执行的,因此每个写操作都会导致磁盘I/O,当然这是为了确保数据的持久性。
优点:
- 数据完整性:与RDB相比,AOF可以提供更好的数据完整性和安全性。
- 易于阅读:AOF文件以文本格式存储,易于阅读和维护。
- 灵活性:提供多种同步策略,如
每秒同步、每修改同步等。
缺点:
- 文件大小:AOF文件可能会比RDB文件大很多,因为它保存了所有的写操作。
- 性能开销:特别是在每次修改同步的配置下,可能会对写入性能产生影响。
重写机制:
AOF 的重写机制是Redis用来优化AOF文件大小和性能的重要机制。随着操作的不断累积,AOF文件可能会变得非常大,包含许多已经不再需要的命令。AOF重写机制就会创建一个新的AOF文件,其中包含了与当前数据库相同的数据,但是采用更紧凑的格式,通常比原始AOF文件要小得多。这有助于减少AOF文件的大小,提高Redis性能,以及降低恢复速度。
工作原理:
创建新的AOF文件:在AOF重写过程中,Redis会创建一个新的AOF文件。并且不是去分析和重放旧的 AOF 日志文件,而是直接读取当前数据库内存中的数据,然后为这些数据生成一套最精简的写入命令。
最小命令集:新的AOF文件仅包含使数据库达到当前状态的最小命令集。例如,如果一个键被修改多次,新的AOF文件只会包含这个键的最终状态。
重写模式:
AOF重写有两种模式,其中一种是混合模式,另一种是纯AOF模式。
混合模式(Mixed Mode):
- 在混合模式下AOF重写生成的新AOF文件既包含AOF格式的写命令,也包含RDB快照的数据。
- 首先,redis会把当前所有的数据以rdb形式存入到AOF文件中,这些都是二进制文件,数据量小,随后新的数据会以AOF格式追加到这个AOF文件中。
- 恢复过程会更快,因为只需要加载一个文件。
纯AOF模式:
- AOF重写生成的新AOF文件仅包含AOF格式的写命令,不包含RDB快照的数据。但是会将一些重复的,没有意义的指令给去除掉,减少文件体积。
触发机制
- 手动触发:可以通过执行
BGREWRITEAOF命令手动触发AOF重写。 - 自动触发:Redis还可以配置为在AOF文件增长到一定大小时自动触发重写。这是通过配置文件中的
auto-aof-rewrite-percentage和auto-aof-rewrite-min-size指令来控制的。
- 手动触发:可以通过执行
过程细节
- 使用子进程:类似于
BGSAVE命令,AOF重写也是在一个子进程中进行的,以避免堵塞主进程。 - 追加写入期间的命令:在重写过程中,对Redis数据库进行的所有写操作同时会被追加到旧的和新的AOF文件中,确保数据一致性。
- 切换文件:一旦新的AOF文件创建完成,Redis会使用新文件替换旧的AOF文件,并从此刻开始只向新文件追加新的写命令。
- 使用子进程:类似于
优点
- 减少磁盘占用:通过删除命令,AOF重写能显著减少AOF文件的大小。
- 提高重启速度:更小的AOF文件意味着重启时重放命令的速度更快。
注意事项
- 性能影响:尽管AOF重写是非堵塞的,但它可能会增加磁盘I/O负担,因此在高负载的系统上运行时需要小心。
- 内存影响:与
BGSAVE类似,AOF重写也会临时增加Redis的内存使用,因为它需要创建一个当前数据库状态的副本。
AOF小结:
- AOF写入:
- AOF操作本身是将每个写命令追加到AOF文件的过程。这种追加操作通常是非堵塞的,但它的行为取决于具体的配置。
- 在AOF配置中,有一个
appendfsync选项,它控制着操作系统刷新数据到磁盘的时机,这个选项有三个设置:- always(每修改同步):每个写命令都同步写入磁盘,这可能导致堵塞,尤其是在磁盘I/O性能较差的时候。
- everysec(每秒同步):在默认情况下,大多数Redis设置会使用这个选项。大约每秒同步一次,这是一种平衡性能和数据安全性的做法,通常不会引起显著的堵塞。
no:交给操作系统决定何时进行数据写入,这种方式下写入操作是非堵塞的,但在系统崩溃的情况下可能会丢失更多数据。
- AOF重写:
- AOF重写操作是非堵塞的。在执行AOF重写时,Redis会启动一个子进程来进行重写工作,而主进程继续处理客户端的请求。
- 由于AOF重写是在子进程中完成的,它不会堵塞正在进行的客户端命令处理。不过,它可能会对系统的整体性能产生影响,主要是因为磁盘I/O和额外的CPU负载。
两种持久化机制如何选择
选择RDB还是AOF持久化取决于应用的需求。通常情况下,两者可以结合使用以获得更好的性能和持久性。例如,可以启用AOF来记录最近的写操作,并同时使用RDB来提供定期的全数据快照。
总的来说,RDB适合对定期备份敏感、数据集较大的场景,而AOF适合对实时性要求较高、数据恢复性要求非常高的场景。具体选择应该根据应用程序的性质和需求来确定。
淘汰策略
Redis 的数据淘汰策略是指当内存使用达到一定阈值时,Redis如何选择删除一些数据以释放内存的方法。这些策略主要用于当Redis被用作缓存时,帮助管理内存的使用。
noeviction(无淘汰策略):当内存使用达到限制时,对写入操作返回错误,但允许读操作。这是默认策略。
allkeys-lru(最近最少使用):在内存达到限制时,在所有键中移除最近最少使用的键。适用于通用缓存场景。
volatile-lru(过期时间中最少使用):仅淘汰设置了过期时间的键中的最近最少使用的键。
allkeys-random(随机):在内存达到限制时,在所有键中随机移除键。
volatile-random(过期时间中随机):仅随机移除设置了过期时间的键。
allkeys-lfu:在所有键中移除最不经常使用的键。
volatile-lfu(最近最少频繁使用):从已设置过期时间的键中,移除最不经常使用的键。
volatile-ttl(最短剩余时间):从已设置过期的键中,移除即将到期的键。
应用场景:
选择哪种淘汰策略取决于具体的使用场景和需求。例如,如果使用Redis作为缓存,并且希望在内存不足时自动删除老旧数据,可以选择allkeys-lru策略。
对于关键数据,可能更倾向于使用noeviction策略,并在应用层面控制内存使用。
非阻塞I/O模型
在此之前,我们先了解下什么是I/O模型。I/O(输入/输出)模型描述的是程序如何处理输入和输出操作。在计算机系统中,I/O操作通常是指与外部设备(如硬盘、网络接口等)的数据交换。I/O模型决定了程序在等待I/O操作完成时的行为,这对程序的性能和响应能力有重要影响。
主要有以下几种I/O模型:
- 堵塞I/O(Blocking I/O):
- 在这种模型中,应用程序发起I/O请求后,必须等待数据准备就绪并完成操作,期间应用程序被堵塞,不能执行其他任务。
- 例如,读取文件操作会一直等待,直到有数据可以读取。
- 非堵塞I/O(New I/O):
- 应用程序发起I/O请求后,如果数据未准备好,操作系统会立即返回一个错误(通常是“资源暂时不可用”),应用程序可以继续执行其他任务。
- 应用程序需要不断地询问操作系统数据是否准备好,这个过程为“轮询(polling)”。
- I/O复用(I/O Multiplexing):
- 应用程序通过一个API(如select、poll、epoll)监控多个I/O流,一旦其中一个或多个I/O流准备好,操作系统通知应用程序。
- 这种模型允许单个线程同时管理多个I/O操作,而不是为每个I/O操作创建单独的线程。
- 信号驱动I/O(Signal-driver I/O):
- 应用程序告诉操作系统启动一个操作,并让操作系统在数据准备好时通过信号来通知它。
- 与非堵塞I/O不同,信号驱动I/O不需要应用程序不断地检查数据是否准备好。
- 异步I/O(Asynchronous I/O):
- 应用程序发起I/O操作后,可以立即开始执行下一个指令。操作系统将完成整个I/O操作(包括数据传输)并在操作完成后通知应用程序。
- 这种模型下,应用程序无需等待I/O操作的完成。
接下来让我们通过一个关于Redis如何利用非阻塞I/O处理客户端请求的例子来更好地理解非堵塞I/O模型概念。
场景:客户端请求处理
传统堵塞I/O模型的限制
在传统的堵塞I/O模型下,当Redis服务器接收来自一个客户端的请求时,它必须等待整个请求的处理完全完成(包括等待所有必要的数据被读取或写入),在此期间,它不能处理来自其他客户端的任何其他请求。这意味着如果某个请求的处理需要一些时间(例如,一个复杂的查询或大量数据的读取),其他客户端必须等待,这降低了整体的响应性和吞吐量。
Redis的非堵塞I/O模型
现在再来看看Redis是如何使用非堵塞I/O来优化这个过程的:
多个客户端同时连接:多个客户端同时向Redis服务器发送请求。
非堵塞I/O操作:
- 当Redis服务器接收到一个请求时,它会开始处理这个请求。如果在处理过程中需要进行I/O操作(比如读取磁盘上的数据),Redis服务器不会在这个操作完成前被堵塞。
- 相反,如果数据尚未准备好,Redis可以暂时停止处理这个请求,并转而处理其他客户端的请求。
- I/O多路复用:
- 在后台,Redis使用I/O多路复用技术(如epoll)来有效地监控所有活跃的客户端连接。
- 一旦某个请求的I/O操作完成(例如,所需数据已准备好读取),I/O多路复用机制会通知Redis服务器,然后Redis可以继续处理这个请求。
- 高效并发处理:
- 通过这种方式,Redis可以在单个线程中高效地处理多个并发请求,而无需为每个请求或连接创建单独的线程。
- 这提高了服务器的响应性和吞吐量,即使在面对大量并发请求时也能保持高性能。
结论:Redis 正是使用了非堵塞I/O和I/O多路复用,才能够快速、高效地处理成千上万的并发连接和请求,而无需创建和管理多个线程,从而大大提高了资源利用率和性能。
事件驱动模型
非堵塞的事件循环模型:
Redis 采用了非堵塞的事件循环模型,允许多个客户端并发请求。虽然Redis在任何给定时间点只能处理一个请求,但它可以快速轮询多个客户端请求,以确保高吞吐量。
- 事件驱动:
- Redis的核心运行机制是
基于事件驱动的。意味着Redis的主要功能是接收和处理客户端请求、执行命令、数据持久化等,都是通过响应各种事件来完成的。 - 在事件驱动模型中,Redis不需要为每个任务或请求创建新的线程,而是在单线程中异步处理这些事件。这种方式使得Redis能够高效地处理大量并发请求,同时保持简单的架构和低延迟。
- Redis的核心运行机制是
- 事件循环:Redis的核心是一个
事件循环,也称为事件驱动循环。这个事件循环不断地检查并处理发生的事件,而不会堵塞整个系统。它会轮询各个已注册的事件,如客户端连接事件、套接字可读事件、套接字可写事件等。 - 事件监听:Redis使用操作系统提供的多路复用机制,如
epoll(Linux)、kqueue(BSD)、select等,来监听多个套接字上的事件。这使得Redis能够同时处理多个客户端连接而无需为每个连接创建一个新线程。 - 协程和事件处理:在一些版本的Redis中,引入了协程(Coroutine)和事件处理机制,可以更有效地处理多个客户端请求和数据库操作,提高了并发性能。
- 事件驱动:
Redis的操作是同步还是异步
先说结论,基于内存操作是同步,基于网络I/O或磁盘I/O是异步。
同步
- 当Redis接收到像
get key1, set key1这样的命令时,如果数据在内存中(即不需要从磁盘加载),Redis会立即处理这个命令并同步返回结果。即使是在这种同步情况下,操作也是非常迅速的,因为它是在内存中进行的,几乎没有什么延迟。