🌺The Begin🌺点点关注,收藏不迷路🌺

📌 前言

Redis之所以被称为“数据结构服务器”,是因为它不仅仅支持简单的Key-Value存储,还提供了丰富的数据类型。理解这些数据类型及其底层实现,是高效使用Redis的关键。本文将全面介绍Redis的9种数据类型,深入剖析它们的底层数据结构、适用场景以及最佳实践。


一、数据类型概览

Redis数据类型

基础类型

String 字符串

Hash 哈希表

List 列表

Set 集合

ZSet 有序集合

高级类型

Bitmap 位图

HyperLogLog 基数统计

Geo 地理位置

Stream 消息流

底层实现

SDS 动态字符串

ZipList 压缩列表

QuickList 快速列表

Dict 字典

SkipList 跳表

IntSet 整数集合

数据类型对比表
类型 存储结构 最大元素 适用场景 底层实现
String 键值对 512MB 缓存、计数器、分布式锁 SDS
Hash 字段-值 2^32-1 对象存储 ZipList/Dict
List 双向链表 2^32-1 消息队列、最新列表 QuickList
Set 无序集合 2^32-1 标签、去重、交集 IntSet/Dict
ZSet 有序集合 2^32-1 排行榜、延时队列 ZipList/SkipList
Bitmap 位数组 2^32位 签到、布隆过滤器 SDS
HyperLogLog 概率统计 2^64 UV统计 特殊结构
Geo 地理位置 2^32-1 附近的人 ZSet
Stream 消息流 无限制 消息队列 Listpack+Rax

二、String(字符串)

2.1 底层实现:SDS(Simple Dynamic String)

SDS结构

len: 已用长度

alloc: 分配长度

flags: 头部类型

buf: 字符数组

// Redis SDS 结构
struct sdshdr {
    int len;      // 已使用字节数
    int alloc;    // 分配字节数
    unsigned char flags;  // SDS类型
    char buf[];   // 字符数组
};
2.2 常用命令
# 基础操作
SET user:1 "John"
GET user:1
DEL user:1

# 带过期时间
SETEX session:abc 3600 "user_data"
SET session:abc "user_data" EX 3600 NX

# 批量操作
MSET a 1 b 2 c 3
MGET a b c

# 数值操作
INCR counter
INCRBY counter 10
DECR counter
DECRBY counter 5

# 字符串操作
APPEND key " suffix"
STRLEN key
GETRANGE key 0 10
SETRANGE key 5 "new"
2.3 应用场景
场景 示例 命令
对象缓存 用户信息JSON SET user:1001 ‘{“name”:“John”}’
计数器 文章阅读量 INCR article:123:views
分布式锁 锁实现 SET lock:key value NX EX 30
Session存储 用户会话 SETEX session:token 3600 data
限流器 滑动窗口 INCR + EXPIRE
// 分布式锁最佳实践
public boolean tryLock(String key, String value, int expireSeconds) {
    Boolean result = redisTemplate.opsForValue()
        .setIfAbsent(key, value, Duration.ofSeconds(expireSeconds));
    return Boolean.TRUE.equals(result);
}

三、Hash(哈希表)

3.1 底层实现

Hash内部结构

元素<512且大小<64字节

超出阈值

Hash对象

条件判断

ZipList
压缩列表

Dict
哈希表

# Hash vs String 存储对象对比
# String方式:需要多次序列化/反序列化
SET user:1001 '{"id":1001,"name":"John","age":25}'

# Hash方式:可直接操作字段
HSET user:1001 id 1001 name John age 25
HGET user:1001 name  # 直接获取name字段
3.2 常用命令
# 基本操作
HSET user:1001 name "John" age 25
HGET user:1001 name
HGETALL user:1001

# 批量操作
HMSET user:1001 email "john@test.com" phone "123456"
HMGET user:1001 name email

# 数值操作
HINCRBY user:1001 age 1
HINCRBYFLOAT product:1 price -5.5

# 判断与删除
HEXISTS user:1001 name
HDEL user:1001 phone

# 获取所有字段/值
HKEYS user:1001
HVALS user:1001
HLEN user:1001

# 迭代(支持大数据量)
HSCAN user:1001 0 MATCH name*
3.3 应用场景
// 购物车实现
@Service
public class CartService {
    private static final String CART_KEY_PREFIX = "cart:user:";
    
    // 添加商品到购物车
    public void addToCart(Long userId, Long productId, Integer quantity) {
        String key = CART_KEY_PREFIX + userId;
        redisTemplate.opsForHash().increment(key, productId.toString(), quantity);
    }
    
    // 获取购物车所有商品
    public Map<Object, Object> getCart(Long userId) {
        String key = CART_KEY_PREFIX + userId;
        return redisTemplate.opsForHash().entries(key);
    }
    
    // 修改商品数量
    public void updateQuantity(Long userId, Long productId, Integer quantity) {
        String key = CART_KEY_PREFIX + userId;
        if (quantity <= 0) {
            redisTemplate.opsForHash().delete(key, productId.toString());
        } else {
            redisTemplate.opsForHash().put(key, productId.toString(), quantity);
        }
    }
}

四、List(列表)

4.1 底层实现

QuickList结构

Head

Node1
ZipList

Node2
ZipList

Node3
ZipList

Tail

# 列表操作方向
LPUSH queue "a"  # 左侧插入
RPUSH queue "b"  # 右侧插入
LPOP queue       # 左侧弹出
RPOP queue       # 右侧弹出
4.2 常用命令
# 插入操作
LPUSH mylist "a" "b" "c"   # 返回: 3
RPUSH mylist "x" "y" "z"   # 返回: 6

# 弹出操作
LPOP mylist                # 返回: "c"
RPOP mylist                # 返回: "z"

# 范围查询
LRANGE mylist 0 -1         # 获取所有元素
LINDEX mylist 2            # 获取索引2的元素
LLEN mylist                # 获取长度

# 阻塞操作(消息队列常用)
BLPOP queue1 queue2 10     # 阻塞10秒弹出
BRPOP queue 10

# 修剪
LTRIM mylist 0 99          # 只保留前100个

# 插入中间
LINSERT mylist BEFORE "b" "a0"
LINSERT mylist AFTER "b" "b0"

# 修改
LSET mylist 0 "new_value"

# 删除
LREM mylist 2 "a"          # 删除前2个"a"
4.3 应用场景
// 1. 最新消息列表(类似时间线)
@Service
public class TimelineService {
    
    // 发布新消息(左侧插入)
    public void postMessage(Long userId, String content) {
        String key = "timeline:user:" + userId;
        String message = userId + ":" + content;
        redisTemplate.opsForList().leftPush(key, message);
        // 保留最近1000条
        redisTemplate.opsForList().trim(key, 0, 999);
    }
    
    // 分页获取消息
    public List<String> getMessages(Long userId, int page, int size) {
        String key = "timeline:user:" + userId;
        int start = (page - 1) * size;
        int end = start + size - 1;
        return redisTemplate.opsForList().range(key, start, end);
    }
}

// 2. 简单消息队列(生产者-消费者)
@Component
public class SimpleMessageQueue {
    
    // 生产者
    public void produce(String queue, String message) {
        redisTemplate.opsForList().rightPush(queue, message);
    }
    
    // 消费者(阻塞)
    public String consume(String queue, long timeout, TimeUnit unit) {
        return redisTemplate.opsForList().leftPop(queue, timeout, unit);
    }
}

五、Set(集合)

5.1 底层实现

全部整数且<512

其他情况

Set对象

元素类型

IntSet
整数集合

Dict
字典

5.2 常用命令
# 添加删除
SADD tags "java" "redis" "mysql"   # 添加
SREM tags "mysql"                  # 删除

# 查询
SMEMBERS tags                      # 获取所有
SISMEMBER tags "java"              # 判断是否存在
SCARD tags                         # 获取数量

# 随机操作
SRANDMEMBER tags 2                 # 随机获取2个
SPOP tags 1                        # 随机弹出1个

# 集合运算
SADD set1 {1,2,3}
SADD set2 {2,3,4}

SINTER set1 set2                   # 交集 {2,3}
SUNION set1 set2                   # 并集 {1,2,3,4}
SDIFF set1 set2                    # 差集 {1}

# 存储结果
SINTERSTORE result set1 set2
SUNIONSTORE result set1 set2
SDIFFSTORE result set1 set2
5.3 应用场景
// 1. 用户标签系统
@Service
public class TagService {
    
    // 给用户打标签
    public void addTags(Long userId, String... tags) {
        String key = "user:tags:" + userId;
        redisTemplate.opsForSet().add(key, tags);
    }
    
    // 获取共同标签(好友匹配)
    public Set<String> getCommonTags(Long userId1, Long userId2) {
        String key1 = "user:tags:" + userId1;
        String key2 = "user:tags:" + userId2;
        return redisTemplate.opsForSet().intersect(key1, key2);
    }
    
    // 推荐相似用户(基于标签交集数量)
    public List<Long> recommendUsers(Long userId, int limit) {
        String userKey = "user:tags:" + userId;
        // 遍历其他用户,计算交集大小
        // ... 排序返回
    }
}

// 2. 抽奖系统
@Component
public class LotteryService {
    
    // 参与抽奖
    public void join(Long userId, String activityId) {
        String key = "lottery:" + activityId;
        redisTemplate.opsForSet().add(key, userId.toString());
    }
    
    // 随机抽取
    public Long draw(String activityId) {
        String key = "lottery:" + activityId;
        String winner = (String) redisTemplate.opsForSet().pop(key);
        return winner != null ? Long.valueOf(winner) : null;
    }
    
    // 抽取多名
    public List<Long> drawMultiple(String activityId, int count) {
        String key = "lottery:" + activityId;
        List<String> winners = redisTemplate.opsForSet().pop(key, count);
        return winners.stream().map(Long::valueOf).collect(Collectors.toList());
    }
}

六、ZSet(有序集合)

6.1 底层实现

元素<128且大小<64字节

超出阈值

SkipList

level4

level2

level1

Header

Node4

Node2

Node1

Node3

ZSet对象

条件判断

ZipList
压缩列表

SkipList + Dict
跳表 + 哈希表

6.2 常用命令
# 添加/更新
ZADD ranking 100 "player1" 95 "player2" 88 "player3"

# 查询分数
ZSCORE ranking "player1"              # 返回100
ZRANK ranking "player1"               # 返回0(排名,升序)
ZREVRANK ranking "player1"            # 返回2(降序排名)

# 范围查询(按排名)
ZRANGE ranking 0 -1                   # 升序全部
ZRANGE ranking 0 -1 WITHSCORES        # 带分数
ZREVRANGE ranking 0 2                 # 前三名

# 范围查询(按分数)
ZRANGEBYSCORE ranking 90 100          # 分数90-100
ZREVRANGEBYSCORE ranking 100 90       # 反向
ZCOUNT ranking 80 100                 # 计数

# 增加分数
ZINCRBY ranking 5 "player1"           # player1分数+5

# 删除
ZREM ranking "player1"
ZREMRANGEBYRANK ranking 0 10          # 删除0-10名
ZREMRANGEBYSCORE ranking 0 60         # 删除60分以下

# 集合运算
ZUNIONSTORE result 2 zset1 zset2 WEIGHTS 1 2
ZINTERSTORE result 2 zset1 zset2
6.3 应用场景
// 1. 排行榜系统
@Service
public class RankService {
    
    // 更新分数
    public void updateScore(String leaderboard, String userId, double score) {
        String key = "rank:" + leaderboard;
        redisTemplate.opsForZSet().add(key, userId, score);
    }
    
    // 获取前三名
    public List<RankItem> getTop3(String leaderboard) {
        String key = "rank:" + leaderboard;
        Set<ZSetOperations.TypedTuple<String>> top3 = 
            redisTemplate.opsForZSet().reverseRangeWithScores(key, 0, 2);
        
        return top3.stream()
            .map(tuple -> new RankItem(tuple.getValue(), tuple.getScore().doubleValue()))
            .collect(Collectors.toList());
    }
    
    // 获取用户排名
    public Long getUserRank(String leaderboard, String userId) {
        String key = "rank:" + leaderboard;
        Long rank = redisTemplate.opsForZSet().reverseRank(key, userId);
        return rank != null ? rank + 1 : null;
    }
    
    // 获取附近分数段用户
    public Set<String> getNearbyUsers(String leaderboard, double score, int range) {
        String key = "rank:" + leaderboard;
        return redisTemplate.opsForZSet()
            .rangeByScore(key, score - range, score + range);
    }
}

// 2. 延时队列(时间戳作为score)
@Component
public class DelayQueue {
    
    // 添加延时任务
    public void addTask(String queue, String task, long delayMillis) {
        long executeTime = System.currentTimeMillis() + delayMillis;
        String key = "delay:" + queue;
        redisTemplate.opsForZSet().add(key, task, executeTime);
    }
    
    // 获取到期任务
    public List<String> getExpiredTasks(String queue) {
        String key = "delay:" + queue;
        long now = System.currentTimeMillis();
        
        // 获取所有到期任务
        Set<String> tasks = redisTemplate.opsForZSet()
            .rangeByScore(key, 0, now);
        
        if (tasks != null && !tasks.isEmpty()) {
            // 删除已获取的任务
            redisTemplate.opsForZSet().removeRangeByScore(key, 0, now);
        }
        
        return tasks != null ? new ArrayList<>(tasks) : Collections.emptyList();
    }
}

七、高级数据类型

7.1 Bitmap(位图)
# 设置位
SETBIT sign:user:1001 0 1     # 第0天签到
SETBIT sign:user:1001 1 1     # 第1天签到

# 获取位
GETBIT sign:user:1001 0       # 返回1

# 统计
BITCOUNT sign:user:1001       # 统计签到的天数

# 位运算
BITOP AND result key1 key2    # AND运算
BITOP OR result key1 key2     # OR运算
// 签到系统
@Component
public class SignService {
    
    // 用户签到
    public boolean sign(Long userId, int dayOfYear) {
        String key = "sign:" + userId + ":" + LocalDate.now().getYear();
        return redisTemplate.opsForValue().setBit(key, dayOfYear - 1, true);
    }
    
    // 获取本月签到天数
    public long getMonthSignCount(Long userId, YearMonth yearMonth) {
        String key = "sign:" + userId + ":" + yearMonth.getYear();
        int days = yearMonth.lengthOfMonth();
        // 需要遍历计算,或用BITCOUNT
        return redisTemplate.execute((RedisCallback<Long>) connection -> 
            connection.bitCount(key.getBytes()));
    }
}
7.2 HyperLogLog(基数统计)
# 添加元素
PFADD uv:2024-01-01 "user1" "user2" "user3"

# 统计
PFCOUNT uv:2024-01-01         # 返回3

# 合并
PFMERGE uv:week uv:mon uv:tue uv:wed
// UV统计
@Service
public class UVService {
    
    // 记录用户访问
    public void recordAccess(String page, Long userId, LocalDate date) {
        String key = "uv:" + page + ":" + date;
        redisTemplate.opsForHyperLogLog().add(key, userId.toString());
    }
    
    // 获取UV数(误差约0.81%)
    public Long getUV(String page, LocalDate date) {
        String key = "uv:" + page + ":" + date;
        return redisTemplate.opsForHyperLogLog().size(key);
    }
}
7.3 Geo(地理位置)
# 添加位置
GEOADD cities 116.397128 39.916527 "Beijing"
GEOADD cities 121.480237 31.236305 "Shanghai"

# 计算距离
GEODIST cities Beijing Shanghai km    # 约1067km

# 查找附近
GEORADIUS cities 116.0 39.0 200 km
GEORADIUSBYMEMBER cities Beijing 200 km
// 附近的人
@Service
public class GeoService {
    
    // 上报位置
    public void reportLocation(Long userId, double lng, double lat) {
        String key = "location:nearby";
        redisTemplate.opsForGeo().add(key, new Point(lng, lat), userId.toString());
    }
    
    // 查找附近的人
    public List<NearbyUser> findNearby(double lng, double lat, double radius) {
        String key = "location:nearby";
        Circle circle = new Circle(new Point(lng, lat), new Distance(radius, Metrics.KILOMETERS));
        GeoResults<RedisGeoCommands.GeoLocation<String>> results = 
            redisTemplate.opsForGeo().radius(key, circle);
        
        return results.getContent().stream()
            .map(result -> new NearbyUser(
                result.getContent().getName(),
                result.getDistance().getValue()
            ))
            .collect(Collectors.toList());
    }
}
7.4 Stream(消息流)
# 添加消息
XADD mystream * name "John" age 30

# 读取消息
XRANGE mystream - +                     # 读取所有
XREAD COUNT 10 STREAMS mystream 0       # 从头读

# 消费者组
XGROUP CREATE mystream mygroup 0
XREADGROUP GROUP mygroup consumer1 COUNT 1 STREAMS mystream >

八、数据类型选择决策树

简单键值

对象字段

有序列表

无序去重

排序去重

位标记

基数统计

地理位置

消息队列

选择数据类型

数据结构?

String

Hash

List

Set

ZSet

Bitmap

HyperLogLog

Geo

Stream

缓存、计数器、锁

用户信息、购物车

消息队列、时间线

标签、抽奖、共同好友

排行榜、延时队列

签到、布隆过滤器

UV统计

附近的人

可靠消息队列


九、内存优化建议

数据类型 优化建议
String 使用整数编码,避免存储过大JSON
Hash 控制字段数,使用ziplist编码
List 使用quicklist,控制单个节点大小
Set 纯整数用intset
ZSet 小数据量用ziplist

十、面试高频问题

Q1:Redis为什么这么快?

纯内存+单线程+高效数据结构+I/O多路复用(见第15篇)

Q2:ZSet底层为什么用跳表而不是红黑树?

跳表实现简单、支持范围查询更高效、并发友好

Q3:Hash什么时候用ziplist?

字段数<512且每个字段大小<64字节

Q4:String最大能存多大?

512MB

Q5:如何选择合适的数据类型?

根据数据结构和访问模式选择(见决策树)


十一、总结

数据类型总结

基础类型

String 缓存/计数器

Hash 对象存储

List 队列/栈

Set 标签/去重

ZSet 排行榜/延时

高级类型

Bitmap 签到/布隆

HyperLogLog UV统计

Geo 附近的人

Stream 消息队列

选择原则

结构决定类型

访问模式决定

内存限制考虑

场景 推荐类型 原因
缓存对象 Hash 可单独操作字段
计数器 String INCR原子操作
排行榜 ZSet 自动排序
去重 Set 自动去重
消息队列 Stream 支持消费组
UV统计 HyperLogLog 内存占用小

如果觉得本文对你有帮助,欢迎点赞、收藏、评论三连支持!

在这里插入图片描述


🌺The End🌺点点关注,收藏不迷路🌺

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐