Redis
Redis(Remote Dictionary Server),远程字典服务,是用C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,提供多种语言的API。Redis 是一个使用 C 语言开发的高速缓存数据库。Redis 的数据是保存在内存中的(内存数据库,支持持久化),因此读写速度非常快,被广泛应用于分布式缓存方向。并且,Redis 存储的是 KV 键值对数据。
参考原文:
Redis基础
Redis 是一款完全开源免费、遵守BSD协议的高性能(NOSQL)的key-value数据库。它使用ANSI C语言编写,支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
Redis 功能及使用场景
功能:数据缓存功能、分布式锁的功能、持久化机制、支持事务、支持消息队列、键过期、lua脚本(基于C语言,性能快)。
Redis的使用场景有如下一些:
- 读写效率要求高,需要将数据进行缓存的。此时,可以把一些需要频繁访问的数据,而且在短时间之内不会发生变化的,放入Redis中进行操作,从而提高用户的请求速度和降低网站的负载,降低数据库的读写次数。
 - 需要实时计算的场景。需要实时变化和展示的功能,就可以把相关数据放在Redis中进行操作,可以大大提高访问效率。
 - 消息队列场景。比如在应对实时聊天系统时,就可以使用Redis,可以大大提高应用的可用性。
 
应用场景:缓存热点且不经常修改的数据、计数器、限时业务、分布式锁(set nx)、队列等。
- 记录帖子点赞数、点击数、评论数
 - 缓存近期热帖
 - 缓存文章详情信息
 - 记录用户会话信息
 
Redis 特点
- 数据库:Redis是一款基于键值对的、线程安全的NoSQL数据库;
 - 内存读写性能:它在内存中读写性能非常高,每秒可以处理超过百万次的读写操作。
 - 服务端线程安全,客户端线程不安全:Redis服务端是线程安全的,永远只有主线程一个线程进行读写,不需要任何的同步机制。虽然Redis6.0增加了多线程的模型,但多线程目的只是为了处理网络的IO事件,读写指令的执行依然由主线程自己处理。Redis客户端层面线程不安全,要引入原子指令(例如INCR是给数值原子性加1)、分布式锁、lua脚本保证Redis的原子操作。
 
简单总结:
Redis 基于内存,内存访问速度比磁盘快很多。一般MySQL 这类数据库 QPS (Query Per Second,服务器每秒可以执行的查询次数)大概在 4k 左右(4 核 8g),但使用 Redis 缓存后很容易达 5w+,甚至能达到 10w+(就单机 Redis 而言,Redis 集群的话会更高)。这样直接操作缓存能够承受的数据库请求数量是远远大于直接访问数据库的,可以考虑把数据库中的部分数据转移到缓存,这样用户的一部分请求会直接到缓存而不经过数据库,提高系统整体的并发。
Redis 为什么这么快
Redis 内部做了非常多的性能优化,比较重要的有下面 4 点:
- 纯内存操作 (Memory-Based Storage) :这是最主要的原因。Redis 数据读写操作都发生在内存中,访问速度是纳秒级别,而传统数据库频繁读写磁盘的速度是毫秒级别,两者相差数个数量级。
 - 高效的 I/O 模型 (I/O Multiplexing & Single-Threaded Event Loop) :Redis 使用单线程事件循环配合 I/O 多路复用技术,让单个线程可以同时处理多个网络连接上的 I/O 事件(如读写),避免了多线程模型中的上下文切换和锁竞争问题。虽然是单线程,但结合内存操作的高效性和 I/O 多路复用,使得 Redis 能轻松处理大量并发请求。
 - 优化的内部数据结构 (Optimized Data Structures) :Redis 提供多种数据类型(如 String, List, Hash, Set, Sorted Set 等),其内部实现采用高度优化的编码方式(如 ziplist, quicklist, skiplist, hashtable 等)。Redis 会根据数据大小和类型动态选择最合适的内部编码,以在性能和空间效率之间取得最佳平衡。
 - 简洁高效的通信协议 (Simple Protocol - RESP) :Redis 使用自己设计的 RESP (REdis Serialization Protocol) 协议。该协议实现简单、解析性能好,并且是二进制安全的。客户端和服务端之间通信的序列化/反序列化开销很小,有助于提升整体交互速度。
 

Redis 和 memcache 的区别
共同点:都是基于内存的数据库,一般都用来当做缓存使用。都有过期策略。性能都非常高。
区别:
- 数据类型:Redis 支持更丰富的数据类型(支持更复杂的应用场景)。Redis 不仅仅支持简单的 k/v 类型的数据,同时还提供 list、set、zset、hash 等数据结构的存储;而 Memcached 只支持最简单的 k/v 数据类型。
 - value 值大小不同:Redis 最大可以达到 512mb;memcache 只有 1mb。
 - 数据持久化:Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用;而 Memcached 把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。也就是说,Redis 有灾难恢复机制,而 Memcached 没有。
 - 集群模式支持:Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;而 Redis 自 3.0 版本起是原生支持集群模式的。
 - 线程模型:Memcached 是多线程、非阻塞 IO 复用的网络模型;而 Redis 使用单线程的多路 IO 复用模型(Redis 6.0 针对网络数据的读写引入了多线程)。
 - 特性支持:Redis 支持发布订阅模型、Lua 脚本、事务等功能,而 Memcached 不支持。并且,Redis 支持更多的编程语言。
 - 过期数据删除:Memcached 过期数据的删除策略只用了惰性删除,而 Redis 同时使用了惰性删除与定期删除。
 - 使用底层模型不同:它们之间底层实现方式,以及与客户端之间通信的应用协议不一样,Redis 自己构建了 vm 机制,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
 
Redis 和 本地缓存 的区别
| 特性 | 本地缓存 | Redis | 
|---|---|---|
| 数据一致性 | 多服务器部署时存在数据不一致问题 | 数据一致 | 
| 内存限制 | 受限于单台服务器内存 | 独立部署,内存空间更大 | 
| 数据丢失风险 | 服务器宕机数据丢失 | 可持久化,数据不易丢失 | 
| 管理维护 | 分散,管理不便 | 集中管理,提供丰富的管理工具 | 
| 功能丰富性 | 功能有限,通常只提供简单的键值对存储 | 功能丰富,支持多种数据结构和功能 | 
Redis Module
Redis 从 4.0 版本开始支持通过 Module 来扩展其功能以满足特殊的需求。这些 Module 以动态链接库(so 文件)的形式被加载到 Redis 中,是非常灵活的动态扩展功能的实现方式。目前,被 Redis 官方推荐的 Modules 官方文档有:
- RediSearch:用于实现搜索引擎的模块。
 - RedisJSON:用于处理 JSON 数据的模块。
 - RedisGraph:用于实现图形数据库的模块。
 - RedisTimeSeries:用于处理时间序列数据的模块。
 - RedisBloom:用于实现布隆过滤器的模块。
 - RedisAI:用于执行深度学习/机器学习模型并管理其数据的模块。
 - RedisCell:用于实现分布式限流的模块。
 
Redis 瓶颈
- 内存:因为读写在内存中进行,内存大小会影响Redis性能。可以通过加内存、读写分离优化性能。
 - 网络带宽:网络 IO是Redis最大瓶颈,即客户端和服务端之间的网络传输延迟。Redis6.0引入网络IO多线程模型,提高了性能瓶颈。
 
jedis 和 Redisson 的区别
jedis:提供了比较全面的 Redis 命令的支持。Redisson:实现了分布式和可扩展的 Java 数据结构,与 jedis 相比 Redisson 的功能相对简单,不支持排序、事务、管道、分区等 Redis 特性。
Redis 支持的 Java 客户端
支持的 Java 客户端有 Redisson、jedis、lettuce 等。
Redis 支持的数据类型
- 5 种基础数据类型:String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合)。
 - 3 种特殊数据类型:HyperLogLog(基数统计)、Bitmap (位图)、Geospatial (地理位置)。
 - 此外,还有一些其他的,比如 Bloom filter(布隆过滤器)、Bitfield(位域)。
 
具体详见文章:Redis支持的数据类型
缓存读写策略
- Cache Aside Pattern(旁路缓存模式)
 - Read/Write Through Pattern(读写穿透)
 - Write Behind Pattern(异步缓存写入)
 
详见文章:三大缓存读写策略
Redis 应用
数据缓存:部分热点数据转存储到缓存,部分请求会直接到缓存而不经过数据库,提高系统整体的并发。
分布式锁:通过 Redis 来做分布式锁是一种比较常见的方式。通常情况下,都是基于 Redisson 来实现分布式锁。关于 Redis 实现分布式锁的详细介绍,可以看这篇文章:分布式锁详解。
限流:一般是通过 Redis + Lua 脚本的方式来实现限流。如果不想自己写 Lua 脚本的话,也可以直接利用 Redisson 中的 RRateLimiter 来实现分布式限流,其底层实现就是基于 Lua 代码+令牌桶算法。
消息队列:Redis 自带的 List 数据结构可以作为一个简单的队列使用。Redis 5.0 中增加的 Stream 类型的数据结构更加适合用来做消息队列。它比较类似于 Kafka,有主题和消费组的概念,支持消息持久化以及 ACK 机制。
延时队列:Redisson 内置了延时队列(基于 Sorted Set 实现的)。
分布式 Session:利用 String 或者 Hash 数据类型保存 Session 数据,所有的服务器都可以访问。
复杂业务场景:通过 Redis 以及 Redis 扩展(比如 Redisson)提供的数据结构,我们可以很方便地完成很多复杂的业务场景,比如通过 Bitmap 统计活跃用户、通过 Sorted Set 维护排行榜、通过 HyperLogLog 统计网站 UV 和 PV。
基于 Redis 实现分布式锁
关于 Redis 实现分布式锁可参考文章:分布式锁详解。
基于Redis 实现消息队列
可以但不建议使用 Redis 来做消息队列。有更成熟的消息队列中间件可以用,与之相比有很多欠缺的地方。
Redis 2.0 前,只能通过 List 实现消息队列。但功能简单,消息确认机制等功能需要自己实现,没有广播机制,消息只能被消费一次。
Redis 2.0 引入了发布订阅 (pub/sub) 功能,引入了 channel(频道)的概念,发布订阅机制的实现就是基于此 channel 来做的,解决了 List 实现消息队列没有广播机制的问题。pub/sub 既能单播又能广播,还支持 channel 的简单正则匹配。不过,消息丢失(客户端断开连接或者 Redis 宕机都会导致消息丢失)、消息堆积(发布者发布消息的时候不会管消费者的具体消费能力如何)等问题依然没有一个比较好的解决办法。
Redis 5.0 新增加的一个数据结构 Stream 来做消息队列。Stream 支持:发布 / 订阅模式;按照消费者组进行消费(借鉴了 Kafka 消费者组的概念);消息持久化( RDB 和 AOF);ACK 机制(通过确认机制来告知已经成功处理了消息);阻塞式获取消息。Stream是一个有序的消息链表,每个消息都有唯一 ID 和对应内容。ID 是一个时间戳和序列号的组合,保证消息的唯一性和递增性。内容是一个或多个键值对(类似 Hash 基本数据类型),用来存储消息的数据。
基于Redis 实现搜索引擎
借助 RediSearch,这是一个基于 Redis 的搜索引擎模块。RediSearch 支持中文分词、聚合统计、停用词、同义词、拼写检查、标签查询、向量相似度查询、多关键词搜索、分页搜索等功能,算是一个功能比较完善的全文搜索引擎了。对于小型项目的简单搜索场景来说,使用 RediSearch 来作为搜索引擎还是没有问题的(搭配 RedisJSON 使用)。
基于 Redis 实现延时任务
订单在 10 分钟后未支付就失效,红包 24 小时未被查收自动退还,如何用 Redis 实现?
- Redis 过期事件监听。存在时效性较差、丢消息、多服务实例下消息重复消费等问题,不被推荐使用
 - Redisson 内置的延时队列。优势如下:
- 减少了丢消息的可能:DelayedQueue 中的消息会被持久化,即使 Redis 宕机了,根据持久化机制,也只可能丢失一点消息,影响不大。当然也可以使用扫描数据库的方法作为补偿机制。
 - 消息不存在重复消费问题:每个客户端都是从同一个目标队列中获取任务的,不存在重复消费的问题。
 
 
关于 Redis 实现延时任务的详细介绍,可以参考这篇文章:如何基于 Redis 实现延时任务?
Redis 的持久化机制
RDB(Redis Database):指定的时间间隔能对你的数据进行快照存储。
- 数据备份机制RDB(默认):数据每隔一段时间写进磁盘rdb文件,故障后从文件读。可以在redis.conf配置多少秒内多少key修改时自动bgsave。占CPU和内存但恢复快,不能恢复完整数据。save命令是主进程立即执行一次RDB,其他所有命令进程阻塞。bgsave是子进程fork主进程,阻塞并拷贝一份主进程的页表(虚拟内存到物理内存的映射关系),然后子进程写数据到rdb文件,主进程继续处理用户请求。
 
AOF(Append Only File):每一个收到的写命令都通过write函数追加到文件中。
- 追加文件机制AOF:命令日志按指定频率(默认立刻,在redis.conf配置为缓存一秒)写进磁盘aof文件,可以按条件(redis.conf配置,比上次重写aof文件超过多少百分比时自动重写、aof文件超过多大自动重写)自动重写aof文件中的命令(多次更新同一数据只有最近一次更新有效),故障后从文件读命令恢复数据。不占CPU和内存占IO,能恢复完整或故障1s前的数据但恢复慢。
 
详见文章:Redis的持久化机制
Redis 的线程模型
对于读写命令来说,Redis 一直是单线程模型。不过,在 Redis 4.0 版本之后引入了多线程来执行一些大键值对的异步删除操作,Redis 6.0 版本之后引入了多线程来处理网络请求(提高网络 IO 读写性能)。
Redis 的单线程模型
Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型(Netty 的线程模型也基于 Reactor 模式),这套事件处理模型对应的是 Redis 中的文件事件处理器(file event handler)。由于文件事件处理器是单线程方式运行的,所以一般说 Redis 是单线程模型。
Redis 基于 Reactor 模式开发了自己的网络事件处理器:这个处理器被称为文件事件处理器(file event handler)。
- 文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字,并根据套接字目前执行的任务来为套接字关联不同的事件处理器。
 - 当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关 闭(close)等操作时,与操作相对应的文件事件就会产生,这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。
 虽然文件事件处理器以单线程方式运行,但通过使用 I/O 多路复用程序来监听多个套接字,文件事件处理器既实现了高性能的网络通信模型,又可以很好地与 Redis 服务器中其他同样以单线程方式运行的模块进行对接,这保持了 Redis 内部单线程设计的简单性。
单线程怎么监听大量的客户端连接?
Redis 通过 IO 多路复用程序 来监听来自客户端的大量连接(或者说是监听多个 socket),它会将感兴趣的事件及类型(读、写)注册到内核中并监听每个事件是否发生。好处在于I/O 多路复用技术的使用让 Redis 不需要额外创建多余的线程来监听客户端的大量连接,降低了资源的消耗(和 NIO 中的 Selector 组件很像)。
文件事件处理器(file event handler)主要是包含 4 个部分:
- 多个 socket(客户端连接)
 - IO 多路复用程序(支持多个客户端连接的关键)
 - 文件事件分派器(将 socket 关联到相应的事件处理器)
 - 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
 
Redis 的为什么是单线程的
因为 cpu 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器内存或者网络带宽。既然单线程容易实现,而且 cpu 又不会成为瓶颈,那就顺理成章地采用单线程的方案了。
关于 Redis 的性能,官方网站也有,普通笔记本轻松处理每秒几十万的请求。
而且单线程并不代表就慢,nginx 和 nodejs 也都是高性能单线程的代表。
Redis读写为什么不采用多线程?
- CPU不是瓶颈:Redis在内存中读写性能非常高,CPU不是Redis的瓶颈,无需使用多线程。
 - 担心加锁影响性能:多线程情况下,想实现线程安全必须加锁,加锁将极大地影响性能。
 
为什么单线程还读写性能这么高?
- 基于内存:Redis是基于内存的,内存的读写速度非常快;
 - 上下文切换:单线程避免了不必要的上下文切换和竞争条件;
 - IO多路复用:底层采用NIO(非阻塞IO),NIO采用IO多路复用技术,一个线程通过多路复用器处理多个连接。IO多路复用技术选用epoll调用模型,红黑树存所有事件,链表存就绪事件。epoll_wait函数链表,通知应用程序读写操作。
 
Redis6.0 为何引入了多线程
Redis6.0 引入多线程主要是为了提高网络 IO 读写性能,因为这算是 Redis 中的一个性能瓶颈(Redis 的瓶颈主要受限于内存和网络)。虽然,Redis6.0 引入了多线程,但 Redis 的多线程只是在网络数据的读写这类耗时操作上使用了,执行命令仍然是单线程顺序执行。因此,你也不需要担心线程安全问题。
Redis 内存管理
Redis 为何要设置过期时间
- 内存有限且珍贵,若不对缓存数据设置过期时间,内存占用会一直增长,最终可能导致 OOM 问题。Redis 中除了字符串类型有自己独有设置过期时间的命令 
setex外,其他方法都靠expire命令设置过期时间 。另外persist命令可移除一个键的过期时间。 - 很多业务场景需要某个数据只在某一时间段内存在,如短信验证码可能只在 1 分钟内有效,用户登录的 Token 可能只在 1 天内有效。若使用传统的数据库处理,需要自己判断过期,很麻烦且性能差。
 
Redis 判断数据是否过期
Redis 通过一个叫做过期字典(可以看作是 hash 表)来保存数据过期的时间。过期字典的键指向 Redis 数据库中的某个 key(键),过期字典的值是一个 long long 类型的整数,这个整数保存了 key 所指向的数据库键的过期时间(毫秒精度的 UNIX 时间戳)。

过期字典是存储在 redisDb 这个结构里的:
1  | typedef struct redisDb {  | 
在查询 key 的时候,Redis 首先检查该 key 是否存在于过期字典中(时间复杂度为 O(1)),如果不在就直接返回,在的话需要判断一下这个 key 是否过期,过期直接删除 key 然后返回 null。
Redis 过期 key 的删除策略
常用过期数据的删除策略如下:
- 惰性删除:只会在取出/查询 key 的时候才对数据进行过期检查。这种方式对 CPU 最友好,但是可能会造成太多过期 key 没有被删除。
 - 定期删除:周期性地随机从设置了过期时间的 key 中抽查一批,然后逐个检查这些 key 是否过期,过期就删除 key。相比于惰性删除,定期删除对内存更友好,对 CPU 不太友好。
 - 延迟队列:把设置过期时间的 key 放到一个延迟队列里,到期之后就删除 key。这种方式可以保证每个过期 key 都能被删除,但维护延迟队列太麻烦,队列本身也要占用资源。
 - 定时删除:每个设置了过期时间的 key 都会在设置的时间到达时立即被删除。这种方法可以确保内存中不会有过期的键,但是它对 CPU 的压力最大,因为它需要为每个键都设置一个定时器。
 
Redis 采用的是 定期删除+惰性/懒汉式删除 结合的策略,这也是大部分缓存框架的选择。定期删除对内存更加友好,惰性删除对 CPU 更加友好。两者各有千秋,结合起来使用既能兼顾 CPU 友好,又能兼顾内存友好。
Redis 的定期删除
Redis 的定期删除过程是随机的(周期性地随机从设置了过期时间的 key 中抽查一批),不保证所有过期键都被立即删除。这也就解释了为什么有的 key 过期了,并没有被删除。并且,Redis 底层通过限制删除操作执行的时长和频率减少删除操作对 CPU 时间的影响。
定期删除还受到执行时间和过期 key 的比例的影响:
- 执行时间已经超过了阈值,那么就中断这一次定期删除循环,以避免使用过多的 CPU 时间。
 - 如果这一批过期的 key 比例超过一个比例,就会重复执行此删除流程,以更积极地清理过期 key。相应地,如果过期的 key 比例低于这个比例,就会中断这一次定期删除循环,避免做过多的工作而获得很少的内存回收。
 
Redis 7.2 版本的执行时间阈值是 25ms,过期 key 比例设定值是 **10%**。
1  | 
每次随机抽查数量是多少?
expire.c 中定义了每次随机抽查的数量,Redis 7.2 版本为 20,也就是说每次会随机选择 20 个设置了过期时间的 key 判断是否过期。
1  | 
如何控制定期删除的执行频率?
Redis 定期删除的频率由 hz 参数控制。hz 默认为 10,代表每秒执行 10 次,也就是每秒钟进行 10 次尝试来查找并删除过期的 key。
hz 的取值范围为 1~500。增大 hz 参数的值会提升定期删除的频率。如果你想要更频繁地执行定期删除任务,可以适当增加 hz 的值,但这会增加 CPU 的使用率。根据 Redis 官方建议,hz 的值不建议超过 100,对于大部分用户使用默认的 10 就足够了,只有在非常低延迟的环境中才提高到100。
下面是 hz 参数的官方注释,我翻译了其中的重要信息(Redis 7.2 版本)。
类似的参数还有一个 dynamic-hz,这个参数开启之后 Redis 就会在 hz 的基础上动态计算一个值。Redis 提供并默认启用了使用自适应 hz 值的能力,这两个参数都在 Redis 配置文件 redis.conf 中:
1  | # 默认为 10  | 
除了定期删除过期 key 这个定期任务之外,还有一些其他定期任务例如关闭超时的客户端连接、更新统计信息,这些定期任务的执行频率也是通过 hz 参数决定。
为什么定期删除不是把所有过期 key 都删除呢?
这样会对性能造成很大影响。key 数量非常庞大时,遍历检查非常耗时严重影响性能。Redis 的这种策略设计目的在于平衡内存和性能。
为什么 key 过期之后不立马把它删掉呢?这样不是会浪费很多内存空间吗?
因为不太好办到,或者说这种删除方式的成本太高了。假如使用延迟队列作为删除策略,存在如下问题:
- 队列本身开销可能很大:key 多的情况下,一个延迟队列可能无法容纳。
 - 维护延迟队列太麻烦:修改 key 的过期时间就需要调整其在延迟队列中的位置,并且还需要引入并发控制。
 
大量 key 集中过期如何处理
当 Redis 中存在大量 key 在同一时间点集中过期时,可能会导致以下问题:
- 请求延迟增加:Redis 在处理过期 key 时需要消耗 CPU 资源,如果过期 key 数量庞大,会导致 Redis 实例的 CPU 占用率升高,进而影响其他请求的处理速度,造成延迟增加。
 - 内存占用过高:过期的 key 虽然已经失效,但在 Redis 真正删除它们之前,仍然会占用内存空间。如果过期 key 没有及时清理,可能会导致内存占用过高,甚至引发内存溢出。
 
为了避免这些问题,可以采取以下方案:
- 尽量避免 key 集中过期:在设置键的过期时间时尽量随机一点。
 - 开启 lazy free 机制:修改 
redis.conf配置文件,将lazyfree-lazy-expire参数设置为yes,即可开启 lazy free 机制。开启 lazy free 机制后,Redis 会在后台异步删除过期的 key,不会阻塞主线程的运行,从而降低对 Redis 性能的影响。 
Redis 的淘汰策略
相关问题:MySQL 里有 2000w 数据,Redis 中只存 20w 的数据,如何保证 Redis 中的数据都是热点数据?
Redis 的内存淘汰策略只有在运行内存达到了配置的最大内存阈值时才会触发,这个阈值是通过 redis.conf 的 maxmemory 参数来定义的。64 位操作系统下,maxmemory 默认为 0,表示不限制内存大小。32 位操作系统下,默认的最大内存值是 3GB。
可以使用命令
config get maxmemory来查看maxmemory的值。
可以使用config get maxmemory-policy命令来查看当前 Redis 的内存淘汰策略。
可以通过config set maxmemory-policy 内存淘汰策略命令修改内存淘汰策略,立即生效,但这种方式重启 Redis 之后就失效了。修改redis.conf中的maxmemory-policy参数不会因为重启而失效,不过,需要重启之后修改才能生效。
Redis 提供了 6 种内存淘汰策略:
volatile-lru(least recently used):从已设置过期时间的数据集(server.db[i].expires)挑选最近最少使用的数据淘汰。volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)挑选将要过期的数据淘汰。volatile-random:从已设置过期时间的数据集(server db[i].expires)任意选择数据淘汰。allkeys-lru(least recently used):从数据集(server.db[i].dict)挑选最近最少使用的数据淘汰。allkeys-random:从数据集(server.db[i].dict)任意选择数据淘汰。no-enviction(驱逐):禁止驱逐数据。
4.0 版本后增加以下两种:
volatile-lfu(least frequently used):从已设置过期时间的数据集(server.db[i].expires)选最不经常使用的数据淘汰。allkeys-lfu(least frequently used):从数据集(server.db[i].dict)中移除最不经常使用的数据淘汰。
allkeys-xxx表示从所有的键值中淘汰数据,而volatile-xxx表示从设置了过期时间的键值中淘汰数据。
config.c 中定义了内存淘汰策略的枚举数组:
1  | configEnum maxmemory_policy_enum[] = {  | 
关于淘汰策略的详细说明可以参考 Redis 官方文档:https://redis.io/docs/reference/eviction/。
Redis 事务
什么是 Redis 事务
可简单理解为:Redis 事务提供了一种将多个命令请求打包的功能。然后,再按顺序执行打包的所有命令,并且不会被中途打断。
Redis 事务实际开发中使用的非常少,功能比较鸡肋,不要将其和关系型数据库的事务混淆了。除了不满足原子性和持久性之外,事务中的每条命令都会与 Redis 服务器进行网络交互,比较浪费资源。因此,Redis 事务不建议在日常开发中使用。
- 实现方式:MULTI(开启事务,将命令都放进队列里),EXEC(执行事务),DISCARD(取消事务,清空队列),WATCH(监听指定的 Key)。可通过WATCH命令监听指定的 Key,当调用 
EXEC命令执行事务时,如果一个被WATCH命令监视的 Key 被 其他客户端/Session 修改的话,整个事务都不会被执行。 - 不支持回滚:在语法正确的情况下,Redis事务一定会执行成功。只有语法错误时才会导致事务失败,而语法问题应该在开发时就避免,所以为了提高性能,Redis事务不支持回滚。事务是一个原子操作,要么全部执行,要么全不执行。
 - 不完全满足ACID特性:Redis只满足隔离性和持久性,不满足原子性和一致性。
- 原子性:事务的所有操作,要么全部成功,要么全部失败。Redis不满足原子性,单个 Redis 命令的执行是原子性的,但事务失败后无法回滚。
 - 一致性:事务前后,数据库的约束没有被破坏,保持前后一致。Redis连约束这个概念都没有。
 - 隔离性:操作同一资源的并发事务之间相互隔离,不会互相干扰。Redis满足隔离性,因为redis server是单线程的,串行化执行事务,肯定是满足隔离性的。
 - 持久性:事务的结果最终一定会持久化到数据库,宕机等故障也无法影响。Redis在开启aof并指定立刻持久化命令时,满足持久性。rdb模式会丢失部分数据,不满足持久性。
 
 
如何解决 Redis 事务的缺陷
Redis 从 2.6 版本开始支持执行 Lua 脚本,它的功能和事务非常类似。可以利用 Lua 脚本来批量执行多条 Redis 命令,这些 Redis 命令会被提交到 Redis 服务器一次性执行完成,大幅减小了网络开销。一段 Lua 脚本可以视作一条命令执行,一段 Lua 脚本执行过程中不会有其他脚本或 Redis 命令同时执行,保证了操作不会被其他指令插入或打扰。
但若 Lua 脚本运行时出错并中途结束,出错之后的命令不会被执行。并且,出错之前执行的命令无法被撤销,无法实现类似关系型数据库执行失败可以回滚的那种原子性效果。因此,严格来说通过 Lua 脚本来批量执行 Redis 命令实际不完全满足原子性。
另外,Redis 7.0 新增了 Redis functions 特性,你可以将 Redis functions 看作是比 Lua 更强大的脚本。
详细介绍参考文章:Redis常见面试题总结(下)
Redis 性能优化
Redis 如何做内存优化
尽量使用 Redis 的散列表,把相关的信息放到散列表里面存储,而不是把每个字段单独存储,这样可以有效的减少内存使用。比如将 Web 系统的用户对象,应该放到散列表里面再整体存储到 Redis,而不是把用户姓名、年龄、密码、邮箱等字段分别设置 key 进行存储。
Redis 常见的性能问题及解决
主服务器写内存快照,会阻塞主线程的工作,当快照比较大时对性能影响非常大,会间断性暂停服务,故主服务器最好别写内存快照。Redis 主从复制的性能问题,为了主从复制的速度和连接的稳定性,主从库最好在同一个局域网内。
详细介绍参考文章:Redis常见面试题总结(下)
Redis 集群
Redis哨兵(Sentinel)是一个高可用性解决方案。哨兵系统可以监测Redis主从服务器的健康状态,自动执行故障转移,选举新的主服务器,并通知应用程序新主服务器的地址。哨兵还负责通知管理员,发送警报,并执行自定义脚本响应各种事件。Redis集群(Cluster)提供了一个数据分区(sharding)和自动管理的环境,支持在多个节点间进行数据共享。它能够在节点间自动分配数据,并在节点故障时提供自动的故障转移功能。集群通过分片来提高数据库的可扩展性,并能在不中断服务的情况下,动态地添加或移除节点。
详见文章:Redis集群
Redis 生产问题
Redis的缓存穿透、缓存击穿、缓存雪崩详见文章:Redis的缓存穿透、缓存击穿、缓存雪崩
Redis 与数据库数据的一致性
合理设置缓存的过期时间。
新增、更改、删除数据库操作时同步更新 Redis,可以使用事务机制来保证数据的一致性。
Cache Aside Pattern(旁路缓存模式) 的读写逻辑如下:
- 读操作: 先尝试从缓存读取数据。如果缓存命中,直接返回数据,否则从数据库查询数据,将查到的数据放入缓存并返回数据。
 - 写操作: 先更新数据库。再直接删除缓存中对应的数据。
 
如果更新数据库成功,而删除缓存这一步失败的情况的话,简单说有两个解决方案:
- 缓存失效时间(TTL - Time To Live)变短(不推荐,治标不治本):我们让缓存数据的过期时间变短,这样的话缓存就会从数据库中加载数据。另外,这种解决办法对于先操作缓存后操作数据库的场景不适用。
 - 增加缓存更新重试机制(常用):如果缓存服务当前不可用导致缓存删除失败的话,我们就隔一段时间进行重试,重试次数可以自己定。不过,这里更适合引入消息队列实现异步重试,将删除缓存重试的消息投递到消息队列,然后由专门的消费者来重试,直到成功。虽然说多引入了一个消息队列,但其整体带来的收益还是要更高一些。
 
详见文章:Redis缓存与数据库的一致性
Redis 使用规范
实际使用 Redis 的过程中,我们尽量要准守一些常见的规范,比如:
- 使用连接池:避免频繁创建关闭客户端连接。
 - 尽量不使用 O(n) 指令,使用 O(n) 命令时要关注 n 的数量:像 
KEYS *、HGETALL、LRANGE、SMEMBERS、SINTER/SUNION/SDIFF等 O(n) 命令并非不能使用,但需要明确 n 的值。另外,有遍历时可使用HSCAN、SSCAN、ZSCAN代替。 - 使用批量操作减少网络传输:原生批量操作命令(比如 
MGET、MSET等等)、pipeline、Lua 脚本。 - 尽量不使用 Redis 事务:Redis 事务实现的功能比较鸡肋,可以使用 Lua 脚本代替。
 - 禁止长时间开启 monitor:对性能影响比较大。
 - 控制 key 的生命周期:避免 Redis 中存放了太多不经常被访问的数据。
 
相关文章推荐:阿里云 Redis 开发规范。




