Redis三大特殊数据类型

文章目录

  • 前言
  • [一、Bitmap 位图:极致节省空间的二元状态存储](#一、Bitmap 位图:极致节省空间的二元状态存储)
    • [1. 底层原理](#1. 底层原理)
    • [2. 核心实操命令](#2. 核心实操命令)
    • [3. 适用场景](#3. 适用场景)
    • [4. 踩坑总结](#4. 踩坑总结)
  • 二、HyperLogLog:亿级去重统计神器
    • [1. 底层原理](#1. 底层原理)
    • [2. 核心实操命令](#2. 核心实操命令)
    • [3. 适用场景](#3. 适用场景)
    • [4. 踩坑总结](#4. 踩坑总结)
  • [三、Geospatial(GEO) 地理坐标类型](#三、Geospatial(GEO) 地理坐标类型)
    • [1. 底层原理](#1. 底层原理)
    • [2. 核心实操命令](#2. 核心实操命令)
    • [3. 适用场景](#3. 适用场景)
    • [4. 踩坑总结](#4. 踩坑总结)
  • 四、三大特殊类型完整总结

前言

Redis藏了三个专门适配特殊业务的新数据类型:Bitmap、HyperLogLog、Geospatial,让我们继续学习


提示:以下是本篇文章正文内容,下面案例可供参考

一、Bitmap 位图:极致节省空间的二元状态存储

1. 底层原理

Bitmap并不是独立的数据结构,底层依托String实现,操作的是字符串里每一个二进制bit位,一个bit只有0、1两种值,完美适配"是/否"类业务状态。

举个对比案例:千万用户签到场景

  • MySQL方案:每条签到记录一条数据,海量数据占用大量磁盘,查询缓慢;
  • Bitmap方案:一个用户单月签到仅占用31bit,百万用户存储只需要几十MB,性能碾压传统方案。
    上限:单个Bitmap最大512M,对应2^32个bit,日常业务完全够用。

2. 核心实操命令

  1. SETBIT key offset 0/1:指定下标存入状态(offset从0开始)
    示例:setbit sign:user1 0 1 用户1当月1号签到
  2. GETBIT key offset:查询指定位置bit值
  3. BITCOUNT key:统计值为1的bit总数(统计签到天数)
  4. BITOP and/or/xor dest key1 key2:位图位运算,统计多天连续签到用户

实例运行:

redis 复制代码
# ========== 第一组:Bitmap基础命令实操 ==========
# setbit key 偏移量 0/1:设置指定bit位的值,返回修改前该位置的旧值
# bit1的第0位设为1,之前是空默认0,返回0
127.0.0.1:6379> setbit bit1 0 1
(integer) 0
# bit1的第1位设为1,旧值0
127.0.0.1:6379> setbit bit1 1 1
(integer) 0
# bit1的第3位设为1,跳过2号位,旧值0
127.0.0.1:6379> setbit bit1 3 1
(integer) 0
# bit1的第5位设为1,旧值0
127.0.0.1:6379> setbit bit1 5 1
(integer) 0
# bit1的第7位设为1,旧值0
127.0.0.1:6379> setbit bit1 7 1
(integer) 0

# getbit key 偏移量:查询指定bit位的值
# 查询第3位,值为1
127.0.0.1:6379> getbit bit1 3
(integer) 1
# 查询第2位,从未赋值,默认0
127.0.0.1:6379> getbit bit1 2
(integer) 0

# bitfield key get uN offset:从offset开始读取N个无符号bit,转十进制
# 从0号位读取2个bit:bit0=1、bit1=1 → 二进制11=十进制3
127.0.0.1:6379> bitfield bit1 get u2 0
1) (integer) 3

# bitpos key bit值:查找第一个等于目标bit的偏移量
# 查找bit1中第一个0,位置是2
127.0.0.1:6379> bitpos bit1 0
(integer) 2

3. 适用场景

用户签到打卡、APP登录状态标记、海量用户黑白名单、日活用户筛选

4. 踩坑总结

offset不要直接使用超大用户ID,会造成大量空bit浪费内存;仅适合二元状态,多状态场景不适用。

二、HyperLogLog:亿级去重统计神器

1. 底层原理

专门做基数统计 (统计不重复元素数量),核心优势是极致压缩内存:无论存入多少数据,单个HyperLogLog固定仅占用12KB内存,最大可统计2^64个不同元素,仅存在0.81%左右微小误差,绝大多数互联网业务可以忽略。

对比Set:存储百万独立访客,Set需要几十MB,HLL仅12KB,差距巨大。

短板:只能统计数量,无法取出存入的原始数据。

2. 核心实操命令

  1. PFADD key 元素:添加数据,自动去重
    示例:pfadd uv:20260807 u001 u002 u003 记录今日访客
  2. PFCOUNT key:查询不重复元素总数(网站UV)
  3. PFMERGE newkey key1 key2:合并多个HLL,统计周期总访客

实例运行:

redis 复制代码
# pfadd key 元素:往HyperLogLog添加数据;元素首次加入返回1,重复添加无变化返回0
127.0.0.1:6379> pfadd k1 java
(integer) 1 # java第一次存入k1,新增成功
127.0.0.1:6379> pfadd k1 html
(integer) 1 # html第一次存入k1,新增成功
127.0.0.1:6379> pfadd k1 javascript
(integer) 1 # javascript第一次存入k1,新增成功
127.0.0.1:6379> pfadd k1 java
(integer) 0 # java已存在,无新增,返回0

# pfcount key:统计HLL中不重复元素的近似基数(去重总数)
127.0.0.1:6379> pfcount k1
(integer) 3 # k1去重后共3个元素:java、html、javascript

# 新建HLL集合k2,存入spring
127.0.0.1:6379> pfadd k2 spring
(integer) 1
# 存入mysql
127.0.0.1:6379> pfadd k2 mysql
(integer) 1
# 统计k2去重数量
127.0.0.1:6379> pfcount k2
(integer) 2 # k2去重后2个元素

# pfmerge 目标key 源key1 源key2:合并多个HLL,所有去重数据存入目标key
127.0.0.1:6379> pfmerge k3 k1 k2
OK # 合并操作执行成功

# 统计合并后k3的总去重数量(3+2无重复,结果为5)
127.0.0.1:6379> pfcount k3
(integer) 5

3. 适用场景

网站独立访客UV统计、搜索关键词去重计数、活动参与人数统计

4. 踩坑总结

如果业务必须获取全部唯一数据,不能用HLL,优先选择Set;对数据精准度要求极高的金融场景慎用。

三、Geospatial(GEO) 地理坐标类型

1. 底层原理

Redis3.2新增地理数据类型,底层依托Zset有序集合实现,将经纬度坐标编码成score存储,原生封装地图相关计算指令,不用自己写复杂地理算法。

2. 核心实操命令

  1. GEOADD key 经度 纬度 名称:存入地点坐标
    示例:geoadd city 121.47 31.23 shanghai
  2. GEOPOS key 地点:获取目标经纬度
  3. GEODIST key A B km/m:计算两点直线距离
  4. GEORANGE key 经度 纬度 半径 km:查询指定范围内所有地点

实例运行:

redis 复制代码
# ===================== 一、GEO地理坐标类型实操 =====================
# geoadd key 经度 纬度 地点名:添加地理坐标,返回本次新增地点数量
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1 # 成功新增上海1个坐标
# 批量添加重庆、深圳、北京三个城市,返回新增总数3
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqing 114.05 22.52 shenzhen 116.38 39.90 beijing
(integer) 3

# geopos key 地点:查询指定城市的经纬度
127.0.0.1:6379> geopos china:city shanghai
1) 1) "121.47000163793563843" # 经度
   2) "31.22999903975783553" # 纬度

# geodist 计算两点直线距离,默认单位米(m)
127.0.0.1:6379> geodist china:city beijing shanghai
"1068153.5181" # 北京到上海距离,单位米
# 追加km参数,单位切换为千米
127.0.0.1:6379> geodist china:city beijing shanghai km
"1068.1535"
# 北京到深圳直线距离,单位千米
127.0.0.1:6379> geodist china:city beijing shenzhen km
"1945.5740"

# georadius 给定经纬度+半径,查询范围内所有存储的地点
127.0.0.1:6379> georadius china:city 110 30 1000 km
1) "chongqing"
2) "shenzhen"

# flushdb:清空当前数据库所有key
127.0.0.1:6379> flushdb
OK

# ===================== 二、Redis事务场景1:正常执行提交 =====================
# multi:开启事务,后续命令全部进入队列缓存,不会立刻执行
127.0.0.1:6379> multi
OK
# 命令入队,返回QUEUED标识
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k1
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
# exec:提交事务,一次性串行执行队列所有命令,逐条返回执行结果
127.0.0.1:6379(TX)> exec
1) OK       # set k1执行结果
2) OK       # set k2执行结果
3) "v1"     # get k1查询结果
4) "v2"     # get k2查询结果

# 清空数据库,准备下一组测试
127.0.0.1:6379> flushdb
OK

# ===================== 三、Redis事务场景2:discard放弃事务 =====================
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k1
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
# discard:丢弃事务队列中全部命令,不执行任何操作
127.0.0.1:6379(TX)> discard
OK
# 事务作废,key不存在返回nil
127.0.0.1:6379> get k1
(nil)
127.0.0.1:6379> get k2
(nil)

127.0.0.1:6379> flushdb
OK

# ===================== 四、Redis事务场景3:入队阶段语法错误,整体作废 =====================
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
# set语法错误(缺少value参数),入队直接报错
127.0.0.1:6379(TX)> set k3
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379(TX)> get k1
QUEUED
127.0.0.1:6379(TX)> mget k2 k3
QUEUED
# 只要入队时有语法错误,exec直接拒绝执行整个事务,全部失效
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors

127.0.0.1:6379> flushdb
OK

# ===================== 五、Redis事务场景4:入队无错、执行时报错(无回滚) =====================
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
# incr仅支持数字,存入字符串,执行阶段才会报错,入队不会报错
127.0.0.1:6379(TX)> incr k1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
# 执行时:正确命令正常生效,出错命令单独报错,不会整体回滚(Redis事务不支持原子回滚)
127.0.0.1:6379(TX)> exec
1) OK                                      # set k1执行成功
2) (error) ERR value is not an integer or out of 
range # incr执行失败
3) OK                                      # set k2执行成功
4) "v2"                                    # get k2查询成功

3. 适用场景

外卖附近商家、同城好友、门店范围检索、打车附近车辆匹配

4. 踩坑总结

经纬度有合法区间(经度-180180,纬度-85.0585.05),超出会报错;只能计算直线距离,不包含道路路程。

四、三大特殊类型完整总结

数据类型 底层依托 核心优势 局限性 核心业务场景
Bitmap String 占用空间极小,读写速度快 仅支持0/1二元状态 用户签到、用户状态标记
HyperLogLog String 12KB固定内存,海量去重统计 无法取出原始数据,存在微小误差 UV独立访客、去重计数
Geospatial ZSet 内置地理计算,无需自研算法 仅直线距离,坐标区间有限 附近门店、同城匹配

整体学习感悟

  1. Redis每种数据结构都是为特定业务量身设计,没有万能类型,选型优先贴合业务需求;
  2. 三大新类型都是底层复用已有基础结构,Redis设计极度精简,没有冗余代码;
  3. 开发选型思路:
    • 二元状态统计 → Bitmap
    • 海量数据去重计数,不查明细 → HyperLogLog
    • 经纬度、范围、距离计算 → G
  4. 实操小建议:平时写Demo多模拟线上大数据量场景,才能直观感受到不同结构的内存差距。
相关推荐
祈禾1 小时前
计算机组成原理之编译、汇编、链接、解释
开发语言·汇编·笔记·学习
东方护航数据恢复(深圳)1 小时前
MySQL_Oracle数据库崩溃修复全攻略_东方护航数据恢复深圳店
数据库·mysql·oracle
四代水门1 小时前
三、计算机网络
服务器·计算机网络·asp.net
孙启超1 小时前
【AI应用开发】什么是混合检索(Hybrid Search)?向量检索 + BM25 关键词检索,适用场景与 RRF 融合原理
人工智能·缓存·llm·向量数据库·bm25·向量化·ai应用开发
y = xⁿ1 小时前
一文掌握Redis常见八股
数据库·redis·缓存
mgaofeid2 小时前
Ubuntu 24.04 中文输入法安装
linux·运维·ubuntu
Cloud云卷云舒2 小时前
HaishanDB(海山)|磐维数据库|YashanDB(崖山)深度对比分析
数据库·人工智能·海山数据库·haishandb·移动云海山数据库
weixin_460443562 小时前
企业考试系统如何对接OA、钉钉和企业微信?SSO单点登录、组织同步与权限一致性设计
java·开发语言·数据库
xywww1682 小时前
真实后台页实测:Opus 5 看图写前端的可用边界在哪
linux·服务器·前端·数据库·人工智能·gpt