Redis 数据类型与使用场景

一、Redis 简介

1.1 基本概念

Redis(Remote Dictionary Server)是一款开源的、基于内存的高性能键值对(Key-Value)存储数据库,由 Salvatore Sanfilippo 于2009年开发。它采用ANSI C语言编写,支持网络访问,并提供多种语言的客户端API。

1.2 核心特性

  1. 高性能:Redis将数据存储在内存中,读写速度极快,官方基准测试显示Redis可以达到10万次/秒的读写操作
  2. 丰富的数据结构:支持字符串(String)、哈希(Hash)、列表(List)、集合(Set)、有序集合(ZSet)等多种数据结构
  3. 持久化支持:提供RDB(快照)和AOF(追加日志)两种持久化机制,确保数据安全
  4. 高可用:通过Redis Sentinel实现故障转移,Redis Cluster提供分片功能
  5. 发布订阅:内置消息队列功能,支持发布/订阅模式

1.3 典型应用场景

  1. 缓存系统 :作为MySQL等关系型数据库的前置缓存,减轻数据库压力
    • 示例:电商网站商品详情页缓存
  2. 会话存储 :存储用户会话信息,实现分布式会话管理
    • 示例:用户登录状态保持
  3. 实时排行榜 :利用有序集合实现实时排名
    • 示例:游戏玩家积分排行榜
  4. 消息队列 :使用List结构实现简单的消息队列
    • 示例:订单处理队列
  5. 计数器系统 :原子性操作实现精准计数
    • 示例:网站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 内存占用敏感场景
  • 内存优化类型优先级
    1. Bitmap(布尔型状态)
    2. HyperLogLog(基数统计)
    3. Hash(小field时ziplist编码)
    4. Sorted Set(元素少时ziplist)
    5. String/List
  • 内存对比示例
    • 1亿用户日活统计:
      • Bitmap:~12MB
      • Set:~1GB
      • String:~4GB

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 TD A[需要存储什么数据?] --> 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:电商平台商品详情页缓存

业务需求与性能考量
  1. 数据存储需求:

    • 商品基本信息(ID、名称、价格、库存、图片URL等)
    • 商品扩展属性(规格参数、颜色选项、促销信息等)
    • 商品状态(上架/下架、热销标志等)
  2. 访问模式特性:

    • 超高读取频率(日均百万级PV的商品详情页访问)
    • 低频更新(价格调整、库存变动平均每天几次)
    • 热点商品集中(20%的商品承载80%的流量)
  3. 性能要求:

    • 毫秒级响应(<50ms)
    • 支持高并发(峰值QPS>10000)
    • 部分字段频繁读取(如库存状态)
数据类型选型与优化

选择Hash类型的深层原因:

  1. 内存效率优势:

    • Redis对小Hash(字段数<=512且value大小<=64字节)采用ziplist压缩存储
    • 相比String存储JSON可节省30-50%内存空间
    • 示例:一个含10个字段的商品信息,Hash存储比JSON String节省约120字节
  2. 操作性能优势:

    • 字段级操作:HGET/HINCRBY等命令时间复杂度O(1)
    • 避免String类型全量替换问题
    • 典型场景:库存扣减时只需传输"-1"和库存字段名
  3. 扩展性考虑:

    • 可动态添加新字段(如新增促销字段)
    • 支持部分更新(只修改价格不影响其他字段)
具体实现与优化策略
  1. Key设计规范:

    • 统一前缀:product:{productId}
    • 示例:product:1001product:2023
    • 加入业务线标识:{biz}:product:{id}(如mobile:product:1001
  2. Field设计原则:

    • 基础字段:id, name, price, stock, images
    • 扩展字段:specs, color_options, promotion
    • 状态字段:status, is_hot
  3. 高级操作示例:

    redis 复制代码
    # 批量设置字段
    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
  4. 缓存策略优化:

    • 热点商品预加载
    • 本地缓存+Redis多级缓存
    • 库存信息单独缓存(更短TTL)

案例 2:社交APP用户积分排行榜

业务需求与挑战
  1. 核心功能需求:

    • 实时积分变动(用户行为即时奖励)
    • 全站排名展示(前100名)
    • 个人排名查询
    • 分段统计(如top10%、前1000名等)
  2. 性能挑战:

    • 高频率积分更新(用户活跃时每分钟可能多次变动)
    • 高并发排名查询(活动期间QPS>5000)
    • 大数据量(百万级用户)
  3. 业务规则:

    • 积分权重计算(不同行为权重不同)
    • 定期积分清零(如赛季制)
    • 多维度排行(如地区排行、好友排行)
数据类型选型解析

选择Sorted Set的核心价值:

  1. 排序特性:

    • 自动维护分数排序(SkipList+HashTable实现)
    • 支持正序/倒序排名
    • 时间复杂度:
      • 插入/更新:O(logN)
      • 排名查询:O(logN)
      • 范围查询:O(logN+M)(M为返回元素数)
  2. 丰富操作:

    • 范围查询:ZRANGE/ZREVRANGE
    • 积分操作:ZINCRBY
    • 集合运算:ZUNION/ZINTER
  3. 内存优化:

    • 当元素数<=128且member大小<=64字节时使用ziplist
    • 大集合时自动转为skiplist+dict
具体实现与高级用法
  1. Key设计体系:

    • 主排行榜:rank:total
    • 子排行榜:rank:{dimension}(如rank:region:shanghai
    • 时效性排行:rank:{season}(如rank:season3
  2. 积分策略示例:

    redis 复制代码
    # 用户行为奖励
    ZINCRBY rank:total 10 user1001  # 登录奖励
    ZINCRBY rank:total 50 user1001  # 完成任务
    ZINCRBY rank:total 5 user1001   # 点赞行为
    
    # 赛季重置
    DEL rank:season2
    RENAME rank:total rank:season2
  3. 高级查询场景:

    redis 复制代码
    # 获取前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分段
  4. 性能优化方案:

    • 读写分离:从节点处理排名查询
    • 定期持久化:避免重启后大量ZADD
    • 本地缓存TopN结果
    • 分片策略:按用户ID范围分片
异常处理与监控
  1. 数据一致性保障:

    • 事务机制:MULTI/EXEC
    • Lua脚本原子操作
    • 双写校验机制
  2. 监控指标:

    • 积分更新延迟
    • 排名查询响应时间
    • 内存增长趋势
    • 热点Key检测
  3. 容灾方案:

    • 定时快照备份
    • 降级策略(如缓存不可用时降级到数据库)
    • 自动扩缩容机制

五、总结

  1. String:简单键值对存储,适合缓存单个值(如用户token)、计数器(如PV统计)、会话存储(如Session)。支持原子性增减操作,最大512MB,是最基础的数据类型。

  2. Hash:多字段对象存储,适合商品信息(如商品ID为key,字段包括价格、库存等)、用户信息(如用户ID为key,字段包括姓名、年龄等)。支持单个字段高效更新(HSET),节省网络开销。

  3. List:有序(插入顺序)数据存储,适合消息队列(LPUSH+RPOP模式)、最新列表(如朋友圈动态)。支持两端操作(LPUSH/RPUSH),底层采用链表实现,适合频繁插入场景。

  4. Set:无序去重存储,适合数据去重(如爬虫URL去重)、好友列表(共同好友计算)。支持集合运算(SINTER/SUNION),时间复杂度O(1),查询效率极高。

  5. Sorted Set:有序去重(按分数)存储,适合排行榜(如游戏积分榜)、优先级任务(延迟队列)。支持排名查询(ZRANGE),底层采用跳表+哈希表,兼具查询和插入效率。

  6. Bitmap:二进制位存储,适合海量布尔状态统计,如用户登录状态(每天1位)、签到日历。通过位运算(SETBIT/GETBIT)实现,1亿数据仅需12MB内存。

  7. HyperLogLog:基数统计,适合UV(独立访客)、DAU(日活)等海量数据不重复计数。标准误差0.81%,统计1亿数据仅需12KB内存,但无法获取具体元素。

  8. Geo:地理信息存储,适合LBS服务(如附近商家)。底层基于Sorted Set实现,支持距离计算(GEODIST)、半径查询(GEORADIUS),精度取决于经纬度编码。

相关推荐
MediaTea3 小时前
Python 第三方库:matplotlib(科学绘图与数据可视化)
开发语言·python·信息可视化·matplotlib
草莓熊Lotso3 小时前
C++ 方向 Web 自动化测试入门指南:从概念到 Selenium 实战
前端·c++·python·selenium
JS.Huang4 小时前
【JavaScript】原生函数
开发语言·javascript·ecmascript
Olrookie4 小时前
若依前后端分离版学习笔记(二十)——实现滑块验证码(vue3)
java·前端·笔记·后端·学习·vue·ruoyi
533_4 小时前
[vue] dayjs 显示实时时间
前端·javascript·vue.js
CoderCodingNo4 小时前
【GESP】C++五级考试大纲知识点梳理, (5) 算法复杂度估算(多项式、对数)
开发语言·c++·算法
倚栏听风雨4 小时前
java.lang.SecurityException异常
java
星河队长4 小时前
VS创建C++动态库和C#访问过程
java·c++·c#
故事与他6455 小时前
XSS_and_Mysql_file靶场攻略
前端·学习方法·xss