基础理论 #
概况 #
Redis (Remote Dictionary Server) 是一个开源的、支持网络、基于内存、可选持久性的键值对数据库。它支持多种类型的数据结构,如字符串(strings)、列表(lists)、集合(sets)、有序集合(sorted sets)以及散列(hashes)、位图(bitmaps)、超日志(hyperloglogs)和地理空间(geospatial)索引半径查询。
特点包括:
- 高性能:因为数据主要在内存中进行操作,读写速度非常快。
- 丰富的数据类型:提供多种数据结构来满足不同场景下的需求。
- 原子性:所有操作都是原子性的,支持事务(通过MULTI/EXEC命令)。
- 丰富的功能:支持发布订阅、Lua脚本、事务等。
数据结构 #
Redis提供的数据结构包括: Redis 支持多种数据类型,以下是一些常用的数据类型以及它们的简要描述和使用场景:
数据类型 | 描述 | 使用场景 |
---|---|---|
String | 二进制安全字符串,最大可以存储 512MB | 存储文本或二进制数据,如缓存用户个人信息等 |
List | 有序集合,按插入顺序排序 | 消息队列、时间线、最新消息列表 |
Set | 无序集合,元素不重复 | 标签、社交网络中的朋友关系等 |
Sorted Set | 有序集合,元素不重复,并且每个元素都会关联一个浮点数分数 | 排行榜、带权重的集合 |
Hash | 键值对集合,适用于存储对象 | 存储、访问和修改对象属性 |
Bitmaps | 通过位来表示数据的数据类型,适合做计数统计 | 在线状态、特性标志、统计等 |
HyperLogLog | 近似去重计数的数据结构 | 大数据量的计数,如统计独立 IP 访问数量 |
Geospatial | 存储地理位置信息,并进行相关地理操作 | 储存经纬度,查询附近的地点 |
Streams | Redis 5.0 新增的数据类型,是一个可持久化的日志数据结构 | 实现消息队列,发布/订阅模式,日志记录 |
Redis 之所以快速,主要原因有以下几点: #
- 内存存储:Redis 将所有数据存储在内存中,内存的读写速度远高于硬盘。
- 数据结构简单:Redis 的数据结构设计简单直观,易于高效操作。
- 非阻塞 IO:Redis 使用非阻塞 IO 和多路复用技术,可以处理多个并发连接。
- 单线程模型:Redis 大部分操作是单线程执行,避免了多线程的上下文切换开销。
Redis高性能IO模型 #
Redis的IO模型使用的是非阻塞IO复用技术,主要是epoll作为IO多路复用技术的实现方式。它通过单线程事件循环来处理所有客户端请求,确保绝大部分请求都是非阻塞的,并且使用异步编程模式来提高性能。
引用一些数据的话,可以提及以下几点:
- 内存读写速度:NVMe SSD 的 IOPS 可以达到数十万到数百万,而内存的 IOPS 可以达到千万级别,延迟通常小于100微秒。相比之下,内存的速度通常是硬盘的数千倍甚至更高。
- 性能表现:根据 Redis 官方给出的数据,在一个标准 Linux 系统上,Redis 可以达到每秒10万级别的读写操作(这个数字可能因配置、数据类型和具体操作而异)。
以上数据只是为了说明 Redis 为什么快速,具体的性能指标会根据使用环境、硬件配置以及具体的工作负载而有所变化。
持久化机制 #
Redis提供两种持久化机制:RDB(Snapshotting)和AOF(Append-only file)。
- RDB:在指定的时间间隔内生成数据集的时间点快照。
- AOF:记录服务器接收到的每个写操作指令,并在服务器启动时重新执行这些命令来恢复数据。
缓存淘汰/删 #
当内存不足时,Redis会根据配置的策略来移除一些键,以释放空间。常见的淘汰策略包括:
- noeviction:不进行淘汰,返回错误。
- allkeys-lru:从所有键中淘汰最近最少使用的键。
- volatile-lru:从设置了过期时间的键中淘汰最近最少使用的键。
- 其他
Redis 缓存淘汰策略是指当内存使用达到一定阈值时,Redis 会按照特定的策略自动移除一些键值对,以释放空间供新的写入操作使用。以下是 Redis 支持的几种缓存淘汰策略:
策略 | 描述 |
---|---|
noeviction | 不进行任何淘汰,尝试写入导致超出内存限制时返回错误信息 |
allkeys-lru | 根据最近最少使用算法(Least Recently Used),淘汰长时间未被查询或修改的键 |
volatile-lru | 只根据 LRU 算法淘汰设置了过期时间的键 |
allkeys-random | 随机移除键 |
volatile-random | 随机移除已经设置了过期时间的键 |
volatile-ttl | 移除即将过期的键 |
volatile-lfu (>=4.0) | 根据最不经常使用算法(Least Frequently Used),淘汰数据访问频率低的键 |
allkeys-lfu (>=4.0) | 同上,但是针对所有键,不仅仅是那些设置了过期时间的键 |
在这些策略中,LFU 和 LRU 是基于数据的访问模式来决定哪个键最有可能被淘汰:
LRU(Least Recently Used):这种策略假设如果数据最近很少用到,那么在将来也不太可能被用到。因此,当需要淘汰数据来腾出空间时,它会选取最长时间未被访问的数据。
LFU(Least Frequently Used):这个策略则是基于数据被访问的频率,删除那些访问次数最少的数据。相比于 LRU,LFU 在处理“热点数据”上表现更加出色。
上述淘汰策略可以通过在 redis.conf
文件中设置 maxmemory-policy
来选择,例如:
maxmemory-policy allkeys-lru
Redis 常用的显式删除操作,如:
DEL
命令直接删除一个或多个键。EXPIRE
命令设置键的过期时间,到期后键自动被删除。SET
命令在设置新值的同时可以设置过期时间。
阻塞点与异步机制 #
Redis 是一个基于事件驱动的服务器,它使用非阻塞 I/O 和事件通知机制来处理并发连接。这意味着 Redis 在执行某些操作时不会被阻塞等待 I/O 操作的完成,从而可以在等待磁盘或网络 I/O 时继续处理其他任务。
阻塞点:
尽管 Redis 大部分操作是非阻塞的,但仍有一些情况会引起阻塞:
- 命令
BLPOP
、BRPOP
、BRPOPLPUSH
用于列表操作时,如果列表为空,会导致客户端阻塞直到有新元素插入。 - 发布订阅 (
PUBLISH
/SUBSCRIBE
命令) 中,客户端可能会等待新消息的到来。 - 对于慢查询和大数据量操作(如
KEYS *
或SMEMBERS
),可能会造成临时阻塞。
异步机制:
为了减少阻塞时间,Redis 提供了异步处理能力:
- 使用 AOF(Append Only File)日志时,Redis 支持后台异步进行磁盘写入。
- 主从同步和 RDB 快照生成也可以异步执行,以避免阻塞主线程。
- 客户端可以通过管道(pipelining)技术同时发送多个命令,然后一次性读取所有响应,这样可以减少网络延迟带来的影响。
原子操作、管道、事务 #
原子操作:
在 Redis 中,单个命令总是原子性执行的。这意味着当一个命令在执行过程中,不会被其他命令打断。例如,INCR
命令增加一个整数值,并返回新值,这个过程是原子的。
管道(Pipelining):
管道是一种技术,可以将多个命令打包后一次性发送给服务器,而无需等待每个命令的响应。这可以显著提高性能,特别是在高延迟网络环境中。管道减少了网络往返次数,使得批量操作更加高效。
事务:
Redis 的事务功能通过 MULTI
、EXEC
、DISCARD
和 WATCH
命令实现。一个事务由一系列命令组成,这些命令会被序列化并按顺序执行。以下是事务的关键点:
MULTI
开始一个事务。- 连续输入多个命令,这些命令不会立即执行,而是被放入队列中。
EXEC
执行所有队列中的命令。- 如果在执行
EXEC
之前调用DISCARD
,则可以取消事务。 WATCH
可以监视一个或���个键,如果在事务开始之后任何监视的键被修改,则事务将被中断。
内存碎片 #
内存碎片是指由于多次申请和释放内存导致可用内存碎片化,从而降低内存使用效率。在 Redis 中,内存碎片化可以因以下原因产生:
- 频繁地创建和删除大量的小对象。
- 分配给字符串或者列表等数据类型的内存大小动态变化。
内存碎片问题可能导致 Redis 占用更多的物理内存,超出实际数据需要的大小。可以通过 info memory
命令检查内存相关的统计信息,其中包括内存碎片率(memory fragmentation ratio),当这个比率超过 1.5 时,就说明存在一定的内存碎片问题。
为了解决内存碎片问题,可以采取以下措施:
- 定期重启 Redis 服务。
- 使用 jemalloc 等先进的内存分配器。
- 适当调整 Redis 中关于对象重用的策略。例如,可以通过调整 maxmemory 策略来控制内存使用,或者使用 lazyfree-lazy-eviction 等参数以更优化的方式释放内存。 优化数据结构和访问模式。比如,尽量避免存储过多的小对象;使用哈希表(hashes)来存储对象集合而非单独的字符串键;对大型列表进行分页处理等。 监控并剔除长时间未使用的键,这可以通过设置键的过期时间(TTL)来实现。
另外,Redis 4.0 及以上版本引入了 active defragmentation 功能,它允许 Redis 在运行时动态地减少内存碎片,而无需重启服务。这个特性可以在 redis.conf 配置文件中开启,并允许你配置不同的参数来控制碎片整理的行为。