Redis 数据类型详解
Redis 数据类型详解
一、Redis 数据类型概览
Redis 不仅仅是一个简单的 Key-Value 存储,它实际上是一个数据结构服务器,支持多种类型的数据结构。这使得 Redis 能够适应各种不同的应用场景。
1.1 底层数据结构
Redis 的每种数据类型都有多种底层实现,Redis 会根据数据的实际情况自动选择最优的编码方式:
| 数据类型 | 底层数据结构 |
|---|---|
| String | int(整数)/ embstr(短字符串)/ raw(长字符串) |
| List | quicklist(快速列表) |
| Hash | ziplist(压缩列表)/ hashtable(哈希表) |
| Set | intset(整数集合)/ hashtable(哈希表) |
| Sorted Set | ziplist(压缩列表)/ skiplist(跳表) |
# 查看 key 的底层编码
OBJECT ENCODING key
二、String 字符串
2.1 基本特性
String 是 Redis 最基础、最常用的数据类型。它是二进制安全的,可以存储任何数据,比如文字、图片、序列化对象等。
- 最大存储容量:512MB
- 底层实现:SDS(Simple Dynamic String)
- 编码方式:
int:8 字节长整型embstr:小于等于 44 字节的字符串raw:大于 44 字节的字符串
# 验证编码类型
SET num 12345
OBJECT ENCODING num
# "int"
SET short_str "hello"
OBJECT ENCODING short_str
# "embstr"
SET long_str "this is a very long string that exceeds 44 bytes limit..."
OBJECT ENCODING long_str
# "raw"
2.2 常用命令详解
基本读写操作
# 设置值
SET key value [EX seconds] [PX milliseconds] [NX|XX]
# EX: 过期时间(秒)
# PX: 过期时间(毫秒)
# NX: 仅当 key 不存在时设置
# XX: 仅当 key 存在时设置
# 示例
SET name "redis"
SET name "redis" EX 3600 # 1小时过期
SET lock:order:1001 "1" EX 10 NX # 分布式锁常用
# 获取值
GET key
# 获取并设置新值
GETSET key newvalue # Redis 6.2 之前
GETDEL key # 获取并删除(Redis 6.2+)
GETEX key EX 100 # 获取并设置过期时间(Redis 6.2+)
# 批量操作(原子性)
MSET key1 value1 key2 value2 key3 value3
MGET key1 key2 key3
# 仅当所有 key 都不存在时设置
MSETNX key1 value1 key2 value2
字符串操作
# 追加字符串
APPEND key " world"
# 返回追加后的字符串长度
# 获取字符串长度
STRLEN key
# 获取子字符串(闭区间)
GETRANGE key start end
GETRANGE name 0 2 # 前3个字符
GETRANGE name -3 -1 # 后3个字符
# 覆盖字符串
SETRANGE key offset value
SET greeting "hello world"
SETRANGE greeting 6 "redis" # "hello redis"
数值操作
# 自增/自减
INCR key # +1
DECR key # -1
INCRBY key 10 # +10
DECRBY key 5 # -5
INCRBYFLOAT key 0.5 # +0.5
# 如果 key 不存在,先初始化为 0
SET counter 100
INCR counter # 101
INCR newkey # 1(初始化为0后+1)
# 注意:字符串必须能转换为数字
SET str "hello"
INCR str # 报错:ERR value is not an integer or out of range
2.3 应用场景
场景 1:缓存对象
# 方式1:JSON 序列化
SET user:1001 '{"name":"张三","age":25,"email":"zhangsan@example.com"}'
# 方式2:配合 Hash 使用(推荐,便于部分更新)
# 后面 Hash 章节详细介绍
场景 2:计数器
# 文章阅读量
INCR article:1001:views
# 网站 PV/UV
INCR site:pv:20240115
PFADD site:uv:20240115 "user_id_xxx"
# 限流计数
SET rate:user:1001 0 EX 60
INCR rate:user:1001
场景 3:分布式锁
# 加锁(NX 保证互斥,EX 防止死锁)
SET lock:resource:1001 "owner_id" EX 30 NX
# 解锁(需要用 Lua 保证原子性,后续章节详细介绍)
场景 4:Session 存储
# 存储 Session
SETEX session:abc123 1800 '{"userId":1001,"role":"admin"}'
# 获取 Session
GET session:abc123
# 续期
EXPIRE session:abc123 1800
2.4 位图操作(Bitmap)
Bitmap 本质上是 String 类型,但提供了位操作的命令,非常适合存储状态信息。
# 设置位(offset 从 0 开始)
SETBIT key offset value
SETBIT user:1001:sign 0 1 # 第1天签到
SETBIT user:1001:sign 1 1 # 第2天签到
SETBIT user:1001:sign 6 1 # 第7天签到
# 获取位
GETBIT key offset
GETBIT user:1001:sign 0 # 1
# 统计值为 1 的位数
BITCOUNT key [start end]
BITCOUNT user:1001:sign # 3(签到3天)
# 查找第一个设置为指定值的位
BITPOS key bit [start end]
BITPOS user:1001:sign 0 # 第一个未签到的日期
# 位运算
BITOP AND destkey key1 key2 # 与
BITOP OR destkey key1 key2 # 或
BITOP XOR destkey key1 key2 # 异或
BITOP NOT destkey key # 非
Bitmap 应用:用户签到
# 用户签到(按月存储)
# key: sign:{userId}:{yearMonth}
# offset: 日期 - 1(0-30)
# 用户1001在2024年1月15日签到
SETBIT sign:1001:202401 14 1
# 查询签到状态
GETBIT sign:1001:202401 14 # 1 已签到
# 统计月签到次数
BITCOUNT sign:1001:202401
# 获取连续签到天数(需要遍历)
# 使用 BITFIELD 命令更高效
Bitmap 应用:用户在线状态
# 记录用户在线状态
SETBIT online:users 1001 1 # 用户1001上线
SETBIT online:users 1002 1 # 用户1002上线
SETBIT online:users 1001 0 # 用户1001下线
# 查询是否在线
GETBIT online:users 1001
# 统计在线人数
BITCOUNT online:users
# 存储优化:
# 1亿用户只需要 100000000 / 8 / 1024 / 1024 ≈ 12MB
三、List 列表
3.1 基本特性
List 是一个双向链表结构,可以在头部或尾部快速插入和删除元素。
- 最大元素数量:2^32 - 1(约 42 亿)
- 底层实现:quicklist(Redis 3.2+)
- 插入和删除:O(1)
- 按索引访问:O(N)
# 查看底层编码
OBJECT ENCODING mylist
# "quicklist"
3.2 常用命令详解
添加元素
# 从左边添加(头部)
LPUSH key value1 value2 value3
# 结果:value3 -> value2 -> value1
# 从右边添加(尾部)
RPUSH key value1 value2 value3
# 结果:value1 -> value2 -> value3
# 仅当 list 存在时添加
LPUSHX key value
RPUSHX key value
# 在指定元素前/后插入
LINSERT key BEFORE pivot value
LINSERT key AFTER pivot value
RPUSH mylist "a" "b" "c" # a -> b -> c
LINSERT mylist BEFORE "b" "x" # a -> x -> b -> c
获取元素
# 通过索引获取(0 表示第一个,-1 表示最后一个)
LINDEX key index
LINDEX mylist 0 # 第一个
LINDEX mylist -1 # 最后一个
# 获取范围(闭区间)
LRANGE key start stop
LRANGE mylist 0 -1 # 获取所有
LRANGE mylist 0 9 # 获取前10个
# 获取列表长度
LLEN key
删除元素
# 从左边弹出
LPOP key [count]
LPOP mylist # 弹出1个
LPOP mylist 3 # 弹出3个(Redis 6.2+)
# 从右边弹出
RPOP key [count]
# 删除指定元素
LREM key count value
# count > 0: 从左到右删除 count 个
# count < 0: 从右到左删除 |count| 个
# count = 0: 删除所有
RPUSH mylist "a" "b" "a" "c" "a"
LREM mylist 2 "a" # 从左删除2个"a",结果:b -> c -> a
LREM mylist -1 "a" # 从右删除1个"a",结果:b -> c
LREM mylist 0 "a" # 删除所有"a"
# 保留指定范围(修剪)
LTRIM key start stop
LTRIM mylist 0 99 # 只保留前100个
修改元素
# 通过索引设置值
LSET key index value
LSET mylist 0 "new_value"
阻塞操作
# 阻塞弹出(常用于消息队列)
BLPOP key [key ...] timeout
BRPOP key [key ...] timeout
# timeout: 阻塞超时时间(秒),0 表示无限等待
# 示例:消息队列消费者
BRPOP task:queue 30 # 等待30秒
# 从一个 list 弹出并推入另一个 list
RPOPLPUSH source destination
BRPOPLPUSH source destination timeout # 阻塞版本
# Redis 6.2+ 新命令
LMOVE source destination LEFT|RIGHT LEFT|RIGHT
BLMOVE source destination LEFT|RIGHT LEFT|RIGHT timeout
3.3 应用场景
场景 1:消息队列
# 生产者:添加任务
LPUSH task:queue '{"taskId":1001,"type":"sendEmail","data":{...}}'
# 消费者:阻塞获取任务
BRPOP task:queue 0
# 可靠消息队列(弹出时备份)
BRPOPLPUSH task:queue task:processing 0
# 处理完成后从 processing 删除
LREM task:processing 1 '{"taskId":1001,...}'
场景 2:最新消息列表
# 添加消息(保持最新的在前面)
LPUSH msg:timeline:user1001 '{"msgId":1,"content":"hello"}'
# 获取最新10条消息
LRANGE msg:timeline:user1001 0 9
# 修剪列表,只保留最新1000条
LTRIM msg:timeline:user1001 0 999
场景 3:排行榜历史记录
# 记录用户排名历史
LPUSH rank:history:user1001 100
LPUSH rank:history:user1001 95
LPUSH rank:history:user1001 88
# 获取最近10次排名
LRANGE rank:history:user1001 0 9
四、Hash 哈希
4.1 基本特性
Hash 是一个键值对集合,特别适合存储对象。相比将对象序列化为 JSON 字符串存储,Hash 可以单独获取或修改某个字段,更加灵活高效。
- 最大字段数量:2^32 - 1
- 底层实现:
- ziplist(压缩列表):字段数少、值小时使用
- hashtable(哈希表):字段数多或值大时使用
- 转换阈值(可配置):
hash-max-ziplist-entries 512:字段数超过 512hash-max-ziplist-value 64:值长度超过 64 字节
# 查看底层编码
HSET test field value
OBJECT ENCODING test
# "ziplist" 或 "hashtable"
4.2 常用命令详解
设置字段
# 设置单个字段
HSET key field value
HSET user:1001 name "张三"
# 设置多个字段(Redis 4.0+)
HSET key field1 value1 field2 value2 ...
HSET user:1001 name "张三" age 25 email "zhangsan@example.com"
# 旧版本批量设置
HMSET key field1 value1 field2 value2 ...
# 仅当字段不存在时设置
HSETNX key field value
HSETNX user:1001 name "李四" # 如果 name 已存在,不会覆盖
获取字段
# 获取单个字段
HGET key field
HGET user:1001 name
# 获取多个字段
HMGET key field1 field2 ...
HMGET user:1001 name age email
# 获取所有字段和值
HGETALL key
HGETALL user:1001
# 返回:name, 张三, age, 25, email, zhangsan@example.com
# 获取所有字段名
HKEYS key
# 获取所有字段值
HVALS key
# 获取字段数量
HLEN key
# 判断字段是否存在
HEXISTS key field
HEXISTS user:1001 name # 1 存在,0 不存在
删除字段
# 删除字段
HDEL key field [field ...]
HDEL user:1001 age email
数值操作
# 整数自增
HINCRBY key field increment
HSET user:1001 score 100
HINCRBY user:1001 score 10 # 110
# 浮点数自增
HINCRBYFLOAT key field increment
HINCRBYFLOAT user:1001 balance 0.5
字符串操作
# 获取字段值长度
HSTRLEN key field
HSTRLEN user:1001 name
迭代遍历
# 增量迭代(大数据量时使用)
HSCAN key cursor [MATCH pattern] [COUNT count]
HSCAN user:1001 0 MATCH "a*" COUNT 10
4.3 应用场景
场景 1:对象存储
# 用户信息
HSET user:1001 name "张三" age 25 email "zhangsan@example.com" status 1
# 获取用户信息
HGETALL user:1001
# 修改单个字段
HSET user:1001 age 26
# 对比 String 存储
# String: SET user:1001 '{"name":"张三","age":25,...}'
# 每次修改都需要全量替换,Hash 更灵活
场景 2:购物车
# 添加商品(field=商品ID,value=数量)
HSET cart:user:1001 product:2001 2
HSET cart:user:1001 product:2002 1
# 增加数量
HINCRBY cart:user:1001 product:2001 1
# 减少数量
HINCRBY cart:user:1001 product:2001 -1
# 删除商品
HDEL cart:user:1001 product:2001
# 获取购物车所有商品
HGETALL cart:user:1001
# 获取商品种类数
HLEN cart:user:1001
场景 3:计数器集合
# 文章统计
HSET article:1001:stats views 0 likes 0 comments 0
# 增加阅读量
HINCRBY article:1001:stats views 1
# 增加点赞
HINCRBY article:1001:stats likes 1
# 获取所有统计
HGETALL article:1001:stats
五、Set 集合
5.1 基本特性
Set 是一个无序的、不重复的字符串集合。它支持集合间的交集、并集、差集运算,非常适合处理好友关系、标签等场景。
- 最大元素数量:2^32 - 1
- 底层实现:
- intset(整数集合):元素都是整数且数量较少时
- hashtable(哈希表):其他情况
- 转换阈值:
set-max-intset-entries 512:元素数超过 512
# 查看底层编码
SADD intset 1 2 3 4 5
OBJECT ENCODING intset
# "intset"
SADD strset "a" "b" "c"
OBJECT ENCODING strset
# "hashtable"
5.2 常用命令详解
添加和删除
# 添加元素
SADD key member [member ...]
SADD tags:1001 "java" "redis" "mysql"
# 删除元素
SREM key member [member ...]
SREM tags:1001 "mysql"
# 随机弹出元素
SPOP key [count]
SPOP tags:1001 1
# 随机返回元素(不删除)
SRANDMEMBER key [count]
SRANDMEMBER tags:1001 2
# count > 0: 返回 count 个不重复元素
# count < 0: 返回 |count| 个可能重复的元素
查询操作
# 获取所有元素
SMEMBERS key
SMEMBERS tags:1001
# 判断元素是否存在
SISMEMBER key member
SISMEMBER tags:1001 "java" # 1 存在,0 不存在
# 批量判断(Redis 6.2+)
SMISMEMBER key member [member ...]
SMISMEMBER tags:1001 "java" "python" "go"
# 获取元素数量
SCARD key
SCARD tags:1001
集合运算
# 假设有两个集合
SADD set1 "a" "b" "c" "d"
SADD set2 "c" "d" "e" "f"
# 交集
SINTER key [key ...]
SINTER set1 set2 # c, d
# 交集并存储
SINTERSTORE destination key [key ...]
SINTERSTORE set3 set1 set2
# 并集
SUNION key [key ...]
SUNION set1 set2 # a, b, c, d, e, f
# 并集并存储
SUNIONSTORE destination key [key ...]
# 差集(set1 - set2)
SDIFF key [key ...]
SDIFF set1 set2 # a, b
# 差集并存储
SDIFFSTORE destination key [key ...]
移动元素
# 将元素从一个集合移动到另一个
SMOVE source destination member
SMOVE set1 set2 "a"
5.3 应用场景
场景 1:标签系统
# 给文章添加标签
SADD article:1001:tags "java" "redis" "database"
SADD article:1002:tags "java" "spring" "microservice"
# 查看文章标签
SMEMBERS article:1001:tags
# 查找同时包含 java 和 redis 标签的文章
# 需要维护反向索引
SADD tag:java:articles 1001 1002
SADD tag:redis:articles 1001
SINTER tag:java:articles tag:redis:articles
场景 2:好友关系
# 添加好友
SADD friend:user:1001 1002 1003 1004
SADD friend:user:1002 1001 1003 1005
# 共同好友
SINTER friend:user:1001 friend:user:1002
# 1003
# 我关注的人也关注了他(推荐好友)
# 1001 的好友中,1002 也关注的人
SINTER friend:user:1001 friend:user:1002
# 我可能认识的人(好友的好友)
SUNION friend:user:1002 friend:user:1003
场景 3:抽奖系统
# 添加参与用户
SADD lottery:1001 user1 user2 user3 user4 user5
# 随机抽取3名中奖用户(不重复)
SRANDMEMBER lottery:1001 3
# 抽取并移除(不能重复中奖)
SPOP lottery:1001 3
# 查看剩余参与者
SMEMBERS lottery:1001
场景 4:用户访问去重
# 记录今日访问用户
SADD visit:20240115 user1 user2 user3
# 统计今日访问用户数
SCARD visit:20240115
# 检查用户是否访问过
SISMEMBER visit:20240115 user1
六、Sorted Set 有序集合
6.1 基本特性
Sorted Set(ZSet)是 Set 的升级版,每个元素关联一个分数(score),元素按分数排序。它兼具 Set 的去重特性和排序功能,是 Redis 最强大的数据结构之一。
- 最大元素数量:2^32 - 1
- 底层实现:
- ziplist(压缩列表):元素少且值小时
- skiplist + hashtable:其他情况
- 转换阈值:
zset-max-ziplist-entries 128zset-max-ziplist-value 64
# 查看底层编码
ZADD test 1 "a" 2 "b"
OBJECT ENCODING test
# "ziplist" 或 "skiplist"
6.2 跳表结构简介
跳表(Skip List)是 Redis 有序集合的核心数据结构,它通过多层索引实现快速查找:
查找过程(查找7):
- 从最高层开始,1 → 9,超过了,下降
- Level 2: 1 → 4 → 9,超过了,下降
- Level 1: 4 → 6 → 9,超过了,下降
- Level 0: 6 → 7,找到
跳表的时间复杂度:
- 插入、删除、查找:O(log N)
- 范围查询:O(log N + M),M 为返回元素数量
6.3 常用命令详解
添加元素
# 添加元素
ZADD key [NX|XX] [GT|LT] [CH] [INCR] score member [score member ...]
# NX: 仅当成员不存在时添加
# XX: 仅当成员存在时更新
# GT: 仅当新分数大于当前分数时更新
# LT: 仅当新分数小于当前分数时更新
# CH: 返回变更的成员数量(默认返回新增数量)
# INCR: 对分数进行增加操作
ZADD ranking 100 user1 200 user2 150 user3
# 仅添加不存在的
ZADD ranking NX 300 user4
# 仅更新已存在的
ZADD ranking XX 250 user2
# 增加分数
ZADD ranking INCR 50 user1
# 或使用专门的命令
ZINCRBY ranking 50 user1
删除元素
# 删除指定元素
ZREM key member [member ...]
ZREM ranking user1 user2
# 按排名范围删除(排名从0开始)
ZREMRANGEBYRANK key start stop
ZREMRANGEBYRANK ranking 0 2 # 删除排名前3的
# 按分数范围删除
ZREMRANGEBYSCORE key min max
ZREMRANGEBYSCORE ranking 0 100 # 删除分数0-100的
# 弹出最高/最低分元素
ZPOPMAX key [count]
ZPOPMIN key [count]
BZPOPMAX key [key ...] timeout # 阻塞版本
BZPOPMIN key [key ...] timeout
查询元素
# 获取分数
ZSCORE key member
ZSCORE ranking user1
# 获取排名(从低到高,0开始)
ZRANK key member
ZRANK ranking user1
# 获取排名(从高到低)
ZREVRANK key member
# 获取元素数量
ZCARD key
# 统计分数范围内的元素数量
ZCOUNT key min max
ZCOUNT ranking 100 200
ZCOUNT ranking (100 200 # 不包含100
ZCOUNT ranking 100 (200 # 不包含200
ZCOUNT ranking -inf +inf # 所有
范围查询
# 按排名范围获取(从低到高)
ZRANGE key start stop [WITHSCORES]
ZRANGE ranking 0 9 WITHSCORES # 前10名
# 按排名范围获取(从高到低)
ZREVRANGE key start stop [WITHSCORES]
ZREVRANGE ranking 0 9 WITHSCORES # 前10名(从高到低)
# 按分数范围获取
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
ZRANGEBYSCORE ranking 100 200 WITHSCORES
ZRANGEBYSCORE ranking 100 200 WITHSCORES LIMIT 0 10
# 按分数范围获取(从高到低)
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
集合运算
# 交集
ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
ZADD set1 1 "a" 2 "b" 3 "c"
ZADD set2 10 "b" 20 "c" 30 "d"
ZINTERSTORE result 2 set1 set2 AGGREGATE SUM
# result: b=12, c=23
# 并集
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
ZUNIONSTORE result 2 set1 set2 WEIGHTS 1 2
# 权重:set1的分数*1,set2的分数*2
6.4 应用场景
场景 1:排行榜
# 添加用户分数
ZADD game:ranking 1000 user1 2000 user2 1500 user3
# 更新分数
ZINCRBY game:ranking 500 user1
# 获取排行榜前10
ZREVRANGE game:ranking 0 9 WITHSCORES
# 获取用户排名
ZREVRANK game:ranking user1
# 获取用户分数
ZSCORE game:ranking user1
# 获取指定分数段的用户
ZRANGEBYSCORE game:ranking 1000 2000 WITHSCORES
场景 2:延迟队列
# 添加延迟任务(分数为执行时间戳)
ZADD delay:queue 1705305600 '{"taskId":1001,"data":{...}}'
ZADD delay:queue 1705306200 '{"taskId":1002,"data":{...}}'
# 轮询获取到期任务
ZRANGEBYSCORE delay:queue 0 1705305600
# 执行后移除
ZREM delay:queue '{"taskId":1001,"data":{...}}'
# 更安全的方式:使用 Lua 脚本原子操作
场景 3:带权重的标签
# 用户标签(权重表示偏好程度)
ZADD user:1001:tags 10 "java" 8 "redis" 5 "mysql" 3 "python"
# 获取用户最感兴趣的标签
ZREVRANGE user:1001:tags 0 2 WITHSCORES
# 增加标签权重
ZINCRBY user:1001:tags 2 "python"
场景 4:时间线
# 添加消息(分数为时间戳)
ZADD timeline:user:1001 1705305600 msg1 1705306200 msg2 1705306800 msg3
# 获取最新消息
ZREVRANGE timeline:user:1001 0 9 WITHSCORES
# 获取某时间范围的消息
ZRANGEBYSCORE timeline:user:1001 1705305000 1705307000
# 删除旧消息(保留最近1000条)
ZREMRANGEBYRANK timeline:user:1001 0 -1001
七、高级数据类型
7.1 HyperLogLog
HyperLogLog 是一种概率数据结构,用于基数(不重复元素数量)统计。它使用极少的内存(约 12KB)就可以统计 2^64 个元素的基数,但有约 0.81% 的标准误差。
# 添加元素
PFADD key element [element ...]
PFADD page:uv:20240115 user1 user2 user3
# 获取基数
PFCOUNT key [key ...]
PFCOUNT page:uv:20240115
# 合并多个 HyperLogLog
PFMERGE destkey sourcekey [sourcekey ...]
PFMERGE page:uv:202401 page:uv:20240101 page:uv:20240102 ... page:uv:20240131
应用场景:UV 统计
# 每日 UV
PFADD uv:20240115 user1
PFADD uv:20240115 user2
PFADD uv:20240115 user1 # 重复添加不影响
# 获取每日 UV
PFCOUNT uv:20240115
# 获取月 UV(合并每日数据)
PFMERGE uv:202401 uv:20240101 uv:20240102 ... uv:20240131
PFCOUNT uv:202401
# 对比 Set 实现
# Set: 1000万用户 = 约 80MB(假设 userId 8字节)
# HyperLogLog: 固定 12KB,误差 0.81%
7.2 Geo 地理位置
Geo 类型用于存储地理位置信息,底层使用 Sorted Set 实现,将经纬度编码为 GeoHash 值作为分数。
# 添加地理位置
GEOADD key longitude latitude member [longitude latitude member ...]
GEOADD stores 116.405285 39.904989 "北京店" 121.472644 31.231706 "上海店"
# 获取位置
GEOPOS key member [member ...]
GEOPOS stores "北京店"
# 1) "116.40528291463851929"
# 2) "39.9049884229125027"
# 获取 GeoHash 值
GEOHASH key member [member ...]
# 计算两点距离
GEODIST key member1 member2 [m|km|mi|ft]
GEODIST stores "北京店" "上海店" km
# "1067.3788"
# 搜索半径范围内的成员
GEORADIUS key longitude latitude radius m|km|mi|ft [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC]
GEORADIUS stores 116.405285 39.904989 100 km WITHDIST COUNT 10
# 搜索成员附近(Redis 3.2+)
GEORADIUSBYMEMBER key member radius m|km|mi|ft [WITHCOORD] [WITHDIST] [COUNT count]
# Redis 6.2+ 新命令
GEOSEARCH key FROMMEMBER member | FROMLONLAT longitude latitude BYRADIUS radius | BYBOX width height [ASC|DESC] [COUNT count] [WITHCOORD] [WITHDIST] [WITHHASH]
应用场景:附近的人/店
# 添加商店位置
GEOADD shops 116.48105 39.996794 "shop1" 116.514203 39.905409 "shop2"
# 查找5km内的商店
GEORADIUS shops 116.489033 39.902332 5 km WITHDIST WITHCOORD ASC COUNT 10
# 查找某商店附近的商店
GEORADIUSBYMEMBER shops "shop1" 10 km WITHDIST
7.3 Stream 消息流
Stream 是 Redis 5.0 引入的数据类型,专门用于消息队列场景。它支持消费者组、消息确认等特性,是 Redis 作为消息队列的最佳选择。
# 添加消息
XADD key [MAXLEN [~] count] *|ID field value [field value ...]
XADD mystream * name "张三" action "login"
# 返回消息ID,如 "1705305600000-0"
# 限制消息数量
XADD mystream MAXLEN ~ 1000 * name "李四" action "logout"
# 获取消息数量
XLEN mystream
# 读取消息
XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] ID [ID ...]
XREAD COUNT 10 STREAMS mystream 0 # 从头读取
XREAD COUNT 10 STREAMS mystream $ # 只读取新消息
XREAD COUNT 10 BLOCK 0 STREAMS mystream $ # 阻塞读取新消息
# 范围读取
XRANGE key start end [COUNT count]
XRANGE mystream - + # 所有消息
XRANGE mystream - + COUNT 10 # 前10条
# 逆序范围读取
XREVRANGE key end start [COUNT count]
消费者组
# 创建消费者组
XGROUP CREATE mystream mygroup $ MKSTREAM
# $: 从最新消息开始消费
# 0: 从头开始消费
# 消费者读取消息
XREADGROUP GROUP mygroup consumer1 COUNT 10 BLOCK 2000 STREAMS mystream >
# > 表示读取未被消费的消息
# 确认消息
XACK mystream mygroup 1705305600000-0
# 查看待确认消息
XPENDING mystream mygroup
# 转移超时消息给其他消费者
XCLAIM mystream mygroup consumer2 60000 1705305600000-0
应用场景:消息队列
// 生产者
String messageId = jedis.xadd("orders", "*",
Map.of("orderId", "1001", "userId", "2001", "amount", "100"));
// 消费者(消费者组)
List<StreamEntry> messages = jedis.xreadGroup(
"orderGroup",
"consumer1",
1, // count
2000, // block ms
false, // noAck
new StreamEntryID(">"),
"orders"
);
for (StreamEntry entry : messages) {
// 处理消息
processOrder(entry.getFields());
// 确认消息
jedis.xack("orders", "orderGroup", entry.getID());
}
八、数据类型选择指南
8.1 选择决策树
8.2 性能对比
| 操作类型 | String | List | Hash | Set | Sorted Set |
|---|---|---|---|---|---|
| 读取 | O(1) | O(N) | O(1) | O(1) | O(log N) |
| 写入 | O(1) | O(1) | O(1) | O(1) | O(log N) |
| 删除 | O(1) | O(N) | O(1) | O(1) | O(log N) |
| 范围查询 | - | O(N) | - | - | O(log N + M) |
| 集合运算 | - | - | - | O(N*M) | O(NK + Mlog M) |
8.3 内存占用
# 查看 key 占用内存
MEMORY USAGE key [SAMPLES count]
MEMORY USAGE user:1001
# 查看整体内存情况
INFO memory
# 内存优化建议:
# 1. 使用合适的数据类型
# 2. 控制 key 和 value 的大小
# 3. 设置合理的过期时间
# 4. 使用压缩列表(ziplist)
# 5. 避免大 key
九、总结
本章详细介绍了 Redis 的数据类型:
- String:最基础的类型,支持字符串、数值、位图操作
- List:双向链表,适合消息队列、最新列表
- Hash:键值对集合,适合存储对象
- Set:无序不重复集合,支持集合运算
- Sorted Set:带分数的有序集合,适合排行榜
- HyperLogLog:基数统计,内存固定
- Geo:地理位置,支持距离计算和范围查询
- Stream:消息流,专业的消息队列实现
选择合适的数据类型是使用 Redis 的关键,需要根据业务场景、性能要求、内存预算等因素综合考虑。