数据库-Redis:常用语法 / Redis 核心知识技能梳理
Redis 是目前最流行的键值存储数据库,因其高性能、丰富的数据结构和原子操作,成为后端开发中不可或缺的组件。下面从基础到高级,系统性地梳理 Redis 的核心知识与技能。
一、Redis 是什么?
| 维度 | 说明 | 补充/细节 |
|---|---|---|
| 全称 | Remote Dictionary Server(远程字典服务器) | C 语言编写,开源、内存中的数据结构存储系统 |
| 定位 | 内存中的数据结构存储系统,可用作数据库、缓存和消息中间件 | 核心定位:高性能的缓存和数据结构服务器 |
| 核心特点 | 基于内存(极快)、单线程(6.0之前)+ I/O多路复用、支持持久化、丰富数据结构 | 原子性:所有单命令操作都是原子性的;丰富数据结构是其区别于 Memcached 的核心优势 |
| 性能 | 读写速度可达 10万+ QPS | 性能瓶颈通常在网络 I/O,而非 CPU(单线程模型下) |
| 适用场景 | 缓存、会话管理、排行榜、消息队列、分布式锁、实时分析 | 不适用场景:需要复杂关系查询、海量数据存储(成本高)、强一致性事务(非 ACID) |
二、Redis 支持的数据结构(5+4种,含模块扩展)
| 类型 | 底层实现 | 常用命令 | 使用场景 | 复杂度(平均) |
|---|---|---|---|---|
| String | SDS(简单动态字符串) | SET/GET/INCR/DECR/SETNX/MSET | 缓存对象、计数器、分布式锁、Session | O(1) |
| Hash | 压缩列表(ziplist) / 哈希表(hashtable) | HSET/HGET/HGETALL/HINCRBY | 存储对象(如用户信息)、购物车 | O(1) |
| List | 压缩列表(ziplist) / 双向链表(linkedlist) | LPUSH/RPUSH/LPOP/RPOP/LRANGE | 消息队列、最新消息列表、文章评论 | O(1) (头尾), O(N) (中间) |
| Set | 整数集合(intset) / 哈希表(hashtable) | SADD/SREM/SMEMBERS/SINTER/SUNION | 标签系统、共同好友、抽奖、去重 | O(1) (增删查), O(N*M) (交集/并集) |
| Sorted Set | 压缩列表(ziplist) / 跳表(skiplist) | ZADD/ZRANGE/ZRANK/ZREVRANGE/ZSCORE | 排行榜、延迟队列、优先级队列、带权重的消息 | O(log N) (增删查) |
| Bitmaps | String 的位操作 | SETBIT/GETBIT/BITCOUNT/BITOP | 签到统计、在线状态、用户行为分析、布隆过滤器 | O(1) |
| HyperLogLog | 概率数据结构 | PFADD/PFCOUNT/PFMERGE | 独立访客数(UV)统计、基数估算 | O(1) |
| GEO | Sorted Set 实现 | GEOADD/GEORADIUS/GEODIST/GEOHASH | 附近的人、位置距离计算、地理围栏 | O(log N) |
| Stream | Radix Tree + Listpack | XADD/XREAD/XGROUP/XACK | 专业消息队列、事件溯源、日志收集 | O(1) (追加) |
三、Redis 底层核心机制
1. 内存管理与数据结构
| 机制 | 说明 | 关键细节 |
|---|---|---|
| SDS (简单动态字符串) | Redis 自定义的字符串实现 | 二进制安全:可存储图片等二进制数据;O(1) 获取长度:len字段直接记录;杜绝缓冲区溢出:free字段预分配空间,API 会检查;减少内存重分配:空间预分配和惰性释放策略。 |
| 跳表 (Skip List) | ZSet 底层实现之一 | 多层链表结构:通过“快车道”实现快速查找;实现简单:相比红黑树,实现和维护更简单;范围查询高效:ZRANGE操作性能优异;空间换时间:需要额外指针存储。 |
| 压缩列表 (ziplist) | 小数据量时使用的连续内存块 | 节省内存:无指针开销,数据紧凑;读写性能:新增/删除元素可能引发连锁更新(cascade update),导致性能下降;配置阈值:hash-max-ziplist-entries等配置决定何时转为哈希表。 |
| 整数集合 (intset) | Set 在元素为整数时使用 | 有序、无重复:元素按升序排列;升级机制:当插入新类型整数(如从 int16 到 int64)时,会升级整个集合并重新分配内存,此操作不可逆。 |
| 对象系统 (RedisObject) | 所有数据类型的统一封装 | 每个对象包含type,encoding,lru(LRU算法),refcount(引用计数) 等元数据,是 Redis 实现多态和内存回收的基础。 |
2. 单线程模型 vs 多线程模型 (6.0+)
| 特性 | Redis < 6.0 (单线程) | Redis >= 6.0 (多线程 I/O) |
|---|---|---|
| 命令执行 | 单线程,避免了锁竞争和上下文切换开销 | 核心命令执行仍是单线程,保证原子性 |
| I/O 处理 | 单线程处理网络读写和命令执行 | 多线程处理网络 I/O(读取和写入响应),主线程只负责命令执行 |
| 性能瓶颈 | CPU 成为瓶颈时,无法利用多核优势 | 主要解决网络 I/O 瓶颈,对于大数据量、高并发的网络场景性能提升显著 |
| 线程安全 | 天然线程安全,无需考虑锁 | 命令执行层仍是单线程,核心逻辑保持简单 |
| 配置 | io-threads-do-reads no(默认) | io-threads 4(建议设置为CPU核心数) |
3. I/O 多路复用
| 技术 | 说明 | Redis 中的应用 |
|---|---|---|
| select | 早期的 I/O 多路复用模型,有连接数限制(1024) | Redis 已不再使用 |
| epoll | Linux 下的高效 I/O 模型,无连接数限制,基于事件回调 | Redis 在 Linux 上的默认实现,性能极高 |
| kqueue | macOS/FreeBSD 下的高效 I/O 模型,类似 epoll | Redis 在相应系统上的默认实现 |
| 工作流程 | 1.epoll_create创建实例2. epoll_ctl注册/修改 Socket 事件3. epoll_wait阻塞等待事件发生4. 事件返回后,由 Redis 单线程逐个处理 | 这是 Redis 单线程能处理大量并发连接的基石 |
四、Redis 持久化机制(深度对比)
RDB(快照) vs AOF(日志) vs 混合持久化
| 对比维度 | RDB (快照) | AOF (仅追加文件) | 混合持久化 (AOF rewrite) |
|---|---|---|---|
| 原理 | 在指定时间间隔内,将内存数据快照写入磁盘 | 记录所有写命令到 AOF 文件,重启时重放命令 | AOF 重写时:将当前内存数据以 RDB 格式写入 AOF 文件开头,之后的增量命令以 AOF 格式追加 |
| 恢复速度 | 快 | 慢(需重放所有命令) | 快(RDB 部分加载快,AOF 部分增量小) |
| 数据完整性 | 可能丢失最后一次快照后的数据 | 高(可配置appendfsync always) | 高(同 AOF,但恢复更快) |
| 文件大小 | 较小,紧凑 | 较大,随时间增长 | 较小(RDB 部分紧凑,AOF 部分增量) |
| 性能影响 | fork子进程时有短暂性能影响(内存拷贝) | 持续影响写入性能,取决于fsync策略 | 重写时有短暂性能影响,平时同 AOF |
| 适用场景 | 备份、灾难恢复、对数据完整性要求不高 | 生产环境首选,追求数据高可靠 | Redis 4.0+ 推荐,兼顾恢复速度和数据完整性 |
| 配置示例 | save 900 1stop-writes-on-bgsave-error yes | appendonly yesappendfsync everysecauto-aof-rewrite-percentage 100 | 开启 AOF 后,重写机制自动生效 |
最佳实践:生产环境强烈建议开启 AOF,并使用appendfsync everysec策略。同时可以开启 RDB 用于定时备份。Redis 4.0+ 默认开启混合持久化,是最佳选择。
五、Redis 高可用与集群方案(详解)
方案对比与选型
| 方案 | 工作原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 主从复制 | Master 写,Slave 读,单向数据同步 | 读写分离,提升读性能;数据备份 | Master 单点故障;手动故障转移麻烦 | 读多写少,对高可用要求不高的场景 |
| 哨兵模式 (Sentinel) | 监控主从节点,自动进行故障转移 | 高可用:自动故障转移;监控:节点健康检查;通知:故障通知 | 配置相对复杂;主从切换期间服务短暂不可用(秒级);不解决写能力瓶颈 | 对高可用有要求,但数据量和并发量不是特别巨大的场景 |
| Redis Cluster | 数据分片(16384个槽),多主多从,去中心化 | 高可用:节点故障自动转移;水平扩展:轻松增加节点提升容量和性能 | 客户端复杂:需支持 Cluster 协议,处理 MOVED/ASK 重定向;不支持多键操作(如MSET跨槽位);事务仅支持单节点 | 海量数据、高并发、需要水平扩展的场景 |
主从复制核心流程与问题
全量同步 (Full Sync):
Slave 发送REPLCONF listening-port和PSYNC ? -1。
Master 响应FULLRESYNC <runid> <offset>,并 fork 子进程生成 RDB 文件。
Master 将 RDB 文件发送给 Slave,Slave 清空旧数据并加载 RDB。
Master 将生成 RDB 期间的写命令存入复制缓冲区 (replication buffer),并发送给 Slave。
Slave 执行这些写命令,达到数据一致。
增量同步 (Partial Sync):
断线重连后,Slave 发送PSYNC <runid> <offset>。
Master 检查runid是否匹配,且offset是否在复制积压缓冲区 (repl_backlog_buffer) 内。
如果都满足,则只发送从offset开始的增量命令。
否则,退化为全量同步。
常见问题:
主从数据不一致:网络延迟、Slave 负载过高、maxmemory配置不当导致 key 淘汰策略不同。
主从延迟:Master 写压力大、Slave 单线程处理慢、网络带宽瓶颈。
Redis Cluster 核心机制
| 机制 | 说明 |
|---|---|
| 数据分片 (Sharding) | 16384 个槽位 (Slot),通过CRC16(key) % 16384计算 key 所属槽位。每个 Master 节点负责一部分槽位。 |
| 节点通信 | 节点间通过 Gossip 协议交换信息(如槽位映射、节点状态),实现去中心化。 |
| 请求路由 | 客户端请求到错误节点时,节点会返回MOVED(永久) 或ASK(临时) 重定向指令,客户端需更新槽位映射。 |
| 高可用 (Failover) | 集群中每个 Master 都有对应的 Slave。当 Master 失效时,其 Slave 会被选举为新的 Master(通过 Raft 类似的共识算法),并接管其负责的槽位。 |
| 不支持的操作 | 涉及多个 key 的操作(如MSET key1 val1 key2 val2),如果 key 不在同一个槽位,会报错。需使用hash tag(如{user}1001)强制 key 分配到同一槽位。 |
六、Redis 缓存三大问题与解决方案(方案对比)
| 问题 | 核心原因 | 解决方案 | 方案对比与选型 |
|---|---|---|---|
| 缓存穿透 | 查询一个不存在的数据,请求绕过缓存直击数据库。 | 1.缓存空对象:将null也缓存,设短过期时间。2.布隆过滤器:在缓存前加一层过滤器,拦截不存在的 key。 | 缓存空对象:简单,但会占用少量内存,且数据不一致时需主动删除。 布隆过滤器:高效,内存占用小,但有误判率(可调),且不支持删除。生产环境常组合使用。 |
| 缓存击穿 | 一个热点 Key在失效瞬间,大量并发请求直击数据库。 | 1.互斥锁 (Mutex):只允许一个线程去数据库加载数据,其他线程等待。 2.逻辑过期 (永不过期):Key 物理上不过期,通过后台线程异步更新。 | 互斥锁:实现简单,但高并发下线程等待会增加响应时间。 逻辑过期:响应快,无等待,但数据一致性较差(可能读到旧数据),实现复杂。推荐互斥锁方案。 |
| 缓存雪崩 | 1. 大量 Key同时失效。 2. Redis服务宕机。 | 1.过期时间加随机值:打散失效时间。 2.高可用架构:哨兵或集群,保证 Redis 不宕机。 3.服务降级/限流:数据库前加限流组件。 4.多级缓存:本地缓存 (Caffeine) + Redis。 | 过期时间加随机值是基础,必须做。 高可用架构是根本保障。 多级缓存和限流是最后的防线,用于应对极端情况。 |
七、分布式锁(深度剖析)
Redis 实现分布式锁的演进
| 版本 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|
| SETNX | SET key value NX | 简单,原子性 | 1. 无过期时间,易死锁。 2. 无法保证释放锁的是加锁的客户端(误删)。 |
| SET NX PX | SET key value NX PX 30000 | 1. 原子性。 2. 自动过期,防死锁。 | 仍有误删他人锁的风险(业务未执行完,锁已过期,被其他客户端获取并删除)。 |
| Lua 脚本 | if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end | 1. 原子性。 2. 防死锁。 3.安全释放:通过 value校验,只删除自己加的锁。 | 1. 业务执行时间超过锁过期时间,仍会导致问题。 2. 主从切换时,锁可能丢失。 |
| Redlock | 在 N 个独立 Redis 实例上依次加锁,成功超过半数即算成功 | 1. 解决主从切换锁丢失问题。 2. 容错性更高。 | 1. 实现复杂,性能开销大。 2. 依赖系统时钟,时钟漂移可能导致问题。 3. 官方文档也建议仅在对强一致性要求极高的场景使用。 |
| Redisson | 基于 Redis 的 Java 客户端库,封装了 Redlock 和可重入锁 | 1.可重入:同一线程可多次加锁。 2.看门狗机制:自动续期,解决业务超时问题。 3.公平/非公平锁、读写锁等多种实现。 4. 封装完善,开箱即用。 | 增加了客户端依赖,但极大地简化了开发。 |
生产环境推荐:
简单场景:使用SET NX PX + Lua 脚本的方案,并确保业务逻辑在锁过期时间内能完成。
复杂/高要求场景:直接使用成熟的客户端库,如Redisson,它内置了看门狗、可重入等高级特性,是事实上的工业标准。
八、Redis 事务与 Pipeline(对比与选择)
| 特性 | 事务 (Transaction) | Pipeline (管道) |
|---|---|---|
| 目的 | 原子性:保证一组命令要么全执行,要么全不执行。 | 性能:减少网络往返次数 (RTT),批量执行命令。 |
| 原子性 | 不保证:命令在队列中时不保证,EXEC后也不保证(某条命令失败不影响其他)。无回滚。 | 无原子性:只是批量发送,不保证原子性。 |
| 隔离性 | 不保证:执行期间其他客户端的命令可能穿插进来(在EXEC前后)。 | 不保证:同上。 |
| 错误处理 | 1.语法错误:EXEC前发现,整个事务失败。2.运行时错误: EXEC后发现,该命令失败,其他命令继续执行。 | 每个命令独立返回结果,需客户端自行判断。 |
| 主要命令 | MULTI,EXEC,DISCARD,WATCH | pipeline()对象,最后execute() |
| 适用场景 | 需要将多个操作作为一个逻辑单元,但对严格的数据库事务(ACID)要求不高。例如:INCR一个计数器,然后LPUSH一条日志。 | 需要批量操作以提升性能,且不关心原子性。例如:一次性GET100 个 key 的值。 |
选择建议:
需要保证一组操作的逻辑原子性(即不希望被其他命令打断),用事务。
只是为了提升批量操作的性能,用Pipeline。
在很多场景下,两者可以结合使用。
九、Redis 常用性能优化(速查表)
| 优化方向 | 问题/现象 | 排查/解决方案 |
|---|---|---|
| 大 Key | 慢查询、内存分布不均、集群迁移困难 | 1.发现:使用redis-cli --bigkeys或memory doctor。2.拆分:将一个大 Hash 拆成多个小 Hash-Key。 3.避免:不要用 GETALL,使用HSCAN游标遍历。 |
| 热点 Key | 单点 QPS 过高,成为性能瓶颈 | 1.发现:hotkeys命令(需插件)或监控平台。2.拆分:将热点 Key 拆分为多个,如 hot_key_1,hot_key_2,通过随机数读写。3.本地缓存:在应用层加 Caffeine 等本地缓存。 |
| 慢查询 | 命令执行时间过长,阻塞其他请求 | 1.发现:SLOWLOG GET 10查看慢查询日志。2.分析:检查慢查询命令(如 KEYS,HGETALL大 Key)。3.解决:用 SCAN替代KEYS;拆分大 Key;优化业务逻辑。 |
| 内存溢出 | maxmemory限制,触发淘汰策略 | 1.监控:INFO memory查看used_memory。2.分析: memory stats分析内存分布。3.解决:调整 maxmemory-policy(如allkeys-lru);增加内存;优化数据结构。 |
| 连接数过高 | CLIENT LIST显示大量连接 | 1.优化:使用连接池(JedisPool, Lettuce),复用连接。 2.排查:检查是否有连接泄漏(未正确关闭)。 |
| CPU 100% | 单线程模型下 CPU 跑满 | 1.排查:INFO commandstats查看高频命令。2.原因:通常是执行了复杂命令(如 SORT,ZUNIONSTORE)或大量 Key 过期。 |
| 持久化开销 | fork子进程时响应变慢 | 1.配置:repl-diskless-sync yes(无盘复制,适用于主从)。2.硬件:使用 SSD 硬盘。 |
十、企业级最佳实践与场景速查
| 业务场景 | 推荐 Redis 数据结构/方案 | 核心命令/实现 | 关键点 |
|---|---|---|---|
| 高并发缓存 | String + 本地缓存 (Caffeine) | SETEX,GET | 缓存穿透/击穿/雪崩防护;热点 Key 预热;数据一致性策略 |
| 计数器/排行榜 | String (INCR) / Sorted Set | INCR,ZINCRBY,ZREVRANGE | 原子性操作;Sorted Set 可带成员信息,实现复杂排行 |
| 分布式 Session | String + Hash | SETEX user:session_id json_str,HSET user:1 name "A" | Key 需有统一前缀;设置合理过期时间;序列化用 JSON 或 Protobuf |
| 分布式锁 | Redisson (推荐) / SET + Lua | RLock.lock(),SET key val NX PX | 防死锁(过期时间/看门狗);安全释放(Lua校验);可重入 |
| 消息队列 | Stream (专业) / List (轻量) | XADD,XREADGROUP,LPUSH,BRPOP | Stream 支持消费组、消息确认 (ACK)、消息回溯,功能完善;List 简单但无ACK |
| 社交关系 | Set / Sorted Set | SADD,SINTER,ZRANGE | 共同好友用SINTER;关注列表用Set或List |
| 实时统计 (UV/PV) | HyperLogLog / Bitmap | PFADD,PFCOUNT,SETBIT,BITCOUNT | HyperLogLog 基数统计,省内存;Bitmap 用于精确签到、在线状态 |
| 地理位置服务 | GEO | GEOADD,GEORADIUS | 基于 Sorted Set 实现,可用于“附近的人”、“打车”等场景 |
| 标签/筛选 | Set / Bitmap | SADD,SISMEMBER,SETBIT | 标签用 Set 实现交集/并集筛选;多维筛选可用 Bitmap 实现 |
| 配置中心 | Hash / String | HSET config_key field val,GET config_key | 监听 Key 的过期或变更消息(需客户端实现或结合 Pub/Sub) |
本文通过多维度的对比表格、深入的机制剖析、方案选型建议以及企业级最佳实践,力求构建一个更加立体和深入的 Redis 知识体系。欢迎交流指正。
