☰
Redis 知识梳理(2)
2026/10/13 7:01:44 网站建设 项目流程

作者:没有四次元口袋的蓝胖
日期:2026-10-12
标签:Redis, Key命令, String, Hash

Redis知识梳理(2)

写在前面

上一篇我们认识了 Redis 和五种数据类型。这篇进入实战——开始敲命令。

面试中,命令的具体语法其实问得不多,但底层原理和典型场景是高频考点。所以我们不讲死记硬背,而是"命令 + 原理 + 场景"三位一体地讲。

本文分四大部分:

  1. Key 通用命令——所有类型的键都适用的操作
  2. String 类型详解——最常用、面试性价比最高
  3. Hash 类型详解——存对象的神器
  4. 常见坑点与面试题

一、Key 通用命令

Key 是所有数据类型的"门牌号"。先掌握通用命令。

1.1 Key 的命名规范

# 推荐使用"业务:模块:id"的形式(冒号分隔,Redis 社区惯例) user:info:1001 order:detail:9527 goods:stock:88 article:like:66

好处:

  • 可读性强,一眼看出是什么
  • 方便KEYS user:*或SCAN按前缀匹配
  • 逻辑上分组

1.2 核心 Key 命令

# 1. 设置键值对SET user:name"zhangsan"# 2. 获取GET user:name# 3. 判断是否存在EXISTS user:name# 返回1存在,0不存在# 4. 删除键DEL user:name# 5. 设置过期时间(秒)EXPIRE user:token3600# 6. 查看剩余生存时间(秒)TTL user:token# -1 表示永不过期,-2 表示键不存在# 7. 查看类型TYPE user:name# 返回 string / hash / list / set / zset# 8. 重命名RENAME oldkey newkey# 9. 随机返回一个 keyRANDOMKEY# 10. 批量删除(配合管道)redis-cli-n0--scan--pattern"user:*"|xargsredis-cli del

1.3 KEYS vs SCAN(重要!)

# KEYS:一次性匹配所有键(⚠️ 会阻塞,生产环境禁用!)KEYS user:*# SCAN:游标式分批扫描(不阻塞,生产环境推荐)SCAN0MATCH user:* COUNT100

为什么生产环境禁用 KEYS?

KEYS 命令会遍历整个键空间,如果 key 数量巨大(几千万上亿),会阻塞 Redis 单线程,导致所有请求卡死——线上事故级别的操作!

SCAN 使用游标分批返回,每次只扫描一部分,不会阻塞。

⚠️重大坑点:生产环境绝对不要用 KEYS !这是面试和实操都要牢记的红线。

1.4 键过期机制

Redis 删除过期键的三种策略(稍后淘汰策略那篇细讲):

策略说明
惰性删除访问键时才发现过期,才删除
定期删除Redis 会定时抽查一些过期的键并删除
内存淘汰内存不足时按策略淘汰(下篇详述)

二、String 类型详解

2.1 String 是什么

String(字符串)是 Redis最基础、最常用的类型。一个键对应一个字符串值,最大 512MB。

底层使用SDS(简单动态字符串)实现(上一篇讲过),支持二进制安全。

2.2 基本命令

# 设置SET user:name"zhangsan"# 获取GET user:name# 返回 "zhangsan"# 设置并设置过期时间SETEX user:token3600"abc123"# 3 个参数:key, 秒, value# 设置(不覆盖已存在的键)SETNX user:name"lisi"# 如果已存在,返回0不覆盖;不存在才设置# 批量设置MSET user:age25user:city"beijing"# 批量获取MGET user:name user:age user:city# 返回 ["zhangsan", "25", "beijing"]# 追加APPEND user:name" hello"# 获取长度STRLEN user:name# 获取子串GETRANGE user:name05# 设置新值并返回旧值(原子)GETSET user:name"wangwu"# 返回旧值 "zhangsan hello"

2.3 数值操作(计数器核心)

String 存的是字符串,但 Redis 提供了数值自增自减命令:

# 设置数字SET user:likes0# 自增 1INCR user:likes# 1# 自增指定步长INCRBY user:likes10# 11# 自减 1DECR user:likes# 10# 自减指定步长(可以减负数实现加法)DECRBY user:likes5# 5# 浮点数自增INCRBYFLOAT user:score0.5

注意:只有值是整数才能用 INCR/DECR,如果存了 “abc”,执行 INCR 会报错。

2.4 原子性原理

客户端A执行 INCR user:likes ↓ Redis 单线程,INCR 命令是一个原子操作 ↓ 不会被其他客户端打断,超安全

INCR 是原子的,所以多个线程/多个客户端同时自增,结果一定是正确的。这才是它敢当计数器的原因。

2.5 String 的典型场景

场景实现示例
缓存存接口结果/对象JSONSET user:1001 {"name":"张三"}
计数器INCR 自增点赞数、访问量、库存
分布式锁SETNX + EXPIRESETNX lock:order "1"
Session 共享存 tokenSETEX user:token 7200 "token值"
限流计数器 + 过期每IP每分钟限定N次

2.6 缓存经典用法

// 缓存对象:序列化为 JSONStringkey="user:"+userId;StringuserJson=jedis.get(key);if(userJson==null){// 缓存未命中,查数据库Useruser=userDao.findById(userId);userJson=JSON.toJSONString(user);jedis.setex(key,3600,userJson);// 写入缓存,1小时过期}returnJSON.parseObject(userJson,User.class);

三、Hash 类型详解

3.1 Hash 是什么

Hash(哈希)用于存储多个字段的对象,类似 Java 里的HashMap。一个 Hash 键下可以有很多 field-value 字段。

为什么用 Hash 存对象更好?

对比两种存用户信息的方式:

方案一:用 String 存整个对象 JSON

SET user:1001 '{"name":"张三","age":25,"city":"北京"}'

问题:想改 age 一个字段,得把整个 JSON 拿出来改,再整个写回,低效。

方案二:用 Hash 存字段

HSET user:1001 name "张三" age 25 city "北京"

好处:可以只改 age 一个字段,不用动整个对象。

结论:Hash 特别适合表示有多个属性的对象。

3.2 基本命令

# 设置单个字段HSET user:1001 name"zhangsan"# 设置多个字段HSET user:1001 age25city"beijing"# 或HMSET user:1001 age25city"beijing"# 另一个写法# 获取单个字段HGET user:1001 name# "zhangsan"# 获取多个字段HMGET user:1001 name age city# ["zhangsan", "25", "beijing"]# 获取所有字段和值HGETALL user:1001# 1) "name" 2) "zhangsan" 3) "age" 4) "25"# 获取所有字段名HKEYS user:1001# ["name", "age", "city"]# 获取所有值HVALS user:1001# ["zhangsan", "25", "beijing"]# 获取字段数量HLEN user:1001# 3# 判断字段是否存在HEXISTS user:1001 name# 1# 删除字段HDEL user:1001 city# 设置字段,仅当字段不存在时(防止覆盖)HSETNX user:1001 email"zs@qq.com"

3.3 数值操作(计数)

# 字段值自增(Hash 里也能计数)HINCRBY user:1001 age1# age 变成 26# 浮点数自增HINCRBYFLOAT user:1001 score0.5

典型场景:Hash 存储对象的"某个数字属性",比如用户积分、商品库存、购物车数量。

# 购物车示例HSET cart:1001 goods:12# 用户1001 购买商品1,数量2HINCRBY cart:1001 goods:11# 数量+1 → 3HINCRBY cart:1001 goods:21# 添加商品2,数量1HGETALL cart:1001# 查看整个购物车

3.4 Hash 底层结构

元素少且值小时用ziplist(压缩列表),紧凑省内存;
元素多或值大时自动升级为hashtable(哈希表)。

阈值(默认):

  • 字段数小于512且
  • 每个字段值长度小于64字节
  • → 用 ziplist,否则用 hashtable

3.5 Hash vs String 选择

场景用 String用 Hash
存整个对象(JSON)✅ 简单✅ 可单独操作字段
需要频繁改单个字段❌ 要整个读写✅ 只改目标字段
简单键值/计数器✅不必要
内存效率(大量字段)偶尔更省字段多时 ziplist 较省

经验法则:

  • 对象字段多、需要局部更新 →Hash
  • 简单值、计数、需要整体缓存 →String

四、常见坑点与面试题

4.1 常见坑点

坑1:用 KEYS 遍历,阻塞线上

# ❌ 千万级 key 时会导致 Redis 停顿KEYS *# ✅ 用 SCAN 分批SCAN0MATCH user:* COUNT100

坑2:忘设过期时间,内存爆满

# 缓存类 key 必须设置过期SET user:1001"xxx"EXPIRE user:10013600# 或用一条命令SETEX user:10013600"xxx"

坑3:大量 key 同时过期,缓存雪崩
避免把所有缓存 key 设成相同过期时间,加随机值打散:

setex(key,3600+random.nextInt(600),value)

坑4:String 存储对象整个序列化/反序列化开销
高频局部更新时改用 Hash,只操作需要的字段。

4.2 面试高频题

Q1:SETNX 和 SET with NX 的区别?

# setnx 命令SETNX key value# set 带参数(推荐,可同时设过期时间,原子)SET key value NX EX30

用SET key value NX EX n可以一条命令完成"不存在才设置 + 设置过期时间",这在实现分布式锁时很重要。

Q2:String 的底层 SDS 相比 C 字符串有哪些好处?
答:O(1) 获取长度、防止缓冲区溢出、二进制安全、减少内存分配次数。

Q3:为什么用 Hash 存对象而不是 String 存 JSON?
答:Hash 可单独操作字段,避免整个对象序列化反序列化;且 ziplist 编码下内存紧凑。

Q4:INCR 命令是原子的吗?
答:是。Redis 单线程执行命令,INCR 不会被并发打断,适合做计数器。

Q5:怎么实现一个"全局唯一数字ID生成器"?

# 利用 INCR 的原子性INCR global:id# 每次调用返回一个递增的ID

思维导图速览

Key命令 + String/Hash │ ├── Key 通用命令 │ ├── SET / GET / EXISTS / DEL │ ├── EXPIRE / TTL / TYPE / RENAME │ ├── KEYS(禁用) vs SCAN(游标分批) │ └── 过期删除:惰性 + 定期 + 内存淘汰 │ ├── String 类型 │ ├── 基本:SET/GET/MSET/MGET/APPEND/STRLEN │ ├── 计数:INCR/INCRBY/DECR/DECRBY │ ├── 原子性(INCR 安全) │ ├── SETNX/SETEX/SET NX EX │ ├── 底层:SDS │ └── 场景:缓存/计数器/锁/Session/限流 │ └── Hash 类型 ├── HSET/HGET/HGETALL/HKEYS/HVALS/HLEN ├── HDEL/HEXISTS/HINCRBY ├── 底层:ziplist → hashtable ├── 场景:对象存储/购物车/积分 └── Hash vs String 选择

写在最后

String 和 Hash 是 Redis 用得最多的两个类型,必须滚瓜烂熟。

  • String:简单值 + 计数器 + 锁,面试最爱问"如何用 Redis 做计数器/分布式锁"
  • Hash:存对象,记得它的"字段级操作"优势

两个铺垫型的坑点:生产禁用 KEYS、缓存一定要设过期,这两条是实战红线。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询