写在前面
String和Hash是Redis中最常用的两种数据类型,掌握它们的命令细节和应用场景,能让你在实际开发中游刃有余。今天我们深入探讨这两种类型的核心命令与实战技巧。

文章目录
-
- 写在前面
- 一、String命令详解
-
- [1.1 基础SET/GET命令](#1.1 基础SET/GET命令)
- [1.2 带参数的SET命令](#1.2 带参数的SET命令)
- [1.3 数值操作命令](#1.3 数值操作命令)
- [1.4 字符串操作命令](#1.4 字符串操作命令)
- [1.5 批量操作命令](#1.5 批量操作命令)
- 二、String应用场景
-
- [2.1 缓存](#2.1 缓存)
- [2.2 计数器](#2.2 计数器)
- [2.3 分布式ID生成](#2.3 分布式ID生成)
- [2.4 分布式锁](#2.4 分布式锁)
- 三、Hash命令详解
-
- [3.1 基础操作命令](#3.1 基础操作命令)
- [3.2 字段管理命令](#3.2 字段管理命令)
- [3.3 数值操作命令](#3.3 数值操作命令)
- [3.4 批量迭代命令](#3.4 批量迭代命令)
- 四、Hash应用场景
-
- [4.1 用户信息存储](#4.1 用户信息存储)
- [4.2 商品信息存储](#4.2 商品信息存储)
- [4.3 购物车实现](#4.3 购物车实现)
- 五、踩坑提醒:value过大问题
-
- [5.1 String类型大value](#5.1 String类型大value)
- [5.2 Hash类型大value](#5.2 Hash类型大value)
- [5.3 如何检测大value](#5.3 如何检测大value)
- 六、命令对比表
-
- [6.1 String vs Hash命令对比](#6.1 String vs Hash命令对比)
- [6.2 场景选择建议](#6.2 场景选择建议)
- 七、面试高频考点
- 八、参考资料
- 九、互动话题
一、String命令详解
1.1 基础SET/GET命令
实际场景:SET/GET是最基础也是最常用的命令,但很多细节你可能不知道。
redis
# 基本设置
set name "redis"
# 基本获取
get name
# 设置并返回旧值
getset name "new_redis"
# 仅当key不存在时设置
setnx name "redis"
# 仅当key存在时设置
set name "redis" xx
上述命令中,GETSET常用于原子性更新并获取旧值的场景;SETNX是分布式锁的基础;XX参数表示仅当key存在时才设置。
1.2 带参数的SET命令
redis
# 设置过期时间(秒)
set session:token "user_data" ex 3600
# 设置过期时间(毫秒)
set session:token "user_data" px 3600000
# 设置过期时间戳(秒)
set session:token "user_data" exat 1700000000
# NX参数:仅当key不存在时设置
set lock:order "uuid" nx ex 30
# GET参数:设置新值并返回旧值
set counter 100 get
注意事项:
- EX和PX不能同时使用
- NX和XX不能同时使用
- SET key value NX EX seconds 是原子操作,适合实现分布式锁
1.3 数值操作命令
经验之谈:Redis的数值操作是原子的,非常适合高并发计数场景。
redis
# 设置数值
set counter 0
# 自增1
incr counter
# 自增指定值
incrby counter 10
# 自增浮点数
incrbyfloat counter 2.5
# 自减1
decr counter
# 自减指定值
decrby counter 5
| 命令 | 说明 | 返回值 |
|---|---|---|
| INCR | 自增1 | 自增后的值 |
| INCRBY | 自增指定整数 | 自增后的值 |
| INCRBYFLOAT | 自增浮点数 | 自增后的值 |
| DECR | 自减1 | 自减后的值 |
| DECRBY | 自减指定整数 | 自减后的值 |
注意事项:
- 如果key不存在,INCR会先初始化为0再自增
- 如果value不是整数,INCR会报错
- INCRBYFLOAT支持科学计数法
1.4 字符串操作命令
redis
# 追加字符串
append name " tutorial"
# 获取字符串长度
strlen name
# 获取子字符串
getrange name 0 4
# 设置子字符串
setrange name 0 "REDIS"
1.5 批量操作命令
redis
# 批量设置
mset key1 "value1" key2 "value2" key3 "value3"
# 批量获取
mget key1 key2 key3
# 批量设置(原子操作,任一失败则全部失败)
msetnx key1 "value1" key2 "value2"
注意事项:
- MGET返回的是数组,不存在的key返回nil
- MSETNX是原子操作,适合需要保证一致性的批量设置
二、String应用场景
2.1 缓存
实际场景:缓存是Redis最常见的用途,能显著提升系统性能。
redis
# 设置缓存(带过期时间)
set cache:user:1001 '{"name":"zhangsan","age":25}' ex 3600
# 获取缓存
get cache:user:1001
# 缓存不存在时的处理(伪代码)
# if redis.get(key) is None:
# data = db.query(...)
# redis.set(key, data, ex=3600)
缓存策略对比:
| 策略 | 说明 | 适用场景 |
|---|---|---|
| Cache Aside | 先查缓存,不存在再查库 | 读多写少 |
| Write Through | 写入时同时更新缓存 | 读写均衡 |
| Write Behind | 先写缓存,异步写库 | 写多读少 |
2.2 计数器
redis
# 文章阅读量
incr article:1001:views
# 用户点赞数
incr user:1001:likes
# 获取计数
get article:1001:views
# 批量获取多个计数
mget article:1001:views article:1002:views
注意事项:
- 计数器不需要初始化,INCR自动创建
- 高并发下INCR是原子操作,不会丢失计数
2.3 分布式ID生成
redis
# 每天重置的ID
set order:id:20240101 0
# 生成订单ID
incr order:id:20240101
# 返回1,订单ID为:20240101000001
# 批量获取ID
incrby order:id:20240101 100
2.4 分布式锁
踩坑提醒:分布式锁实现要考虑锁超时、误删等问题。
redis
# 加锁(原子操作)
set lock:order:1001 "uuid-xxx" nx ex 30
# 业务逻辑执行...
# 释放锁(Lua脚本保证原子性)
# if redis.call("get", KEYS[1]) == ARGV[1] then
# return redis.call("del", KEYS[1])
# else
# return 0
# end
分布式锁注意事项:
- 设置过期时间,防止死锁
- value使用唯一标识,防止误删
- 释放锁时验证value,使用Lua保证原子性
三、Hash命令详解
3.1 基础操作命令
实际场景:Hash天生适合存储对象,比String+JSON更灵活。
redis
# 设置单个字段
hset user:1001 name "zhangsan"
# 设置多个字段
hmset user:1001 name "zhangsan" age 25 city "beijing"
# 获取单个字段
hget user:1001 name
# 获取多个字段
hmget user:1001 name age city
# 获取所有字段和值
hgetall user:1001
# 获取所有字段名
hkeys user:1001
# 获取所有值
hvals user:1001
3.2 字段管理命令
redis
# 删除字段
hdel user:1001 city
# 检查字段是否存在
hexists user:1001 name
# 仅当字段不存在时设置
hsetnx user:1001 email "test@example.com"
# 获取字段数量
hlen user:1001
3.3 数值操作命令
redis
# 字段自增整数
hincrby user:1001 age 1
# 字段自增浮点数
hincrbyfloat user:1001 salary 1000.5
3.4 批量迭代命令
redis
# 迭代获取字段(适合大Hash)
hscan user:1001 0 match "field_*" count 10
四、Hash应用场景
4.1 用户信息存储
经验之谈:用户信息用Hash存储,可以只更新某个字段,比String+JSON更高效。
redis
# 存储用户信息
hmset user:1001 name "zhangsan" age 25 city "beijing" email "zhangsan@example.com"
# 只更新年龄
hset user:1001 age 26
# 只获取姓名和邮箱
hmget user:1001 name email
# 检查邮箱是否存在
hexists user:1001 email
Hash vs String存储对象对比:
| 对比项 | Hash存储 | String+JSON存储 |
|---|---|---|
| 部分读取 | 支持(HGET) | 需要整体读取 |
| 部分更新 | 支持(HSET) | 需要整体更新 |
| 内存占用 | 较小(ziplist) | 较大 |
| 字段查询 | 支持(HEXISTS) | 需要解析JSON |
| 适用场景 | 字段多、部分访问 | 字段少、整体访问 |
4.2 商品信息存储
redis
# 存储商品信息
hmset product:2001 name "iPhone 15" price 6999 stock 100 category "phone"
# 库存减1
hincrby product:2001 stock -1
# 获取库存
hget product:2001 stock
# 获取商品基本信息
hmget product:2001 name price stock
4.3 购物车实现
redis
# 添加商品到购物车
hset cart:user:1001 product:2001 2
hset cart:user:1001 product:2002 1
# 修改商品数量
hset cart:user:1001 product:2001 3
# 获取购物车所有商品
hgetall cart:user:1001
# 删除购物车商品
hdel cart:user:1001 product:2002
# 获取购物车商品数量
hlen cart:user:1001
五、踩坑提醒:value过大问题
踩坑提醒:大value是Redis性能杀手,务必控制value大小!
5.1 String类型大value
问题表现:
- 单个String value超过10KB
- 网络传输慢,阻塞其他操作
- 内存碎片增加
解决方案:
redis
# 方案1:压缩存储
# 将大对象压缩后存储
set big:object "compressed_data" ex 3600
# 方案2:拆分存储
set user:1001:profile "基本信息"
set user:1001:detail "详细信息"
set user:1001:settings "设置信息"
# 方案3:使用Hash拆分
hmset user:1001 profile "基本信息" detail "详细信息" settings "设置信息"
5.2 Hash类型大value
问题表现:
- 单个Hash字段数超过5000
- HGETALL操作耗时
- 内存占用高
解决方案:
redis
# 拆分为多个Hash
hmset user:1001:basic name "zhangsan" age 25
hmset user:1001:contact email "xxx" phone "xxx"
hmset user:1001:address city "beijing" street "xxx"
# 使用HSCAN迭代获取
hscan user:1001:basic 0
5.3 如何检测大value
shell
# 使用redis-cli扫描大key
redis-cli --bigkeys
# 查看指定key的内存占用
redis-cli memory usage keyname
# 使用RDB工具分析
rdb --command json dump.rdb | python analyze.py
六、命令对比表
6.1 String vs Hash命令对比
| 操作 | String命令 | Hash命令 |
|---|---|---|
| 设置值 | SET key value | HSET key field value |
| 获取值 | GET key | HGET key field |
| 删除 | DEL key | HDEL key field |
| 检查存在 | EXISTS key | HEXISTS key field |
| 自增 | INCR key | HINCRBY key field num |
| 批量设置 | MSET k1 v1 k2 v2 | HMSET key f1 v1 f2 v2 |
| 批量获取 | MGET k1 k2 | HMGET key f1 f2 |
6.2 场景选择建议
| 场景 | 推荐类型 | 理由 |
|---|---|---|
| 简单KV缓存 | String | 简单高效 |
| 对象存储(字段多) | Hash | 支持部分读写 |
| 计数器 | String | INCR原子操作 |
| 分布式锁 | String | SET NX EX原子操作 |
| 购物车 | Hash | field为商品ID,value为数量 |
| 用户Session | String | 整体存取,带过期时间 |
七、面试高频考点
Q1:String和Hash存储对象如何选择?
答案:
- 字段少(<5个)且整体访问:选String
- 字段多或需要部分访问:选Hash
- 需要设置过期时间:选String(Hash不支持对单个field设置过期)
- 内存敏感:选Hash(ziplist编码更省内存)
Q2:INCR命令有什么注意事项?
答案:
- value必须是整数格式,否则报错
- key不存在时会初始化为0再自增
- 范围是64位有符号整数,超出范围会报错
- 是原子操作,高并发下安全
Q3:如何实现一个分布式锁?
答案:
redis
# 加锁(原子操作)
set lock:resource "uuid" nx ex 30
# 释放锁(Lua脚本保证原子性)
eval "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end" 1 lock:resource "uuid"
关键点:
- 使用SET NX EX原子加锁
- value使用唯一标识防止误删
- 释放锁时验证value
- 设置合理的过期时间
八、参考资料
九、互动话题
你在项目中遇到过String和Hash选型的困惑吗?有没有因为选错类型导致的性能问题?欢迎在评论区分享你的经验!
下一篇我们将深入探讨List和Set的命令细节与应用场景。