Redis:数据结构与基础操作(String、List、Hash、Set、Sorted Set) - 实践

一、数据结构与操作

(一)基本数据结构

1. 字符串(String)

String 是 Redis 中最基础、最常用的数据结构。它是二进制安全的,也就是说可以存储任意类型的数据,例如:

普通字符串

整数、浮点数

图片(Base64 编码后)

JSON / 序列化对象 / 二进制内容


(1)典型应用场景
①简单数据存储

适用于存储常规文本或对象的场景:

        用户 session、token

        图片地址 / 用户昵称等基础属性

        存储序列化对象(相比 Hash 更节省内存)

相关命令:SET、GET


②计数器业务

Redis 对整数操作原子化,非常适合:

        接口限流(统计用户单位时间请求数)

        文章浏览量 PV

        点赞次数计数

相关命令:INCR、DECR


分布式锁(SETNX 原理版)

可通过 SETNX key value 实现简单锁(生产需改良,如 RedLock)。


(2)基本操作
操作含义
SET key value设置 key 的值
GET key获取 key 的值
EXISTS key判断 key 是否存在
DEL key删除指定 key

(3)批量操作
命令含义
MSET key value [key value ...]批量设置键值对(批量写)
MGET key1 [key2 ...]批量获取多个 key 的值(批量读)

(4)数值操作(原子性)
命令含义
INCR key值加 1
DECR key值减 1
INCRBY key increment增加指定整数
INCRBYFLOAT key increment增加浮点数

(5)设置过期时间
命令含义
EXPIRE key seconds设置过期时间(秒)
TTL key查询剩余生存时间
SETEX key seconds value设置值并设置过期时间
PSETEX key milliseconds value毫秒级过期时间
SETNX key value不存在时才设置(可用于锁)
(6)String 常用命令(按功能分类)

设置/获取

SET key value

GET key

GETSET key value:设置新值并返回旧值

STRLEN key:返回字符串长度


追加、覆盖

APPEND key value:追加字符串

SETRANGE key offset value:从 offset 开始覆盖


范围和位图

GETRANGE key start end:获取子字符串

GETBIT key offset:获取某位的二进制值

SETBIT key offset value:修改某位的二进制值


批量操作

MSET key value [...]

MGET key1 [...]


(7)Java 中的 String 类型映射(Jedis / Spring Data Redis)
①Jedis 使用示例

基本 set/get

Jedis jedis = new Jedis("localhost", 6379);
jedis.set("username", "xiaoman");
String name = jedis.get("username");
System.out.println(name);

数值操作(INCR / DECR)

Jedis jedis = new Jedis("localhost", 6379);
jedis.incr("count");
jedis.decr("count");
System.out.println(jedis.get("count"));

SETNX 分布式锁

/**
 * 获取锁(SETNX)
 */
public boolean tryLock(Jedis jedis, String lockKey, String lockValue) {
    Long result = jedis.setnx(lockKey, lockValue);
    return result == 1;  // 返回 1 代表成功获取锁
}
/**
 * 释放锁
 */
public void unlock(Jedis jedis, String lockKey, String lockValue) {
    String current = jedis.get(lockKey);
    if (lockValue.equals(current)) {
        jedis.del(lockKey);
    }
}

说明:这是最基础的分布式锁实现,仅用于理解原理。


②Spring Data Redis 使用示例

基本 set/get

@Autowired
private StringRedisTemplate stringRedisTemplate;
// 写入
stringRedisTemplate.opsForValue().set("token", "abc123");
// 读取
String token = stringRedisTemplate.opsForValue().get("token");

数值操作(increment/decrement)

// 自增
stringRedisTemplate.opsForValue().increment("count");
// 自减
stringRedisTemplate.opsForValue().decrement("count");

分布式锁(SETNX + 过期时间)

/**
 * 获取分布式锁:setIfAbsent 相当于 SETNX
 */
public boolean tryLock(String key, String value, long expireSeconds) {
    Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(
        key, value, Duration.ofSeconds(expireSeconds)
    );
    return Boolean.TRUE.equals(success);
}
/**
 * 释放锁(确保只释放自己的锁)
 */
public void unlock(String key, String value) {
    String currentValue = stringRedisTemplate.opsForValue().get(key);
    if (value.equals(currentValue)) {
        stringRedisTemplate.delete(key);
    }
}

2. 列表(List)

List 是 按插入顺序排序 的字符串列表,支持:

        双端插入/弹出(栈/队列)

        范围查询(LRANGE)

        阻塞操作(BLPOP)

(1)核心特性
特性说明类似的数据结构
有序元素按插入顺序排序链表
可从两端插入/弹出LPUSH / RPUSH / LPOP / RPOP栈、队列、消息队列
支持范围查询LRANGE 速度快(O(n))分页展示

(2)典型应用场景
① 消息流(Timeline)

如:微博最新动态

常用命令:LPUSH + LRANGE

② 队列(先进先出 FIFO)

异步任务队列

RPUSH(入队)+ LPOP(出队)

③ 栈(后进先出 LIFO)

浏览器前进后退

LPUSH(入栈)+ LPOP(出栈)

④ 阻塞队列(BLPOP/BRPOP)

可实现简单 MQ,消费者等待生产者。


(3)常用操作汇总

插入

命令说明
LPUSH key v1 [v2...]从左侧插入
RPUSH key v1 [v2...]从右侧插入
LPUSHX key v列表存在才左插
RPUSHX key v列表存在才右插

删除 / 弹出

命令说明
LPOP key左侧弹出
RPOP key右侧弹出
LREM key count value删除 count 个指定 value

获取 / 查询

命令说明
LRANGE key start stop获取区间元素,如:0 -1 获取全部
LINDEX key index按索引取值
LLEN key获取列表长度

修改 / 限制

命令说明
LSET key index value修改指定位置的值
LTRIM key start stop保留某一段,其余删除

阻塞操作(适合消息队列)

命令说明
BLPOP key timeout阻塞式 LPOP
BRPOP key timeout阻塞式 RPOP
BRPOPLPUSH source dest timeout阻塞式 RPOPLPUSH

(4)Java 中操作 Redis List 的示例(Spring Data Redis + Lettuce)

配置 RedisTemplate:

@Autowired
private StringRedisTemplate redisTemplate;

插入元素(LPUSH / RPUSH)

// 左侧插入
redisTemplate.opsForList().leftPush("list1", "a");
// 右侧插入
redisTemplate.opsForList().rightPush("list1", "b");
// 批量插入
redisTemplate.opsForList().rightPushAll("list1", "c", "d", "e");

弹出元素(LPOP / RPOP)

String v1 = redisTemplate.opsForList().leftPop("list1");
String v2 = redisTemplate.opsForList().rightPop("list1");

查看区间元素(LRANGE)

List list = redisTemplate.opsForList().range("list1", 0, -1);

获取长度(LLEN)

Long len = redisTemplate.opsForList().size("list1");

获取某一个索引的值(LINDEX)

String value = redisTemplate.opsForList().index("list1", 2);

设置某一位置的值(LSET)

redisTemplate.opsForList().set("list1", 1, "newValue");

删除指定元素(LREM)

redisTemplate.opsForList().remove("list1", 2, "a");
// 删除2个"a"

保留指定区间(LTRIM)

redisTemplate.opsForList().trim("list1", 0, 10);
// 只保留前11个元素

阻塞式弹出(BLPOP / BRPOP)

// 等待 5 秒,直到有元素可取
String v = redisTemplate.opsForList().leftPop("queue", 5, TimeUnit.SECONDS);

适合做消费者。


(5)生产环境 List 

用 List 实现简单队列

生产者:RPUSH

消费者:BLPOP(阻塞)

不推荐用 List 做无限长度队列

建议配合 LTRIM 控制长度:

redisTemplate.opsForList().rightPush("feed", "item");
redisTemplate.opsForList().trim("feed", 0, 999); // 控制列表最多 1000 条

3. 哈希(Hash)

Redis Hash 是一个 field → value 的映射表,非常适合存储对象,支持对对象字段进行独立修改,而不需要整个对象序列化后再存一次。

(1)Hash 的典型应用场景

对象存储

非常适合保存结构化对象,例如:

用户信息

商品信息

文章信息

配置项信息

示例结构:

user:1001
 ├── name → xiaoman
 ├── age → 18
 └── city → beijing

相关命令:

HSET / HMSET(设置字段)

HGET / HMGET(获取字段)

HDEL(删除字段)

HGETALL(获取整个对象)


购物车

key = cart:userId
field = 商品ID
value = 数量或 JSON 信息

常用命令:

添加商品:HSET

数量+1:HINCRBY

删除商品:HDEL

商品总数:HLEN

获取全部商品:HGETALL


(2)基本操作:
命令说明
HSET key field value设置某个字段值(覆盖)
HMSET key field1 value1 ...设置多个字段(Redis 4.0 后建议使用 HSET 多参数形式)
HGET key field获取某字段
HMGET key f1 f2 ...获取多个字段
HGETALL key获取所有字段和值
HDEL key f1 [f2 …]删除一个或多个字段
HEXISTS key field判断字段是否存在
HLEN key字段数量
HKEYS key获取所有 field
HVALS key获取所有 value
HINCRBY key field n对整数字段加 n
HINCRBYFLOAT key field n对浮点字段加 n
HSETNX key field value字段不存在时设置
HSCAN key cursor [MATCH pattern] [COUNT n]遍历大哈希表(分页迭代)

(3)Hash 操作汇总

写字段

HSET key field value

HSET key field1 value1 field2 value2 ...(Redis 4.0+ 推荐)

HSETNX key field value

HINCRBY key field increment

HINCRBYFLOAT key field increment

读字段

HGET key field

HMGET key field1 field2 ...

HGETALL key

HKEYS key

HVALS key

HLEN key

删字段

HDEL key field1 [field2 ...]

遍历

HSCAN key cursor MATCH xx COUNT n


(4)Hash 使用注意事项

不要把特别大的对象放在一个 Hash

例如包含 10 万字段,这会:

        HGETALL 非常慢

        集群模式下扩容不均衡

        HSCAN 复杂度变大

Hash 一个字段的值不能超过 512MB

Hash 适合结构化数据,不适合 JSON 长文本

如果 JSON 只是单值存储,推荐用 String。


(5)Java(Spring Data Redis + Lettuce)操作 Hash 示例

假设已注入:

@Autowired
private StringRedisTemplate redisTemplate;

添加 / 修改字段(HSET)

redisTemplate.opsForHash().put("user:1001", "name", "xiaoman");
redisTemplate.opsForHash().put("user:1001", "age", "18");

批量添加字段(HMSET → 使用 putAll)

Map data = new HashMap<>();
data.put("name", "xiaoman");
data.put("city", "beijing");
redisTemplate.opsForHash().putAll("user:1001", data);

获取字段(HGET)

String name = (String) redisTemplate.opsForHash().get("user:1001", "name");

获取多个字段(HMGET)

List list = redisTemplate.opsForHash().multiGet("user:1001", Arrays.asList("name", "city"));

字段+1(HINCRBY)

redisTemplate.opsForHash().increment("cart:1001", "item:2001", 1);

获取所有字段和值(HGETALL)

Map entries = redisTemplate.opsForHash().entries("user:1001");

删除字段(HDEL)

redisTemplate.opsForHash().delete("user:1001", "city");

4. 集合(Set)

Redis Set 是一个无序、元素唯一 的集合结构,类似于 Java 的 HashSet。
适用于需要:

保证元素不重复

快速判断某元素是否存在

随机获取元素

进行交集、并集、差集计算


(1)Set 的典型应用场景

抽奖 / 随机选人

需要随机从一个集合中抽取成员:

SADD:加入抽奖池

SPOP:随机抽取并移除(适合不可重复中奖)

SRANDMEMBER:随机获取但不移除

场景示例:

抽奖系统随机选中奖人

随机推荐


点赞 / 收藏 / 点踩等去重场景

Set 的“元素唯一”特性非常适合这种场景:

操作Redis 命令
点赞SADD article:123 likes user1
取消点赞SREM article:123 likes user1
是否点过赞SISMEMBER article:123 likes user1
点赞人数SCARD article:123 likes
所有点赞用户SMEMBERS article:123 likes

共同关注、共同爱好(交集)

例如两个用户共同关注列表:

SINTER follows:userA follows:userB

用户标签系统(并集、差集)

并集:两个用户的所有爱好

差集:A 关注但 B 没关注的

这些计算在内存中非常快,非常适合实时推荐系统。


(2)Set 的基本操作

添加元素

SADD key member1 [member2...]

获取所有元素

SMEMBERS key

是否存在(检查成员)

SISMEMBER key member

随机弹出一个元素

SPOP key

删除成员

SREM key member1 [member2...]

(3)集合运算(Set 的强大之处)

交集(共同元素)

SINTER key1 [key2...]

并集(所有不同元素)

SUNION key1 [key2...]

差集(A 有 B 没有)

SDIFF key1 [key2...]

(4)Set 操作汇总
操作分类命令功能说明
添加SADD key m1…添加成员
删除SREM key m1…删除成员
随机弹出SPOP key移除并返回随机元素
随机返回但不移除SRANDMEMBER key [count]随机获取元素
查询所有成员SMEMBERS key获取集合全部成员
判断是否存在SISMEMBER key m检查是否是成员
长度SCARD key成员数量
移动SMOVE source dest member将元素从一个集合移动到另一个集合
交集SINTER k1 k2…返回交集
并集SUNION k1 k2…返回并集
差集SDIFF k1 k2…返回差集
交集存储SINTERSTORE dest k1 k2…保存交集到 dest
并集存储SUNIONSTORE dest k1 k2…保存并集到 dest
差集存储SDIFFSTORE dest k1 k2…保存差集到 dest
遍历SSCAN key cursor MATCH p COUNT n分批遍历集合

(5)Set 结构的常见注意点

Set 是无序的

不要依赖元素顺序。

适合存放小而多的元素

如:用户ID、标签、商品ID。

大集合建议用 SSCAN 避免一次性遍历造成阻塞

适合大并发访问,因为 Set 的写入速度非常快


(6)Java 中操作 Redis Set(Spring Data Redis 示例)

注入:

@Autowired
private StringRedisTemplate redisTemplate;

添加元素(SADD)

redisTemplate.opsForSet().add("likes:article:1001", "user1", "user2");

获取所有成员(SMEMBERS)

Set users = redisTemplate.opsForSet().members("likes:article:1001");

判断是否存在(SISMEMBER)

boolean liked = redisTemplate.opsForSet().isMember("likes:article:1001", "user1");

删除成员(SREM)

redisTemplate.opsForSet().remove("likes:article:1001", "user1");

随机弹出成员(SPOP)

String randomUser = redisTemplate.opsForSet().pop("onlineUsers");

交集/并集/差集

Set common = redisTemplate.opsForSet().intersect("follow:a", "follow:b");
Set union = redisTemplate.opsForSet().union("follow:a", "follow:b");
Set diff = redisTemplate.opsForSet().difference("setA", "setB");

5. 有序集合(Sorted Set)

Redis Sorted Set(简称 ZSet)是一种 元素唯一、有序、可按 score 排序 的集合结构。
每个成员都会绑定一个 double 类型的 score(权重),Redis 会根据 score 自动排序。

非常适合做排行榜、热度榜、权重衰减系统等。


(1)应用场景

排行榜系统

Sorted Set 最典型的应用场景:

微信步数排行榜

游戏积分排行

直播送礼物排行榜

话题热度榜(score = 热度)

常用命令:

ZINCRBY:每发生一次操作,就让分值 +1

ZREVRANGE:获取从大到小的 Top N

ZRANK / ZREVRANK:获取用户的当前排名


基于权重的排序系统

例如:

        按热度排序文章

        按活跃度排序用户

        根据时间衰减进行排序(类似知乎、微博热度)

Sorted Set 能自动保持数据有序,非常高效。


定时任务 / 延迟队列(高级用法)

score 可以用来表示“执行时间戳”,实现延迟队列。

ZADD delay_queue 1700000123 "order123"

组合多天的数据(并集/交集)

ZUNIONSTORE 7daysHot 7 day1 day2 ... day7

(2)Sorted Set 基本操作

添加或更新分数(ZADD)

ZADD key score1 member1 [score2 member2 ...]

获取元素数量(ZCARD)

ZCARD key

移除元素(ZREM)

ZREM key member

查看某成员的分数(ZSCORE)

ZSCORE key member

按 score 从低到高获取成员(ZRANGE)

ZRANGE key start stop [WITHSCORES]

按 score 从高到低获取成员(ZREVRANGE)

ZREVRANGE key start stop [WITHSCORES]

(3)获取指定成员的排名

score 从小到大的排名:ZRANK

ZRANK key member

score 从大到小的排名:ZREVRANK

ZREVRANK key member

常用于排行榜中查看用户排名


(4)交集运算(ZINTERSTORE)

用于获取多个有序集合的 共同成员,并根据 score 进行聚合。

ZINTERSTORE destination numkeys key1 key2 ...

用途示例:

多天累计排行榜(必须每天都出现)

交叉活跃用户榜


(5)并集运算(ZUNIONSTORE)

将多个有序集合按 score 合并。

ZUNIONSTORE destination numkeys key1 key2 ...

常用于:

周排行榜(7 天热度求和)

多类型权重的总评分排行(例如活跃度 + 点击数 + 关注数)


(6)差集(ZDIFF)
ZDIFF numkeys key [key...]

将第一个集合的元素减去其他集合(按 score 计算)。

使用较少,但适用于:

提取 A 排行榜中排除掉 B 中出现的人

找出“今天未活跃但昨天活跃的用户”


(7)Redis Sorted Set(有序集合)Java 示例(Spring Data Redis)

 注入:

@Autowired
private StringRedisTemplate redisTemplate;

向 Sorted Set 添加元素(ZADD)

@Autowired
private StringRedisTemplate redisTemplate;
public void addToZSet() {
    ZSetOperations zset = redisTemplate.opsForZSet();
    zset.add("rank:gift", "userA", 10.0);
    zset.add("rank:gift", "userB", 20.0);
    zset.add("rank:gift", "userC", 15.0);
}

自增分数(ZINCRBY)——排行榜经典用法

public void incrementScore(String userId) {
    redisTemplate.opsForZSet().incrementScore("rank:gift", userId, 1);
}

获取前 N 名(ZREVRANGE,从大到小)

public Set getTopN(int n) {
    return redisTemplate.opsForZSet().reverseRange("rank:gift", 0, n - 1);
}

带上分数:

public Set> getTopNWithScore(int n) {
    return redisTemplate.opsForZSet().reverseRangeWithScores("rank:gift", 0, n - 1);
}

获取指定成员排名(ZRANK / ZREVRANK)

默认 ZRANK 是从 低到高排序
排行榜一般用 ZREVRANK(高到低)

public Long getUserRank(String userId) {
    return redisTemplate.opsForZSet().reverseRank("rank:gift", userId);
}

删除成员(ZREM)

public void removeMember(String userId) {
    redisTemplate.opsForZSet().remove("rank:gift", userId);
}

获取成员的分数(ZSCORE)

public Double getUserScore(String userId) {
    return redisTemplate.opsForZSet().score("rank:gift", userId);
}

获取指定分数区间内成员(ZRANGEBYSCORE)

例如获取步数 1 万–2 万之间的用户:

public Set getByScoreRange(double min, double max) {
    return redisTemplate.opsForZSet().rangeByScore("rank:step", min, max);
}

交集(ZINTERSTORE)

Spring Data Redis 以 ZSetOperations 的 intersectAndStore 实现:

public void zsetIntersect() {
    ZSetOperations zset = redisTemplate.opsForZSet();
    // 求交集并存储到 newKey
    zset.intersectAndStore("day1:rank", List.of("day2:rank", "day3:rank"), "rank:intersect");
}

并集(ZUNIONSTORE)

public void zsetUnion() {
    ZSetOperations zset = redisTemplate.opsForZSet();
    zset.unionAndStore("day1:rank", List.of("day2:rank", "day3:rank"), "rank:union");
}

差集(ZDIFF)—Spring 6.0+

public void zsetDiff() {
    ZSetOperations zset = redisTemplate.opsForZSet();
    zset.differenceAndStore("rank:A", List.of("rank:B"), "rank:diff");
}

常见排行榜完整示例

例如直播礼物排行榜:

public class RankService {
    private static final String KEY = "rank:gift";
    @Autowired
    private StringRedisTemplate redisTemplate;
    // 礼物增加积分
    public void addGift(String userId, int score) {
        redisTemplate.opsForZSet().incrementScore(KEY, userId, score);
    }
    // 获取前10名
    public List> getTop10() {
        Set> result =
                redisTemplate.opsForZSet().reverseRangeWithScores(KEY, 0, 9);
        if (result == null) return List.of();
        List> list = new ArrayList<>();
        for (ZSetOperations.TypedTuple t : result) {
            Map m = new HashMap<>();
            m.put("userId", t.getValue());
            m.put("score", t.getScore());
            list.add(m);
        }
        return list;
    }
}

posted @ 2026-01-24 18:56  yangykaifa  阅读(7)  评论(0)    收藏  举报