Redis五种数据类型与应用场景

Redis五种数据类型与应用场景

Redis 的五种数据结构各有特色,用对了才能发挥它的优势。很多人只用到了 String 和 Hash,却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发,讲什么时候用什么类型。

一、String(字符串)

#

1.1 基本特点

  • 最基本的数据类型,一个key对应一个value
  • 最大可存储512MB
  • 可以存储字符串、整数、浮点数

#

1.2 常用命令

# 基本操作
SET key value # 设置值
GET key # 获取值
DEL key # 删除
EXISTS key # 判断是否存在

# 批量操作
MSET key1 v1 key2 v2 # 批量设置
MGET key1 key2 # 批量获取

# 数值操作
INCR key # 自增1
DECR key # 自减1
INCRBY key 5 # 增加指定值

# 过期时间
SETEX key 60 value # 设置并指定过期时间(秒)
SET key value EX 60 # 同上
TTL key # 查看剩余过期时间

#

1.3 应用场景

场景一:缓存

// 缓存用户信息
String userJson = redis.get("user:1001");
if (userJson == null) {
User user = userMapper.findById(1001L);
redis.setex("user:1001", 3600, JSON.toJSONString(user));
return user;
}
return JSON.parseObject(userJson, User.class);

场景二:计数器

// 文章阅读量
redis.incr("article:read:10001");

// 限流计数
String key = "rate_limit:" + userId;
Long count = redis.incr(key);
if (count == 1) {
redis.expire(key, 60); // 首次设置60秒过期
}
if (count > 100) {
throw new RateLimitException("请求过于频繁");
}

场景三:分布式锁

// 简单版分布式锁(需改进)
String key = "lock:order:10001";
String value = UUID.randomUUID().toString();
Boolean locked = redis.set(key, value, "NX", "EX", 30);
if (locked) {
try {
// 执行业务逻辑
} finally {
// 释放锁(需验证value防止误删)
String script =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) else return 0 end";
redis.eval(script, Collections.singletonList(key),
Collections.singletonList(value));
}
}

场景四:Session存储

// 分布式Session
redis.setex("session:" + sessionId, 1800, userId);

二、Hash(哈希)

#

2.1 基本特点

  • 键值对的集合,适合存储对象
  • 每个Hash可存储约40亿个键值对
  • 相比String序列化,更节省空间,支持字段级操作

#

2.2 常用命令

HSET user:1001 name "Alice"     # 设置字段
HGET user:1001 name # 获取字段
HMSET user:1001 name "Alice" age 25 city "Beijing" # 批量设置
HGETALL user:1001 # 获取所有字段
HDEL user:1001 city # 删除字段
HLEN user:1001 # 字段数量
HINCRBY user:1001 age 1 # 字段值增加
HEXISTS user:1001 name # 判断字段是否存在
HKEYS user:1001 # 获取所有字段名
HVALS user:1001 # 获取所有字段值

#

2.3 应用场景

场景一:存储对象

// 存储用户信息
Map<String, String> userMap = new HashMap<>();
userMap.put("name", "Alice");
userMap.put("age", "25");
userMap.put("city", "Beijing");
redis.hsetAll("user:1001", userMap);

// 获取单个字段
String name = redis.hget("user:1001", "name");

// 修改单个字段
redis.hset("user:1001", "age", "26");

场景二:购物车

// 添加商品到购物车
redis.hset("cart:user:1001", "sku:2001", "2"); // 商品ID -> 数量
redis.hset("cart:user:1001", "sku:2002", "1");

// 修改数量
redis.hincrBy("cart:user:1001", "sku:2001", 1);

// 获取购物车
Map<String, String> cart = redis.hgetAll("cart:user:1001");

// 删除商品
redis.hdel("cart:user:1001", "sku:2001");

场景三:配置信息

// 存储系统配置
redis.hset("config:app", "max_upload_size", "10485760");
redis.hset("config:app", "default_timeout", "30");
redis.hset("config:app", "retry_times", "3");

// 读取配置
String timeout = redis.hget("config:app", "default_timeout");

String vs Hash存储对象对比

方式 优点 缺点
String(JSON) 序列化简单 修改需整体更新,空间占用大
Hash 字段级操作,节省空间 复杂对象不便存储

三、List(列表)

#

3.1 基本特点

  • 双向链表,有序可重复
  • 两端插入和删除都是O(1)
  • 支持阻塞操作

#

3.2 常用命令

LPUSH list value            # 左侧插入
RPUSH list value # 右侧插入
LPOP list # 左侧弹出
RPOP list # 右侧弹出
LRANGE list 0 -1 # 获取所有元素
LLEN list # 列表长度
LINDEX list 0 # 获取指定索引元素
LREM list 1 value # 删除指定元素
LTRIM list 0 99 # 只保留前100个
BLPOP list 30 # 阻塞左侧弹出,等待30秒
BRPOP list1 list2 30 # 阻塞弹出,多个列表

#

3.3 应用场景

场景一:消息队列

// 生产者
redis.lpush("queue:email", emailJson);

// 消费者
while (running) {
List<String> result = redis.brpop(30, "queue:email");
if (result != null) {
String emailJson = result.get(1);
processEmail(emailJson);
}
}

场景二:最新列表

// 添加新文章到列表
redis.lpush("articles:latest", articleId);
redis.ltrim("articles:latest", 0, 99); // 只保留100条

// 获取最新文章
List<String> latestArticles = redis.lrange("articles:latest", 0, 9);

场景三:时间线/动态

// 用户发布动态
redis.lpush("timeline:user:1001", momentJson);
redis.ltrim("timeline:user:1001", 0, 999);

// 获取动态
List<String> moments = redis.lrange("timeline:user:1001", 0, 20);

场景四:栈和队列

// 栈(后进先出)
redis.lpush("stack", value); // 入栈
redis.lpop("stack"); // 出栈

// 队列(先进先出)
redis.rpush("queue", value); // 入队
redis.lpop("queue"); // 出队

四、Set(集合)

#

4.1 基本特点

  • 无序不重复集合
  • 支持集合运算(交、并、差)
  • 判断元素是否存在O(1)

#

4.2 常用命令

SADD set value              # 添加元素
SREM set value # 删除元素
SMEMBERS set # 获取所有元素
SISMEMBER set value # 判断元素是否存在
SCARD set # 集合大小
SPOP set # 随机弹出一个元素
SRANDMEMBER set 3 # 随机获取3个元素(不删除)
SINTER set1 set2 # 交集
SUNION set1 set2 # 并集
SDIFF set1 set2 # 差集
SINTERSTORE result set1 set2 # 交集并存储

#

4.3 应用场景

场景一:标签系统

// 给文章添加标签
redis.sadd("article:tags:10001", "Java", "Redis", "缓存");

// 获取文章标签
Set<String> tags = redis.smembers("article:tags:10001");

// 获取有相同标签的文章
redis.sinter("tag:Java", "tag:Redis");

场景二:共同好友

// 用户的好友集合
redis.sadd("friends:user:1001", "2001", "2002", "2003");
redis.sadd("friends:user:1002", "2002", "2003", "2004");

// 共同好友
Set<String> common = redis.sinter("friends:user:1001", "friends:user:1002");
// 结果: 2002, 2003

// 可能认识的人(差集)
Set<String> suggest = redis.sdiff("friends:user:1002", "friends:user:1001");
// 结果: 2004

场景三:抽奖/随机

// 添加参与用户
redis.sadd("lottery:activity:1", "user1", "user2", "user3", ...);

// 随机抽取中奖者
Set<String> winners = redis.srandmember("lottery:activity:1", 3);

// 抽出并移除(不重复中奖)
List<String> winners = new ArrayList<>();
for (int i = 0; i < 3; i++) {
String winner = redis.spop("lottery:activity:1");
winners.add(winner);
}

场景四:黑名单/白名单

// IP黑名单
redis.sadd("blacklist:ip", "192.168.1.100", "10.0.0.50");

// 检查是否在黑名单
Boolean blocked = redis.sismember("blacklist:ip", clientIp);

五、ZSet(有序集合)

#

5.1 基本特点

  • 每个元素关联一个分数(score)
  • 按分数排序,分数可重复
  • 支持按分数范围查询

#

5.2 常用命令

ZADD zset score member         # 添加元素
ZREM zset member # 删除元素
ZSCORE zset member # 获取分数
ZRANGE zset 0 -1 # 按分数升序获取
ZREVRANGE zset 0 -1 # 按分数降序获取
ZRANGEBYSCORE zset 0 100 # 按分数范围获取
ZCARD zset # 元素数量
ZCOUNT zset 0 100 # 分数范围内元素数量
ZINCRBY zset 1 member # 增加分数
ZRANK zset member # 获取排名(升序,从0开始)
ZREVRANK zset member # 获取排名(降序)
ZREMRANGEBYRANK zset 0 99 # 删除排名范围内的元素
ZREMRANGEBYSCORE zset 0 100 # 删除分数范围内的元素

#

5.3 应用场景

场景一:排行榜

// 更新用户积分
redis.zincrby("leaderboard:weekly", 10, "user:1001");

// 获取前10名
Set<Tuple> top10 = redis.zrevrangeWithScores("leaderboard:weekly", 0, 9);

// 获取用户排名
Long rank = redis.zrevrank("leaderboard:weekly", "user:1001");

// 获取用户分数
Double score = redis.zscore("leaderboard:weekly", "user:1001");

场景二:延时队列

// 添加延时任务(score为执行时间戳)
redis.zadd("delay:queue", System.currentTimeMillis() + 60000, taskJson);

// 消费到期任务
while (running) {
Set<String> tasks = redis.zrangeByScore("delay:queue",
0, System.currentTimeMillis(), 0, 10);
for (String task : tasks) {
// 删除并处理(保证不被其他消费者处理)
Long removed = redis.zrem("delay:queue", task);
if (removed > 0) {
processTask(task);
}
}
Thread.sleep(1000);
}

场景三:滑动窗口限流

public boolean rateLimit(String userId, int maxRequests, int windowSeconds) {
String key = "rate_limit:" + userId;
long now = System.currentTimeMillis();
long windowStart = now - windowSeconds * 1000;

// 移除窗口外的请求记录
redis.zremrangeByScore(key, 0, windowStart);

// 获取当前窗口内的请求数
Long count = redis.zcard(key);
if (count >= maxRequests) {
return false; // 限流
}

// 记录本次请求
redis.zadd(key, now, String.valueOf(now));
redis.expire(key, windowSeconds);
return true;
}

场景四:热门文章/商品

// 记录文章点击量(按时间衰减)
double score = System.currentTimeMillis() / 1000.0;
redis.zadd("articles:hot", score, "article:10001");

// 只保留最近1000篇
redis.zremrangeByRank("articles:hot", 0, -1001);

// 获取热门文章
Set<String> hotArticles = redis.zrevrange("articles:hot", 0, 9);

六、数据类型选择指南

需求 推荐类型 原因
简单键值缓存 String 最简单
存储对象 Hash 字段级操作,节省空间
队列/栈 List 双向链表,天然支持
去重/集合运算 Set 无序不重复
排序/排名 ZSet 按分数排序
计数器 String INCR原子操作
分布式锁 String SET NX EX
最新消息 List LPUSH + LTRIM
共同关注 Set SINTER
排行榜 ZSet 自动排序

七、总结

Redis五种数据类型各有特点:

类型 底层结构 特点 典型应用
String SDS 简单高效 缓存、计数器、锁
Hash ziplist/hashtable 字段级操作 对象存储、购物车
List quicklist 双向链表 队列、时间线
Set intset/hashtable 无序不重复 标签、共同好友
ZSet ziplist/skiplist 有序集合 排行榜、延时队列

理解每种数据类型的特性和适用场景,才能在实际开发中做出正确的选择,发挥Redis的最大价值。

核心要点

  1. String:简单的键值对,适合缓存、计数器

  2. Hash:存储对象属性,适合用户信息、配置

  3. List:有序列表,适合消息队列、最新列表

  4. Set:无序去重,适合共同好友、抽奖

  5. ZSet:有序集合,适合排行榜、积分系统

总结

选择合适的数据结构是使用 Redis 的关键。在实际项目中,根据业务需求选择合适的类型,可以提升性能和开发效率。


   转载规则


《Redis五种数据类型与应用场景》 小乐 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录