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
相关推荐
小番茄程序猿2 小时前
别再死背 44 字节了:12 轮压测,实测 Redis String 三种编码的真实差距
redis
Rain的Java大神之路4 小时前
高并发下的热点账户余额扣减:Redis+Lua脚本实现无锁记账
java·spring boot·redis·后端·spring cloud·缓存·lua
程序员黎剑1 天前
Redis缓存击穿:热点Key过期打崩数据库的3种解决方案
数据库·redis·缓存
xixiaoyunya1 天前
Redis 缓存一致性方案深度对比:从理论到工程落地
redis·spring·缓存
职场的momo1 天前
16384个槽怎么搬?解析Redis集群迁移与脑裂防御
数据库·redis·缓存
vivo互联网技术1 天前
四年、十次告警、数十个技术决策:一个海外电商平台的高并发治理实录
服务器·redis·高并发·热key·性能治理·缓存治理