
Redis使用清单
Redis 是一个开源的高性能键值对数据库,支持多种数据结构(如字符串、列表、集合等)和功能(如持久化、发布订阅、Stream 消息队列等)。基于内存操作和单线程模型,Redis 提供极快的读写速度和高并发性能,广泛用于缓存、消息队列、实时数据处理等场景。
Redis 是一个开源的、高性能的键值对(Key-Value)数据库,它支持多种数据结构并且非常快速,通常用作缓存或消息队列,并且由于其高性能,它常用于需要快速读写数据的场景。
Redis支持以下数据结构:
LPUSH和RPOP命令,可以实现一个高效的先进先出队列,可以用于消息队列、任务调度、推送系统等。LPUSH或RPUSH都能够在O(1)时间内完成。HSET来设置用户的属性,HGET获取属性,可以用于分布式缓存、简易配置存储等。ZADD添加用户及其积分、ZRANGE获取排名前N的用户)、实时数据分析、延迟队列等。可以看到Redis的数据结构是相对而言比较简单的,对数据操作也简单明了,在其完全基于内存的加持之下,Redis拥有高效的数据访问。
Redis还有一个特别之处就是单线程特性,这使得Redis避免了多线程导致的上下文切换开销以及数据竞争。因为Redis是单线程的,自然也就不存在加锁释放锁的问题,更加没有可能因为死锁而导致性能消耗。
虽然是单线程,但是却能够同时处理大量的请求,因为Redis采用了非阻塞I/O,使用epoll(基本思想是通过一个内核事件表来跟踪多个文件描述符的状态。它允许应用程序在一个线程中监控大量的文件描述符,而无需每次都轮询每个文件描述符,减少了性能开销)或其他类似的时间通知机制,能够处理大量的并发请求而不需要使用多线程。每一次请求都会被排队等待处理,Redis通过事件循环不断轮询这些请求,确保请求能够被高效执行。
Redis Stream是Redis 5.0版本引入的一种新的数据类型,它被用来高效地处理消息队列、事件流、日志系统等场景。Redis Stream结合了消息队列和日志系统的特点,具有高效、可扩展、可靠的优势,适用于处理大规模数据流的应用。
Redis Stream是一个有序的消息集合,由一系列按时间顺序生成的消息组成,每条消息都附带一个唯一的ID,消息的ID是自动生成的,通常由时间戳和序号组合而成。时间戳是毫秒级单位,是生成消息的Redis服务器时间,它是个64位整型(int64)。序号是在这个毫秒时间点内的消息序号,是单调有序的,它也是个64位整型。由于ID包含时间戳部分,为了避免服务器时间错误带来的问题,Redis的每个Stream类型数据都会维护一个latest_generated_id属性,用于记录最后一个消息的ID。如果发现时间戳退后,就采用时间戳不变而序号递增的方案来作为新消息的ID。
LPUSH往队列中插入信息,使用RPOP从队列中消费信息,比较适合单生产者-消费者的场景,然而这种方式不支持消费分组,多个消费者会争抢消息,难以实现负载均衡;同时没有持久化ACK确认机制,消费者异常退出时会丢失未处理的消息。PUBLISH发布消息到某个频道,消费者通过SUBSCRIBE订阅该频道并且接收信息,这种方式适合广播场景,一个消息可以被多个订阅者同时接收,同时实时性较高,消息一旦发布,所有的订阅者都能够接收到。但是这种方式并不支持消息的持久化,消费者离线期间的消息会丢失,无法确保消息被成功处理。Redis Stream很好地弥补了以上消息队列的缺点。为了避免消费者崩溃带来的信息丢失的问题,Stream设计了Pending列表,用于记录读取但未处理完毕的消息。可以使用命令XPENDING来获取消费组或者消费组内的未处理完毕的消息。每个Pending消息有四个属性:消息ID、所属消费者、IDLE(已读取时长)、delivery counter(被读取次数),当消费者处理完毕以后会使用命令XACK来完成告知消息处理完成,这种方式意味着消费者在读取消息但是未处理时,消息是不会丢失的。等待消费者再次上线之后,可以读取Pending列表,就可以继续处理该消息,保证消息的有序性与不丢失。此外,如果某个消息不能被消费者处理,就会长时间在Pending列表中,此时delivery counter就会累加,当累加到某个临界值的时候,就会认为该消息是死信,从而将其删除。
虽然Redis是一个基于内存的数据库,但是出于容灾考虑,它也有进行持久化的需要。Redis的持久化机制主要有RDB、AOF以及RDB与AOF混用三种。
RDB (Redis DataBase) 是 Redis 提供的一种持久化方式,它会将 Redis 在内存中的数据以快照 (snapshot) 的形式保存到磁盘上的一个二进制文件中,这个文件通常命名为dump.rdb。当 Redis 重启时,可以通过加载 RDB 文件来快速恢复数据到保存快照时的状态。因此,RDB 也常被称为“内存快照”。
save指令来配置自动出发RDB快照的条件。save的指令格式为save <seconds> <changes>,表示在指定的秒数内,如果数据库发生了指定次数的修改,就会自动触发RDB快照。如下:SAVE或BGSAVE来手动触发RDB快照。区别之处就在于SAVE命令会在主进程中执行,阻塞主进程直到RDB快照完成,不适合在生产环境中使用。而BGSAVE则是会在后台创建一个子进程来执行RDB快照,不会阻塞主进程。SHUTDOWN命令:当执行SHUTDOWN时,如果RDB持久化是开启的并且没有开启AOF持久化,Redis会先执行一次BGSAVE命令,然后再关闭服务器。BGSAVE操作,生成当时的快照并发送到从节点。主从复制使用的是RDB而不是AOF的原因是RDB是经过压缩的二进制数据,文件很小且使用快速,并且不需要像AOF一样重放每个写命令,经历冗长的处理逻辑。假设使用AOF做全量复制,就必须要打开AOF功能,打开AOF就需要选择同步的策略,选择不当还会严重影响Redis的性能。RDB存储的文件是二进制文件,加载速度要比AOF文件快得多,同时其存储的是数据的快照,文件大小通常会比AOF更小,节省磁盘空间,可以方便地将RDB文件复制到其他地方进行备份和灾难恢复,对性能的影响也较小。但是也有其缺点:如果Redis在两次快照之间发生故障,那么最后一次快照之后的数据就会丢失,数据丢失的量取决于快照的频率。同时在执行BGSAVE命令的时候,由于需要fork子进程,如果内存较大,fork的过程会比较耗时,而且如果在fork以后主进程又修改了大量的数据,可能就会导致内存占用翻倍。
RDB进行快照的时候如果发生了服务崩溃,那么在没有将数据全部写入到磁盘前,这次快照操作都不算成功,如果出现了服务器崩溃的情况,就会以上一次完整的RDB快照文件作为恢复内存数据的参考,即在快照操作的过程中不能影响上一次备份数据。Redis会在磁盘上创建一个临时文件进行数据操作,等待操作成功之后才会使用该临时文件替换掉上一次的备份。
AOF通过将写操作逐条记录到日志文件来实现持久化,从而保证数据的安全性。相比于RDB机制,AOF的主要目标是提供更高的数据可靠性和更细粒度的持久化。
aof_buf缓冲区:当客户端发送写命令给Redis服务器时,Redis会先将这些命令追加到aof_buf缓冲区中。aof_buf缓冲区中的数据同步到磁盘上的AOF文件中。RDB 和 AOF 混用持久化方式,也称为混合持久化,是 Redis 4.0 引入的一种新的持久化方式。它结合了 RDB 和 AOF 各自的优点,既能实现快速的数据恢复,又能最大限度地减少数据丢失的风险。
核心思想其实就是在AOF重写时,将重写以前的数据以RDB的格式写入到AOF文件的开头,然后将重写期间的增量操作以AOF格式追加到文件末尾。这样AOF文件就分为了两部分:一部分是RDB,一部分是AOF,这样能够很好地利用RDB与AOF各自的优点。
参考资料:
Java 全栈知识点问题汇总(上) | Java 全栈知识体系
Data Structures - Redis
Redis持久化|文档 --- Redis persistence | Docs
Redis 中仅追加文件的重要性 |数九 --- Importance of Append-only File in Redis | Severalnines