Redis使用及数据结构

Redis跟Mysql性能比较

Reids是基于内存的非关系性数据,而mysql的数据存在磁盘上,所以redis性能普遍高于mysql。但读写性能还受硬件、配置、数据大小、持久化策略、事务、是否集群、是否锁冲突等影响。普遍的认为单机redis一般支持10万级并发读写,mysql只到万级。对于同一热点数据(同一个key或同一行数据),redis单线程排队而mysql需要加锁阻塞,redis依旧支持万级QPS而mysql只支持几百。

Redis的基本命令

安装Redis后,会同步安装一个客户端(reids-cli),客户端可以只接启动,然后在客户端中对redis数据库进行操作。

key操作:

java 复制代码
# 查看key是否存在,返回1存在 0不存在
exists key

# 删除key,支持多个
del key1 key2

# 设置过期时间(秒)
expire key 60
# 设置过期时间(毫秒)
pexpire key 60000
# 查看剩余过期秒数,-1永不过期,-2已删除
ttl key
pttl key

# 查看key类型
type key

# 模糊匹配key,⚠️生产禁止线上用,会阻塞redis
keys user*

# 安全遍历key,游标迭代
scan 0 match user* count 100

# 重命名key
rename old new

# 持久化:把key从内存刷RDB,不用手动执行 

String 字符串(最常用,缓存、计数、分布式锁)

java 复制代码
# 设置
set k1 v1
# 设置+过期时间,原子
set k1 v1 ex 30
# 仅key不存在才设置(分布式锁NX)
set k1 v1 nx ex 10
# 仅key存在才设置
set k1 v1 xx

# 获取
get k1

# 批量
mget k1 k2 k3
mset k1 v1 k2 v2

# 数字自增,原子计数
incr num
incrby num 10
decr num
decrby num 5

# 追加字符串
append k1 "_suffix"
# 获取字符串长度
strlen k1

Hash 哈希(对象存储,user:{id})

java 复制代码
# 左边插入(头)
lpush list1 a b c
# 右边插入(尾)
rpush list1 d e

# 左边弹出
lpop list1
# 右边弹出
rpop list1

# 阻塞弹出,没有数据就等待秒数,消息队列
blpop list1 10
brpop list1 10

# 指定下标取值,下标从0开始
lindex list1 0
# 区间获取 [start,end],-1代表最后一个
lrange list1 0 -1

# 裁剪list,只保留区间,用于限流队列
ltrim list1 0 99

# list长度
llen list1

Set 集合,无序不重复(去重、交集并集)

java 复制代码
# 添加
sadd set1 a b c a
# 查询全部元素
smembers set1

# 判断是否存在
sismember set1 a

# 删除元素
srem set1 b

# 随机取元素
srandmember set1 2
# 随机弹出
spop set1

# 集合运算
sinter set1 set2   # 交集
sunion set1 set2   # 并集
sdiff set1 set2    # 差集

# 元素数量
scard set1

List 列表,双向链表(队列、栈)

java 复制代码
# 左边插入(头)
lpush list1 a b c
# 右边插入(尾)
rpush list1 d e

# 左边弹出
lpop list1
# 右边弹出
rpop list1

# 阻塞弹出,没有数据就等待秒数,消息队列
blpop list1 10
brpop list1 10

# 指定下标取值,下标从0开始
lindex list1 0
# 区间获取 [start,end],-1代表最后一个
lrange list1 0 -1

# 裁剪list,只保留区间,用于限流队列
ltrim list1 0 99

# list长度
llen list1

ZSet 有序集合,score 权重(排行榜、延时队列)

java 复制代码
# 添加 member 带score分数
zadd rank 100 user1 95 user2 98 user3

# 正序查询 [start end],从小到大
zrange rank 0 -1
# 带上分数
zrange rank 0 -1 withscores

# 倒序,从大到小(排行榜)
zrevrange rank 0 -1 withscores

# 根据分数范围查询
zrangebyscore rank 90 100

# 删除成员
zrem rank user1

# 获取成员排名
zrank rank user2
zrevrank rank user2

# 分数自增
zincrby rank 5 user2

# zset元素总数
zcard rank
# 指定分数区间数量
zcount rank 90 100

全局服务命令

java 复制代码
# 选择数据库 0‑15,默认db0
select 1

# 清空当前库,危险
flushdb
# 清空全部库,非常危险
flushall

# 查看信息,cpu、内存、持久化、客户端
info
info memory
info stats

# 查看当前连接客户端
client list

# 持久化触发
bgsave   # 后台生成RDB
bgrewriteaof # AOF重写

# 测试连通
ping

info命令查看持久化相关信息命令:info persistence

返回示例:

java 复制代码
# Persistence
loading:0
rdb_changes_since_last_save:0   # 上次RDB之后改动key数量
rdb_bgsave_in_progress:0
rdb_last_save_time:1756234000   # 上次成功bgsave的时间戳
rdb_last_bgsave_status:ok       # ok代表RDB成功
rdb_last_bgsave_time_sec:0
rdb_current_bgsave_time_sec:-1
rdb_enabled:1                   # RDB功能是否开启(save配置是否存在)
aof_enabled:0                   # 0=AOF关闭;1=AOF开启
aof_rewrite_in_progress:0
aof_rewrite_scheduled:0
aof_last_rewrite_time_sec:-1
aof_current_rewrite_time_sec:-1
aof_last_bgrewrite_status:ok
aof_last_write_status:ok
aof_last_fsync:everysec         # appendfsync策略:everysec / always / no

查看rdb持久化规则:config get save

java 复制代码
 127.0.0.1:6379> config get save
1) "save"
2) "900 1 300 10 60 10000" # 表示900s内改动key至少为1个 或300s内至少10个 或60s内至少10000个 则触发rdb持久化

若返回""则表示RDB 自动快照关闭 ,不会自动 bgsave;但你依然可以手动执行 bgsave 生成 rdb 文件。

Redis Bitmap(位图)

Bitmap 不是独立数据类型,底层就是 String 字符串 ,按 bit(位)操作。一个 key 的字符串最多 512MB,对应 2^32 个 bit,约 42.9 亿位。

1 byte = 8 bit;100 万用户只需要~125KB 内存。

典型场景:用户签到、日活统计、是否访问、布尔标记、去重

以统计用户签到为例,一个二进制位代表一个用户,每个日期存一个bitmap,统计当前日期已签到用户,已签到对应二进制位存1否则存0。若用户id已满足自增可直接使用用户id,若用户ID非自增或偏移量较大(如ID从1000000开始),可以考虑通过hashcode方式映射,但hash映射方式可能出现hash冲突导致统计异常。要想统计准确可以单独添加用户id和自增id的映射表,保证用ID从1开始。

核心命令

1. setbit 设置位

java 复制代码
# setbit key offset value
# offset:位下标,从0开始;value只能 0 / 1
setbit sign:20260901  100  1  

含义:把 offset=100 的位设置为 1,代表 id=100 用户今天签到。

offset 可以非常大,中间未设置的位默认是 0。

2. getbit 获取某一位

java 复制代码
getbit sign:20260901 100

返回 0 / 1;判断用户 100 是否签到。

3. bitcount 统计为 1 的总数(统计签到人数)

java 复制代码
# 统计整个key里面值为1的bit总数
bitcount sign:20260901

# 按字节范围统计,start、end 是**字节偏移**,不是bit!
bitcount sign:20260901 0 100

⚠️注意:start end 单位是字节 (byte),不是 bit,不要搞错。

4. bitop 位图运算(与、或、异或、非)

做多天签到合并、多日活跃用户统计

java 复制代码
# bitop op destkey key1 key2 ...
# op: AND(与) OR(或) XOR(异或) NOT(非)

# OR:任意一天签到过的用户,结果存入 sign:all
bitop OR sign:all sign:20260901 sign:20260902 sign:20260903

# AND:三天全部都签到的用户
bitop AND sign:all sign:20260901 sign:20260902 sign:20260903

bitop 会生成新 key,运算量大,大 bitmap 会阻塞 redis,线上谨慎

5. bitpos 查找第一个 0/1 的位置

java 复制代码
# 找第一个值为1的bit下标
bitpos sign:20260901 1
# 找第一个0
bitpos sign:20260901 0

HyperLogLog

HyperLogLog(简称 HLL)是 Redis 用来做基数统计的数据结构。

基数:集合中不重复元素的个数。例如集合 {1,2,2,3},基数 = 3。

核心特性

  1. 不存储原始数据,只存概率统计估算值;
  2. 空间极小:每个 HLL key 只占用约 12KB 内存,最多统计 264 个元素;
  3. 结果是估算值,有误差:标准误差 0.81%;
  4. 适合大数据量去重计数,不适合要拿到原始元素的场景。

使用场景

  1. 统计页面 UV、每日独立访客(海量用户,不需要精确值,容忍小误差)
  2. 直播观看人数、活动去重访问量
  3. 多维度去重统计合并(PFMERGE合并多天、多渠道)

数据结构

整体结构 = HLL 头(hllhdr) + 寄存器数据区 ,寄存器区有两种编码:稀疏编码 (Sparse)密集编码 (Dense),Redis 自动动态转换

java 复制代码
struct hllhdr {
    char magic[4];        // 魔术标记 "HYLL",标识这是HLL结构
    uint8_t encoding;     // 编码:0=DENSE密集,1=SPARSE稀疏
    uint8_t notused[3];   // 保留3字节,预留扩展,恒为0
    uint8_t card[8];      // 基数缓存,小端序;最高1bit标记缓存是否有效,低63bit存上次PFCOUNT结果
    uint8_t registers[];  // 柔性数组:寄存器数据区
};

参数常量:

  • HLL_P =14 → 桶数量 (M=214=16384) 个寄存器(桶)
  • HLL_BITS=6:每个寄存器占 6bit,最大存 63,足够保存最多 50 位哈希的前导零最大值。

总大小:

(16384 ×6bit ÷8 =12288字节 =12KB)

加上头部 16 字节,完整对象约 12KB+

编码切换时机

  • 初始化:默认稀疏编码
  • 触发转密集:
    1. PFADD 导致某个寄存器值 > 32
    2. 稀疏编码自身占用超过阈值

底层原理简要

HyperLogLog 基于伯努利试验 + 调和平均数估算基数:

  1. 对输入元素做 hash,得到 64bit 哈希值;
  2. 低 14 位用来选桶(Redis HLL 一共 16384 个桶,(214=16384));
  3. 剩余高位计算前导 0 的个数,每个桶记录该桶出现过最大前导 0 数量,(前导0越多,表明出现该数的概率越小,基数就越多。相同基数由于hash一样,选择的桶一样,前导0的个数也一样所以总的基数不会更新);
  4. 最后对 16384 个桶的值做调和平均,修正偏差,估算出总基数。
  • 桶数量固定 16384,所以内存固定约 12KB;
  • 误差来源是概率估算,标准误差 0.81%;
  • Redis HLL 内部有稀疏、稠密两种编码:数据少时用稀疏编码占用更小,超过阈值自动转为稠密(12KB)。

使用示例

统计某个页面每天的访问次数

java 复制代码
# 添加访问用户id
PFADD uv:20260902 user1 user2 user3 user2 user1

# 统计今日UV(去重用户数)
PFCOUNT uv:20260902

# 合并两天UV到新key
PFMERGE uv:total uv:20260901 uv:20260902
PFCOUNT uv:total

GEO

Redis 3.2 新增 GEO,用于存储经纬度、计算距离、查询附近点位;底层没有新数据结构,基于 ZSet(有序集合)+ GeoHash 算法实现

典型业务场景

  1. LBS 业务:附近门店、附近商家、附近的人;
  2. 配送业务:骑手、车辆位置,计算两点距离;
  3. 简单地理围栏(小数据量)。

核心命令

1. GEOADD 添加点位

复制代码
# key 经度 纬度 member
GEOADD shop:geo 104.06 30.67 shop‑001 104.07 30.68 shop‑002

2. GEOPOS 获取 member 的经纬度

复制代码
GEOPOS shop:geo shop‑001 shop‑002

3. GEODIST 计算两点球面距离

单位:m米 (默认)、km千米、mi英里、ft英尺

复制代码
GEODIST shop:geo shop‑001 shop‑002 km

4. GEOHASH 获取 geohash 字符串

复制代码
GEOHASH shop:geo shop‑001
相关推荐
IT大白鼠2 天前
Redis 系列 · 第 01 篇——认知入门:Redis 是什么
redis·nosql
彧azz2 天前
Linux 环境下 Redis 学习总结:数据类型、持久化、锁、事务、主从与缓存问题
linux·redis·笔记·学习·面试
lbb 小魔仙2 天前
OpenClaw + cpolar 实战:远程 NAS、分享小游戏、RDP,再配置公网 AI 入口
数据库·人工智能·redis·oracle·prometheus
夕除2 天前
redis--018
redis
两锭子2 天前
Redis基础
redis
烟沙九洲2 天前
Redis 缓存穿透、缓存击穿、缓存雪崩
数据库·redis
字节探索2 天前
Redis 只会当缓存用?这 10 大实战场景,让你的系统快到飞起
redis·后端
于樱花森上飞舞2 天前
【Redis】哨兵详解
java·开发语言·数据库·redis
Rain的Java大神之路3 天前
如何快速上传10G文件
java·spring boot·redis·后端·mysql·spring cloud·面试
Wang's Blog3 天前
Java 接入Redis: 通用命令与键管理
java·服务器·redis