Redis 数据类型与使用场景
一、Redis 简介
1.1 基本概念
Redis(Remote Dictionary Server)是一款开源的、基于内存的高性能键值对(Key-Value)存储数据库,由 Salvatore Sanfilippo 于2009年开发。它采用ANSI C语言编写,支持网络访问,并提供多种语言的客户端API。
1.2 核心特性
- 高性能:Redis将数据存储在内存中,读写速度极快,官方基准测试显示Redis可以达到10万次/秒的读写操作
- 丰富的数据结构:支持字符串(String)、哈希(Hash)、列表(List)、集合(Set)、有序集合(ZSet)等多种数据结构
- 持久化支持:提供RDB(快照)和AOF(追加日志)两种持久化机制,确保数据安全
- 高可用:通过Redis Sentinel实现故障转移,Redis Cluster提供分片功能
- 发布订阅:内置消息队列功能,支持发布/订阅模式
1.3 典型应用场景
- 缓存系统:作为MySQL等关系型数据库的前置缓存,减轻数据库压力
- 示例:电商网站商品详情页缓存
- 会话存储:存储用户会话信息,实现分布式会话管理
- 示例:用户登录状态保持
- 实时排行榜:利用有序集合实现实时排名
- 示例:游戏玩家积分排行榜
- 消息队列:使用List结构实现简单的消息队列
- 示例:订单处理队列
- 计数器系统:原子性操作实现精准计数
- 示例:网站PV/UV统计
1.4 版本演进
- Redis 2.8:引入部分复制功能
- Redis 3.0:正式支持集群模式
- Redis 4.0:新增模块系统、混合持久化等特性
- Redis 5.0:新增Stream数据类型
- Redis 6.0:支持多线程I/O
1.5 安装与部署
Redis支持多种部署方式:
- 单机模式(开发测试)
- 主从复制(读写分离)
- Sentinel模式(高可用)
- Cluster模式(分布式)
1.6 性能对比
与传统关系型数据库相比:
- 读写速度:Redis > MySQL
- 数据结构灵活性:Redis > MySQL
- 数据一致性:MySQL > Redis
- 存储容量:MySQL > Redis
Redis特别适合处理高并发、低延迟的应用场景,是构建现代互联网应用的重要基础设施之一。
二、Redis 核心数据类型详解
(一)String(字符串)
1. 数据结构特性
String 是 Redis 中最基本的数据类型,它可以存储字符串、整数和浮点数。一个 String 类型的 value 最大可以存储 512MB 的数据。String 类型的数据结构简单,操作直观,是 Redis 中使用频率非常高的数据类型之一。
2. 常用命令
- SET key value:设置指定 key 的值。例如,SET username "zhangsan",表示将 key 为 username 的值设置为 "zhangsan"。
- GET key:获取指定 key 的值。如果 key 不存在,返回 nil。比如,GET username,若之前设置过 username 的值为 "zhangsan",则返回 "zhangsan"。
- INCR key:将 key 中存储的数字值加 1。如果 key 不存在,那么初始值为 0,执行 INCR 后值为 1;如果 key 存储的不是数字,则返回错误。例如,INCR user_count,可用于统计用户数量的递增。
- DECR key:将 key 中存储的数字值减 1,用法与 INCR 类似,只是操作是递减。
- APPEND key value:将 value 追加到 key 原来的值的末尾。例如,APPEND username "123",若之前 username 的值是 "zhangsan",执行后变为 "zhangsan123"。
- STRLEN key:获取 key 所存储的字符串值的长度。如STRLEN username,可得到 "zhangsan123" 的长度为 9。
3. 使用场景
- 缓存数据:将频繁访问的数据(如数据库中的热门商品信息、用户基本信息等)存储在 String 类型中,当用户再次访问时,直接从 Redis 中获取,减少数据库的访问压力,提高系统响应速度。例如,缓存商品详情,key 为 "product:1001",value 为商品的 JSON 字符串。
- 计数器:利用 INCR 和 DECR 命令实现计数器功能,如统计网站的访问量、文章的阅读数、商品的销量等。比如,统计某篇文章的阅读数,key 为 "article:read:2001",每有一次阅读就执行INCR article:read:2001。
- 存储会话信息:在 Web 应用中,将用户的会话信息(如登录状态、权限信息等)存储在 String 类型中,key 为用户的会话 ID,value 为会话数据的 JSON 字符串,实现分布式系统中的会话共享。
(二)Hash(哈希)
1. 数据结构特性
Hash 类型类似于 Java 中的 HashMap,它是一个键值对的集合,其中 value 又是一个键值对(field - value)的结构。Hash 类型适合存储对象类的数据,能够方便地对对象的某个字段进行操作,而无需修改整个对象的数据。
2. 常用命令
- HSET key field value:将哈希表 key 中的字段 field 的值设为 value。如果 key 不存在,创建一个新的哈希表并进行 HSET 操作;如果 field 已经存在于哈希表中,旧值将被覆盖。例如,HSET user:100 name "zhangsan" age "25" gender "male",表示创建一个 key 为 user:100 的哈希表,包含 name、age、gender 三个字段及对应的值。
- HGET key field:获取哈希表 key 中指定字段 field 的值。如HGET user:100 name,返回 "zhangsan"。
- HGETALL key:获取哈希表 key 中所有的字段和值。执行HGETALL user:100,将返回 name "zhangsan"、age "25"、gender "male" 这些字段和对应的值。
- HDEL key field1 [field2...]:删除哈希表 key 中的一个或多个指定字段,不存在的字段将被忽略。例如,HDEL user:100 gender,删除 user:100 哈希表中的 gender 字段。
- HLEN key:获取哈希表 key 中字段的数量。HLEN user:100,在删除 gender 字段后,返回 2。
- HINCRBY key field increment:为哈希表 key 中的指定字段 field 的值加上增量 increment。增量可以是正数或负数。比如,HINCRBY user:100 age 1,将 user:100 哈希表中 age 字段的值增加 1,从 25 变为 26。
3. 使用场景
- 存储对象信息:Hash 类型非常适合存储对象,如用户信息、商品信息、订单信息等。以存储用户信息为例,key 为用户 ID(如 user:1001),field 为用户的各个属性(如 name、age、email、address 等),value 为对应属性的值。这样在更新用户的某个属性时,只需使用 HSET 命令修改对应的 field,无需更新整个用户对象,操作更加高效。
- 购物车:在电商平台中,购物车可以用 Hash 类型来实现。key 为用户 ID(如 cart:1001),field 为商品 ID(如 product:2001),value 为商品的数量。当用户添加商品到购物车时,使用 HSET 命令设置对应的 field 和 value;修改商品数量时,使用 HINCRBY 命令;删除商品时,使用 HDEL 命令;查看购物车所有商品时,使用 HGETALL 命令。
(三)List(列表)
1. 数据结构特性
Redis 的 List 类型是一个双向链表结构,它可以存储有序的字符串元素,支持在链表的两端进行插入和删除操作,并且可以根据索引获取元素。List 类型的底层实现在元素数量较少时使用 ziplist(压缩列表),当元素数量较多或元素体积较大时,会自动转换为 linkedlist(双向链表)。
2. 常用命令
- LPUSH key value1 [value2...]:将一个或多个值插入到列表 key 的表头(左端)。如果 key 不存在,创建一个空列表并执行 LPUSH 操作。例如,LPUSH fruits "apple" "banana",列表 fruits 中的元素顺序为 "banana"、"apple"。
- RPUSH key value1 [value2...]:将一个或多个值插入到列表 key 的表尾(右端)。用法与 LPUSH 类似,只是插入位置在表尾。如RPUSH fruits "orange",列表 fruits 变为 "banana"、"apple"、"orange"。
- LPOP key:移除并返回列表 key 的表头元素。执行LPOP fruits,返回 "banana",列表变为 "apple"、"orange"。
- RPOP key:移除并返回列表 key 的表尾元素。RPOP fruits,返回 "orange",列表变为 "apple"。
- LRANGE key start stop:返回列表 key 中从索引 start 到 stop(包含 start 和 stop)的元素。索引以 0 为底,-1 表示列表的最后一个元素,-2 表示倒数第二个元素,以此类推。例如,LRANGE fruits 0 -1,返回列表中的所有元素;LRANGE fruits 0 0,返回表头元素。
- LLEN key:获取列表 key 的长度。LLEN fruits,在列表只剩 "apple" 时,返回 1。
3. 使用场景
- 消息队列:List 类型的 LPUSH 和 RPOP(或 LPOP 和 RPUSH)命令组合可以实现简单的消息队列功能。生产者使用 LPUSH 命令将消息插入到列表的表头,消费者使用 RPOP 命令从列表的表尾获取消息并进行处理。这种方式可以实现消息的异步处理,解耦生产者和消费者。例如,在订单系统中,订单创建后,生产者将订单信息通过 LPUSH 命令加入到消息队列(如 queue:order),消费者通过 RPOP 命令获取订单信息并进行后续的订单处理(如库存扣减、物流下单等)。
- 排行榜(时间顺序):当需要按照时间顺序展示数据时,List 类型非常合适。例如,展示用户的最近登录记录、网站的最新公告列表等。将最新的数据通过 LPUSH 命令插入到列表的表头,然后使用 LRANGE 命令获取指定数量的最新数据进行展示。比如,存储网站公告的列表 key 为 "notice:list",每次发布新公告时,使用LPUSH notice:list "新公告内容",展示最新的 5 条公告时,执行LRANGE notice:list 0 4。
- 栈和队列的实现:基于 List 类型的 LPUSH 和 LPOP 命令可以实现栈(先进后出);基于 LPUSH 和 RPOP 命令可以实现队列(先进先出)。
(四)Set(集合)
1. 数据结构特性
Set 类型是一个无序的、不允许重复元素的集合。它支持交集、并集、差集等集合运算,适合用于存储需要去重的数据,以及进行数据之间的关联操作。Set 类型的底层实现是一个哈希表,因此添加、删除、查找元素的时间复杂度都是 O (1)。
2. 常用命令
- SADD key member1 [member2...]:将一个或多个成员元素加入到集合 key 中,已经存在于集合的成员元素将被忽略。如果 key 不存在,创建一个只包含添加的成员元素的新集合。例如,SADD tags "redis" "java" "mysql",创建一个 key 为 tags 的集合,包含 "redis"、"java"、"mysql" 三个成员。
- SMEMBERS key:返回集合 key 中的所有成员元素。SMEMBERS tags,返回 "redis"、"java"、"mysql"。
- SISMEMBER key member:判断 member 元素是否是集合 key 的成员。如果是,返回 1;否则,返回 0。如SISMEMBER tags "redis",返回 1;SISMEMBER tags "python",返回 0。
- SREM key member1 [member2...]:移除集合 key 中的一个或多个成员元素,不存在的成员元素将被忽略。例如,SREM tags "mysql",集合 tags 变为 {"redis","java"}。
- SCARD key:获取集合 key 的成员元素个数。SCARD tags,返回 2。
- SINTER key1 [key2...]:返回所有给定集合的交集。例如,有集合 A(key 为 setA,成员为 "a"、"b"、"c")和集合 B(key 为 setB,成员为 "b"、"c"、"d"),执行SINTER setA setB,返回 {"b","c"}。
- SUNION key1 [key2...]:返回所有给定集合的并集。SUNION setA setB,返回 {"a","b","c","d"}。
- SDIFF key1 [key2...]:返回给定集合之间的差集,即 key1 中存在但其他集合中不存在的元素。SDIFF setA setB,返回 {"a"}。
3. 使用场景
- 数据去重:当需要存储一组不允许重复的数据时,Set 类型是理想的选择。例如,存储用户的兴趣标签、网站的访问 IP 地址、参加活动的用户 ID 等。比如,记录网站的访问 IP,每次有新 IP 访问时,使用SADD ip:list "192.168.1.100",由于 Set 不允许重复,即使同一个 IP 多次访问,也只会存储一次。
- 好友关系管理:在社交应用中,可以使用 Set 类型存储用户的好友列表。例如,用户 A 的好友列表 key 为 "friend:1001",成员为好友的用户 ID。通过 SINTER 命令可以获取两个用户的共同好友,如SINTER friend:1001 friend:1002,得到用户 1001 和用户 1002 的共同好友;通过 SUNION 命令可以获取两个用户的所有好友(去重);通过 SDIFF 命令可以获取用户 A 有但用户 B 没有的好友。
- 标签系统:在博客、电商商品等系统中,标签系统可以使用 Set 类型实现。例如,一篇博客可以有多个标签,将博客 ID 与标签关联起来,key 为 "blog:tags:2001",成员为该博客的标签(如 "java"、"redis"、"编程")。通过 SINTER 命令可以查找同时具有多个指定标签的博客;通过 SUNION 命令可以查找具有任意一个指定标签的博客。
(五)Sorted Set(有序集合)
1. 数据结构特性
Sorted Set(也称为 ZSet)类型与 Set 类型类似,也是一个不允许重复元素的集合,但它通过为每个元素关联一个分数(score),使得集合中的元素可以按照分数进行有序排列。Sorted Set 类型的底层实现是跳跃表(Skip List)和哈希表的结合,跳跃表用于保证元素的有序性,哈希表用于快速查找元素,因此它既支持按照分数范围获取元素,也支持快速查找元素的分数和排名。
2. 常用命令
- ZADD key score1 member1 [score2 member2...]:将一个或多个成员元素及其分数值加入到有序集合 key 中。如果某个成员已经是有序集合的成员,则更新该成员的分数值,并根据新的分数值调整该成员在有序集合中的位置。分数值可以是整数值或双精度浮点数。例如,ZADD ranking 95 "zhangsan" 88 "lisi" 92 "wangwu",创建一个 key 为 ranking 的有序集合,包含三个成员,分数分别为 95、88、92。
- ZRANGE key start stop [WITHSCORES]:返回有序集合 key 中,指定索引范围内的成员。成员按分数值递增(从小到大)顺序排列。如果指定 WITHSCORES 选项,返回的结果中将包含每个成员及其分数值。索引以 0 为底,-1 表示最后一个成员,-2 表示倒数第二个成员,以此类推。例如,ZRANGE ranking 0 -1,返回 ["lisi","wangwu","zhangsan"];ZRANGE ranking 0 -1 WITHSCORES,返回 ["lisi","88","wangwu","92","zhangsan","95"]。
- ZREVRANGE key start stop [WITHSCORES]:返回有序集合 key 中,指定索引范围内的成员。成员按分数值递减(从大到小)顺序排列,其他用法与 ZRANGE 类似。ZREVRANGE ranking 0 -1,返回 ["zhangsan","wangwu","lisi"]。
- ZSCORE key member:返回有序集合 key 中,指定成员 member 的分数值。如果 member 不是有序集合 key 的成员,返回 nil。如ZSCORE ranking "zhangsan",返回 "95"。
- ZINCRBY key increment member:为有序集合 key 中的指定成员 member 的分数值加上增量 increment。增量可以是正数或负数。例如,ZINCRBY ranking 3 "lisi",将 lisi 的分数从 88 增加到 91,此时有序集合的顺序变为 ["wangwu"(92),"lisi"(91),"zhangsan"(95)](按递增顺序)。
- ZCOUNT key min max:返回有序集合 key 中,分数值在 min 和 max 之间(包含 min 和 max)的成员的数量。例如,ZCOUNT ranking 90 100,返回 2(wangwu 的 92 和 zhangsan 的 95)。
- ZRANK key member:返回有序集合 key 中,指定成员 member 按分数值递增顺序排列的排名(排名从 0 开始)。如ZRANK ranking "zhangsan",返回 2;ZRANK ranking "lisi",返回 1。
- ZREVRANK key member:返回有序集合 key 中,指定成员 member 按分数值递减顺序排列的排名(排名从 0 开始)。ZREVRANK ranking "zhangsan",返回 0;ZREVRANK ranking "lisi",返回 2。
3. 使用场景
- 排行榜系统:Sorted Set 类型最典型的应用场景就是排行榜系统,如游戏排行榜、商品销量排行榜、用户积分排行榜等。以游戏排行榜为例,key 为 "game:ranking",member 为用户 ID,score 为用户的游戏分数。通过 ZADD 命令更新用户的分数,通过 ZREVRANGE 命令获取排名前 N 的用户(按分数递减顺序),通过 ZRANK 或 ZREVRANK 命令获取指定用户的排名,通过 ZCOUNT 命令统计分数在某个区间的用户数量。
- 范围查询:由于 Sorted Set 类型支持按照分数范围获取元素,因此可以用于实现范围查询功能。例如,在电商平台中,根据商品的价格(score)进行范围查询,获取价格在 100 元到 200 元之间的商品(member 为商品 ID);在招聘网站中,根据职位的薪资水平(score)进行范围查询,获取薪资在8000 元到 15000 元之间的职位(member 为职位 ID),满足用户的精准查询需求。
- 带权重的任务调度:在任务调度系统中,可以根据任务的优先级(score)对任务进行排序,优先级高的任务(score 大)先被执行。例如,key 为 "task:queue",member 为任务 ID,score 为任务的优先级数值。调度器通过 ZREVRANGE 命令获取优先级最高的任务进行执行,执行完成后使用 ZREM 命令将任务从有序集合中删除。
(六)Bitmap(位图)
1. 数据结构特性
Bitmap 并非独立的数据类型,而是基于 String 类型实现的一种二进制位操作的数据结构。它将 String 类型的每个字节拆分为 8 个二进制位,通过对这些二进制位的设置(0 或 1)来表示特定的状态。Bitmap 可以高效地存储大量的布尔型数据,每个状态仅占用 1 个二进制位,极大地节省了存储空间。例如,存储 100 万个布尔值,使用 Bitmap 仅需约 125KB 的存储空间(1000000 / 8 / 1024 ≈ 122KB)。
2. 常用命令
- SETBIT key offset value:将位图 key 中指定偏移量(offset)处的二进制位设置为 value(value 只能是 0 或 1)。偏移量从 0 开始计数,如果 key 不存在,会自动创建一个足够大的 String 来容纳指定的偏移量;如果偏移量超出了当前 String 的长度,会在中间填充 0。例如,SETBIT user:login:20251020 1001 1,表示记录用户 ID 为 1001 的用户在 2025 年 10 月 20 日登录过(1 表示登录,0 表示未登录)。
- GETBIT key offset:获取位图 key 中指定偏移量(offset)处的二进制位的值(0 或 1)。如果偏移量超出了位图的长度,返回 0。如GETBIT user:login:20251020 1001,返回 1,说明用户 1001 在该日登录过。
- BITCOUNT key [start end]:统计位图 key 中值为 1 的二进制位的数量。可选参数 start 和 end 用于指定统计的字节范围(默认统计整个位图),这里的 start 和 end 是字节索引,而非二进制位偏移量。例如,BITCOUNT user:login:20251020,统计 2025 年 10 月 20 日登录的用户总数;BITCOUNT user:login:20251020 0 10,统计前 11 个字节(对应 88 个用户)中登录的用户数量。
- BITOP operation destkey key1 [key2...]:对一个或多个位图执行位运算,并将结果存储到 destkey 中。支持的运算包括 AND(与)、OR(或)、XOR(异或)、NOT(非)。例如,BITOP AND user:login:20251019-20 user:login:20251019 user:login:20251020,计算出在 2025 年 10 月 19 日和 20 日都登录过的用户,结果存储在 user:login:20251019-20 中,该位图中值为 1 的偏移量对应的用户就是连续两天登录的用户。
- BITPOS key value [start end]:查找位图 key 中第一个值为 value(0 或 1)的二进制位的偏移量。可选参数 start 和 end 用于指定查找的字节范围。例如,BITPOS user:login:20251020 1,查找 2025 年 10 月 20 日第一个登录的用户 ID(偏移量即为用户 ID);BITPOS user:login:20251020 0 1000 2000,在用户 ID 1000 到 2000 的范围内,查找第一个未登录的用户 ID。
3. 使用场景
- 用户行为统计:Bitmap 非常适合用于统计用户的各种行为状态,如登录状态、签到状态、点击状态等。例如,统计用户每月的签到情况,每天使用一个位图(key 为 "user:sign:202510:day1"、"user:sign:202510:day2" 等),偏移量为用户 ID,值为 1 表示签到,0 表示未签到。通过 BITOP OR 命令可以统计用户整个月的签到天数(统计结果位图中 1 的数量),通过 BITOP AND 命令可以找出连续多天签到的用户。
- 活跃用户分析:通过 Bitmap 存储每日的活跃用户(登录用户),然后利用 BITOP 命令进行多日活跃用户的交集、并集分析。例如,计算近 7 天的活跃用户总数(对 7 天的位图执行 BITOP OR,再统计 1 的数量),计算近 7 天连续活跃的用户数(对 7 天的位图执行 BITOP AND,再统计 1 的数量),帮助运营人员了解用户活跃度情况。
- 权限控制:可以使用 Bitmap 存储用户的权限状态,每个权限对应一个二进制位,偏移量表示权限 ID,值为 1 表示用户拥有该权限,0 表示没有。例如,用户 ID 为 1001 的权限位图 key 为 "user:permission:1001",若GETBIT user:permission:1001 3返回 1,说明用户 1001 拥有 ID 为 3 的权限(如修改数据权限)。这种方式存储权限信息占用空间小,且权限判断(GETBIT)操作高效。
(七)HyperLogLog(基数统计)
1. 数据结构特性
HyperLogLog 是一种用于基数统计的数据结构,基数是指一个集合中不重复元素的个数。它的核心优势是在保证一定统计精度(误差率通常在 0.81% 左右)的前提下,能够以极小的存储空间统计海量数据的基数。例如,统计一个包含 1 亿个不重复元素的集合,使用 HyperLogLog 仅需占用约 12KB 的存储空间,而传统的 Set 类型存储则需要占用大量的内存(每个元素按平均 10 字节计算,1 亿个元素需约 1GB)。HyperLogLog 不存储具体的元素,仅存储用于计算基数的概率性数据,因此无法获取集合中的具体元素。
2. 常用命令
- PFADD key element1 [element2...]:将一个或多个元素添加到 HyperLogLog 结构 key 中。如果 key 不存在,会自动创建一个新的 HyperLogLog 结构;如果元素已经存在于集合中,不会对 HyperLogLog 的统计结果产生影响(因为基数统计不重复元素)。例如,PFADD user:visit:20251020 "user1001" "user1002" "user1003",将用户 1001、1002、1003 添加到 2025 年 10 月 20 日的访问用户统计中。
- PFCOUNT key1 [key2...]:计算一个或多个 HyperLogLog 结构的基数估算值。如果指定多个 key,会先将这些 HyperLogLog 结构合并(逻辑上的并集),再计算合并后的基数。例如,PFCOUNT user:visit:20251020,返回 2025 年 10 月 20 日访问用户的估算基数;PFCOUNT user:visit:20251019 user:visit:20251020,返回 10 月 19 日和 20 日两天访问用户的总估算基数(去重后)。
- PFMERGE destkey key1 [key2...]:将一个或多个 HyperLogLog 结构合并到 destkey 中,合并后的 destkey 的基数估算值等于所有源 key 对应集合的并集的基数估算值。例如,PFMERGE user:visit:202510 user:visit:20251001 user:visit:20251002 ... user:visit:20251031,将 2025 年 10 月每天的访问用户 HyperLogLog 结构合并到 user:visit:202510 中,之后通过PFCOUNT user:visit:202510即可获取 10 月整月的访问用户估算基数。
3. 使用场景
- 网站 UV 统计:UV(Unique Visitor,独立访客)是指在一定时间内(如一天、一周、一个月)访问网站的不重复用户数。使用 HyperLogLog 可以高效地统计 UV,无需存储每个访问用户的 ID,极大地节省内存。例如,每天创建一个 HyperLogLog key(如 "uv:20251020"),用户每次访问网站时,执行PFADD uv:20251020 用户ID,当天结束后,通过PFCOUNT uv:20251020即可得到当日的 UV 值。
- APP 日活 / 月活统计:与网站 UV 统计类似,HyperLogLog 也适用于 APP 的日活跃用户(DAU)和月活跃用户(MAU)统计。例如,统计 DAU 时,key 为 "dau:20251020",用户每次打开 APP 时,执行PFADD dau:20251020 用户ID;统计 MAU 时,通过PFMERGE mau:202510 dau:20251001 ... dau:20251031合并当月每天的 DAU HyperLogLog,再执行PFCOUNT mau:202510得到 MAU 值。
- 大数据量集合基数统计:在需要统计海量数据集合基数,且对精度要求不是极高(允许约 1% 误差)的场景中,HyperLogLog 是理想选择。例如,统计搜索引擎中某个关键词的搜索次数(去重用户)、统计电商平台中某个商品的浏览用户数(去重)、统计社交平台中某个话题的参与用户数(去重)等。
(八)Geo(地理信息)
1. 数据结构特性
Geo 类型是 Redis 用于存储和处理地理信息数据的数据结构,它基于 Sorted Set 实现,通过将经纬度坐标映射为一个 64 位的整数(GeoHash 编码)作为 Sorted Set 的 score,将地理位置关联的标识(如地点名称、用户 ID)作为 member,从而支持地理位置的存储、距离计算、范围查询等操作。Geo 支持的经纬度范围为:经度 - 180 到 180,纬度 - 85.05112878 到 85.05112878(超出该范围的坐标会被拒绝)。
2. 常用命令
- GEOADD key longitude latitude member [longitude latitude member...]:将一个或多个地理位置(经度、纬度、标识)添加到 Geo 结构 key 中。如果 key 不存在,会自动创建一个基于 Sorted Set 的 Geo 结构;如果 member 已经存在,会更新其对应的经纬度坐标。例如,GEOADD city:location 116.403874 39.914885 "Beijing" 121.473662 31.230416 "Shanghai",将北京和上海的经纬度及名称添加到 city:location 中。
- GEOPOS key member1 [member2...]:获取 Geo 结构 key 中一个或多个 member 对应的经纬度坐标。返回结果为一个数组,每个元素包含对应 member 的经度和纬度(保留 15 位小数)。如GEOPOS city:location "Beijing" "Shanghai",返回北京(116.40387403964996, 39.91488499752405)和上海(121.47366189956665, 31.230415908412933)的经纬度。
- GEODIST key member1 member2 [unit]:计算 Geo 结构 key 中两个 member 对应的地理位置之间的距离。可选参数 unit 用于指定距离单位,支持的单位有 m(米,默认)、km(千米)、mi(英里)、ft(英尺)。例如,GEODIST city:location "Beijing" "Shanghai" km,返回北京到上海的距离约为 1318.3878 千米。
- GEORADIUS key longitude latitude radius unit [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC]:以指定的经纬度(longitude, latitude)为中心,在 Geo 结构 key 中查找距离中心不超过 radius(半径)的地理位置。可选参数说明:
-
- WITHCOORD:返回结果中包含每个地理位置的经纬度。
-
- WITHDIST:返回结果中包含每个地理位置到中心的距离。
-
- WITHHASH:返回结果中包含每个地理位置的 GeoHash 编码值(64 位整数)。
-
- COUNT count:限制返回结果的数量,默认返回所有符合条件的地理位置。
-
- ASC|DESC:按距离从近到远(ASC,默认)或从远到近(DESC)排序。
例如,GEORADIUS city:location 118.8062 32.0581 500 km WITHCOORD WITHDIST COUNT 5 ASC,以南京(经纬度 118.8062, 32.0581)为中心,查找 500 千米范围内的城市,返回前 5 个,包含经纬度和距离,并按距离升序排列。
- GEORADIUSBYMEMBER key member radius unit [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC]:与 GEORADIUS 命令功能类似,区别在于以 Geo 结构 key 中指定的 member 对应的地理位置为中心,查找距离该中心不超过 radius 的地理位置。例如,GEORADIUSBYMEMBER city:location "Nanjing" 500 km WITHDIST COUNT 3,以南京为中心,查找 500 千米范围内的 3 个城市,并返回距离。
- GEOHASH key member1 [member2...]:获取 Geo 结构 key 中一个或多个 member 对应的地理位置的 GeoHash 编码值(字符串形式,11 位)。GeoHash 编码将二维的经纬度映射为一维的字符串,编码越接近,对应的地理位置越近。例如,GEOHASH city:location "Beijing" "Shanghai",返回北京的 GeoHash 编码(wx4g0s8q3jf9)和上海的 GeoHash 编码(wtw3sj5zbj5)。
3. 使用场景
- 附近的人 / 地点推荐:在社交 APP 或本地生活服务 APP 中,“附近的人” 或 “附近的商家 / 景点” 功能可以通过 Geo 类型实现。例如,存储用户的实时地理位置(key 为 "user:location",member 为用户 ID,经纬度为用户当前坐标),当用户查看附近的人时,执行GEORADIUSBYMEMBER user:location 当前用户ID 1 km COUNT 20,即可获取 1 千米范围内的 20 个其他用户;存储商家地理位置(key 为 "merchant:location",member 为商家 ID),用户可以查找附近 3 千米内的餐厅、超市等。
- 地理位置距离计算:在需要计算两个地点之间距离的场景中,Geo 类型的 GEODIST 命令可以快速实现。例如,在物流 APP 中,计算快递网点与用户收货地址之间的距离,估算配送时间;在出行 APP 中,计算两个景点之间的距离,为用户规划行程路线。
- 区域范围筛选:在一些需要按地理位置范围筛选数据的场景中,GEORADIUS 或 GEORADIUSBYMEMBER 命令非常适用。例如,在房产 APP 中,筛选某个商圈(如以人民广场为中心,3 千米范围内)的二手房源;在招聘 APP 中,筛选某个办公区(如以科技园为中心,2 千米范围内)的招聘职位,方便用户就近求职。
三、Redis 数据类型选型建议
1. 根据数据结构需求选型
1.1 简单键值对存储
- 适用类型:String
- 典型场景:
- 缓存单个商品信息(key: product:1001, value: "iPhone 13")
- 存储用户会话ID对应的用户ID(key: session:abc123, value: "user123")
- 计数器(key: page:views:home, value: "1024")
- 优势:操作简单,性能极高(O(1)复杂度)
- 注意事项:避免存储过大的值(建议小于10KB)
1.2 对象类数据存储
- 适用类型:Hash
- 典型场景:
- 用户信息(key: user:1001, fields: name/age/email)
- 商品详情(key: product:2001, fields: title/price/stock)
- 优势:
- 支持单独更新/获取字段(HGET/HINCRBY等)
- 内存优化(小field时使用ziplist编码)
- 反模式:使用String存储JSON(更新时需要全量替换)
1.3 有序数据存储(按插入顺序)
- 适用类型:List
- 典型场景:
- 消息队列(LPUSH/RPOP)
- 最新公告列表(LPUSH/LRANGE 0 9)
- 操作日志(LPUSH/LTRIM保持固定长度)
- 优势:两端操作高效(O(1)复杂度)
- 注意事项:
- 避免大列表(元素过多时影响性能)
- 不支持自定义排序权重
1.4 无序去重数据存储
- 适用类型:Set
- 典型场景:
- 用户标签(SADD tags:user1001 tech/sports)
- 好友列表(SADD friends:user1001 user1002)
- 数据去重(SADD unique:items item123)
- 抽奖活动参与者(SADD lottery:2023 user1001)
- 优势:
- 自动去重
- 支持集合运算(SINTER/SUNION等)
- 内存优化:元素较少时使用intset编码
1.5 有序去重数据存储(按分数排序)
- 适用类型:Sorted Set
- 典型场景:
- 游戏排行榜(ZADD leaderboard 1000 player1)
- 带优先级任务队列(ZADD tasks 1630000000 "task1")
- 时间线(ZADD timeline 1630000000 "post123")
- 价格区间查询(ZRANGEBYSCORE products 100 200)
- 优势:
- 自动维护排序
- 支持范围查询(ZRANGE/ZRANGEBYSCORE)
- 实现原理:跳表+哈希表组合结构
1.6 布尔型批量数据存储
- 适用类型:Bitmap
- 典型场景:
- 用户签到(SETBIT sign:202301 1001 1)
- 功能开关(SETBIT features:user1001 3 1)
- AB测试分组(SETBIT abtest:groupA 1001 1)
- 优势:
- 极致空间效率(1亿用户每日签到仅需12MB)
- 支持位运算(BITOP AND/OR)
- 注意事项:偏移量过大时预分配内存
1.7 海量数据基数统计
- 适用类型:HyperLogLog
- 典型场景:
- UV统计(PFADD uv:20230101 user1001)
- 搜索词去重计数(PFADD keywords:2023 "redis")
- 优势:
- 固定12KB存储空间
- 误差率约0.81%
- 限制:无法获取具体元素
1.8 地理信息存储与计算
- 适用类型:Geo
- 典型场景:
- 附近的人(GEORADIUS users 116.4 39.9 5 km)
- 门店查询(GEOADD shops 121.4 31.2 "store1")
- 配送范围筛选(GEORADIUSBYMEMBER stores store1 3 km)
- 实现原理:基于Sorted Set的GeoHash编码
- 优势:
- 内置距离计算
- 支持半径查询
2. 根据性能需求选型
2.1 高频读写场景
- 优化建议:
- 避免大Key操作(List元素不超过1万,Hash field不超过1千)
- 高频更新字段用Hash替代String
- 排名更新用Sorted Set替代List
- 示例对比:
- 用户积分更新:HINCRBY user:1001 score 10(优于SET全量更新)
- 实时排行榜:ZINCRBY leaderboard 10 player1(优于List手动排序)
2.2 内存占用敏感场景
- 内存优化类型优先级:
- Bitmap(布尔型状态)
- HyperLogLog(基数统计)
- Hash(小field时ziplist编码)
- Sorted Set(元素少时ziplist)
- String/List
- 内存对比示例:
- 1亿用户日活统计:
- Bitmap:~12MB
- Set:~1GB
- String:~4GB
- 1亿用户日活统计:
3. 根据功能需求选型
3.1 集合运算需求
- 必备类型:Set
- 典型运算:
- 共同好友(SINTER friends:user1 friends:user2)
- 兴趣推荐(SUNION tags:user1 tags:user2)
- 差异化内容(SDIFF news:user1 news:user2)
3.2 排序与排名需求
- 必备类型:Sorted Set
- 特殊功能:
- 范围查询(ZRANGEBYSCORE)
- 排名查询(ZREVRANK)
- 增量更新(ZINCRBY)
3.3 地理计算需求
- 必备类型:Geo
- 特色功能:
- 距离计算(GEODIST)
- 位置查询(GEOPOS)
- 半径搜索(GEORADIUS)
3.4 位运算需求
- 必备类型:Bitmap
- 典型应用:
- 连续签到统计(BITOP AND 7days_sign)
- 权限组合判断(BITOP OR permissions)
- 用户画像分析(BITCOUNT tags:male)
选型流程图
graph TDA[需要存储什么数据?] --> B{简单键值}B -->|是| C[String]B -->|否| D{需要多个字段?}D -->|是| E[Hash]D -->|否| F{需要排序?}F -->|是| G{需要自定义权重?}G -->|是| H[Sorted Set]G -->|否| I[List]F -->|否| J{需要去重?}J -->|是| K[Set]J -->|否| L{特殊需求?}L -->|布尔状态| M[Bitmap]L -->|基数统计| N[HyperLogLog]L -->|地理位置| O[Geo]
四、实际应用案例分析
案例 1:电商平台商品详情页缓存
业务需求与性能考量
-
数据存储需求:
- 商品基本信息(ID、名称、价格、库存、图片URL等)
- 商品扩展属性(规格参数、颜色选项、促销信息等)
- 商品状态(上架/下架、热销标志等)
-
访问模式特性:
- 超高读取频率(日均百万级PV的商品详情页访问)
- 低频更新(价格调整、库存变动平均每天几次)
- 热点商品集中(20%的商品承载80%的流量)
-
性能要求:
- 毫秒级响应(<50ms)
- 支持高并发(峰值QPS>10000)
- 部分字段频繁读取(如库存状态)
数据类型选型与优化
选择Hash类型的深层原因:
-
内存效率优势:
- Redis对小Hash(字段数<=512且value大小<=64字节)采用ziplist压缩存储
- 相比String存储JSON可节省30-50%内存空间
- 示例:一个含10个字段的商品信息,Hash存储比JSON String节省约120字节
-
操作性能优势:
- 字段级操作:HGET/HINCRBY等命令时间复杂度O(1)
- 避免String类型全量替换问题
- 典型场景:库存扣减时只需传输"-1"和库存字段名
-
扩展性考虑:
- 可动态添加新字段(如新增促销字段)
- 支持部分更新(只修改价格不影响其他字段)
具体实现与优化策略
-
Key设计规范:
- 统一前缀:
product:{productId}
- 示例:
product:1001
、product:2023
- 加入业务线标识:
{biz}:product:{id}
(如mobile:product:1001
)
- 统一前缀:
-
Field设计原则:
- 基础字段:
id
,name
,price
,stock
,images
- 扩展字段:
specs
,color_options
,promotion
- 状态字段:
status
,is_hot
- 基础字段:
-
高级操作示例:
# 批量设置字段 HMSET product:1001 id 1001 name "iPhone 15" price 5999 stock 1000 status 1# 原子性库存扣减 HINCRBY product:1001 stock -1# 条件更新(仅当库存充足时扣减) EVAL "if redis.call('HGET', KEYS[1], 'stock') >= ARGV[1] then return redis.call('HINCRBY', KEYS[1], 'stock', -ARGV[1]) else return 0 end" 1 product:1001 1# 获取部分字段 HMGET product:1001 name price stock
-
缓存策略优化:
- 热点商品预加载
- 本地缓存+Redis多级缓存
- 库存信息单独缓存(更短TTL)
案例 2:社交APP用户积分排行榜
业务需求与挑战
-
核心功能需求:
- 实时积分变动(用户行为即时奖励)
- 全站排名展示(前100名)
- 个人排名查询
- 分段统计(如top10%、前1000名等)
-
性能挑战:
- 高频率积分更新(用户活跃时每分钟可能多次变动)
- 高并发排名查询(活动期间QPS>5000)
- 大数据量(百万级用户)
-
业务规则:
- 积分权重计算(不同行为权重不同)
- 定期积分清零(如赛季制)
- 多维度排行(如地区排行、好友排行)
数据类型选型解析
选择Sorted Set的核心价值:
-
排序特性:
- 自动维护分数排序(SkipList+HashTable实现)
- 支持正序/倒序排名
- 时间复杂度:
- 插入/更新:O(logN)
- 排名查询:O(logN)
- 范围查询:O(logN+M)(M为返回元素数)
-
丰富操作:
- 范围查询:ZRANGE/ZREVRANGE
- 积分操作:ZINCRBY
- 集合运算:ZUNION/ZINTER
-
内存优化:
- 当元素数<=128且member大小<=64字节时使用ziplist
- 大集合时自动转为skiplist+dict
具体实现与高级用法
-
Key设计体系:
- 主排行榜:
rank:total
- 子排行榜:
rank:{dimension}
(如rank:region:shanghai
) - 时效性排行:
rank:{season}
(如rank:season3
)
- 主排行榜:
-
积分策略示例:
# 用户行为奖励 ZINCRBY rank:total 10 user1001 # 登录奖励 ZINCRBY rank:total 50 user1001 # 完成任务 ZINCRBY rank:total 5 user1001 # 点赞行为# 赛季重置 DEL rank:season2 RENAME rank:total rank:season2
-
高级查询场景:
# 获取前100名带积分 ZREVRANGE rank:total 0 99 WITHSCORES# 查询用户排名及前后5名 ZREVRANK rank:total user1001 ZREVRANGE rank:total rank-5 rank+5 WITHSCORES# 分段统计 ZCOUNT rank:total 1000 +inf # 积分>1000的人数 ZREVRANGEBYSCORE rank:total 5000 1000 # 1000-5000分段
-
性能优化方案:
- 读写分离:从节点处理排名查询
- 定期持久化:避免重启后大量ZADD
- 本地缓存TopN结果
- 分片策略:按用户ID范围分片
异常处理与监控
-
数据一致性保障:
- 事务机制:MULTI/EXEC
- Lua脚本原子操作
- 双写校验机制
-
监控指标:
- 积分更新延迟
- 排名查询响应时间
- 内存增长趋势
- 热点Key检测
-
容灾方案:
- 定时快照备份
- 降级策略(如缓存不可用时降级到数据库)
- 自动扩缩容机制
五、总结
-
String:简单键值对存储,适合缓存单个值(如用户token)、计数器(如PV统计)、会话存储(如Session)。支持原子性增减操作,最大512MB,是最基础的数据类型。
-
Hash:多字段对象存储,适合商品信息(如商品ID为key,字段包括价格、库存等)、用户信息(如用户ID为key,字段包括姓名、年龄等)。支持单个字段高效更新(HSET),节省网络开销。
-
List:有序(插入顺序)数据存储,适合消息队列(LPUSH+RPOP模式)、最新列表(如朋友圈动态)。支持两端操作(LPUSH/RPUSH),底层采用链表实现,适合频繁插入场景。
-
Set:无序去重存储,适合数据去重(如爬虫URL去重)、好友列表(共同好友计算)。支持集合运算(SINTER/SUNION),时间复杂度O(1),查询效率极高。
-
Sorted Set:有序去重(按分数)存储,适合排行榜(如游戏积分榜)、优先级任务(延迟队列)。支持排名查询(ZRANGE),底层采用跳表+哈希表,兼具查询和插入效率。
-
Bitmap:二进制位存储,适合海量布尔状态统计,如用户登录状态(每天1位)、签到日历。通过位运算(SETBIT/GETBIT)实现,1亿数据仅需12MB内存。
-
HyperLogLog:基数统计,适合UV(独立访客)、DAU(日活)等海量数据不重复计数。标准误差0.81%,统计1亿数据仅需12KB内存,但无法获取具体元素。
-
Geo:地理信息存储,适合LBS服务(如附近商家)。底层基于Sorted Set实现,支持距离计算(GEODIST)、半径查询(GEORADIUS),精度取决于经纬度编码。