Redis 学习手册

内容纲要

目录

  1. Redis 架构详解 – 单机模式、主从复制、哨兵 Sentinel、Cluster 集群模式的设计与实现原理
  2. 安装与部署 – Linux 环境安装、Docker 容器化部署,单机/主从/哨兵/集群模式的部署步骤与注意事项
  3. 与主流语言的接入 – 提供 Java、Python、Golang 接入 Redis 的常用客户端、基本用法、连接池配置、超时与异常处理
  4. Redis 数据结构详解 – 字符串、列表、哈希、集合、有序集合、位图、HyperLogLog、Geo 的底层实现原理与典型使用方式
  5. Redis 持久化机制 – RDB 快照与 AOF 日志原理、配置、优缺点、混合持久化以及推荐场景
  6. 高可用方案 – Sentinel 哨兵与 Redis Cluster 的对比选型、故障切换机制及注意事项
  7. 应用场景 – 使用 Redis 实现缓存、分布式锁、消息队列、排行榜、限流、会话存储等场景的方案与代码示例
  8. 性能优化 – 慢查询分析、热点 Key 优化、防止缓存穿透、雪崩、击穿、内存淘汰策略与 maxmemory-policy 合理配置
  9. 监控与运维INFO 信息解读、关键性能指标说明、日志分析、常用运维工具以及 Prometheus+Grafana 监控方案
  10. 常见问题与坑 – Pipeline 误用、事务与 Lua 脚本问题、Key 管理、持久化潜在数据丢失、内存膨胀、客户端 GC 导致的超时等
  11. 实战与最佳实践 – 结合高并发秒杀、缓存穿透防护等真实场景,分析 Redis 在架构设计中的最佳实践案例

1. Redis 架构详解

单机模式: 单机模式即在一台服务器上启动一个 Redis 实例提供服务。优点是部署简单、成本低且性能高(无需数据同步,天然一致性);但缺点是可靠性差(单点故障)、性能受限于单机 CPU 和内存blog.csdn.net[blog.csdn.net](https://blog.csdn.net/zzhongcy/article/details/108446687#:~:text=假设上千万、上亿用户同时访问 Redis,QPS 达到 10 万%2B。这些请求过来,单机,直接就挂了。系统的瓶颈就出现在 Redis 单机问题上,此时我们可以通过 主从复制 解决该问题,实现系统的高并发。)。单机 QPS 通常可达数万,但在上千万并发请求时会达到瓶颈,需要引入多实例架构[blog.csdn.net](https://blog.csdn.net/zzhongcy/article/details/108446687#:~:text=单机 Redis 能够承载的 QPS(每秒查询速率)大概在几万左右。取决于业务操作的复杂性,Lua 脚本复杂性就极高。假如是简单的,key value 查询那性能就会很高。)。

单机架构下,所有读写请求都由同一台 Redis 服务器处理。当并发请求量过高,单实例可能无法支撑,需要引入主从或集群扩展[blog.csdn.net](https://blog.csdn.net/zzhongcy/article/details/108446687#:~:text=,的处理能力,Redis 是单线程的。)。

主从复制: Redis 的主从复制(Replication)允许将一台 Redis 的数据复制到多个副本,从节点只读,主节点负责写入[blog.csdn.net](https://blog.csdn.net/zzhongcy/article/details/108446687#:~:text=Redis 的复制(Replication)功能允许用户根据一个 Redis 服务器来创建任意多个该服务器的复制品,其中被复制的服务器为主服务器(Master),而通过复制创建出来的复制品则为从服务器(Slave)。 只要主从服务器之间的网络连接正常,主服务器就会将写入自己的数据同步更新给从服务器,从而保证主从服务器的数据相同。)。复制是单向的(只能主到从),常用于读写分离和数据备份。当 Master 宕机时需要手动或借助其他机制提升 Slave 为新的 Master 保证可用性[blog.csdn.net](https://blog.csdn.net/zzhongcy/article/details/108446687#:~:text=数据的复制是单向的,只能由主节点到从节点,简单理解就是从节点只支持读操作,不允许写操作。主要是读高并发的场景下用主从架构。主从模式需要考虑的问题是:当 Master 节点宕机,需要选举产生一个新的 Master 节点,从而保证服务的高可用性。)blog.csdn.net。优点是读扩展性好(增加 Slave 分担读取),主节点故障时有备份顶上blog.csdn.net;缺点是仍存在单点写入瓶颈,主机故障切换需人工干预(原从节点需手动配置为新主)blog.csdn.net

https://blog.csdn.net/zzhongcy/article/details/108446687 在主从架构中,Master 处理写操作并将数据同步给 Slave,Slave 可提供只读查询,实现读负载分担[blog.csdn.net](https://blog.csdn.net/zzhongcy/article/details/108446687#:~:text=Redis 的复制(Replication)功能允许用户根据一个 Redis 服务器来创建任意多个该服务器的复制品,其中被复制的服务器为主服务器(Master),而通过复制创建出来的复制品则为从服务器(Slave)。 只要主从服务器之间的网络连接正常,主服务器就会将写入自己的数据同步更新给从服务器,从而保证主从服务器的数据相同。)。但若 Master 故障,Slave 需手动提升为 Master,期间服务会中断blog.csdn.net

哨兵模式 (Sentinel): 为了在主从模式下实现自动故障切换,Redis 2.8 引入了 Sentinel 哨兵机制。在哨兵模式中,运行若干哨兵进程监控 Redis 主从实例的健康状况,自动完成主从切换和通知客户端blog.csdn.net[blog.csdn.net](https://blog.csdn.net/zzhongcy/article/details/108446687#:~:text=,Automatic failover):当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作。)。Sentinel 定期 PING 各节点判断存活(主观下线),多个哨兵相互通信达成共识确认故障(客观下线)并选举领头哨兵执行故障转移[blog.csdn.net](https://blog.csdn.net/zzhongcy/article/details/108446687#:~:text=Redis Sentinel 是分布式系统中监控 Redis 主从服务器,并提供主服务器下线时自动故障转移功能的模式。其中三个特性为:)cnblogs.com。切换过程中,某个 Slave 会被提升为新 Master,其他 Slave 指向新 Master,客户端也会被通知主节点地址变更[cnblogs.com](https://www.cnblogs.com/chenwenyin/p/13549492.html#:~:text=(1)集群监控:负责监控Redis master和slave进程是否正常工作 (2)消息通知:如果某个Redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员 (3)故障转移:如果master node挂掉了,会自动转移到slave,node上 (4)配置中心:如果故障转移发生了,通知client客户端新的master地址)cnblogs.com。哨兵模式提高了高可用性,主机宕机可自动无缝切换,但由于仍是单主架构,写能力和存储受单机限制,无法解决扩容问题[blog.csdn.net](https://blog.csdn.net/zzhongcy/article/details/108446687#:~:text=match at L191 哨兵模式中,单个节点的写能力,存储能力受到单机的限制,动态扩容困难复杂。于是,Redis 3,分布式方面的需求。Redis Cluster 集群模式具有 高可用、可扩展性、分布式、容错 等特性。)[blog.csdn.net](https://blog.csdn.net/zzhongcy/article/details/108446687#:~:text=单机、主从、哨兵的模式数据都是存储在一个节点上,其他节点进行数据的复制。而单个节点存储是存在上限的,集群模式就是把数据进行 分片 存储,当一个分片数据达到上限的时候,还可以分成多个分片。)。

https://blog.csdn.net/zzhongcy/article/details/108446687 在哨兵架构中,Sentinel 进程(蓝色盾牌图标)监控 Master 和 Slave 的心跳。当 Master 宕机并被多数哨兵判断下线后,哨兵集群将选举出新主节点并通知客户端更新连接,实现自动故障转移cnblogs.comcnblogs.com

Cluster 集群模式: 当单实例性能和存储上限不足时,可采用 Redis Cluster 分布式集群。Redis 3.0 引入 Cluster 模式,将数据分片存储在多个节点上,实现高性能、高可扩展和高可用的分布式架构[blog.csdn.net](https://blog.csdn.net/zzhongcy/article/details/108446687#:~:text=哨兵模式中,单个节点的写能力,存储能力受到单机的限制,动态扩容困难复杂。于是,Redis 3,分布式方面的需求。Redis Cluster 集群模式具有 高可用、可扩展性、分布式、容错 等特性。)。Cluster 没有中心节点,每个节点保存部分数据和集群的元数据,所有节点彼此互联并通过 Gossip 协议交换状态[blog.csdn.net](https://blog.csdn.net/zzhongcy/article/details/108446687#:~:text=match at L207 如上图所示,该集群中包含 6,节点之间进行数据复制外,所有 Redis 节点之间采用 Gossip 协议进行通信,交换维护节点元数据信息。)。集群将整个键空间划分为 16384 个哈希槽,每个主节点负责一部分槽位范围blog.csdn.netblog.csdn.net。一个典型集群至少包含 3 个 Master 节点和对应的 3 个 Slave 节点(共6节点)来保障高可用[blog.csdn.net](https://blog.csdn.net/zzhongcy/article/details/108446687#:~:text=Redis Cluster 采用无中心结构,每个节点都可以保存数据 和整个集群状态,每个节点都和其他所有节点连接。Cluster 一般由多个节点组成,节点数量至少为,6 个才能保证组成完整高可用的集群,其中三个为主节点,三个为从节点。三个主节点会分配槽,处理客户端的命令请求,而从节点可用在主节点故障后,顶替主节点。)。任何 Master 挂掉时,其从节点可自动升级为 Master(需多数投票)继续提供服务[blog.csdn.net](https://blog.csdn.net/zzhongcy/article/details/108446687#:~:text=所以我们在创建集群的时候,一定要为每个主节点都添加对应的从节点。比如,集群包含主节点 A、B、C,以及从节点 A1、B1、C1,那么即使 B 挂掉系统也可以继续正确工作。)。Cluster 的优点是线性扩展读写能力,部分节点故障时集群仍可用,支持自动故障转移和槽位迁移扩容blog.csdn.net。缺点是实现和运维复杂,某些跨键操作受限制,节点间通信开销,且为了保证高可用要求至少一台 Slave 节点,否则单个 Master 挂掉将导致相应槽位不可用blog.csdn.netblog.csdn.net

https://blog.csdn.net/zzhongcy/article/details/108446687 Redis Cluster 采用无中心架构,将键按照哈希函数映射到 16384 个槽,由 3 个 Master 分片负责存储,3 个 Slave 作为副本提高高可用。客户端请求通过计算键的槽映射到正确节点,写操作在 Master 执行并异步复制到 Slave,读请求可走从节点[blog.csdn.net](https://blog.csdn.net/zzhongcy/article/details/108446687#:~:text=Redis Cluster 采用无中心结构,每个节点都可以保存数据 和整个集群状态,每个节点都和其他所有节点连接。Cluster 一般由多个节点组成,节点数量至少为,6 个才能保证组成完整高可用的集群,其中三个为主节点,三个为从节点。三个主节点会分配槽,处理客户端的命令请求,而从节点可用在主节点故障后,顶替主节点。)blog.csdn.net

拓展说明: Redis 集群模式通过虚拟槽和数据重分布实现在线扩容/缩容[blog.csdn.net](https://blog.csdn.net/zzhongcy/article/details/108446687#:~:text=Redis Cluster 提供了灵活的节点扩容和缩容方案。在不影响集群对外服务的情况下,可以为集群添加节点进行扩容也可以下线部分节点进行缩容。可以说,槽是 Redis Cluster,管理数据的基本单位,集群伸缩就是槽和数据在节点之间的移动。)。当增加新节点时,可将部分槽位从现有节点迁移过去,实现水平扩展而无需停机[blog.csdn.net](https://blog.csdn.net/zzhongcy/article/details/108446687#:~:text=此时,我们如果要存储数据,按照 Redis Cluster 哈希槽的算法,假设结果是: CRC16,A、B、C 任何一个节点获取 key,都会这样计算,最终通过 B 节点获取数据。)。哨兵模式适合数据量可由单机承载但需要高可用的场景,而 Cluster 适用于数据量或并发超出单机能力,需要分片扩展的场景cnblogs.comcnblogs.com。在实际应用中,也可以将哨兵机制用于监控管理 Cluster 中各节点,以提高集群稳定性[cloud.tencent.com](https://cloud.tencent.com/developer/article/2166188#:~:text=Redis Sentinel和Redis Cluster是提升系统高可用性和扩展性的关键技术。Sentinel提供监控和自动故障转移,适合单点监控;Cluster支持分布式数据分片,解决扩容难题。)。总之,Sentinel 聚焦高可用(主从自动切换),而 Cluster 侧重可扩展(数据分片消除单点性能瓶颈)cnblogs.comcnblogs.com

2. 安装与部署

Linux 环境安装: 推荐使用稳定版本源码编译或使用系统包管理器。以源码安装为例:先从官网下载 Redis 压缩包并上传至服务器,然后执行 make 编译,再使用 make install 安装可执行文件51cto.com。安装后可通过修改默认配置文件(redis.conf)设置守护进程模式(daemonize yes)、绑定网卡、端口、密码等参数[blog.csdn.net](https://blog.csdn.net/qq18346342939/article/details/115825525#:~:text=Redis在Linux服务器中详细安装与配置步骤原创 ,1、将redis linux版压缩包放置于一个路径下,通过命令tar)。编译安装需要确保系统已安装 GCC 等编译工具[help.fanruan.com](https://help.fanruan.com/finedatalink/doc-view-529.html#:~:text=Linux系统安装配置Redis ,3 安装)。也可使用 apt-get install redis (Ubuntu) 或 yum install redis (CentOS) 快速安装。安装完成后,通过 redis-server redis.conf 启动服务,并用 redis-cli 进行本地连接测试。

Docker 容器部署: 采用容器可以快速运行 Redis 实例。首先拉取官方 Redis 镜像:如 docker pull redis:7.0[blog.csdn.net](https://blog.csdn.net/weixin_55127182/article/details/139898803#:~:text=Linux使用docker部署redis 原创 ,· 5、修改默认配置信息 · 6)。然后运行容器:例如

docker run -d --name redis-server -p 6379:6379 \  
    -v /myredis/data:/data \  
    -v /myredis/redis.conf:/etc/redis/redis.conf \  
    redis:7.0 redis-server /etc/redis/redis.conf  

这将以后台模式启动 Redis,映射宿主机6379端口,并挂载数据目录和配置文件[blog.csdn.net](https://blog.csdn.net/weixin_55127182/article/details/139898803#:~:text=Linux使用docker部署redis 原创 ,· 5、修改默认配置信息 · 6)。通过挂载自定义 redis.conf,可以设置持久化、密码等。容器启动后,用 docker exec -it redis-server redis-cli 进入客户端验证。容器部署需注意数据卷持久化,以免容器销毁导致数据丢失[blog.csdn.net](https://blog.csdn.net/qq_35080796/article/details/129853564#:~:text=linux服务器用docker部署redis · 前言 · 1,连接到redis)。

单机部署: 在生产中通常将 Redis 配置为后台服务。主要步骤:1)修改 redis.conf,将 bind 改为 0.0.0.0(允许远程连接,但需设置 requirepass 密码确保安全),设置 daemonize yes 后台运行[blog.csdn.net](https://blog.csdn.net/qq18346342939/article/details/115825525#:~:text=Redis在Linux服务器中详细安装与配置步骤原创 ,1、将redis linux版压缩包放置于一个路径下,通过命令tar);2)设置持久化策略,如需要 RDB 则保持 save 参数,若需 AOF 则开启 appendonly yes[cnblogs.com](https://www.cnblogs.com/javaguide/p/redis-persistence.html#:~:text=什么是 AOF 持久化?);3)启动 Redis:redis-server /path/to/redis.conf,并可将其加入 systemd 服务或开机自启脚本。注意调优 Linux 内核参数(如关闭透明大页、调整内存 overcommit)以优化 Redis 性能[pdai.tech](https://pdai.tech/md/db/nosql-redis/db-redis-y-monitor.html#:~:text=,the huge pages already created)。

主从部署: 准备两台或多台服务器(或同一台不同端口)作为 Master 与 Slave。配置 Master 正常启动,然后配置从节点的 redis.conf:指定 replicaof <master-ip> <master-port>(在 Redis 5 以前参数名为 slaveof)来让从库连接到主库进行同步[blog.csdn.net](https://blog.csdn.net/Drink_23/article/details/135936248#:~:text=Redis Sentinel 哨兵集群搭建原创 ,启动集群)。启动 Slave 时,它会向 Master 发送 SYNC,Master 执行 BGSAVE 生成 RDB 快照并传给 Slave,再源源不断复制新写入命令cnblogs.com。待同步完成,Slave 切换到同步模式。Master 可以设置 requirepass 密码时,Slave 也需配置 masterauth <pass> 以正常认证。主从部署要确保网络通畅,建议在同一局域网或机房以减少延迟。Master 宕机后可手动将某个 Slave 配置为 Master(去除 replicaof 配置并重启),原 Master 恢复后可作为从库。

哨兵部署: Sentinel 本身也是一个特殊的 Redis 进程。典型部署为至少 3 个哨兵节点形成仲裁机制,部署在不同服务器上防止单点。步骤:1)在每台部署哨兵的机器上准备 sentinel.conf 配置文件,指定监控的主库信息,如:

sentinel monitor mymaster <master-ip> <master-port> 2  
sentinel auth-pass mymaster <master-password>  

其中 mymaster 是自定义集群名称,“2” 表示至少 2 个哨兵认为主观下线才触发故障转移[cnblogs.com](https://www.cnblogs.com/javaguide/p/redis-persistence.html#:~:text=由于 RDB 和 AOF 各有优势,于是,Redis,preamble 开启)。)。还可设置故障判定时长 (down-after-milliseconds)、选举成功后新主生效时长 (failover-timeout)、以及各从库优先级[blog.csdn.net](https://blog.csdn.net/Drink_23/article/details/135936248#:~:text=Redis Sentinel 哨兵集群搭建原创 ,启动集群)。2)启动哨兵进程:使用命令 redis-sentinel /path/to/sentinel.conf 或通过 redis-server 加 --sentinel 参数启动。3)测试:手动停掉 Master,看哨兵日志输出,某一哨兵应宣布故障并选出新 Master,通知其他从库和客户端**自动完成切换**[cnblogs.com](https://www.cnblogs.com/chenwenyin/p/13549492.html#:~:text=主观下线:一个哨兵节点判定主节点down掉是主观下线。 客观下线:只有半数哨兵节点都主观判定主节点down掉,此时多个哨兵节点交换主观判定结果,才会判定主节点客观下线。)[cnblogs.com](https://www.cnblogs.com/chenwenyin/p/13549492.html#:~:text=sentinel发现master挂了后,就会从slave中重新选举一个master。)。注意哨兵端口默认 26379,需要防火墙放行。哨兵模式下应用端应使用哨兵提供的机制获取 Master 地址(例如 Jedis 的 JedisSentinelPool` 会自动连接到新 Master[blog.csdn.net](https://blog.csdn.net/weixin_43888891/article/details/130948322#:~:text=%2F%2F初始化连接池 jedisSentinelPool %3D new JedisSentinelPool(,%2F%2F 从池中获取一个Jedis对象)),以实现故障转移时无感知。

集群部署: Redis Cluster 要求至少 3 主 3 从的 6 个实例组合。可在3台服务器每台启动2个 Redis 实例(端口不同)组成集群[help.fanruan.com](https://help.fanruan.com/finereport-en/doc-view-6803.html#:~:text=Linux系统安装配置Redis集群,个节点,3 台服务器运行6 个Redis 实例,组成一个经典的「三主三)。配置方面,每个 redis.conf 需开启 cluster-enabled yes,指定集群配置文件 cluster-config-file nodes.conf 以及集群节点通信端口(默认为主端口+10000)[blog.csdn.net](https://blog.csdn.net/weixin_43808898/article/details/149279149#:~:text=使用Ruby脚本部署Redis Cluster集群步骤讲解 · 在本文中,我们将深入探讨如何使用Ruby脚本来部署Redis Cluster集群。Redis,Cluster是Redis的分布式解决)。启动所有节点后,使用 redis-cli --cluster create 命令组建集群。例如:

redis-cli --cluster create \
  192.168.0.1:7001 192.168.0.1:7002 192.168.0.2:7001 \
  192.168.0.2:7002 192.168.0.3:7001 192.168.0.3:7002 \
  --cluster-replicas 1  

以上命令将6个节点组建成3主3从的集群,每个主节点会自动分配槽位,--cluster-replicas 1 表示每个主有一个从blog.csdn.net。执行过程中按提示输入 yes 确认,集群即可建立。然后通过 redis-cli -c -p 7001 连接集群并测试:试着 SET 一些键,观察它们分布到不同节点。当需要扩容时,可启动新实例并使用 redis-cli --cluster add-node 将其加入集群,再用 --cluster reshard 迁移部分槽位到新节点,实现在线扩容[blog.csdn.net](https://blog.csdn.net/zzhongcy/article/details/108446687#:~:text=Redis Cluster 提供了灵活的节点扩容和缩容方案。在不影响集群对外服务的情况下,可以为集群添加节点进行扩容也可以下线部分节点进行缩容。可以说,槽是 Redis Cluster,管理数据的基本单位,集群伸缩就是槽和数据在节点之间的移动。)。集群部署注意事项:必须确保各节点互相可连通,尤其集群总线端口(比如 17001 等)也要开放;生产环境应配置每个 Master 至少一个 Slave,避免 Master 宕机导致槽位丢失[blog.csdn.net](https://blog.csdn.net/zzhongcy/article/details/108446687#:~:text=回到刚才的例子中,集群有 A、B、C 三个主节点,如果这 3 个节点都没有对应的从节点,如果,11000 范围内的哈希槽提供服务。)。还需注意某些命令在集群模式下限制,如涉及多个键的操作需要所有键在同一槽(可以通过使用哈希标签确保键哈希到同槽)。此外,为简化搭建测试,可使用官方提供的 redis-cli --cluster create 命令一步创建,或用 Docker Compose/ Kubernetes 编排 Redis Cluster。

3. 与主流语言的接入方式

Redis 提供丰富的客户端库支持多种语言。下面介绍 Java、Python、Golang 环境下如何连接和使用 Redis,并讨论连接池配置和异常处理等。

Java 客户端(Jedis 与 Redisson 等)

常用客户端: Java 领域官方推荐的 Redis 客户端有 Jedis、Lettuce、Redisson 三种blog.csdn.net。Jedis 是老牌同步客户端,API 直接对应 Redis 命令,性能高、实现简单,但单个 Jedis 实例非线程安全,需要配合连接池使用[blog.csdn.net](https://blog.csdn.net/weixin_43888891/article/details/130948322#:~:text=优点: 它非常小巧,实现原理也很简单,最重要的是很稳定,而且使用的方法参数名称和官方的文档非常 match,如果有什么方法不会用,直接参考官方的指令文档阅读一下 就会了,省去了非必要的重复学习成本。不像有些客户端把方法名称都换了,虽然表面上给读者带来了便捷,但是需要挨个重新学习这些 API,提高了学习成本。)[blog.csdn.net](https://blog.csdn.net/weixin_43888891/article/details/130948322#:~:text=虽然 redis 服务端是单线程操作,但是在实际项目中,使用 Jedis 对象来操作,TCP 连接,连接资源开销很高,同时 Jedis 对象的个数不受限制,在极端情况下可能会造成连接泄漏,同时 Jedis)。Lettuce 是线程安全的可伸缩客户端,基于 Netty 实现,支持同步和异步、响应式调用,常用于 Spring Data Redis 默认实现。Redisson 则是一个高级客户端,除了提供 Redis 命令接口,还实现了许多分布式数据结构和工具(如分布式锁、Map、Set 等),便于构建复杂场景,但抽象层次高性能稍逊[cloud.tencent.com](https://cloud.tencent.com/developer/article/2315738#:~:text=Jedis 和Redisson 是Java 连接Redis 的主流客户端,Jedis,性能高、易用但缺多线程支持;Redisson 功能强、支持分布式锁但性能稍低。根据项目需求选择,高并发选Jedis,)[zhuanlan.zhihu.com](https://zhuanlan.zhihu.com/p/599086592#:~:text=Java Redis 客户端对比总结与使用建议 ,命令封装的客户端,Redisson 提供的功能更加高端和抽象,Redisson 可以类比Spring 框架,这些框架搭建了应用程序的基础)。一般高并发场景可选择 Jedis 或 Lettuce 获得更直接的性能,而需要分布式对象功能时可选 Redisson[blog.csdn.net](https://blog.csdn.net/youanyyou/article/details/104666500#:~:text=Jedis客户端实例不是线程安全的,所以需要通过连接池来使用Jedis。 Redisson使用非阻塞的I%2FO和基于Netty框架的事件驱动的通信层,其方法调用是异步的。)。

基本用法: 以 Jedis 为例,先添加 Maven 依赖 redis.clients:jedis:<version>,然后创建连接并执行命令blog.csdn.net[blog.csdn.net](https://blog.csdn.net/weixin_43888891/article/details/130948322#:~:text=public class RedisJava ,jedis.ping)。示例代码:

Jedis jedis = new Jedis("localhost", 6379);  
// 如 Redis 设置了密码,需要认证  
// jedis.auth("密码");  
System.out.println("Ping: " + jedis.ping());  // 测试连通性  
jedis.set("name", "Alice");  
String value = jedis.get("name");  
System.out.println("GET name = " + value);  
jedis.close();  // 用完关闭连接  

上述程序连接本地 Redis 设置键值并读取[blog.csdn.net](https://blog.csdn.net/weixin_43888891/article/details/130948322#:~:text=%2F%2F连接本地的 Redis 服务 Jedis jedis,swawdwa)[blog.csdn.net](https://blog.csdn.net/weixin_43888891/article/details/130948322#:~:text=System.out.println(,)。Jedis 提供的方法与 Redis 命令一一对应,非常直观[blog.csdn.net](https://blog.csdn.net/weixin_43888891/article/details/130948322#:~:text=目前Redis 官网推荐使用的 Java客户端 有三款:,些基于Redis的特殊功能,比如分布式锁等等…)。需要注意每次 new Jedis() 相当于创建一个 TCP 连接,频繁创建开销大,因此推荐使用 连接池 来重复利用连接。

连接池配置: Jedis 使用 Apache Commons Pool2 维护连接池。一般通过 JedisPool 类创建池,例如:

JedisPoolConfig config = new JedisPoolConfig();  
config.setMaxTotal(100);          // 最大连接数  
config.setMaxIdle(10);           // 最大空闲连接数  
config.setMinIdle(5);            // 最小空闲连接数  
config.setMaxWait(Duration.ofMillis(2000)); // 获取连接最大等待时间  
JedisPool pool = new JedisPool(config, "127.0.0.1", 6379, 2000, "password");  

然后在应用中用 try (Jedis jedis = pool.getResource()) { ... } 获取连接执行命令,用完会自动归还连接池[blog.csdn.net](https://blog.csdn.net/weixin_43888891/article/details/130948322#:~:text=所以我们需要将 Jedis 交给线程池来管理,使用 Jedis 对象时,从连接池获取,Jedis,使用完成之后,再还给连接池。)blog.csdn.net。JedisPoolConfig 可调整诸多参数如 maxTotal 最大连接数、maxIdle/minIdleblockWhenExhausted 等[blog.csdn.net](https://blog.csdn.net/weixin_43888891/article/details/130948322#:~:text=,maxactive和maxidle这两个值最好设置得比较接近一些,不然maxidle设置得太小,单方面把maxactive调得很高, 这样会导致频繁的连接销毁和新建,这跟连接池的理念就相悖了。)[blog.csdn.net](https://blog.csdn.net/weixin_43888891/article/details/130948322#:~:text=%23 和maxIdle类似,maxIdle告诉连接池最多维持多少个空闲连接,minIdle告诉tomcat即使客户端没有需求,也要至少维持 多少个空闲连接,以应对客户端的突发需求。 min,连接池出借连接的最长期限,单位是毫秒)。超时设置也很重要,Jedis 构造时第四个参数为连接超时时间 ms(默认2000ms)[blog.csdn.net](https://blog.csdn.net/weixin_43888891/article/details/130948322#:~:text=%2F%2F连接本地的 Redis 服务 Jedis jedis,jedis.ping)。另外注意 Jedis 3.x 开始区分 connectionTimeoutsoTimeout(读写超时)两个参数,可根据网络状况调整[blog.csdn.net](https://blog.csdn.net/weixin_43888891/article/details/130948322#:~:text=%2F%2F redisPassword是实例的密码 %2F%2F timeout,这里既是连接超时又是读写超时,从Jedis 2)。连接池启用后,要确保及时关闭 Jedis 归还池中(Jedis 的 close() 已被改造为归还而非真正关连接[blog.csdn.net](https://blog.csdn.net/weixin_43888891/article/details/130948322#:~:text=match at L214 使用Jedis连接池之后,在每次用完连接对象后一定要记得把连接归还给连接池。Jedis对close方法进行了改造,如果是连接池中的连接对象,调用Close方法将 会是把连接对象返回到对象池,若不是则关闭连接。可以查看如下代码))。通过合理配置池大小,可以避免频繁建立连接和线程安全问题blog.csdn.net[blog.csdn.net](https://blog.csdn.net/weixin_43888891/article/details/130948322#:~:text=match at L209 Jedis连接池是基于apache,如最大连接数,最大空数等)。)。

异常处理: 常见异常包括连接超时、读写超时以及 Redis 本身返回的错误。例如 Jedis 在网络异常时会抛出 JedisConnectionException,应用应捕获此异常进行重试或切换节点。对于命令执行异常(如操作错误类型的数据),Jedis 会抛 JedisDataException,可以通过 .getMessage() 查看 Redis 返回的错误信息。实际使用中,可以对pool.getResource()获取的 Jedis 封装一层,捕获异常后将连接 close() 返还池,并进一步处理。特别要注意命令超时,如大量阻塞命令可能耗尽 Redis 处理能力导致慢查询,客户端等待超时。建议调整合理的 soTimeout 并对热点操作进行限流。对于 Redisson 和 Lettuce,它们采用 Netty 异步通信,也需设置超时参数(如 CommandTimeout)。Redisson 提供了 WatchDog 自动延期锁,但也要防止锁长时间未释放导致的问题。

Python 客户端(redis-py)

常用客户端: Python 使用最广的是 redis (redis-py) 库。安装:pip install redis[blog.csdn.net](https://blog.csdn.net/footless_bird/article/details/134022669#:~:text=,x 版本。它具有易于使用的 API,可以轻松地执行 Redis 命令,例如设置值、获取值、处理列表、集合、有序集合等。)。它提供 redis.Redisredis.ConnectionPool 等类方便操作 Redis。redis-py 支持 Python 2.x 和 3.x,并且既支持直连模式也支持 Sentinel 模式和集群模式(后者通过子库 redis-py-cluster 实现)[blog.csdn.net](https://blog.csdn.net/footless_bird/article/details/134022669#:~:text=,x 版本。它具有易于使用的 API,可以轻松地执行 Redis 命令,例如设置值、获取值、处理列表、集合、有序集合等。)[blog.csdn.net](https://blog.csdn.net/footless_bird/article/details/134022669#:~:text=注:python 的 redis 库支持直连和哨兵模式,但并不支持集群模式,推荐库:redis)。

基本用法: 连接本地 Redis 示例:

import redis  
r = redis.Redis(host='127.0.0.1', port=6379, password=None, decode_responses=True)  
r.set('count', 100)  
print(r.get('count'))  # 输出: 100  

这里 decode_responses=True 可让返回的二进制自动解码为字符串blog.csdn.net[blog.csdn.net](https://blog.csdn.net/footless_bird/article/details/134022669#:~:text=import redis)。redis-py 默认会维护一个连接池,在第一次连接时创建(可通过 connection_pool 参数自定义池),因此无需手动实现池机制[blog.csdn.net](https://blog.csdn.net/pppython123/article/details/123705226#:~:text=Python操作Redis例子原创 ,ConnectionPool(host%3D'localhost'%2C port%3D6379%2C)。常用参数包括:socket_timeout 响应超时、socket_connect_timeout 连接超时、max_connections 最大连接数等,可在 Redis() 时传入[blog.csdn.net](https://blog.csdn.net/footless_bird/article/details/134022669#:~:text=single_connection_client%3DFalse%2C ,重试错误列表)。示例中未设置则使用默认。

连接池与线程安全: redis-py 的 ConnectionPool 可以通过 redis.ConnectionPool(...).from_url() 等创建。多个 Redis 实例可以共享同一个池实例,这样就不会每次新建物理连接[blog.csdn.net](https://blog.csdn.net/pppython123/article/details/123705226#:~:text=Python操作Redis例子原创 ,ConnectionPool(host%3D'localhost'%2C port%3D6379%2C)。redis-py 的客户端是线程安全的,多线程情况下可以安全复用同一个 Redis 对象,它内部会获取连接执行命令[blog.csdn.net](https://blog.csdn.net/footless_bird/article/details/134022669#:~:text=,x 版本。它具有易于使用的 API,可以轻松地执行 Redis 命令,例如设置值、获取值、处理列表、集合、有序集合等。)。若需要手工管理,可以像:

pool = redis.ConnectionPool(host='localhost', port=6379, max_connections=10)  
r = redis.Redis(connection_pool=pool)  

这样所有 r 实例调用会共用该连接池。异常情况下,如执行命令网络中断,会抛出 redis.exceptions.ConnectionErrorTimeoutError,应捕获后重试或记录。使用 BLPOP 等阻塞命令要注意给定合理的 socket_timeout 以免一直阻塞。

Sentinel 和集群模式: redis-py 支持 Sentinel:使用 redis.sentinel.Sentinel 创建哨兵对象,然后通过 sentinel.master_for('mymaster') 获取主库的 Redis 实例,slave_for() 获取从库实例[blog.csdn.net](https://blog.csdn.net/footless_bird/article/details/134022669#:~:text=from redis)。这样哨兵模式下发生主从切换时,master_for 会自动连接新的 Master。对于集群模式,需要安装 redis-py-cluster (pip install redis-py-cluster)[blog.csdn.net](https://blog.csdn.net/footless_bird/article/details/134022669#:~:text=注:python 的 redis 库支持直连和哨兵模式,但并不支持集群模式,推荐库:redis)。使用 RedisCluster 类连接,如:

from redis.cluster import RedisCluster  
cluster = RedisCluster(startup_nodes=[{"host":"127.0.0.1","port":6379}, ...], decode_responses=True)  
cluster.set("key", "value")  

它会自动发现集群节点并路由命令到正确分片[blog.csdn.net](https://blog.csdn.net/footless_bird/article/details/134022669#:~:text=* redis,Redis 集群的客户端库,允许以编程方式连接到 Redis 集群,并执行各种操作,而无需手动管理分片和节点。)[blog.csdn.net](https://blog.csdn.net/footless_bird/article/details/134022669#:~:text=from rediscluster import StrictRedisCluster)。Cluster 客户端也支持事务和批量操作,但需要注意 key 分区。异常方面,集群模式如果键不在已知槽位,会收到 redis.exceptions.RedisClusterException,需检查键slot或刷新集群信息。总之,Python 接入 Redis 简单直接,但在高并发时要注意使用 ConnectionPool、调优超时和合理拆分长批量操作。

Golang 客户端(go-redis 等)

常用客户端: Go 语言主流客户端为 go-redis(github.com/redis/go-redis),这是由 Redis 官方团队维护的 Go 驱动,功能完备且支持 Cluster、Sentinel 模式。另一老牌客户端是 redigo (gomodule/redigo),轻量但目前社区使用趋少。这里以 go-redis v9 为例。

基本用法: 使用 go-redis 前先导入依赖:go get github.com/redis/go-redis/v9。然后初始化客户端:

import (
    "context"
    "github.com/redis/go-redis/v9"
)
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
    Addr: "localhost:6379",
    Password: "",  // 无密码则为空字符串
    DB: 0,         // 默认数据库
    PoolSize: 10,  // 连接池大小
})
err := rdb.Set(ctx, "name", "Bob", 0).Err()
val, err := rdb.Get(ctx, "name").Result()

NewClient 接受一个 Options 结构,可配置项非常丰富,包括网络类型、连接超时 DialTimeout、读写超时、最大重试次数、连接池大小等[blog.csdn.net](https://blog.csdn.net/qq254606826/article/details/143030954#:~:text=%2F%2F 连接超时 DialTimeout%3A 5 ,是否启用上下文超时 ContextTimeoutEnabled%3A true%2C %2F%2F 连接池类型,FIFO)[blog.csdn.net](https://blog.csdn.net/qq254606826/article/details/143030954#:~:text=%2F%2F 基础连接数 PoolSize%3A 10%2C %2F%2F,最大空闲连接数 MaxIdleConns%3A 5%2C %2F%2F 最大活跃连接数)。如上示例我们设置了 PoolSize 等。go-redis 默认就使用了连接池,无需额外代码。执行命令使用上下文 ctx 控制超时和取消,建议在生产中为每次调用设置 context.WithTimeout 以防止阻塞过久。

高级配置: go-redis 支持哨兵集群:对于哨兵模式,使用 redis.NewFailoverClient(&redis.FailoverOptions{ ... }),提供 master 名称和多个哨兵地址即可,内部会自动从哨兵获取 Master 地址并建立连接。对于集群,使用 redis.NewClusterClient(&redis.ClusterOptions{ ... }),提供集群节点列表,go-redis 会自动处理槽位和路由[cnblogs.com](https://www.cnblogs.com/lori/p/18113064#:~:text=在Go语言中使用Redis,通常需要使用第三方库来实现与Redis服务器的交互。目前比较流行的Go语言Redis客户端库有 go)[cloud.tencent.com](https://cloud.tencent.com/developer/article/2056319#:~:text=2种Go Redis客户端使用对比 ,同时还提供了灵活的Hook机制%2C 其底层实际也是调用的万能Do 方法)。并且 go-redis 支持 Pipeline、事务(Watch/Multi)以及 Lua 脚本等。例如 Pipeline:

pipe := rdb.Pipeline()
incr := pipe.Incr(ctx, "counter")
pipe.Expire(ctx, "counter", 3600*time.Second)
_, err := pipe.Exec(ctx)  
fmt.Println(incr.Val())  

go-redis 的 Pipeline 会自动将命令打包发送、Exec 返回结果切片。Lua 脚本可以用 rdb.Eval(ctx, "script", keys, args...) 执行。

连接池与超时: go-redis 内部有连接池实现,参数如 PoolSizeMinIdleConns(最小空闲)、PoolTimeout(等待空闲连接超时)等在 Options 配置[blog.csdn.net](https://blog.csdn.net/qq254606826/article/details/143030954#:~:text=%2F%2F 基础连接数 PoolSize%3A 10%2C %2F%2F,最大空闲连接数 MaxIdleConns%3A 5%2C %2F%2F 最大活跃连接数)。默认 PoolSize 为 10*CPU 数,可根据并发情况调整。若 Redis 操作超时,会返回 context.DeadlineExceeded 或自定义的 redis.TimeoutError。应该对 Err() 结果检查,区分网络超时、拒绝等错误进行相应处理。另要注意 Go 客户端的错误重试机制:go-redis 默认会对网络错误自动重试(MaxRetries 默认 3 次),可根据需求调整[blog.csdn.net](https://blog.csdn.net/qq254606826/article/details/143030954#:~:text=%2F%2F 数据库选择 DB%3A 0%2C %2F%2F,time.Millisecond%2C %2F%2F 连接超时)。确保超时和重试策略符合应用需求,避免因重试过多延长请求延迟。

异常处理: 常见异常包括连接错误(如 dial tcp: connection refused)、命令执行错误(Redis 返回错误字符串),它们都会在 .Err().Result() 中体现。可以通过 errors.Is(err, redis.Nil) 判断是否键不存在(Redis 对不存在键的 GET 返回特殊的 nil),以区别于其他错误。对于集群模式,如果执行跨槽命令会返回 MOVED/ASK 重定向错误,go-redis 会透明处理,但在短时间内大量 resharding 时可能出现 Cluster slot not covered 错误,这种情况下应用应稍等重试或刷新集群状态。

总之,各语言客户端对于 Redis 的使用都较简单直观。在开发中,务必合理使用连接池,设置超时,并对异常进行重试或降级处理,从而提高 Redis 接入的健壮性和效率。

4. Redis 数据结构详解

Redis 提供了丰富的数据类型,每种类型在内部通过不同的数据结构实现,以兼顾性能和内存效率[xiaolincoding.com](https://www.xiaolincoding.com/redis/data_struct/data_struct.html#:~:text=Redis 为什么那么快?)。我们将介绍常见的键值类型及其底层实现与使用场景。

字符串(String)

字符串是 Redis 最基本的数据类型,一个键对应一个值,可以存储普通文本、数字、二进制数据等,单值最大可达512MBcnblogs.com。Redis 的字符串操作包括 GET/SET 等,还有 INCR/DECR 处理数值、APPEND 字符串拼接、GETRANGE 子串等,非常灵活。

底层原理: Redis 并未直接使用 C 语言的 char* 字符串,而是实现了一种 简单动态字符串 (SDS) 来作为默认字符串表示[pdai.tech](https://pdai.tech/md/db/nosql-redis/db-redis-x-redis-ds.html#:~:text=%23 简单动态字符串 )。SDS 在结构上包含已使用长度和剩余空间等元数据,能够高效地执行追加、截断等操作而不会频繁重分配内存[pdai.tech](https://pdai.tech/md/db/nosql-redis/db-redis-x-redis-ds.html#:~:text=,dynamic string%2CSDS)的抽象类型,并将 SDS 作为 Redis的默认字符串表示。)。针对不同长度和内容,字符串有三种内部编码:

  • int 编码: 如果值可以表示为 64 位整数且长度 ≤ 12 字节,则以整数存储,提高空间与处理效率cnblogs.com。例如 SET count 100 实际会存为整数100而非字符数组。
  • embstr 编码: 对较短的字符串(长度 ≤ 39 字节)使用 embstr,将 Redis 对象和字符数组一次性分配在连续内存,减少内存碎片cnblogs.com。这种方式使小字符串的创建和回收开销更低。
  • raw 编码: 对于长字符串(>39字节)或无法作为整数的内容,则采用 raw,即典型的动态字符数组(SDS),支持自动扩容/缩容[cnblogs.com](https://www.cnblogs.com/yidengjiagou/p/17305653.html#:~:text=少内存碎片和结构体的开销。 3)。

SDS 特性带来很多优势:获取长度是 O(1)、修改时按需扩展并预留空间避免多次 realloc,还通过惰性空间回收减少碎片[pdai.tech](https://pdai.tech/md/db/nosql-redis/db-redis-x-redis-ds.html#:~:text=,dynamic string%2CSDS)的抽象类型,并将 SDS 作为 Redis的默认字符串表示。)。因此,Redis 字符串的读写性能都非常高,同时也能存储二进制数据(例如图片的 Base64、序列化对象)而不受 C 字符串的 \0 截断限制。

典型场景: 字符串类型用途广泛:

  • 缓存对象:可将数据库查询结果、计算结果作为 JSON 字符串缓存,提高读性能cnblogs.com
  • 计数器:利用 INCR/DECR 可实现原子计数,如网站访问量、点赞数计数cnblogs.com。Redis 的原子自增操作非常快捷,适合高并发计数场景。
  • 分布式锁:使用 SETNX(或新版 SET key value NX PX <ttl>)命令配合过期实现简单的锁机制cnblogs.com。字符串值可存放锁持有者标识。
  • 会话令牌:将用户会话数据序列化后存入字符串,并设置 TTL 实现分布式 Session 管理cnblogs.com
  • 限流计数:使用字符串的原子计数和过期可以实现固定窗口计数器用于限流cnblogs.com

列表(List)

列表类型是一个有序链表,可以从两端推入弹出元素,也能根据索引获取元素(链表索引基于0)。常用命令有 LPUSH/RPUSH 插入、LPOP/RPOP 弹出、LRANGE 范围获取等。列表按插入顺序排序,可用于消息队列等场景。

底层原理: 早期 Redis 列表在元素较少时用 压缩列表 (ziplist) 存储,元素较多时用 双向链表 (linkedlist)。自 Redis 3.2 起,引入了 快速列表 (quicklist),融合了链表和压缩列表的优点[xiaolincoding.com](https://www.xiaolincoding.com/redis/data_struct/data_struct.html#:~:text=可以看到,Redis 数据类型的底层数据结构随着版本的更新也有所不同,比如:)。quicklist 本质是一个双向链表,每个节点存储一个 ziplist(或新版 listpack)压缩块,将多个元素批量存储,既减少内存碎片又保持插入删除的灵活[xiaolincoding.com](https://www.xiaolincoding.com/redis/data_struct/data_struct.html#:~:text=,在最新的 Redis 代码(还未发布正式版本)中,压缩列表数据结构已经废弃了,交由 listpack 数据结构来实现了。)。因此,Redis 列表在头尾操作依然是 O(1) 快速,而中间定位需要遍历(O(n))。列表在达到一定长度或单个元素较大时自动采用 quicklist 表现,以平衡性能和内存。

典型场景:

  • 消息队列: 利用 LPUSH + BRPOP 可以实现生产-消费队列[cnblogs.com](https://www.cnblogs.com/yidengjiagou/p/17305653.html#:~:text=Redis List类型由于支持在列表的头部或尾部添加元素,也支持在列表任意位置插入或删除元素,因此非常适合以下场景:)。生产者 RPUSH 消息到列表尾,消费者用阻塞式 BRPOP 从头部弹出,无消息时阻塞等待,实现简易的可靠队列。
  • 最新消息排序: 列表天然按插入排序,可用来存储最近的操作日志、最近访问记录等。使用 LPUSH 新记录,再用 LTRIM 截断只保留最新N条。
  • 分页处理: 可以将一些数据如文章评论存入列表,通过 LRANGE start end 分页读取。不过当列表很长时性能会退化,不如用有序集合。
  • 栈/队列模型: 列表两端操作让它既能当栈(LPUSH/LPOP)又能当队列(LPUSH/RPOP),用于算法模拟或工作线程池任务存取等。

注意事项: 列表适合元素数量适中(几千到数万),极长列表操作大范围 LRANGE 或索引访问效率较低。当列表元素超过数百万时,应考虑拆分或改用更适合的数据结构。尽量使用列表提供的块范围读取,而避免遍历整个列表。此外,不要对列表使用 LINDEX 随机访问过深的下标,否则会遍历影响性能。

哈希(Hash)

哈希类型相当于内部的小键值存储,一个 Hash 可包含多个字段-值对。常用操作有 HSET/HGET 设置读取字段、HMGET 批量取、多字段设置 HSET key field1 val1 field2 val2、以及 HGETALL 获取全部字段等。哈希适合存储对象,例如用户信息,用 user:1000 哈希保存 name、age、email 等字段。

底层原理: Redis 哈希在底层使用两种结构:当字段数量较少且每个值都很小(默认 < 512 个字段且每个值 < 64 字节),采用压缩列表 ziplist 存储cnblogs.com;超过阈值则转为 哈希表 (dict) 实现cnblogs.com。压缩列表将连续的 field/value 顺序存储在紧凑内存中,节省空间但查找需要线性扫描cnblogs.com。而哈希表则提供 O(1) 平均时间复杂度的快速访问,但会消耗更多内存(需要额外的哈希结构和指针)。Redis 会根据哈希大小在 ziplist 和 dict 之间自动转换。现代 Redis 7.0 已将 ziplist 替换为更高效的 listpack 格式存储小哈希,以减少内存碎片。

典型场景:

  • 存储对象: 使用 Hash 将一个对象的多个属性存一起,如 user:1001 哈希保存用户资料cnblogs.com。这样获取某个属性不用一次性取出整个对象字符串,更加灵活。
  • 计数器集合: 可以用 Hash 存储一组计数器,例如网站各栏目浏览次数,以栏目名为 field,自增存储cnblogs.com。使用 HINCRBY 可对特定字段计数原子递增。
  • 配置或状态存储: 用哈希存储一些配置项、状态值,比如应用运行时参数,可通过 HGETALL 一次取出全部配置。
  • 避免键过多: 将很多相关的小数据存在一个哈希里,可减少顶层键数量,方便管理。不过也要注意单个哈希不要无限增大。

注意事项: 若哈希的字段非常多(如数百万),HGETALL 等操作会非常慢,占用网络流量大,应避免。可以通过 HSCAN 分批遍历大型哈希。对于超大的哈希,也要留意 rehash 过程可能阻塞(Redis rehash 渐进进行,影响不大,但大哈希整体迁移在集群下可能耗时)。另外,如果只有一个字段,没必要用 Hash,多此一举;反之数据非常稀疏或独立的,也可直接用多个键。总之,Hash 非常适合存储结构化对象,可读性好且节省内存(小哈希用 ziplist 内存效率高)。

集合(Set)

集合是无序的唯一值集合,支持添加删除元素和判断成员存在。主要命令有 SADD 添加、SREM 删除、SISMEMBER 判断存在、SMEMBERS 获取所有元素等。还提供集合运算如 SINTER 交集、SUNION 并集、SDIFF 差集等。集合适合维护不重复元素的集合,如标签、好友列表等。

底层原理: 当集合中的元素全为整数且数量较少时,Redis 使用一种紧凑结构 整数集合 (intset) 存储[cnblogs.com](https://www.cnblogs.com/yidengjiagou/p/17305653.html#:~:text=Redis Set类型的内部编码有两种:)。intset 将所有整数元素按序存放,并动态选择最小能容纳的类型(16/32/64位),保证节省空间[cnblogs.com](https://www.cnblogs.com/yidengjiagou/p/17305653.html#:~:text=1,hashtable(字典):当Set类型包含字符串类型或者元素数量较多时,Redis会使用hashtable作为Set类型的内部编码。hashtabl e是一种基于链表的哈希表结构,可以快速地进行随机访问、插入和删除操作。在hashtable中,每个元素都被存储为一个字符串,并且使用哈希函数将字符串映射到一个桶 中,然后在桶中进行查找、插入和删除操作。)。如果集合元素不是纯整数或数量超过配置阈值(默认 512 个),Redis 就改用 哈希表 来存储集合[cnblogs.com](https://www.cnblogs.com/yidengjiagou/p/17305653.html#:~:text=Redis Set类型的内部编码有两种:)。此时集合的每个元素作为哈希表的 key(值总是设置为 null占位),利用哈希表快速判断存在与否。哈希表方式下操作复杂度平均 O(1)。因此,小整数集合非常紧凑,大集合性能也有保障。

典型场景:

  • 标签与关注列表: 比如用户关注的 ID 列表,用集合存储确保不重复,可快速加入删除,检查关系用 SISMEMBER[cnblogs.com](https://www.cnblogs.com/yidengjiagou/p/17305653.html#:~:text=Redis Set类型的使用场景包括:)。共同好友可用 SINTER 求交集得到cnblogs.com
  • 抽奖或去重: 将参与者放入集合,天然去重;随机抽奖可用 SRANDMEMBER 随机取集合元素。
  • 黑白名单: 封禁 IP 列表维护成集合,每次登录检查 IP 是否在黑名单 (SISMEMBER) 即可,高效判定。
  • 集合运算: 通过 SINTER/SUNION,可以方便地执行类似数据库的集合查询,比如找既有属性A又有属性B的用户(交集)cnblogs.com

注意事项: 集合支持的成员为字符串,单个元素最大 512MB,但实际应用应避免存放超大元素,否则集合操作内存消耗大。尽管 SMEMBERS 可以列出所有元素,但对于超大集合要慎用,可用 SSCAN 增量遍历。涉及集合的大量并集交集运算,Redis 会将结果保存在内存后再返回,可能造成阻塞,必要时可考虑拆分操作。集合的哈希表实现在大量元素时会消耗一定内存做 key 映射,所以当元素数很多且都是简单整数时,也可以考虑使用 bitmap 或 BloomFilter 之类结构进一步节省内存。

有序集合(Sorted Set)

有序集合是带分值的集合,每个元素附加一个分数 (score),集合按照分数升序排列。常用命令有 ZADD key score member 添加成员及分数、ZRANGE/ZREVRANGE 按排名取元素、ZSCORE 查询成员得分、ZREM 删除成员等。它结合了数组和哈希的特性,既保证成员唯一,又能根据分值排序,支持按分值或排名范围查找 (ZRANGEBYSCOREZRANK 等)。

底层原理: 有序集合内部由两种结构维持:跳表 (SkipList)哈希表[xiaolincoding.com](https://www.xiaolincoding.com/redis/data_struct/data_struct.html#:~:text=Redis 数据结构,复杂度的节点查找。 zset 结构体里有两个数据结构:一个是跳表,一个是哈希表。 这样的好处是既能)[cnblogs.com](https://www.cnblogs.com/yidengjiagou/p/17305653.html#:~:text=Redis中的Zset(有序集合)是一个键值对集合,其中每个元素都关联一个分值(score),通过分值进行排序,可以看作是一个字典(dict)和一个跳跃列表(s kip list)的混合体,它可以存储多个相同的元素,但每个元素必须有一个唯一的score值。)。跳表按分数排序存储所有元素,节点按分数递增且包含指向其它节点的多级“跳跃”指针,实现平均 O(log N) 的插入和范围查找性能[pdai.tech](https://pdai.tech/md/db/nosql-redis/db-redis-x-redis-ds.html#:~:text=跳跃表结构在Redis 中的运用场景只有一个,那就是作为有序列表(Zset) 的使用。跳跃表的性能可以保证在查找,删除,添加等操作的时候在对数期望时间内完成,这个性能是可以和 )。同时,Redis 还用一个哈希表从成员值映射到分数,用于 O(1) 快速按成员查分值[xiaolincoding.com](https://www.xiaolincoding.com/redis/data_struct/data_struct.html#:~:text=Redis 数据结构,复杂度的节点查找。 zset 结构体里有两个数据结构:一个是跳表,一个是哈希表。 这样的好处是既能)。这样增加一个元素需要在跳表和哈希表各插入一次,删除也是对应移除,查询成员得分直接查哈希表,按序遍历则使用跳表结构。Redis 会根据有序集合元素数量和长度决定编码:小的有序集合(元素 < 128 且所有成员 <64字节)会暂时用压缩列表 (ziplist) 存储cnblogs.com,当超过阈值转为跳表+哈希表的复合结构cnblogs.com

典型场景:

  • 排行榜: Sorted Set 广泛用于排行榜系统,如积分排名、成绩排名[cnblogs.com](https://www.cnblogs.com/yidengjiagou/p/17305653.html#:~:text=Redis Zset是一种有序集合,其使用场景主要包括以下几个方面:)。使用用户ID作为成员,分数为用户得分,ZADD 更新分数,ZRANGE 可以获取按分数排序的名单。支持 ZREVRANGE 方便取 Top N 排名[cnblogs.com](https://www.cnblogs.com/yidengjiagou/p/17305653.html#:~:text=Redis Zset是一种有序集合,其使用场景主要包括以下几个方面:)。
  • 延迟队列: 将时间戳作为 score,任务作为 member 存入 zset,消费时用 ZRANGEBYSCORE 查找 <= 当前时间的任务,实现按时间顺序处理延时任务。
  • 带权重的队列: 根据任务优先级作为 score 实现优先队列,高优先级任务分数低,ZRANGE 最先取出。
  • 附近地点查询: 有序集合还可结合 GEOHASH 算法用于地理位置按距离排序(Redis GEO 原理其实也是有序集合存经纬度转换的分值)。

注意事项: 有序集合较消耗内存,因为每个元素在跳表和哈希表各存一份。但查找和排序性能优秀。避免在有序集合中存放过长字符串成员,节省内存。对于需要按多重条件排序的场景,Redis zset 只能排序一个分值,可以通过将综合评分作为score使用。批量删除某个分值区间的大量成员时 (如 ZREMRANGEBYSCORE) 可能阻塞,因为需要遍历跳表区间删除,应用应酌情限制每次删除规模或分批处理。总体而言,Sorted Set 是构建排行榜和有序队列的利器,其跳表实现保证了高效的范围操作和排序。

位图(Bitmap)

位图并非 Redis 独立数据类型,而是利用字符串的位操作将二进制位视为数组。Redis 提供诸如 SETBIT key offset valueGETBIT key offset 来操作字符串值的特定位,以及 BITCOUNT 统计1的位数,BITOP 做按位与或非等运算。位图常用于表示大规模布尔值状态,例如用户签到、在线状态等,用1和0表示。

底层原理: 位图直接复用字符串类型的实现,即一串连续的二进制cnblogs.com。比如长度为 N 位的位图会占用 N/8 字节的字符串。Redis 的位操作命令会针对底层字节进行高效的位运算(C 语言实现)。位图没有单独的编码,实际存储就是普通动态字符串 (SDS)。不过可以将 Bitmap 理解为一个超长的位数组,通过偏移定位位所在的字节然后修改相应位。Redis 的 BITCOUNT 等命令会在 C 层面采用位运算指令,速度非常快,可每秒处理数十亿位。

典型场景:

  • 用户签到: 用一个位图记录用户某月每天是否签到。例如键名 sign:user:1001:202309,偏移0表示9月1日。每次签到执行 SETBIT key dayOffset 1。月末用 BITCOUNT 统计签到天数cnblogs.com[cnblogs.com](https://www.cnblogs.com/yidengjiagou/p/17305653.html#:~:text=Redis Bitmaps适用于需要高效地存储大规模的布尔值,并进行位运算、统计等操作的场景。比如:)。
  • 在线用户: 为每个用户分配一个 bit,用户登录时将对应 bit 置1,下线置0,这样可快速得到全网在线用户数 (BITCOUNT)[cnblogs.com](https://www.cnblogs.com/yidengjiagou/p/17305653.html#:~:text=Redis Bitmaps适用于需要高效地存储大规模的布尔值,并进行位运算、统计等操作的场景。比如:)。
  • UV 统计: 将每个用户ID对应 bit 位(需要一个足够长的位图,例如用户ID直接做偏移),用户访问某资源则置位。BITCOUNT 可以统计不重复访问人数。
  • 布隆过滤器基础: 位图是构建布隆过滤器的基础,通过多个哈希函数映射到位数组的不同位置blog.csdn.net。虽然 Redis 也有更高级的 BF 模块,但本质上由位图实现。

注意事项: 位图的索引操作非常高效,但一个键的位图大小取决于最大索引。例如如果用用户ID当偏移,则需要确保位图长度覆盖最大用户ID,否则访问超出范围时 Redis 会自动扩展字符串,可能引发内存骤增。位图因为以字符串保存,也受512MB键值大小限制,这相当于可表示 2^32 个bit(约42亿),大多数情况足够。使用 BITOP 进行大规模位运算也会根据字符串长度处理,可能阻塞主线程,要注意不要对过长位图频繁做 BITOP。除了这些,位图是操作比特级别的数据,非常节省空间(一个 1亿用户在线状态的位图仅约12MB),适用于各类大规模布尔标记场景。

HyperLogLog

HyperLogLog (HLL) 是 Redis 提供的一种基数统计数据结构,用于近似去重计数。它可以非常小的空间实现对不重复元素数量 (基数) 的估计。Redis 通过 PFADD 将元素加入 HLL,PFCOUNT 返回估计的基数,PFMERGE 可以合并多个 HLL。误差标准差约 0.81%,能容忍小误差的场景非常有用。

底层原理: HyperLogLog 基于概率算法,通过哈希并统计前导零来估计集合基数。Redis HyperLogLog 的实现细节较复杂:内部用一个稀疏数组表示,当元素数量较少时,大部分寄存器都是 0,则用稀疏表示节省空间[cnblogs.com](https://www.cnblogs.com/yidengjiagou/p/17305653.html#:~:text=Redis HyperLogLog类型的内部编码使用的);当计数较多超过阈值,会转换为稠密数组(dense representation)存储每个寄存器的值[cnblogs.com](https://www.cnblogs.com/yidengjiagou/p/17305653.html#:~:text=Redis HyperLogLog类型的内部编码使用的)。Redis HLL 默认使用 16384 (2^14) 个寄存器,整个结构只占用 12KB 内存,即使计数接近 2^64 规模也不再增长[cnblogs.com](https://www.cnblogs.com/yidengjiagou/p/17305653.html#:~:text=Redis HyperLogLog(基数统计)是一种基于概率统计的数据结构,用于估计大型数据集合的基数(不重复元素的数量),以及对多个集合进行并、交运算等。Hy perLogLog的优点是可以使用极少的内存空间,同时可以保证较高的准确性。)[cnblogs.com](https://www.cnblogs.com/yidengjiagou/p/17305653.html#:~:text=每个 HyperLogLog 键只需要花费 12 KB,64 个不同元素的基数。)。当元素增加时,如果尚在稀疏表示,会逐渐在内部 bitfield 标记,当超过某点转成稠密12KB结构[cnblogs.com](https://www.cnblogs.com/yidengjiagou/p/17305653.html#:~:text=Redis HyperLogLog类型的内部编码使用的)blog.csdn.net。不论元素多少,最大也就12KB。这正是 HLL 的魅力:用固定小内存换取计数精度上的轻微牺牲。

典型场景:

  • 网站 UV 统计: 用 PFADD 记录每天访问网站的用户ID,PFCOUNT 可以估算独立访客数[cloud.tencent.com](https://cloud.tencent.com/developer/article/2333100#:~:text=学透Redis HyperLogLog,看这篇就够了 ,是一种概率数据结构,它使用概率算法来统计集合的近似基数。而它算法的最本源则是伯努利过程。 伯努利过程就是一个抛硬币实验的过程。抛一枚正常)。相比用集合存用户ID,HLL 只需极小内存且误差可接受。
  • 日志唯一IP: 统计一段时间内独立 IP 数量,也可将 IP 作为元素 PFADD 进 HLL。同理用于运营数据里某活动参与用户数等指标估算。
  • 大数据去重计数: 例如需要大概知道一个大文件包含多少不同单词,HLL 非常适合:不需存所有单词,只需不断 PFADD,然后 PFCOUNT 得结果。
  • 分布式去重: 多台计数也可用 HLL 合并:各节点分别收集 HLL,最后 PFMERGE 合并,再 PFCOUNT 得全局基数。

注意事项: HLL 的结果是近似值,误差一般在接近真实值的 ±0.81%[developer.aliyun.com](https://developer.aliyun.com/article/830826#:~:text=HyperLogLog 使用及其算法原理详细讲解 ,是被)。对于统计用户量等宏观指标通常足够,但不适合需要精准去重的场合(如结算金额这种必须精确的)。如果 PFCOUNT 显示基数很小(小于登记的元素数),可能是误差造成,可以通过多次计算平均消除但一般不必。HLL 初始为空时 PFCOUNT 返回0,一旦 PFADD 加入至少一个元素,内部就分配结构。HLL 的实现算法在元素数量极少时也能较准确(Redis 针对小基数做了线性校正)。总之,用 HLL 能极大节省为去重计数所需的内存和 CPU,是一种空间换时间的极致方案。

GEO(地理位置)

Geo 是 Redis 基于有序集合提供的地理位置存储和查询功能。可以将地点的经纬度信息保存,并执行附近查询等操作。常用命令:GEOADD key lon lat member 添加地理坐标,GEOPOS 获取坐标,GEODIST 计算两成员距离,GEOSEARCH(或旧版 GEORADIUS)按指定中心和半径搜索附近成员等。

底层原理: Redis 并没有引入新数据结构来存地理信息,而是巧妙地使用 Sorted Set 来存储。在内部,它将经纬度转换为一个52位的 Geohash 整数作为 Score 值,将地点名称作为 member 存入有序集合[cnblogs.com](https://www.cnblogs.com/yidengjiagou/p/17305653.html#:~:text=match at L250 Redis Geo类型内部使用zset来存储地理位置信息,其中元素的score值为经度,member值为经纬度组合的字符串。在使用GEORADIUS和GEORA,DIUSBYMEMBER命令搜索元素时,Redis会构建一个跳跃表,以实现高效的搜索。)。Geohash 算法可以将二维经纬度映射到一维数字,且相近的地理位置其 Geohash 前缀相同。Redis GEO 利用 sorted set 的范围查询来实现经纬度范围扫描。当使用 GEOSEARCH (或 GEORADIUS) 时,Redis 会以给定圆心坐标计算出对应的 Geohash 前缀范围,利用跳表对 score 的排序索引快速定位潜在范围内元素,然后逐一计算精确距离筛选[cnblogs.com](https://www.cnblogs.com/yidengjiagou/p/17305653.html#:~:text=match at L250 Redis Geo类型内部使用zset来存储地理位置信息,其中元素的score值为经度,member值为经纬度组合的字符串。在使用GEORADIUS和GEORA,DIUSBYMEMBER命令搜索元素时,Redis会构建一个跳跃表,以实现高效的搜索。)。另外,为提高精度,Redis 在 score 之外可能结合了一定的 bit 操作构建一个矩形或圆形区域的近似框架,再通过跳表找到候选并精确判断。

典型场景:

注意事项: Redis GEO 基于 zset,因此每个成员实际上占用和普通字符串类似的空间外加浮点运算开销。半径查询结果数量如果很多(如在密集城市中心查大范围)可能返回上千上万结果,要做好分页或限制。GEORADIUS 命令在 Redis 6 被标记过时,建议使用更通用灵活的 GEOSEARCH/GEOSEARCHSTORE。Geo 查询默认使用作为距离单位,可通过命令选项指定 km/mile 等。距离计算使用的是标准球面余弦公式,足够精确。一般场景下 Redis GEO 完全可以胜任附近检索,但对于非常高精度或复杂地理计算(多边形内筛选等)就需要专门的地理信息系统。

5. Redis 持久化机制

为了防止数据因进程退出或机器重启而丢失,Redis 提供两种主要的持久化方式:RDB 快照和 AOF 日志,以及 Redis 4.0 引入的混合持久化将两者结合[cnblogs.com](https://www.cnblogs.com/javaguide/p/redis-persistence.html#:~:text=Redis 不同于 Memcached 的很重要一点就是,Redis 支持持久化,而且支持,3 种持久化方式)[cnblogs.com](https://www.cnblogs.com/javaguide/p/redis-persistence.html#:~:text=match at L234 由于 RDB,preamble` 开启)。)。以下分别说明它们的原理、配置与优缺点。

RDB 持久化(快照)

原理: RDB(Redis DataBase)持久化是将某一时刻的内存数据快照保存到磁盘文件(dump.rdb)。Redis 会在指定的触发条件下创建快照:可通过配置 save <秒> <更改key数> 多组条件,例如默认配置:save 900 1(15分钟内至少1个键改动)、save 300 10(5分钟10个键改动)、save 60 10000(1分钟10000个键改动)[cnblogs.com](https://www.cnblogs.com/javaguide/p/redis-persistence.html#:~:text=快照持久化是 Redis 默认采用的持久化方式,在 )。满足条件时 Redis 会自动触发 BGSAVE:fork 一个子进程,将内存数据写入临时 RDB 文件,完成后再替换旧的 RDB[cnblogs.com](https://www.cnblogs.com/javaguide/p/redis-persistence.html#:~:text=Redis 提供了两个命令来生成 RDB 快照文件:)。因为是 fork 子进程执行,主线程不会阻塞(除了 fork 时短暂阻塞)。也可以手动执行 SAVE(但会阻塞主线程)或 BGSAVE 命令来生成快照[cnblogs.com](https://www.cnblogs.com/javaguide/p/redis-persistence.html#:~:text=Redis 提供了两个命令来生成 RDB 快照文件:)。

配置: 除了 save 条件外,dir 参数指定 RDB 文件保存路径,dbfilename 指定文件名(默认 dump.rdb)。可通过 stop-writes-on-bgsave-error(默认开启)设置当后台保存出错时是否停止写操作,以避免坏快照造成状态不一致。Redis 还支持 压缩校验选项:rdbcompression yes 使用 LZF 压缩 RDB 减小体积,rdbchecksum yes 在文件末尾添加 CRC64 校验和来验证文件完整性。

优缺点: RDB 的优点是对性能影响小(子进程在做快照,主进程继续处理请求)、文件紧凑(二进制压缩格式比AOF小得多),并且恢复速度快(直接载入内存即可)[cloud.tencent.com](https://cloud.tencent.com/developer/article/2478686?policyId=1004#:~:text=Redis 持久化揭秘:选择RDB、AOF 还是混合持久化? ,和AOF 两种持久化方式及混合持久化。RDB 性能高效但数据丢失风险高,AOF 数据安全性高但性能开销大。混合持久化结合两者优点,)。因此非常适合作定期备份和跨环境数据迁移[cnblogs.com](https://www.cnblogs.com/javaguide/p/redis-persistence.html#:~:text=Redis 可以通过创建快照来获得存储在内存里面的数据在 某个时间点 上的副本。Redis 创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis,主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用。)。缺点是可能丢失数据:快照按间隔生成,如服务器意外宕机,两次快照间的新数据将全部丢失[cloud.tencent.com](https://cloud.tencent.com/developer/article/2478686?policyId=1004#:~:text=Redis 持久化揭秘:选择RDB、AOF 还是混合持久化? ,和AOF 两种持久化方式及混合持久化。RDB 性能高效但数据丢失风险高,AOF 数据安全性高但性能开销大。混合持久化结合两者优点,)。例如默认 5 分钟一快照,则最多丢失5分钟数据。另一个缺点是 fork 子进程时如果内存很大,会占用较多 CPU 和内存(需要 Copy On Write),在数据量特别大时快照过程也可能导致磁盘 IO 压力。

使用建议: RDB 通常搭配 AOF 一起开启,以定期提供完整备份文件。同时也有场景单独用RDB:比如缓存为主且对数据完整性要求不高的场景,可以只用 RDB 降低持久化开销,并接受崩溃时丢几分钟数据。生产环境可以调整 save 配置,使快照更频繁或者更稀疏,根据数据变化率决定。需要即时全量备份时,可以执行 BGSAVE 触发快照,完成后将 RDB 文件异地备份。恢复时,只需将 RDB 文件放入 Redis 工作目录,启动服务会自动加载[cloud.tencent.com](https://cloud.tencent.com/developer/article/2478686?policyId=1004#:~:text=Redis 持久化揭秘:选择RDB、AOF 还是混合持久化? ,和AOF 两种持久化方式及混合持久化。RDB 性能高效但数据丢失风险高,AOF 数据安全性高但性能开销大。混合持久化结合两者优点,)。注意:RDB 文件是紧凑二进制,不易直接编辑,可借助 redis-check-rdb 工具校验完整性。

AOF 持久化(只追加文件)

原理: AOF(Append Only File)通过记录每一条写命令实现持久化。开启 AOF 后(appendonly yes),Redis 在执行完影响数据的命令后,将该命令以 Redis 协议格式追加写入 AOF 缓冲,然后根据策略刷盘[cnblogs.com](https://www.cnblogs.com/javaguide/p/redis-persistence.html#:~:text=什么是 AOF 持久化?)。AOF 记录了数据变更的历史,重启时只需重放文件中的命令即可重建数据集[open.alipay.com](https://open.alipay.com/portal/forum/post/125501203#:~:text=持久化机制,是对每条写入命令作为日志,重启的时候,可以通过回放日志中的写入指令来重新构建整个数据集。 不 )。AOF 写入流程:命令执行后先写入服务器内存的 AOF 缓冲-> 再调用 write 写入操作系统内核缓冲-> 再根据配置调用 fsync 将数据真正刷到磁盘[cnblogs.com](https://www.cnblogs.com/javaguide/p/redis-persistence.html#:~:text=appendonly yes)[cnblogs.com](https://www.cnblogs.com/javaguide/p/redis-persistence.html#:~:text=AOF 持久化功能的实现可以简单分为 5 步:)。其中 appendfsync 参数决定刷盘策略:always 表示每个命令后立即 fsync,最安全但性能慢;everysec (默认)表示每秒钟异步 fsync 一次,可能丢失最近1秒数据但性能好[cloud.tencent.com](https://cloud.tencent.com/developer/article/2478686?policyId=1004#:~:text=Redis 持久化揭秘:选择RDB、AOF 还是混合持久化? ,和AOF 两种持久化方式及混合持久化。RDB 性能高效但数据丢失风险高,AOF 数据安全性高但性能开销大。混合持久化结合两者优点,);no 则完全依赖操作系统自行调度刷新(风险较高,一般不用)。AOF 文件默认名 appendonly.aof

AOF 重写: 随着命令不断追加,AOF 文件会越来越大。为避免无限膨胀,Redis 引入 “AOF 重写”(BGREWRITEAOF)机制:Redis 会在后台扫描当前内存数据,用最少命令重构相同数据的新的 AOF 文件。例如若某key经历连续100次INCR,自然AOF累积100条命令,重写时只需保存这key当前值的一条 SET 命令即可[cloud.tencent.com](https://cloud.tencent.com/developer/article/2478686?policyId=1004#:~:text=Redis 持久化揭秘:选择RDB、AOF 还是混合持久化? ,和AOF 两种持久化方式及混合持久化。RDB 性能高效但数据丢失风险高,AOF 数据安全性高但性能开销大。混合持久化结合两者优点,)。重写过程由子进程执行,不阻塞主线程:期间新写入命令仍写入旧 AOF 和一个重写缓冲,待新 AOF 写好后再合并重写缓冲追加,最后原子替换文件。auto-aof-rewrite-percentageauto-aof-rewrite-min-size 参数可配置自动重写阈值,例如默认当 AOF 大小超过上次重写后大小的100%且文件超过64MB时触发重写。AOF 重写不会中断服务,但由于需要遍历大量数据和写文件,也会消耗磁盘和CPU,触发时间可根据业务低峰选择。

优缺点: AOF 的优点是数据更安全,默认每秒刷盘设置下最多丢1秒数据,若设 always 几乎不丢[cloud.tencent.com](https://cloud.tencent.com/developer/article/2478686?policyId=1004#:~:text=Redis 持久化揭秘:选择RDB、AOF 还是混合持久化? ,和AOF 两种持久化方式及混合持久化。RDB 性能高效但数据丢失风险高,AOF 数据安全性高但性能开销大。混合持久化结合两者优点,)。AOF 文件是命令日志,日志顺序追加写对硬盘顺序写性能友好。并且 AOF 是可读的文本(Redis 协议),在出现问题时可以通过编辑文件来修复数据或调试。缺点是 AOF 体积通常比 RDB 大许多,恢复速度也较慢(需要重放大量命令)[cloud.tencent.com](https://cloud.tencent.com/developer/article/2478686?policyId=1004#:~:text=Redis 持久化揭秘:选择RDB、AOF 还是混合持久化? ,和AOF 两种持久化方式及混合持久化。RDB 性能高效但数据丢失风险高,AOF 数据安全性高但性能开销大。混合持久化结合两者优点,)。另外 AOF 长时间运行可能会产生碎片和无效命令,需要依赖重写进行瘦身。还有极少数情况下,AOF 可能存在一致性问题:比如恰好宕机在半条命令写入后,下次启动可能 AOF 末尾半条命令不完整,Redis 会检测到并在启动时redis-check-aof修剪不完整命令(因此 AOF 恢复时也可能丢最后一点数据)。综合来说,AOF 适合对数据要求高完整性的场景,通常与 RDB 结合开启。

配置与使用: 在 redis.conf 打开 appendonly yes 后,一定要同时检查 appendfsync 策略,everysec 在性能和安全上折中,被普遍采用。AOF 文件每次启动时都会自动加载执行,可以通过启动日志确认 Loading AOF 过程及耗时。可用 redis-cli 发 BGREWRITEAOF 手动触发重写。监控方面,应监控 AOF 当前大小和重写触发频率、AOF 最近改写情况(在 INFO persistence 里可见)。如果 AOF 文件意外损坏无法加载,可以考虑fallback使用 RDB 恢复,再使用备份或者人工校对数据。AOF 也可用于复制:可以将一实例 AOF 文件拷贝到另一相同版本Redis上加载,实现跨环境数据迁移。

混合持久化

原理: Redis 4.0 引入混合持久化选项,将 RDB 快照和 AOF 日志结合,发挥两者优势。开启方式是配置 aof-use-rdb-preamble yes[cnblogs.com](https://www.cnblogs.com/javaguide/p/redis-persistence.html#:~:text=match at L234 由于 RDB,preamble 开启)。)。当开启混合模式后,在进行 AOF 重写时,Redis 会先将当前内存快照以 RDB 格式写入新 AOF 文件的开头,然后继续将重写期间收到的增量命令附加在其后[cnblogs.com](https://www.cnblogs.com/javaguide/p/redis-persistence.html#:~:text=由于 RDB 和 AOF 各有优势,于是,Redis,preamble 开启)。)。这样得到的 AOF 文件前半部分是二进制 RDB 快照(记录了重写时刻数据库的完整状态),后半部分是正常 AOF 文本命令日志[cnblogs.com](https://www.cnblogs.com/javaguide/p/redis-persistence.html#:~:text=由于 RDB 和 AOF 各有优势,于是,Redis,preamble 开启)。)。恢复时,Redis 检测到 AOF 文件以 RDB 开头,就先载入这部分快照,再继续应用后续的追加命令[cnblogs.com](https://www.cnblogs.com/javaguide/p/redis-persistence.html#:~:text=match at L234 由于 RDB,preamble 开启)。)。因此混合文件既包含全量数据又包含增量更新。

优点: 混合持久化显著缩短了 恢复时间:因为 RDB 部分加载远比重放大量命令快[zhuanlan.zhihu.com](https://zhuanlan.zhihu.com/p/683567108#:~:text=优点: 更快的启动速度:混合持久化结合了RDB的速度优势,所以Redis可以更快地重新启动,不用等待很久。 数据安全:利用AOF的方式,即使服务器突然断电,也只会丢失极短的时间内 )。同时文件体积也更小:RDB 部分是压缩的,仅存最终状态,避免传统AOF重复命令造成的冗余[zhuanlan.zhihu.com](https://zhuanlan.zhihu.com/p/683567108#:~:text=优点: 更快的启动速度:混合持久化结合了RDB的速度优势,所以Redis可以更快地重新启动,不用等待很久。 数据安全:利用AOF的方式,即使服务器突然断电,也只会丢失极短的时间内 )。此外,因为使用 RDB 记录基线,哪怕工作负载有很多写操作,每次重写都能归并成小文件,降低磁盘IO和存储成本[zhuanlan.zhihu.com](https://zhuanlan.zhihu.com/p/683567108#:~:text=优点: 更快的启动速度:混合持久化结合了RDB的速度优势,所以Redis可以更快地重新启动,不用等待很久。 数据安全:利用AOF的方式,即使服务器突然断电,也只会丢失极短的时间内 )。而数据安全仍由 AOF 后半部分保障,基本不丢数据。可以说混合持久化结合了 RDB 和 AOF 的优点:快速加载+较小文件+较少数据丢失[cnblogs.com](https://www.cnblogs.com/javaguide/p/redis-persistence.html#:~:text=如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF,文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点)。

潜在问题: 混合持久化本质还是通过 AOF 文件呈现,对于旧版本 Redis 或某些解析工具来说可能不识别嵌入的 RDB 前缀,因此在需要跨版本、跨平台解析时要注意。另一个考虑是第一次启动开启混合模式需要一次 BGREWRITEAOF 生成带 RDB 前缀的新 AOF,所以开启后建议手动触发重写。混合模式在 Redis 4.x 默认关闭,需要手动开启,Redis 5+ 默认开启了该选项(以保证恢复效率)。

场景推荐: 强烈建议在生产环境启用混合持久化。当数据集较大时,使用纯 AOF 恢复会非常慢,而混合模式极大改善了这个问题[cnblogs.com](https://www.cnblogs.com/javaguide/p/redis-persistence.html#:~:text=,持久化或者开启 RDB 和 AOF 混合持久化。)。如果对历史操作日志没有特别需求,混合持久化几乎没有缺点。因此大多数场景下选择 AOF (everysec) + 混合持久化 + RDB 定期快照,是综合的数据安全和恢复效率方案[cnblogs.com](https://www.cnblogs.com/javaguide/p/redis-persistence.html#:~:text=,持久化或者开启 RDB 和 AOF 混合持久化。)。在极端对数据0丢失要求的场景,可以配合配置 appendfsync always,此时性能有所牺牲,但混合模式依然有效提升加载速度。

持久化方案对比与选择

  • 仅 RDB: 性能影响最小,但一次崩溃可能丢失最后几分钟数据。适合缓存为主或可以容忍一定数据丢失的应用。需定期异地备份 RDB 文件。
  • 仅 AOF: 几乎不丢数据(everysec 至多1秒),但持续写日志稍影响性能且恢复较慢。适合对数据要求绝对完整的场景。需要监控AOF大小并利用重写。
  • RDB + AOF (混合): 这一组合被广泛认为是生产首选[cnblogs.com](https://www.cnblogs.com/javaguide/p/redis-persistence.html#:~:text=,持久化或者开启 RDB 和 AOF 混合持久化。)。RDB 提供周期性备份,AOF 确保实时性,两者互为补充。而混合持久化使 AOF 恢复效率提高。

综合来说,同时开启 RDB 和 AOF 可以提供容灾保障(RDB作为快照备份)和较小数据丢失(AOF保证实时性)[cnblogs.com](https://www.cnblogs.com/javaguide/p/redis-persistence.html#:~:text=,持久化或者开启 RDB 和 AOF 混合持久化。)。Redis 会在重启时优先使用 AOF 文件恢复(若存在),因为 AOF通常更完整。只有没有 AOF 时才用 RDB。可以根据需求调整 auto-aof-rewrite 参数,避免 AOF 过大也减少频繁重写。最后,一定要保证持久化文件的存储可靠性,例如将 RDB/AOF 保存到稳固的磁盘,并做好权限和容量管理。如有条件,可将重要数据的 AOF/RDB 定时上传云存储或远端备份,以防本地磁盘故障。

6. 高可用方案

高可用的目标是确保 Redis 服务在单点故障时可以快速自动恢复,不影响业务连续。Redis 提供的哨兵模式和集群模式都能实现故障转移,但应用场景有所不同。下面从选型和机制两方面说明。

Sentinel vs. Cluster 选型:

  • Sentinel 哨兵模式 针对的是单个主库的高可用。它基于主从复制,通过哨兵监控来自动切换 Master。适用于数据量能被单机内存容纳、写 QPS 不超单机能力,但要求服务 7x24 不中断的场景。例如缓存层或中小规模系统cnblogs.com。它的优点是架构简单,部署和运维成本低,客户端改造小(通过哨兵获取 Master 地址即可)。缺点是不提升单机性能,所有写仍在一台主上,容量和吞吐受限[blog.csdn.net](https://blog.csdn.net/zzhongcy/article/details/108446687#:~:text=match at L191 哨兵模式中,单个节点的写能力,存储能力受到单机的限制,动态扩容困难复杂。于是,Redis 3,分布式方面的需求。Redis Cluster 集群模式具有 高可用、可扩展性、分布式、容错 等特性。)。
  • Cluster 集群模式 则面向扩展性需求。通过分片将数据和负载分散到多台节点,从而突破单机内存和 QPS 上限cnblogs.comcnblogs.com。适合大数据量、高并发场景,如亿级键或高并发写入的场景(排行榜、社交 feed 等)。其优点是水平扩展能力强,无中心架构避免单点瓶颈,可存储更多数据、支持更高吞吐blog.csdn.net。缺点是实现复杂,对运维和客户端要求高,需要处理数据分布、迁移以及某些不支持的多键操作blog.csdn.net

因此,如果数据和流量规模在单机范围内,但需要自动故障恢复,用 Sentinel 较合适;如果已经超出单机能力,必须水平拆分,则上 Cluster。另外也可以混合:对于一个 cluster 集群,也可以用哨兵来监控每个节点的存活(但 Redis 官方 cluster 本身有故障检测和投票,不需要 sentinel)。总的来说,“哨兵关注高可用,Cluster 侧重扩容” 是两者的本质区别cnblogs.com。很多公司在规模较小时先采用哨兵,等需要扩容再平滑迁移到集群。

哨兵故障切换机制: 当 Master 节点发生故障时,Sentinel 系统会自动执行故障转移。流程大致如下cnblogs.com

  1. 故障发现: 每个哨兵每秒 PING 主从和其他哨兵。当某哨兵对 Master 连续 down-after-milliseconds(默认为 30秒)未收到响应,则标记 Master 主观下线cnblogs.com。若在指定时间内至少 quorum 个哨兵都报告该 Master 不可达,则 Master 被判定客观下线cnblogs.com
  2. 领袖选举: 哨兵们使用 Raft 类似算法选举出一个领头哨兵来执行故障转移[cnblogs.com](https://www.cnblogs.com/chenwenyin/p/13549492.html#:~:text=主观下线:一个哨兵节点判定主节点down掉是主观下线。 客观下线:只有半数哨兵节点都主观判定主节点down掉,此时多个哨兵节点交换主观判定结果,才会判定主节点客观下线。)。选举由哨兵间互发 SENTINEL is-master-down-by-addr 消息进行投票,确保只有一个哨兵来切换。
  3. 故障转移: 领袖哨兵从故障 Master 的从节点中挑选“最新”的一个(根据复制偏移量)作为新 Mastercnblogs.com。它向该从节点发送命令让其升级为 Master,并同时让集群中其他从节点改为复制这个新 Master。之前的 Master 如恢复上线,会被自动转为从节点。
  4. 通知客户端: 哨兵通过发布订阅和 Redis Sentinel API 通知应用程序新的主节点地址[cnblogs.com](https://www.cnblogs.com/chenwenyin/p/13549492.html#:~:text=(1)集群监控:负责监控Redis master和slave进程是否正常工作 (2)消息通知:如果某个Redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员 (3)故障转移:如果master node挂掉了,会自动转移到slave,node上 (4)配置中心:如果故障转移发生了,通知client客户端新的master地址)[cnblogs.com](https://www.cnblogs.com/chenwenyin/p/13549492.html#:~:text=(3)故障转移:如果master node挂掉了,会自动转移到slave node上 (4)配置中心:如果故障转移发生了,通知client客户端新的master地址)。如果客户端用 Sentinel 提供的机制(如 mymaster 名称解析主地址),将无缝切换到新 Master。

整个过程通常在故障发生后十几秒内完成(取决于 down-after 等配置)。Sentinel 切换时也会考虑一些配置:如 parallel-syncs 限制同时从节点同步新 Master 的数量,防止过多从库同期开启同步压垮主库;failover-timeout 限定整个failover必须在此时间内完成,否则视为失败。哨兵切换的注意事项:为了避免脑裂,需要奇数个哨兵(至少3个)部署在不同主机[cnblogs.com](https://www.cnblogs.com/leffss/p/11993646.html#:~:text=哨兵模式(sentinel),集群模式(cluster),第三方模式优缺点分析,集群部署简单。 能够解决Redis 主从模式下的高可用切换问题。 很方便)。当网络发生分区时,哨兵可能误判 Master 下线导致不必要的切换,因此 quorum 要结合实际设置。客户端侧要支持哨兵,使用哨兵提供的 Master discovery 接口或连接哨兵订阅频道以感知主库变化。

集群故障转移机制: Redis Cluster 本身具备故障检测和自动failover能力。机制与哨兵类似,但由集群中的各主节点充当“哨兵”角色互相监控:

  • 每个节点通过 Gossip 协议定期交流状态。如果超过 cluster-node-timeout 时间未收到某 Master 节点心跳,则标记其为疑似下线。若超过半数的 Master 节点(不含自身)报告该节点下线,则确认为主观下线 (PFAIL -> FAIL 状态)。
  • 这时,失联 Master 的从节点中会有一个被选举为新的 Master。集群用投票方式:每个 Master 节点对某个 Slave 的晋升请求投票,获得多数同意的从库将执行故障转移[blog.csdn.net](https://blog.csdn.net/zzhongcy/article/details/108446687#:~:text=,协议交换状态信息,用投票机制完成 Slave 到 Master 的角色提升。)。该 Slave 切换为 Master、接管故障分片槽位,其余原 Master 的从库改为复制新 Masterblog.csdn.net。整个过程在 cluster-node-timeout * 2 以内通常完成。
  • 集群所有节点都保存最新的槽位分配表,客户端如果连到旧 Master,会收到重定向 MOVED 消息指引其连接新 Master。

Cluster 切换要求每个 Master 至少有一个 Slave,否则主节点宕机会导致槽位不可用blog.csdn.net[blog.csdn.net](https://blog.csdn.net/zzhongcy/article/details/108446687#:~:text=回到刚才的例子中,集群有 A、B、C 三个主节点,如果这 3 个节点都没有对应的从节点,如果,11000 范围内的哈希槽提供服务。)。因此,在搭建集群时务必给每个 Master 配置从节点备份[blog.csdn.net](https://blog.csdn.net/zzhongcy/article/details/108446687#:~:text=match at L258 所以我们在创建集群的时候,一定要为每个主节点都添加对应的从节点。比如,集群包含主节点 A、B、C,以及从节点,A1、B1、C1,那么即使 B 挂掉系统也可以继续正确工作。)。在容量规划上,Redis 官方建议至少3主3从起步。集群的故障转移无需外部干预,但需要注意:当某个节点和集群其他部分通信不良时,可能被多数标记 FAIL 而触发主从切换。然而如果只是网络闪断导致误切换,会出现短暂数据不一致(老主恢复又变从)。Redis Cluster 尽量通过 cluster epoch 等机制避免脑裂,但在极端网络分区下可能同时多个子集群各选出主,导致写入分裂(不过这种概率极低且可通过 cluster-require-full-coverage 等配置强制 slot 缺失时停止服务来保护一致性)。

高可用部署注意事项:

  • 奇数原则: 无论 Sentinel 还是 Cluster,仲裁都需要多数投票,因此部署节点数最好为奇数,避免票数对等无法决策。Sentinel 至少3个。Cluster 至少3主,且整个集群 Master 数量+Sentinel(如果有)都是奇数以提高健壮性。
  • 本地化与隔离: 将高可用节点尽可能分散于不同物理机/机架/可用区,避免单点失效影响所有副本。同时注意网络隔离问题,哨兵和集群通信对网络稳定性要求高。
  • 客户端配置: 对于哨兵,客户端应当使用哨兵模式连接池或配置多 Sentinel 地址实现 failover,而不是直连某一主机 IP。对于 Cluster,客户端库需要支持 cluster 模式(JedisCluster、Lettuce Cluster 等)来自动处理 slot 和重定向。
  • 故障演练: 建议定期模拟 Master 节点宕机测试 failover,检查切换时间和系统影响。尤其关注在切换期间,应用是否有请求失败或重试逻辑正常。Sentinel 切换期间会有几秒无法写入(因为需要选举),客户端应做好重连重试。Cluster 切换相对更快但也有短暂不可写窗口。
  • 数据完整性: 切换过程中,旧 Master 未同步到从节点的最后一些写入可能丢失。这在异步复制架构下无法完全避免(除非采用Wait机制等待ack)。一般通过业务层容忍或补偿。若要求强一致可考虑 Redis Enterprise 等多主方案或其他一致性层。

综上,高可用方案选型取决于规模和需求:小规模用 Sentinel 经济可靠,大规模用 Cluster 保证容量扩展。两者也可结合,如先搭建 Cluster 保证容量,再在各分片层面用从库提供高可用(Cluster 本身已cover高可用,但也可额外用外部 sentinel 监控整个集群)。在云环境下,也有一些 Redis 云服务提供托管的哨兵或集群自动容灾,总之核心是消除单点、快速恢复。

7. 应用场景

Redis 功能强大,在多个典型业务场景中都有应用。下面介绍几种常见场景的解决方案和示例代码。

缓存

作用: 缓存是 Redis 最核心的应用场景。通过缓存,可以将后端数据库或服务的数据暂存在内存中,提供高吞吐、低延迟的读取,加速应用响应,减轻后端压力[didispace.com](https://www.didispace.com/youtube/20231003-redis-top-5-case.html#:~:text=在该场景下,有一些存储于数据库中的数据会被频繁访问,如果频繁的访问数据库,数据库负载会升高,同时由于数据库IO比较慢,应用程序的响应会比较差。此时,如果引入Re dis来存储这些被频繁访问的数据,就可以有效的降低数据库的负载,同时提高应用程序的请求响应。)。典型缓存模式包括:数据库查询缓存、配置缓存、全文检索结果缓存等。

使用: 最简单的模式是读写穿透缓存:查询数据时先查 Redis,如果命中则直接返回,否则查询数据库,再将结果写入 Redis 缓存并返回[didispace.com](https://www.didispace.com/youtube/20231003-redis-top-5-case.html#:~:text=在该场景下,有一些存储于数据库中的数据会被频繁访问,如果频繁的访问数据库,数据库负载会升高,同时由于数据库IO比较慢,应用程序的响应会比较差。此时,如果引入Re dis来存储这些被频繁访问的数据,就可以有效的降低数据库的负载,同时提高应用程序的请求响应。)。例如:

cache_key = f"user:{user_id}"
data = redis.get(cache_key)
if data:
    return data  # 缓存命中
data = db.query_user(user_id)
if data:
    redis.set(cache_key, serialize(data), ex=300)  # 设置缓存300秒
return data

通过设置过期时间 (TTL),让缓存数据定期失效更新,保证最终一致性。对于热点数据,也可不设置 TTL 而采用主动更新或淘汰策略。

效果: 缓存大幅降低数据库 IO 压力,提高性能。例如,将经常访问的不变配置、热门商品信息缓存,后续请求无需再次访问数据库,响应会更快[didispace.com](https://www.didispace.com/youtube/20231003-redis-top-5-case.html#:~:text=在该场景下,有一些存储于数据库中的数据会被频繁访问,如果频繁的访问数据库,数据库负载会升高,同时由于数据库IO比较慢,应用程序的响应会比较差。此时,如果引入Re dis来存储这些被频繁访问的数据,就可以有效的降低数据库的负载,同时提高应用程序的请求响应。)。需要注意缓存与数据库的一致性问题,可通过适当缩短 TTL,或采用 Cache Aside 模式(先更新数据库再删除缓存)来控制。

注意点: 缓存层要考虑缓存雪崩(大批缓存同时过期)和缓存穿透(请求不存在数据)等问题,在性能优化部分详述。简单措施如给缓存 key TTL 加随机,避免集中失效blog.csdn.net。另外如果缓存用于敏感数据,要考虑安全,比如为不同租户数据使用不同前缀隔离。

分布式锁

需求: 在分布式系统中,当多个节点需要竞争共享资源(例如对同一行数据更新,或操作串行化)时,需要一个跨进程的锁机制。Redis 提供简易高效的分布式锁实现手段。

实现: 利用 Redis 的原子操作 SET resource_name unique_id NX EX max_ttl。早期版本用 SETNX + EXPIRE 两步实现,现在可以用单条 SET ... NX PX确保原子性didispace.com。伪代码:

# 获取锁
result = SETNX(lockKey, lockValue)  # lockValue 可用UUID标识持有者
if result == 1:   # 成功设置
    EXPIRE(lockKey, ttl)
    # 执行临界区代码
    DEL lockKey
else:
    # 获取失败,等待/重试

改进版为:

SET lockKey lockValue NX PX 30000   # 请求锁并自动设置30秒过期

返回 OK 则获得锁,否则未获取。didispace.com中示意:Client1 尝试 SETNX,如果返回1表示拿到锁,执行共享资源操作,然后 DEL 释放锁;如果返回0表示已有锁,则等待一段时间再重试didispace.com

https://www.didispace.com/youtube/20231003-redis-top-5-case.html 上图展示了 Redis 分布式锁简单流程:客户端使用 SETNX 尝试创建锁键(带 TTL)。成功则进入临界区并在完成后删除锁;失败则说明锁已被占用,需要等待重试didispace.com。通过全局唯一的锁键,Redis 保证了不同客户端对资源的互斥访问。

完善: 需要考虑锁的自动释放(因此设置 EX/ PX TTL 防止死锁)以及解锁安全(只释放自己加的锁)。通常锁值用 UUID 标识持锁者,释放时先 GET 判断值匹配再 DEL,避免误删他人锁。Redisson 等客户端已封装这些细节[didispace.com](https://www.didispace.com/youtube/20231003-redis-top-5-case.html#:~:text=match at L199 上面这个简单实现虽然可以满足很多用例,但它并不具备良好的容错机制。如果要在生产上是用的话,更推荐采用一些更高质量的分布式锁实现。比如,Java平台的话,可以选择 :Redisson)。Redis 作者也提供了 RedLock 算法,使用多个独立 Redis 实例提高锁可靠性。不过一般业务使用单Redis锁已满足要求。

应用: 分布式锁可用于:电商扣库存防止超卖、定时任务调度集群选主执行、用户并发请求串行化等。通过短暂地加锁,可以确保敏感操作不被并发打乱。需要注意锁的 TTL 要根据业务执行时间设置合理,太短可能任务没执行完锁就过期导致多个客户端并发,太长则降低吞吐。使用锁要尽量减少锁定范围的耗时操作。

消息队列

需求: 在微服务和异步处理场景,经常需要一个简单的队列来传递和缓冲消息。Redis 列表和流(5.0 引入的 Streams)都可实现消息队列。这里介绍基于 List 列表 的简易队列。

实现: 生产者用 LPUSH queue_key message 将消息插入列表左端,消费者用 BRPOP queue_key timeout 从右端阻塞弹出[cnblogs.com](https://www.cnblogs.com/yidengjiagou/p/17305653.html#:~:text=Redis List类型由于支持在列表的头部或尾部添加元素,也支持在列表任意位置插入或删除元素,因此非常适合以下场景:)。BRPOP 在队列为空时会阻塞等待指定超时,当有消息进入则立即返回。这样实现了一个可靠队列:消息被一个消费者取出后即出队,不会被他人重复处理。示例:

// Producer (e.g., Web server)
LPUSH "order_queue" "{order_id:123, user:1001}"  

// Consumer (Worker service)
BRPOP "order_queue" 0  ->  得到 value "{order_id:123, user:1001}"
// 处理订单...

多个消费者对同一队列 BRPOP,相当于竞争消费模式,每条消息只给一个消费者。Redis 的列表操作在头尾都是 O(1) 高效,非常适合此场景[cnblogs.com](https://www.cnblogs.com/yidengjiagou/p/17305653.html#:~:text=Redis List类型由于支持在列表的头部或尾部添加元素,也支持在列表任意位置插入或删除元素,因此非常适合以下场景:)。

Stream vs List: Redis 5.0 新增了 Streams 数据类型,提供更丰富的消息队列功能,如消费组、消息确认等。如果需要多消费者且需要消费确认防丢,可以考虑 Redis Stream 和 XREADGROUP 等命令。但对于简单的单消费者模型,List 已足够而且实现更直观。即使多个消费者以竞争模式读 List 也可行。需要发布订阅实时广播的话,可以用 Redis Pub/Sub 机制,但 Pub/Sub 无法持久保存消息,服务宕机会丢消息,所以队列用 List/Stream 更合适。

应用: Redis 消息队列适用于异步任务处理,例如:日志收集、异步发送通知、削峰填谷。比如秒杀抢购,用户下单后写入队列,由后台慢慢消费减库存下订单,实现请求立即返回、下单异步处理。再如图片处理、邮件发送等耗时操作,通过队列交给独立的 Worker 完成。Redis 队列虽然不具备 RabbitMQ 那样复杂的路由和确认机制,但胜在轻量快捷,无需引入新组件,能满足大量场景。应注意,Redis 不是持久消息中间件,如果 Redis 宕机内存消息会丢失(除非结合 AOF 持久化)。对高可靠要求的消息,可考虑 Streams 或额外持久化方案。

排行榜

需求: 各种排行榜/积分榜/热度榜场景,需要快速更新分数按分排序取出 top 列表。例如游戏高分榜、微博热搜榜等。

实现: Redis 的 Sorted Set 完美契合排行榜需求。将“成员”设为对象ID,“score”设为分数/热度值。使用 ZADD 更新分数,ZREVRANGE 获取前 N 名,ZRANK 查询某成员排名等[cnblogs.com](https://www.cnblogs.com/yidengjiagou/p/17305653.html#:~:text=Redis Zset是一种有序集合,其使用场景主要包括以下几个方面:)。示例:

// 玩家得分更新
ZADD "game:ranking" 500 "player:1001"   // 玩家1001得500分
ZADD "game:ranking" 600 "player:1002"   // 玩家1002得600分

// 获取前3名
ZREVRANGE "game:ranking" 0 2 WITHSCORES  
-> 返回 [player:1002, 600], [player:1001, 500], ...  

// 查询某玩家排名(0表示第一名)
rank = ZREVRANK "game:ranking" "player:1001"  
score = ZSCORE "game:ranking" "player:1001"

ZADD 可以直接用于初始插入和后续分数更新(更新即修改score并重新排序),操作复杂度 O(log n)。ZREVRANGE 等获取有序数据子集也很高效。Redis 有序集合的跳表实现保证这些操作性能,即使上百万元素也可接受。

场景扩展:

  • 积分排行榜: 游戏或活动积分,经常更新并取 TopN。可以定期取前N缓存到页面。
  • 热搜榜: 将搜索关键词作为成员,score 为热度,每次搜索对该词执行 ZINCRBY 增加热度。即可用 ZREVRANGE 实时得到热搜排行榜。
  • 点赞排行榜: 抖音点赞数前十的视频ID排序,也可通过 sorted set 维护实时更新的点赞数量序。
  • 权重队列: 虽非典型排行榜用途,但 sorted set 可用作“延迟队列”(score 为待执行时间戳)或“优先级队列”(score 为优先级),前面已提到。

注意: 排行榜往往关注 top N,不必一次取出所有元素。对于海量元素的 sorted set,ZREVRANGE 0 9 这样的小范围取操作成本低,可以频繁调用。但如果一次要取出几百万名,还是要谨慎,必要时使用ZSCAN分批或建立二级索引。Sorted set 会随数据增多内存上涨,应定期清理不需要再排行的老成员(比如非活跃用户可ZREM掉或者保留一定名次内成员)。Redis 也支持根据score范围删除 ZREMRANGEBYSCORE,或按排名删除 ZREMRANGEBYRANK,可用于维护排行榜长度,如只保留前1万名。

限流

需求: 对于接口调用、用户操作等,需要进行频率限制(如每分钟不超过 X 次),以保护系统免于过载或防止恶意刷接口。

实现: 利用 Redis 的原子递增过期可以轻松实现计数器限流didispace.com。最简单的是固定窗口算法:为每个用户每个时间窗口维护一个计数键。示例:限制每用户每分钟最多10次:

key = "req_count:{user_id}:{current_minute}"  # 如 req_count:1001:202309081230
if INCR key == 1:
    EXPIRE key 60         # 第一次设置过期60秒
if value > 10:
    # 超过限额,拒绝服务
else:
    # 允许请求

通过 key 中包含当前分钟的时间戳,让不同分钟使用不同计数key,互不影响。INCR 保证并发安全计数。如果在1分钟内 INCR 超过10,则触发限流didispace.com[didispace.com](https://www.didispace.com/youtube/20231003-redis-top-5-case.html#:~:text=同时,请求数量的计数器需要设置一个时间窗口,比如:1分钟。也就是没过一分钟时间,计数器将被清零,重新计数。所以,当一个时间窗口中被限流之后,等到下一个时间窗口, 就能恢复继续请求。以实现限制速率的效果。)。EXPIRE 确保计数器在窗口结束后自动删除清零,下个窗口重新计数。这样实现简单有效。

didispace.com图示了一个基本限速器:以用户ID或IP为 key,用 INCR 递增访问计数,并用 expire 设置1分钟窗口,到期自动清零didispace.com。每次请求比较计数与上限,决定是否拒绝请求。通过这种滑动窗口限流,可以防止单用户过于频繁的操作。

改进: 固定窗口算法可能出现边界问题(如在窗口切换瞬间短期内允许了2倍限额)。可以改用滑动窗口漏桶/令牌桶算法。滑动窗口实现稍复杂,需要记录精细到秒级时间戳的操作队列,可使用 Redis 列表或有序集合存时间戳,然后根据当前时间计算窗口内元素个数,超限则限流。漏桶/令牌桶算法可以用 Redis 原子操作+Lua 脚本实现,定期向桶添加令牌,用户请求消耗令牌,无令牌则限流。

Redis 5.0 后也出现了 Redis Module 实现的限流,如 Redis-Cell 模块,提供 CL.THROTTLE 命令,内部用令牌桶算法执行精细限流,不用我们手动实现滑动窗口算法。不过没有模块时,上述 INCR 简单方案已经覆盖很多场景。

应用: 限流用于接口防刷(如登录短信发送频率限制)、API 网关每IP每秒请求数限制、秒杀场景限流(避免瞬间流量压垮系统)等。通常配合Nginx等也做限流,但Redis 可以提供按用户、按更复杂维度的限流统计。比如可以维护 user_id 和 API_key 组合的计数器,实现精细的多级限流策略。

会话存储

需求: 在分布式Web应用中,实现无状态部署,用户 Session 不能存在单台服务器内存。需要共享会话(如登录状态)存储,使多节点可访问统一的会话数据。

实现: Redis 作为内存KV,非常适合作 Session Store。常见做法是:用户登录后在 Redis 保存其会话信息,用 session_id 作为 key,内容包含用户标识、权限等序列化后作为 value,TTL 设置为会话有效期。例如:

session_id = generate_random_token()
redis.setex(f"session:{session_id}", 3600, json.dumps({"user_id":1001, "role":"admin"}))

服务器返回 session_id 给客户端(如放入 Cookie)。后续请求带上 session_id,服务器拿它到 Redis 验证并获取会话数据[didispace.com](https://www.didispace.com/youtube/20231003-redis-top-5-case.html#:~:text=当用户登录Web应用时候,将会话数据存储于Redis,并将唯一的会话ID(Session ID)返回到客户端的Cookie中。当用户再向应用发送请求时 ,会将此会话ID包含在请求中。无状态的Web服务器,根据这个会话ID从Redis中搜索相关的会话数据来进一步请求处理。)。通过这样,哪怕请求落到不同的应用实例,都能从 Redis 获取一致的会话状态,实现会话共享。

优点: Redis 内存访问快,比数据库存session延迟低,对频繁访问的会话数据效果好。而且 Redis 可设置过期时间自动清理会话,避免无效session占用资源didispace.com。在需要多地session同步时,也可借助 Redis 主从或者集群同步,实现跨机房会话统一(但要注意延迟)。

注意: 因为 Redis 默认将所有数据放内存,如果会话数据量巨大(比如几百万用户同时在线),要确保内存足够或适当缩短过期时间。也可考虑开启 Redis 内存淘汰策略,选择 allkeys-lru 以便内存满时淘汰最久未用的 session[xiaolincoding.com](https://www.xiaolincoding.com/redis/module/strategy.html#:~:text=* allkeys,lfu(Redis 4.0 后新增的内存淘汰策略):淘汰整个键值中最少使用的键值。)xiaolincoding.com。另外,为安全起见,session 数据建议设置稍短 TTL(比如30分钟),并在用户每次操作时刷新 TTL,这样 Redis 自动剔除长时间不活动的session防止一直占内存didispace.com。需要高可用时,可用 Redis 哨兵/集群保证 session 数据不丢,否则单点 Redis 挂掉会导致所有用户下线,需要重新登录。

Session 存储是 Redis 应用非常普遍的一种,许多Web框架都内置对接(如 Spring Session 可以直接使用 Redis 存储 session)。通过简单配置即可将分布式系统的会话管理集中在 Redis,实现真正的无状态web层扩展。

8. 性能优化

Redis 单线程性能很高,但在复杂场景下仍需注意一些优化技巧和避免踩坑。以下列举常见的性能问题及优化手段。

慢查询分析

问题: Redis 的慢查询指那些执行时间过长的命令,会阻塞后续请求导致全局性能下降pdai.tech。常见原因包括:KEYS * 之类全量遍历命令、大集合/列表的获取整个数据、复杂 Lua 脚本执行、超大键的删除/过期等[pdai.tech](https://pdai.tech/md/db/nosql-redis/db-redis-y-monitor.html#:~:text=,events in different conditions%2C especially)。识别并优化慢查询是提升性能的关键。

慢日志 Slow Log: Redis 提供慢查询日志,可通过 CONFIG SET slowlog-log-slower-than <微秒> 设置记录阈值(默认 10000 微秒 = 0.01秒)。当命令执行超过此时间,就会被记录在 slowlog 中[pdai.tech](https://pdai.tech/md/db/nosql-redis/db-redis-y-monitor.html#:~:text= 设置合理慢查询日志阀值%2Cslowlog,len建议大于1024个,因监控采集周期1分钟,建议,避免慢查询日志被删除;另外慢查询的参数过多时,会被省略,对内存消耗很小 每次采集使用slowlog len获取慢查询日志个数)。可用 SLOWLOG GET [n] 查看日志,包含命令详情和耗时。默认只保存最近128条(slowlog-max-len 可调)[pdai.tech](https://pdai.tech/md/db/nosql-redis/db-redis-y-monitor.html#:~:text=match at L1264 redis慢查询保存在内存中,最多保存slowlog)。应根据QPS和业务需求把慢查询阈值调合理(建议设置为1毫秒或更低,这样超过1ms的操作就记录,以便及时发现[pdai.tech](https://pdai.tech/md/db/nosql-redis/db-redis-y-monitor.html#:~:text=match at L1269 设置合理慢查询日志阀值%2Cslowlog,len建议大于1024个,因监控采集周期1分钟,建议,避免慢查询日志被删除;另外慢查询的参数过多时,会被省略,对内存消耗很小 每次采集使用slowlog len获取慢查询日志个数))。慢日志是在内存维护,重启会清空,必要时可定期取出保存pdai.tech

优化措施:

  • 找出大键:使用 INFO memorymem_fragmentation_ratioused_memory_dataset 判断内存使用,或用 redis-cli --bigkeys 扫描找出体积超大的键,然后拆分这些数据结构或改进算法。
  • 分批操作:将一次性大量处理转换为多次小操作。比如不要 LRANGE list 0 100000 取十万元素,可用 LRANGE 每次取1000逐步获取;不要一次删除上百万keys,可用 SCAN 搭配 DEL 分批删除。
  • 避免全键扫描:生产绝不能使用 KEYSFLUSHALL 等阻塞指令。用 SCAN 迭代或在应用层记录需要的key列表。
  • 合理数据结构:选择最适合的结构处理任务,尽量利用 Redis 本身提供的高性能操作。如统计计数用 INCR 代替自己维护set长度;批量增长数据用 HyperLogLog 近似算法降低开销。

案例: 曾有应用每次请求都调用 SMEMBERS 取集合所有元素(上万),然后在应用过滤。优化方案是在 Redis 侧用 SINTER 等直接计算交集减少传输量,或者维护额外的计数键避免遍历。又如某脚本使用 EVAL 获取所有键名然后循环DEL,这是灾难,应改成Lua里用 redis.call('SCAN', ...) yield 分批删除或直接用 FLUSHDB+重导数据更快。

总之,要监控 Redis 指令执行时间。可以使用 Redis 自身 latency 监控:LATENCY DOCTOR 会分析最近的延迟 spike 并给出建议[pdai.tech](https://pdai.tech/md/db/nosql-redis/db-redis-y-monitor.html#:~:text=,events in different conditions%2C especially)。INFO 命令也显示 slowlog 计数。通过慢查询日志和经验,我们可以定位瓶颈操作,然后逐一优化,保证大部分命令都能在<1ms完成。

热点 Key 优化

问题: “热点 Key”指某个键被非常频繁地访问,导致Redis压力集中在这一个key上。表现为 CPU 在处理海量对同一key的操作,从而可能拖慢其他请求。典型如某热门明星新闻缓存、某商品库存在秒杀时被疯狂 INCR/DECR

影响: 单线程Redis如果一半以上请求都命中一个key,那么这个key相关操作稍有复杂度都会放大影响。此外网络带宽也可能成为瓶颈——比如 GET bigkey 这个大key被疯狂读,每次传输100KB数据,QPS高时网卡吃不消。

优化思路:

  • 分散热点:尽量将热点数据做分片。例如将一个热点列表拆成多个小列表,根据用户id哈希映射不同列表,这样访问分散到多个key。对计数这类可加随机前缀,但要牺牲精确度。
  • 多级缓存:在 Redis 前增加一层本地缓存(如Guava Cache)缓解压力。热点 key 可以在应用内存缓存几秒,减少Redis访问次数。或者使用 CDN/边缘缓存对于热点读数据。
  • 使用 Cluster 分区:如果热点 key 无法切分且流量巨大,可以用 cluster 模式把不同key分到不同节点上。但单个key超高QPS的情况下,因为Redis单线程,迁移到集群某一节点上其实没变。所以还需客户端分担:比如对热点数据读取,可以一次从Redis取出后在应用广播给各服务器缓存。
  • Lua 脚本批处理:把对同一key的多个操作尽量通过 EVAL 合并为一次,这样热点key也只单次处理。
  • 扩容替代:在极端情况下,可以考虑垂直扩容:给Redis实例绑定更高性能CPU(Redis主要吃CPU),或开启多线程IO(Redis 6.0 提供了配置可启用多线程处理网络IO,减少单线程负担,但是计算仍单线程)。

案例: 某抢购活动,库存 key "item:123:stock" 成为热点,上万并发调用 DECR。为避免单机Redis瓶颈,可考虑不每次都直连Redis:如在Java应用内维护一个计数,本地扣减,小量频率更新 Redis,或者干脆通过消息队列将扣库存排队(牺牲实时性换取保护Redis)。再如排行榜 topN 这些热点读取,也可以定期计算好缓存到内存,用户请求先返回缓存结果,Redis 每隔几秒钟更新一次。

总之,应识别热点Key:通过分析慢查询日志、info keyspace hits/misses 或者 Redis自带 monitor 工具,看哪些key访问频率最高,然后有针对性进行分流。必要时甚至考虑复制:可以部署多个只读从节点,专门让一部分客户端读取热点key的从库,以读扩散模式缓解单点压力(但这种不适用于需要强一致性的写场景)。

缓存穿透

现象: 缓存穿透是指查询一个不存在的数据,因为缓存层没有(通常缓存对不存在值不存储),也查不到数据库,从而每次请求都会落到数据库blog.csdn.net。攻击者可以利用不停请求不存在的 key 来绕过缓存,导致数据库压力激增。

解决方案:

  1. 缓存空对象: 对于数据库中确认不存在的数据,也在 Redis 中缓存一个标记值(如特殊的空字符串或 null)并设置较短过期时间blog.csdn.net。下次相同请求先查到这个“空”缓存,直接返回不存在,而不会再打到DBblog.csdn.net。这样避免重复查库。优点实现简单blog.csdn.net,缺点是会有大量空缓存占用一些内存,但通常比起数据库崩溃,这点代价可以接受。需要注意给空值设置较短 TTL,比如60秒,防止长期占用和当数据库中后来插入该数据时仍旧缓存空值。
  2. 布隆过滤器: 在缓存前增加一层 BloomFilter,对于非法或不存在的 key 直接拦截blog.csdn.net。布隆过滤器用很少内存即可判断一个key是否可能存在(允许极小误判),如果判定一定不存在就不走后续缓存/DB流程blog.csdn.net。例如可以把数据库已有的所有合法 key 预先添加到一个布隆过滤器。对一个查询,先 BF 判断,若不在集合,则直接返回不存在;如果在集合再走正常缓存流程。BloomFilter几乎在内存操作,无IO,性能高。但缺点是实现复杂,且存在误判率,需要定期维护、扩容。

使用场景: 缓存穿透常由恶意构造大量不存在 key 或不小心的错误导致。例如传递了一个用户ID=-1这种无效ID。通过以上手段可以有效避免这些请求反复查询数据库blog.csdn.net。对于攻击性质的请求,除了技术手段,还应配合在防火墙、接口层对参数做校验拦截。

注意: 不要将布隆过滤器误用为精确集合,它有一定误判(可能误把不存在key认为存在导致去查DB,其实还好,因为至少不会把不存在的去查DB无限次地查,因为BF永远说不存在或可能存在,但在不存在时BF基本都拦)。缓存空对象要选择一个合理的占位值和TTL,不要无限缓存不存在值以免数据插入后用户老是读到旧缓存。同时需要对缓存层做防爆破设计,如对连续未命中同一IP做限制,防爬虫/恶意刷接口也是一层防护。

缓存雪崩

现象: 缓存雪崩是大量缓存值在同一时间失效,使得大量请求绕过缓存全部打到后端数据库,可能压垮后端blog.csdn.net。另一种雪崩是缓存服务器本身宕机或不可用,导致类似效果blog.csdn.net。总之,雪崩是缓存集中失效引发的“击穿”效应,但区别是它是批量的。

应对策略:

  • 错峰过期: 不要让大量 key 拥有相同 TTL。同一批数据缓存时,给它们的 TTL 添加随机偏差blog.csdn.net。例如基础TTL 600秒,每个key加一个 0~60秒的随机值,这样即使它们在同一时刻缓存,也不会同时失效,而是分散开blog.csdn.net。这样请求回落数据库也逐渐地进行,避免峰值。
  • 二级缓存: 在Redis缓存之上,再加一层如Ehcache/本地缓存。当Redis雪崩不可用时,可从本地返回部分旧数据(尽管可能不新但能抗住流量)。或者实现缓存降级:检测到Redis不正常时,直接短路返回默认值或空响应,宁可牺牲部分功能,防止数据库被海啸请求淹没blog.csdn.net
  • 缓存高可用: 防止雪崩的关键还是避免缓存整体失效。部署Redis哨兵/集群,避免整机宕机。如果是机房网络中断导致缓存集群不可用,要有熔断机制:应用层发现缓存连接全部失败时,立即启用降级策略或限制对数据库的请求,让系统渡过难关[learn.lianglianglee.com](https://learn.lianglianglee.com/专栏/Redis 核心技术与实战/26 缓存异常(下):如何解决缓存雪崩、击穿、穿透难题?.md#:~:text=26 缓存异常(下):如何解决缓存雪崩、击穿、穿透难题? ,缓存中进行处理,紧接着,应用将大量请求发送到数据库层,导致数据库层的压力激增。 缓存雪崩一般是由两个原因导致的,应对方案也有所)。
  • 预缓存预热: 在系统上线或大促前,提前把常用数据加载进缓存,避免刚开始流量涌入时缓存都为空导致DB瞬时压力过大[learn.lianglianglee.com](https://learn.lianglianglee.com/专栏/Redis 核心技术与实战/26 缓存异常(下):如何解决缓存雪崩、击穿、穿透难题?.md#:~:text=26 缓存异常(下):如何解决缓存雪崩、击穿、穿透难题? ,缓存中进行处理,紧接着,应用将大量请求发送到数据库层,导致数据库层的压力激增。 缓存雪崩一般是由两个原因导致的,应对方案也有所)。可以通过脚本批量访问关键数据触发缓存填充。

实践: 比如某网站首页有上百条内容缓存,每晚0点整全部失效,结果0点后大量用户访问,缓存全没命中,数据库压力暴涨导致崩溃。这可以通过将缓存TTL随机在 3600±300秒,使它们分散在0点到0:05失效,不会一瞬间全部消失blog.csdn.net。另一个例子,Redis 服务意外挂掉重启期间,请求全打DB,发生雪崩。可以做法:应用通过一个AtomicBoolean检测Redis状态,当Redis故障则快速返回错误/默认值,并有限制的逐步恢复服务,避免数据库被压死。

缓存击穿

现象: 缓存击穿是指某个热点 Key 在失效瞬间,有大量并发请求涌入缓存发现失效,进而同时访问后端数据库[blog.csdn.net](https://blog.csdn.net/weixin_43696483/article/details/128551144#:~:text=缓存击穿也叫热点key问题,就是一个被高并发访问并且缓存重建业务较复杂的key失效了,无数的请求访问会在瞬间打到数据库,带来巨大压力。 1、通过互斥锁解决缓存击穿 给缓存重建过程加锁,确保重建过程只有一个线程执行,其它线程等待。)。与雪崩不同,它是单个key超高并发导致瞬时击穿DB。比如一个热点商品库存缓存正好过期,在下一毫秒有上万请求来查库存,缓存没了,这上万请求全跑数据库查询。

解决方案:

  • 互斥锁:对缓存重建过程加锁,保证同一时间只有一个线程去加载数据,其它请求等待[blog.csdn.net](https://blog.csdn.net/weixin_43696483/article/details/128551144#:~:text=缓存击穿也叫热点key问题,就是一个被高并发访问并且缓存重建业务较复杂的key失效了,无数的请求访问会在瞬间打到数据库,带来巨大压力。 1、通过互斥锁解决缓存击穿 给缓存重建过程加锁,确保重建过程只有一个线程执行,其它线程等待。)。实现:在缓存 miss 时,不立刻查DB,而是使用如 SETNX("lock:xxx") 尝试加锁成功者查询数据库并回填缓存,其他线程阻塞一会轮询缓存或等待锁释放[blog.csdn.net](https://blog.csdn.net/weixin_43696483/article/details/128551144#:~:text=1、通过互斥锁解决缓存击穿 给缓存重建过程加锁,确保重建过程只有一个线程执行,其它线程等待。)。这样数据库只负担一次查询,其它请求稍微延迟但最终读到缓存。这种锁可用 Redis 分布式锁(前面提过)或本地mutex。如果应用服务器多台,则必须用 Redis 全局锁。
  • 提前刷新:对热点key,在其TTL将到时,主动延长或刷新缓存。比如设置一个线程定期检查热点key剩余TTL,小于阈值时,主动从DB取新值更新缓存(即“逻辑过期”方案)。这样用户永远不会见到缓存过期[blog.csdn.net](https://blog.csdn.net/weixin_43696483/article/details/128551144#:~:text=Image%3A 在这里插入图片描述 2、根据id查询商品信息,基于互斥锁解决缓存击穿问题 Image%3A 在这里插入图片描述,逻辑过期的优点是 性能好,缺点是 不保证一致性,有额外的内存消耗,实现复杂。 Image%3A 在这里插入图片描述)。这需要能识别热点key并额外消耗一点后台资源。
  • 永不过期+主动失效:一种思路是热点数据干脆设置为不过期,由后台任务在需要更新时手动失效。这避免了高并发场景下自然过期。但缺点是缓存与数据库更新同步要靠程序维护,否则可能存在过期数据。
  • 备份缓存:在缓存要过期前,把旧值暂存一份,过期后如果重建失败则用旧值应急。实际上这有些复杂,一般不这么搞。

对比: 使用锁(互斥锁/信号量)是较通用可靠的方法[blog.csdn.net](https://blog.csdn.net/weixin_43696483/article/details/128551144#:~:text=1、通过互斥锁解决缓存击穿 给缓存重建过程加锁,确保重建过程只有一个线程执行,其它线程等待。)。Redis作者也曾建议在缓存失效时通过 SETNX(key_lock) 控制数据库访问[blog.csdn.net](https://blog.csdn.net/weixin_43696483/article/details/128551144#:~:text=1、通过互斥锁解决缓存击穿 给缓存重建过程加锁,确保重建过程只有一个线程执行,其它线程等待。)。逻辑过期(Double Cache)更趋近无缝,但要维护稍复杂逻辑:缓存值里放一个字段表示逻辑过期时间,平时即使真正过期也让它留存,当有请求发现逻辑过期时间到了,则后台异步重建缓存,而当前请求仍返回旧值避免打DB[blog.csdn.net](https://blog.csdn.net/weixin_43696483/article/details/128551144#:~:text=Image%3A 在这里插入图片描述 3、通过逻辑过期解决缓存击穿 逻辑过期的优点是 性能好,缺点是,不保证一致性,有额外的内存消耗,实现复杂。 Image%3A 在这里插入图片描述)。这种方案性能最好但实现较复杂,需要配合后台线程和Lua保证原子性。

实践: 例如一篇热门文章浏览量在Redis缓存,设TTL=1h。如果过期瞬间1万用户请求阅读,用锁策略:第一个请求取不到缓存,获得锁后查DB更新缓存,其它9999个请求等待锁释放再读新的缓存[blog.csdn.net](https://blog.csdn.net/weixin_43696483/article/details/128551144#:~:text=1、通过互斥锁解决缓存击穿 给缓存重建过程加锁,确保重建过程只有一个线程执行,其它线程等待。)。这样DB只承受1次查询,击穿问题解决。锁等待时间不宜设太久,一般几毫秒到几十毫秒,因为数据库查一次很快完成就释放锁了。

内存淘汰机制

问题: Redis 是内存数据库,内存满了需要按照策略淘汰数据,否则新的写入无法进行。maxmemory 配置指定 Redis 可用内存上限,当达到上限时就触发淘汰策略 (Eviction)。Redis 提供8种淘汰策略,需根据场景合理配置[xiaolincoding.com](https://www.xiaolincoding.com/redis/module/strategy.html#:~:text=Redis 内存淘汰策略共有八种,这八种策略大体分为「不进行数据淘汰」和「进行数据淘汰」两类策略。)。

常见策略:

  • noeviction(默认):不淘汰任何数据,达到内存上限后,对写入操作直接返回错误xiaolincoding.com。适用于纯缓存之外场景(如持久store),但需要应用自己处理写失败情况。
  • allkeys-lru:对所有键采用最近最少使用淘汰。LRU 算法根据访问时间选出最久未使用的键驱逐,腾出空间[xiaolincoding.com](https://www.xiaolincoding.com/redis/module/strategy.html#:~:text=* allkeys,lfu(Redis 4.0 后新增的内存淘汰策略):淘汰整个键值中最少使用的键值。)。适合做缓存且希望尽可能保持热点数据,默认3.x以前就是 volatile-lru,4.0+ 提供 allkeys-lfu 作为改进。
  • allkeys-lfu:根据访问频率淘汰最少使用的键[xiaolincoding.com](https://www.xiaolincoding.com/redis/module/strategy.html#:~:text=* allkeys,lfu(Redis 4.0 后新增的内存淘汰策略):淘汰整个键值中最少使用的键值。)。LFU 能避免某些一次性批量访问导致的缓存污染,更加智能xiaolincoding.com[xiaolincoding.com](https://www.xiaolincoding.com/redis/module/strategy.html#:~:text=LFU 全称是 Least Frequently Used,翻译为 最近最不常用,LFU 算法是根据数据访问次数来淘汰数据的,它的核心思想是“如果数据过去被访问多次,那么将来被访问的频率也更高”。)。Redis 为每个对象维护一个 8-bit 的频率计数,并有时间衰减机制,让长期未访问的频率也降低[xiaolincoding.com](https://www.xiaolincoding.com/redis/module/strategy.html#:~:text=在 LFU 算法中,Redis对象头的 24 bits,Logistic Counter)。)。LFU 对较新版本Redis可选用。
  • volatile-lru / volatile-lfu:类似上面LRU/LFU,但只从设置了过期时间的键中淘汰[xiaolincoding.com](https://www.xiaolincoding.com/redis/module/strategy.html#:~:text=* volatile,lfu(Redis 4.0 后新增的内存淘汰策略):淘汰所有设置了过期时间的键值中,最少使用的键值;)。不带 TTL 的键(可能重要数据)不会被淘汰。
  • volatile-ttl:从将过期的键中挑选剩余寿命最短的淘汰xiaolincoding.com。优先删除快过期的,可以认为对要过期的数据“提早回收”。
  • volatile-random / allkeys-random:不按使用频率,仅随机挑选键淘汰xiaolincoding.com。随机适合对缓存命中要求不高,但有均衡删除目的(较少用)。

配置方法: 可在 redis.conf 设置 maxmemory 大小和 maxmemory-policy 策略,也可运行时通过 CONFIG SET 修改xiaolincoding.com。查看当前策略用 CONFIG GET maxmemory-policy[xiaolincoding.com](https://www.xiaolincoding.com/redis/module/strategy.html#:~:text=可以使用 config get maxmemory,Redis 的内存淘汰策略,命令如下:)。合理的选择:对于纯缓存服务,一般用 allkeys-lruallkeys-lfu,这样不管有没有过期时间,都会根据使用情况淘汰冷数据[xiaolincoding.com](https://www.xiaolincoding.com/redis/module/strategy.html#:~:text=* allkeys,lfu(Redis 4.0 后新增的内存淘汰策略):淘汰整个键值中最少使用的键值。)。如果 Redis 里同时有缓存和不想淘汰的持久数据,可以考虑使用 volatile-lru`,给缓存数据设置 expire,而重要数据不设 expire,这样只淘汰缓存部分。xiaolincoding.com

注意: LRU/LFU 算法在Redis中是近似实现而非精确LRU。它随机采样一批键然后选择最冷的淘汰(默认采样5个,可调节maxmemory-samples配置)xiaolincoding.com。采样少效率高但准确性稍差,采样多更接近真实LRU但耗时。可根据需要调大samples提高命中率。

监控: 可通过 INFO 的 evicted_keys 计数来观察淘汰情况,如发现持续增长说明内存紧张频繁淘汰,需要扩大内存或检查是否有内存泄漏(比如没有设置TTL的数据不断增长)。expired_keys 则统计被动过期的键数量。两者区别:expired是过期删除,evicted是主动淘汰。[pdai.tech](https://pdai.tech/md/db/nosql-redis/db-redis-y-monitor.html#:~:text=,redis使用内存突然增长,快达到maxmemory%3B 可能其个大键写入,键个数增长,某类键平均长度突增,fork COW%2C 客户端输入%2F输出缓冲区%2Clua程序占用等等)

小结: 合理的淘汰策略能让 Redis 在内存限制下稳定运行。大多数缓存场景选 LRU/LFU 无脑淘汰旧数据即可。但要注意使用 noeviction 时,客户端要处理写入失败,否则可能发生不可预期错误。还有, 一旦发生淘汰,意味着缓存命中率在下降,可以考虑增加 Redis 实例内存或引入多级缓存。

合理配置 maxmemory-policy

建议配置:

  • 纯缓存场景:如 Redis 专门用来做浏览数据缓存,没有永不过期的数据,采用 allkeys-lruallkeys-lfu 是最佳选择[xiaolincoding.com](https://www.xiaolincoding.com/redis/module/strategy.html#:~:text=* allkeys,lfu(Redis 4.0 后新增的内存淘汰策略):淘汰整个键值中最少使用的键值。)。LFU 在访问模式有明显冷热区分时性能更好(如局部热点)。LRU 实现简单可靠。Redis 4+ 默认设置 changed,从volatile-lru改为了noeviction,需要手动改。
  • 混合场景:如果Redis里有部分数据绝不能丢(例如队列、会话等),而其他是缓存,可以考虑 volatile-lru[xiaolincoding.com](https://www.xiaolincoding.com/redis/module/strategy.html#:~:text=* volatile,lfu(Redis 4.0 后新增的内存淘汰策略):淘汰所有设置了过期时间的键值中,最少使用的键值;)。这样必须给缓存数据设置 TTL,而重要数据无 TTL,就不会被淘汰。此时如果非缓存数据过多占用了大部分内存,也可能出现内存满却无淘汰对象可选的情况,那Redis就退化为 noeviction 会报错写不进,需要做好监控。
  • 禁止swap:确保物理内存足够或maxmemory低于物理内存。Redis性能依赖内存,若系统开始swap,Redis延迟会骤增。一般在Linux上应关闭或锁住内存,或者使用 memreserve
  • 适当裕量:maxmemory尽量不要设到和物理一样,给Redis留些headroom,因为fork子进程RDB/AOF rewrite时需要额外内存(COW)开销。如果内存占满,fork可能失败或造成性能抖动。
  • 持久化注意:如果启用AOF,AOF rewrite也是基于当前内存数据,若内存不断淘汰导致数据集变化,要考虑持久化一致性。一般没大问题,因为AOF写当前真实数据集,不包括被淘汰的历史数据。

通过这些策略调整,可以使Redis在高并发、有限内存情况下依然保持高命中率和稳定响应。

9. 监控与运维

对 Redis 进行有效的监控和日常运维,可预防问题、及时发现瓶颈。以下介绍 Redis 自带的信息指标、日志分析,以及开源监控方案。

INFO 命令与关键指标

Redis 提供 INFO 命令,输出服务器各项状态数据。常用部分:

  • Server: Redis 版本、运行模式(主从)、启动时间、配置参数等。
  • Clients: 连接客户端数量 (connected_clients)、异常阻塞客户端 (blocked_clients) 等。blocked_clients >0 时表示有命令如BLPOP正在等待。
  • Memory: 内存使用情况,包括已用内存 (used_memory)、高峰内存 (used_memory_peak)、内存碎片率 (mem_fragmentation_ratio) 等。碎片率 = 操作系统看到的内存 / Redis 内存,正常约1,明显高(>1.5)表示碎片严重可以考虑active-defrag。
  • Persistence: 最近一次 RDB 和 AOF 状态(如 rdb_last_bgsave_statusaof_last_write_status),上次快照时间,AOF 当前文件大小等。
  • Stats: 统计信息,如累计执行命令数 total_commands_processed,每秒操作数 instantaneous_ops_per_sec,网络输入输出速率等。还有rejected_connections(拒绝连接数,可能是超过maxclients)、expired_keys 过期删除数、evicted_keys 淘汰数[pdai.tech](https://pdai.tech/md/db/nosql-redis/db-redis-y-monitor.html#:~:text=match at L1279 ,redis慢查询日志最长耗时值 (slowlog_max_time):获取慢查询耗时最长值,因有的达10秒以下的慢查询,可能导致复制中断,甚至出来主从切换等故障。)等。这些对了解缓存效率很有帮助,evicted持续增加说明内存不足发生淘汰。
  • Replication: 主从相关状态。如果是主节点,会列出 connected_slaves 以及每个从库IP端口、同步偏移量等;如果是从节点,会显示当前复制哪个主、状态 (master_link_status)、延迟等。可以从 master_sync_in_progress 看是否正在全量同步。复制的 lag 也可监控,从属落后超一定量说明同步不及时。
  • CPU: Redis自身 CPU 使用,used_cpu_sys/used_cpu_user 分别表示系统态和用户态累计CPU秒数,可用于计算CPU占用率。另外 used_cpu_sys_children 是后台子进程用的 CPU,主要是 RDB/AOF rewrite 用时。可以监测这些以评估持久化开销。
  • Keyspace: 列出每个数据库的键数量和过期键数,如 db0: keys=10000,expires=500,avg_ttl=3600000。可以看到各库数据规模和过期情况。

通过定期 (如每秒) INFO,可以获取 Redis 各种实时指标。这些数据可上传到监控系统,如 Prometheus Redis exporter 就是调 INFO和INFO ALL等。

关键监控项:

  • CPU: Redis 单线程,应监控主线程 CPU 占用率。如果长时间100%,说明已达瓶颈,需要优化命令或考虑分片。
  • 内存: used_memory 接近 maxmemory 时,会频繁淘汰或写入错误(noeviction模式)。mem_fragmentation_ratio持久高于1.5表示碎片多,可以触发 MEMORY PURGE 或启用activedefrag。
  • 连接数: connected_clients 增长是否正常,有无持续增大导致逼近 maxclients 设置的风险。客户端暴增也可能是有程序泄漏连接或SLOWLOG过多阻塞导致堆积。
  • 慢查询: 结合 slowlog 监控慢操作出现频率。可定期获取 slowlog_len,如果在短时间暴涨需警惕。
  • 主从延迟: 对于有从库的集群,监控 master_last_io_seconds_ago 或通过主从偏移量计算延迟差。延迟过大可能导致读到旧数据或在failover时丢数据。
  • 过期/淘汰: expired_keys 和 evicted_keys 速率。过高的 expired_keys 可能表示有大量短TTL键,可以评估缓存策略。evicted_keys 有增长则需关注是否热点数据被淘汰,命中率降低,可考虑扩容。

Redis 日志分析

Redis 日志 (redis.log) 主要记录服务器启动信息、每次 RDB/AOF 操作和错误等。日志级别默认为 notice,包含比较重要的信息。比如:

  • Background saving started by pid <x> / Background saving terminated with success – BGSAVE 启动和完成[cnblogs.com](https://www.cnblogs.com/javaguide/p/redis-persistence.html#:~:text=RDB 创建快照时会阻塞主线程吗?)。
  • DB saved on disk – RDB 快照成功写磁盘。
  • Started append only file rewriting / Background AOF rewrite finished – AOF 重写流程。
  • 如果复制中断,会有 Master/Slave link lost,重连则 Synchronization with slave succeeded
  • Sentinel 模式下,哨兵日志记录主节点客观下线和切换信息。
  • Error 情况:如出现 MISCONF(比如只读模式写入) 或 OOM command not allowed (超出内存写命令被拒绝),Redis 会在日志写原因,可据此调整配置。

运维中要查看日志:常见运维场景如:Redis 意外退出,可在日志末尾找原因(内存分配失败?主动shutdown?)。RDB/AOF 失败也会记录 error code。生产环境建议设置日志轮转防止过大,可通过 logrotate 定期切分。也可将日志输出重定向到集中日志系统。

常用运维工具

redis-cli 内置工具:

  • redis-cli monitor – 实时打印 Redis 收到的每条命令,调试用途。在线上慎用,因为monitor本身对性能有影响(每个命令还要copy发给monitor客户端)。
  • redis-cli --stat – 每秒显示一行 info 摘要,包括 qps、内存、客户端数等,方便看变化趋势。
  • redis-cli --bigkeys – 扫描数据库找出各数据类型最大的 key,帮助发现可能的内存大户[learn.lianglianglee.com](https://learn.lianglianglee.com/专栏/Redis 核心技术与实战/26 缓存异常(下):如何解决缓存雪崩、击穿、穿透难题?.md#:~:text=26 缓存异常(下):如何解决缓存雪崩、击穿、穿透难题? ,缓存中进行处理,紧接着,应用将大量请求发送到数据库层,导致数据库层的压力激增。 缓存雪崩一般是由两个原因导致的,应对方案也有所)。在大数据量时用时较长。
  • redis-cli latency doctor – 输出潜在延迟原因分析[pdai.tech](https://pdai.tech/md/db/nosql-redis/db-redis-y-monitor.html#:~:text=* 度量延迟Baseline )。如会指出慢日志命令存在,或者 Linux 透明大页未关等建议[pdai.tech](https://pdai.tech/md/db/nosql-redis/db-redis-y-monitor.html#:~:text=,the huge pages already created)。

可视化面板:

  • redis-stat:第三方Ruby写的小工具,可以采集 INFO信息并在终端或Web图表显示[pdai.tech](https://pdai.tech/md/db/nosql-redis/db-redis-y-monitor.html#:~:text=> redis)pdai.tech。适合快速看Redis基本性能指标,不用于长期监控。
  • RedisLive:Python写的web界面,可查询某时段的 QPS、内存等曲线pdai.tech。也能分析热key,它存储监控数据在Redis本身。项目比较旧了。
  • Redmon:Redis monitoring GUI,支持查看实时性能、CLI命令行、配置修改等pdai.tech。Docker 上可跑一个容器即用pdai.tech

以上工具都是轻量级的,仅用于临时观察或小规模集群。

Prometheus + Grafana: 这是现代监控流行方案。通过部署 redis_exporter 将 Redis 的 INFO、延迟、内存等数据作为指标提供给 Prometheus 收集pdai.tech。Grafana 则可套用 Redis 监控模板插件,展示丰富的图表。常见监控项如CPU利用、内存使用率、命中率(=keyspace_hits/(hits+misses))、连接数、每秒命令数、慢查询次数等在仪表盘上一目了然[pdai.tech](https://pdai.tech/md/db/nosql-redis/db-redis-y-monitor.html#:~:text=,x,配合Prometheus以及grafana的Prometheus Redis插件,可以在grafana进行可视化及监控)。也可以设置报警规则,如内存使用接近maxmemory、主从断连、复制延迟等,通过邮件/短信通知运维。

pdai.tech中讨论了如何构建Redis监控体系,要关注哪些维度和指标。提及应该从服务可用性、性能、容量等多个方面监控。Redis export出来的指标非常多,比如 redis_cpu_user_seconds_totalredis_memory_used_bytesredis_net_input_bytes_total等等,结合Grafana可生成详细图表。还有第三方Grafana仪表板如 Redis Dashboard 912样板,可以导入直接用。

日常运维:

  • 备份恢复: 定期冷备 RDB文件,AOF也可增量备份。恢复时,停止Redis换上备份RDB重启即可。如果AOF损坏,可用 redis-check-aof --fix 修复。
  • 故障转移: 对哨兵监控的Redis升级或维护时,可以利用命令 SENTINEL failover <master> 手动触发切主,把流量转移走然后维护原master。
  • 容量规划: 通过info的内存指标和key数增长估计多久内存会满,从而提前扩容或清理。
  • 升级: Redis升级小版本一般平滑,较新版本先测试兼容性。可以采取双机替换:新版本Redis启动为从库同步数据,然后升级为主(切换写流量),实现无停机升级。

良好的监控和运维手段可以让我们对 Redis 的负载情况、潜在风险心中有数,避免突然崩溃或性能劣化而不自知。在生产环境中,应尽量做到监控报警完善,比如 QPS异常升高、缓存命中率骤降、慢查询骤增都应当有报警以便及时介入处理。

10. 常见问题与坑

在使用 Redis 过程中,有一些容易被忽略或误用的地方,可能导致性能问题或数据问题。这里总结几项常见“坑”和注意事项:

Pipeline 误用: Redis Pipeline 允许将一批命令打包后一次性发送,提高吞吐。但误用可能适得其反:如果 pipeline 一次性积累了过多命令,会导致:1)客户端等待所有命令结果的时间变长(期间服务端返回的数据堆积在内存)[cloud.tencent.com](https://cloud.tencent.com/developer/article/1875542#:~:text=详解redis 中Pipeline流水线机制,);2)网络拥塞和大数据包传输时延增加[cloud.tencent.com](https://cloud.tencent.com/developer/article/1875542#:~:text=详解redis 中Pipeline流水线机制,)。正确的做法是控制 pipeline 批次大小。例如有10万次写入,不要一次性 pipeline 全部,可以分比如每1000条一批的 pipeline 多次发送[cloud.tencent.com](https://cloud.tencent.com/developer/article/1875542#:~:text=详解redis 中Pipeline流水线机制,)。此外,Pipeline 并不保证原子性,某条命令失败不会回滚前面的命令,所以不能把有依赖的操作乱用 pipeline 并假定事务效果[cnblogs.com](https://www.cnblogs.com/lxwphp/p/15452474.html#:~:text=详解redis 中Pipeline流水线机制 · 1、原生批命令是原子性,pipeline是非原子性 ·,2、原生批命令一命令多个key%2C 但pipeline支持多命令(存在事务),非原子性 · 3)。另一个坑是 pipeline 下内存飙升:如果每条返回数据很大,一次管道太多命令会在客户端堆积大量回复占内存。所以要根据应用和网络情况选择合适批量大小。经验来说100~1000条一批常是比较平衡的点。如果使用 spring-redis 等,要确保没有把 pipeline 当事务用或者忘记consume结果导致内存泄漏。

事务问题: Redis 事务由 MULTI ... EXEC 实现,和关系型事务不同:Redis 事务不具备隔离级别回滚。一旦在 EXEC 中某条命令报错,其他命令仍继续执行,不会自动回滚之前已执行的命令blog.csdn.net[blog.csdn.net](https://blog.csdn.net/weixin_43888891/article/details/130948322#:~:text=multi.set(,%2F%2F 放弃事务 multi.discard)。开发者容易误解,以为事务里的操作要么都成功要么都失败,其实Redis不会回滚,失败命令报错跳过,其他命令仍生效。所以要在应用层自己确保原子性(可以用 Lua 脚本替代,使一系列操作在脚本内一次完成)。另一个常见误区是将读取命令放在 MULTI/EXEC 里期望读取“事务内最新值”,实际上除非用 WATCH 监视,否则事务内读取只是队列形式,真正执行要等 EXEC。所以如果需要根据某值判断再写,不能简单在事务里 GET 再判断,要在应用预先取好或者使用 Lua 实现复杂逻辑。还有就是WATCH机制:很多人以为 WATCH 实现乐观锁很好,但其实如果并发冲突高,会不断重试EXEC提交,可能性能很差。所以WATCH只适合冲突很少的场景,否则考虑 RedLock 或业务锁来控制。

Lua 脚本陷阱: Redis Lua 脚本(EVAL)提供原子执行保障,但要注意脚本执行时间。Redis 在执行 Lua 期间会阻塞其他命令,所以脚本应当短小精悍。不要在脚本里进行超大量循环或慢操作,否则会阻塞服务器。虽然可以使用 redis.call('BLPOP') 等在脚本中阻塞等待,但这不常见且容易导致脚本超时。另一个坑是脚本缓存:使用 EVALSHA 时,如果Redis重启过,缓存的脚本sha可能失效,需要重新EVAL。一般Redis会返回NOSCRIPT错误,这时客户端应fallback EVAL普通执行。还有是不要在Lua里调用一些可能产生随机或时间依赖的命令(如 TIMERANDOMKEY),因为 Redis 可能在AOF重放时无法重现这些结果(Redis 后来通过在AOF中记录随机种子解决了一些问题,但尽量避免)。

大Key与内存膨胀: Redis 单键可以容纳非常大的数据结构(如一个List有百万元素)。滥用会导致内存膨胀和操作阻塞。比如一个 Hash 包含上百万字段,占用内存大且 HGETALL 会阻塞很久甚至网络卡顿。应定期检查是否有超大Key(前述 --bigkeys 工具)cnblogs.com。将过大的键拆分或清理。例如如果有个集合存所有用户ID,规模上千万,可以考虑分片或引入外部存储。内存膨胀还可能由于惰性删除造成:当一个超大key过期或被DEL,Redis采用惰性策略逐步删除以免阻塞,这期间内存不会马上下降,如果删除很慢可能导致短时内存膨胀甚至超出物理内存。所以对于大数据删除,可用 UNLINK(异步删除)或SSCAN+SREM分批删除。

过期和持久化丢数据: Redis 采用惰性+定期扫描删除过期键。惰性删除意味着如果Redis没有来得及扫描到,它不会主动删除直到有人访问那个key才发现过期清除。这可能导致过期数据占用内存时间比TTL稍长。另外,如果依赖 RDB 快照或AOF,过期数据可能写入磁盘:RDB 时刻快照时包含了一些其实马上过期的key。AOF 则如果某key过期前写入了AOF文件,但直到很久后才被清除,那么AOF重放期间又会把这个key带回来一瞬间再过期。一般这不影响最终一致性,但对内存瞬间占用有影响。要避免丢数据:如果只开RDB,崩溃会丢失最近的写;AOF everysec 丢1秒,这些在设计上就需接受。如果想绝对不丢,必须 AOF always,但性能损耗大,不常用。可以通过wait命令确保写已同步指定数从机,来换取更可靠持久性。

连接管理和TIMEOUT: 默认Redis对闲置连接不强制断开,但可以配置 timeout 参数让闲置多少秒后自动断开,防止某些客户端死连占资源。不过timeout可能造成一些客户端长时间订阅/阻塞时误断,所以通常置0禁用。需要关注连接过多的问题:maxclients默认10000,如果业务没考虑可能被用光(比如发生短时间大量新连接进来,或者有程序未正确关闭连接造成泄漏)。运维应监控 connected_clients,超过某阈值发警告。

客户端 GC 压力: 对于 Java/C#等有GC的客户端,高并发场景下操作Redis返回大量数据,可能引起频繁GC而影响性能(表现在应用侧Redis响应超时)。[cloud.tencent.com](https://cloud.tencent.com/developer/article/1758600#:~:text=QPS过万,redis大量连接超时怎么解决? ,)例如一次性从Redis取出非常大的结果到Java内存,如果年轻代装不下会产生Full GC暂停。这并非Redis本身问题,但常被误以为Redis卡顿。解决方法是在客户端分批取数据、或使用流式处理避免一次载入太多。Netty等框架的连接池/ByteBuf管理也要注意释放,否则产生大量垃圾。对这些语言,可调高堆、调优GC算法,或在压力极大时考虑改用Rust/C++客户端来避免GC瓶颈。

多数据库 (SELECT) 限制: Redis 虽支持多 database (默认16个),但实际上多库只是逻辑分区,没有隔离作用。使用多个db可能带来困扰:如复制时全部库数据都会同步,没有独立权限控制。此外一些客户端不经意使用 select 切换db而忘记切回来,可能读写错库。生产中往往不使用多数据库,默认就用db0,把逻辑库在key前加前缀来区分。

键名管理: Redis 键支持任意二进制,但最好使用统一的命名风格。如采用 object_type:id:field 这样的格式,方便管理和查找。切勿使用包含空格、换行等特殊字符的键名,调试和脚本处理会很麻烦。过长的键名浪费内存(每个key对象会有附加约40字节元数据,键名长也算),但太短又可能冲突或可读性差。一般几十字节以内键名适中。禁止使用 KEYS 模糊删除生产数据,这里重申,因为时常有人用 keys pattern | xargs del 来清理,导致阻塞。应使用SCAN替代。

过量订阅与Push: Redis Pub/Sub 模式下,消息推送采用遍历订阅者机制,如果有成千上万订阅者,会造成推送阻塞。因此Pub/Sub适用于小规模实时消息,不能当作企业级消息系统那样用在非常多消费者场景。另外Redis6 引入了 client side caching 特性,可以让订阅key的变化推送失效消息给客户端,也要注意如果订阅key多推送也多,慎用。

每个“坑”对应的解决方案在前文某些部分已有涉及。关键在于充分测试和理解Redis行为,在上线前就规避或验证。良好的监控也能让我们及时发现这些异常迹象,比如突发大量keys、内存异常增长、频繁full GC等等。遵循 Redis 官方建议和社区最佳实践,可以避免大部分陷阱。

11. 实战与最佳实践

将上述知识应用于实际业务架构,可以极大提升系统的并发能力和可靠性。下面结合两个典型场景,分析 Redis 的实战用法和最佳实践。

秒杀系统架构设计

场景描述: 电商秒杀/抢购系统通常面临短时高并发的挑战:数十万甚至上百万用户同时抢购少量商品,要求系统快速响应并正确扣减库存、生成订单。Redis 在秒杀架构中发挥重要作用[cnblogs.com](https://www.cnblogs.com/binghe001/p/13656462.html#:~:text=在电商领域,存在着典型的秒杀业务场景,那何谓秒杀场景呢。简单的来说就是一件商品的购买人数远远大于这件商品的库存,而且这件商品在很短的时间内就会被抢购一空。比如每 年的618、双11大促,小米新品促销等业务场景,就是典型的秒杀业务场景。)[cnblogs.com](https://www.cnblogs.com/binghe001/p/13656462.html#:~:text=秒杀业务最大的特点就是瞬时并发流量高,在电商系统中,库存数量往往会远远小于并发流量,比如:天猫的秒杀活动,可能库存只有几百、几千件,而瞬间涌入的抢购并发流量可能 会达到几十到几百万。)。

架构要点: "削峰填谷"+"快速决策" 是秒杀架构的核心。Redis 可以承担流量缓冲快速存储关键数据的职责:

  1. 请求入口限流: 大促开始时,在用户请求进入应用层前,利用 Redis 计数器进行限流(如漏桶算法,前面限流部分已述)。例如总量10000件库存,可以在入口用 Redis 的原子计数限制只允许1万请求进入下阶段,其余直接返回秒杀结束time.geekbang.org。这样防止后端订单服务超载。
  2. 队列削峰: 将通过限流的请求放入Redis 列表排队处理[help.aliyun.com](https://help.aliyun.com/zh/redis/use-cases/use-apsaradb-for-redis-to-build-a-business-system-that-can-handle-flash-sales#:~:text=搭建高并发的电商秒杀系统 ,消息队列组件依然可以使用Redis实现,在R2中用list数据结构表示。)。前端快速返回用户一个排队反馈,真正扣库存和下订单操作异步进行[help.aliyun.com](https://help.aliyun.com/zh/redis/use-cases/use-apsaradb-for-redis-to-build-a-business-system-that-can-handle-flash-sales#:~:text=搭建高并发的电商秒杀系统 ,消息队列组件依然可以使用Redis实现,在R2中用list数据结构表示。)。Redis 列表高性能入队出队,确保顺序。后台多个工作线程 BRPOP 从队列取请求,逐个处理。遇到库存不足就停止进一步处理。
  3. 库存预热与扣减: 在秒杀开始前,把商品 库存数预存到 Redis,如使用 INCRBY 或放在 hash 中cnblogs.comcnblogs.com。例如 hset seckill:goodsStock:1001 totalCount 10000cnblogs.com。当处理每个请求时,直接用 Redis 原子递减库存:DECR seckill:1001:stockHINCRBY stock -1[gongfukangee.github.io](https://gongfukangee.github.io/2019/06/09/SecondsKill/#:~:text=如何设计一个秒杀系统 ,RedisPoolUtil 中对Jedis 常用API 进行简单封装,每个方法调用完毕则关闭)。Redis 原子操作保证并发下库存不会出错超卖。扣减成功则继续生成订单,扣减结果若返回负值则表示库存不足,要撤销操作(或将值加回去)。Lua脚本可以进一步保证判断和减库存原子进行,避免Race(如先检查库存>0再减,在极高并发下可能不够原子,用Lua把判断和减写一起)。
  4. 订单数据缓存: 订单生成后,可将订单简要信息也存入 Redis,前端查询状态时先查Redis,减轻数据库压力。完整订单异步写数据库。
  5. 多级缓存和分区: 对于热点商品,甚至可以采用 Redis Cluster 把库存等热点数据分布。或者采用本地缓存+Redis 结合,如本地缓存1秒内请求,Redis 减一次全局库存,然后广播结果,这属于极致优化,在超大型活动可以考虑。
  6. 防重复和安全: 利用 Redis 集合或位图记录用户是否已抢购过,防止一人多单。用户请求进入时可以先 SADD participants user_id,如返回0说明已抢过就拒绝。

Redis 优势: 在秒杀场景,Redis 提供纳秒级别的原子操作能力,确保在超高并发下仍能正确处理共享数据(库存、名额)。其内存性能支撑几十万QPS扣减操作毫无问题。而通过队列削峰,数据库的订单写入被平滑到较长时间,避免瞬间写入过载[help.aliyun.com](https://help.aliyun.com/zh/redis/use-cases/use-apsaradb-for-redis-to-build-a-business-system-that-can-handle-flash-sales#:~:text=搭建高并发的电商秒杀系统 ,消息队列组件依然可以使用Redis实现,在R2中用list数据结构表示。)。

实战案例: 例如某电商秒杀1000件商品,有100万用户参与。架构做法:活动开始前,Redis 设置 stock:pid1001 = 1000,并设置一个原子布隆过滤器包含用户资格,防止无资格的人刷。开始时,应用快速执行:

if BF.exists(user) == false:
    return "无参与资格"
if DECR(stock:pid1001) >= 0:
    LPUSH orderQueue, {user, product}
    return "抢购成功,排队下单"
else:
    return "已售罄"

Redis 将100万并发中的绝大多数在内存中快速处理完扣减和反馈。库存扣完后,后续DECR结果为负,应用即可通知售罄。排队订单由后台消费者消费,调用支付服务或写DB。整个过程前端体验良好,数据库也仅承受了1000条订单插入。某大型电商实践表明,通过类似Redis方案,可以支撑每秒几十万并发的秒杀请求而系统稳定time.geekbang.org。Redis 在其中不可或缺。

注意点: 秒杀系统还需考虑防黄牛(可以在Redis中记录IP频次,结合验证码)、超卖双重校验(即使Redis控制,也以数据库最终确认为准,多层把关)。但总体来说,Redis 极大增强了系统处理峰值的能力,让秒杀这种过去棘手的问题变得可控。

缓存穿透防护实践

场景描述: 某网站遭遇恶意攻击,请求了大量根本不存在的商品ID,每次查询都避开缓存直击数据库,数据库压力骤增导致服务不可用。需要设计方案防止这种缓存穿透攻击。

方案设计: 结合我们在性能优化章节讨论的技术,这里给出一个综合防护方案:使用 布隆过滤器 + 缓存空值 双保险。

  1. 构建布隆过滤器: 在 Redis 中维护一个布隆过滤器(可使用 Redis Module 如 bf.reserve 命令,或者用位图+自定义hash实现)。启动时将数据库中所有有效商品ID插入布隆过滤器。假设有 BF key bf:validGoods。它占用内存很少,但能以很高概率判断ID是否存在。对于每次商品查询请求,在检查缓存前,先执行一个命令:BF.EXISTS bf:validGoods <goods_id>blog.csdn.net。如果返回 0(不存在),直接返回给用户“商品不存在”而不走后续数据库查询blog.csdn.net。这样所有不存在ID都被挡在外面。布隆过滤器有万分之几误判率,但那些极少数误判当作存在处理,走正常流程顶多查一次库,不影响整体。
  2. 缓存空对象: 对于那些通过BF判断存在但实际数据库查不到的情况(布隆误判或数据库数据被删除了而BF还没更新),采用缓存空值策略blog.csdn.net。也就是如果查DB无此商品,则写 Redis:SET product:<id> "" EX 60(空字符串,过期60秒)blog.csdn.net。下次再查这个 ID,会命中空缓存,程序识别空值表示不存在,直接返回。这样避免重复打DBblog.csdn.net。TTL不宜太长以防万一商品后来上架。布隆过滤器也应定期更新(比如每天重建或在删除商品时同步将其对应bit置0 - 但标准BF不能删除,可能需要Counting BF,这里简单周期重建足够)。
  3. 应用层限流: 除技术手段外,我们还可以在应用层增加针对某些模式的限流。如同一IP在1分钟内请求大量不存在ID,就触发IP封禁。这需要记录请求日志,可用Redis的计数。比如 INCR ip:1.2.3.4:badreq。如果某IP的“未命中缓存请求数”过多,则加入黑名单集合,后续请求一律拦截。

效果: 通过布隆过滤器,大部分完全无效ID连 Redis 缓存都不走,直接被拒绝blog.csdn.net。极个别漏网之鱼查了DB,也马上缓存空值,下次同样ID不会再查库blog.csdn.net。配合应用层IP黑名单,可减少恶意请求。这个方案对正常请求完全透明,用户查不存在的商品也能快速返回。

实现细节: Redis 目前官方BloomFilter在 RedisBloom 模块中,需要安装才能用 BF.EXISTS 等。如不能用模块,也可以用位图+N个hash函数自己实现,但略复杂。考虑性能,一个BF查询耗时微毫级,比查询数据库快很多,不是瓶颈。空值缓存TTL可以调短一点比如60秒甚至10秒,这样万一有人请求真的刚上架的商品而BF误判不存在,在空值缓存过期后能再查DB得到更新(当然更健壮办法是插入商品时也更新BF)。内存占用方面,一个布隆过滤器按设计1亿元素误判率万分之一大概需要2-3百MB,视项目规模权衡。商品ID量没这么大时BF非常小几MB。

实践效果: 经此方案处理后,攻击者即使请求100万个不存在ID,绝大多数被 BF 拦下返回,Redis和DB几乎不受影响。即使万一有千分之一误判,也只是打到后端1000次查询,这相对于100万次攻击已经微不足道了。某实际项目应用该方案后,数据库CPU从接近100%降至几乎0,对于不存在记录的请求,Redis BF操作应对自如,QPS上万也没问题。

总结: 缓存穿透看似简单问题,但在大规模下会造成严重后果。Redis 提供的布隆过滤器模块,以及巧用自身缓存机制,可以构筑有效的防线。最佳实践是在项目启动阶段就考虑这些边界情况,将布隆过滤器构建和空值缓存逻辑集成到数据访问层,这样系统天然具有防穿透能力,而不需要出事后紧急补救。


以上案例只是冰山一角。Redis 的高性能和丰富功能,可以在各种场景下灵活运用。最佳实践是充分理解 Redis 特性,根据业务特点做设计:

  • 将热点和关键数据放在 Redis 中,充分利用内存性能,但也要设计好降级方案;
  • 使用 Redis 原子操作和Lua脚本确保并发正确性,替代分布式锁/事务场景,减小DB压力;
  • 结合缓存+消息队列+持久化模块,构建稳健的分布式系统架构。

在具体实现时,要不断监控、调优,如观察命中率调整缓存策略,观察慢查询优化数据结构等。通过理论结合实践,Redis 将不再只是一个缓存,而会成为支撑高并发、高可用系统的中坚力量。参考以上手册内容,读者应对从入门搭建到架构实战都有了全面认识,可在自己的项目中灵活应用 Redis 的最佳实践。

Leave a Comment

您的电子邮箱地址不会被公开。 必填项已用*标注

close
arrow_upward