当前位置: 首页 > news >正文

深入剖析Redis Cluster集群,Redis持久化机制,Redis数据类型及其数据结构

一、Redis Cluster 高可用部署方案


1. 部署拓扑设计(推荐)

为了保证高可用 + 扩展性 + 性能,建议采用:

6 主 6 从结构(12 实例)
每个主节点管理 2,738 个 slot,总计 16,384 个 slot

节点分布:
┌─────────────┬──────────────┐
│ 主节点 M1   │ 从节点 S1(备份 M1)│
│ 主节点 M2   │ 从节点 S2(备份 M2)│
│ 主节点 M3   │ 从节点 S3(备份 M3)│
│ 主节点 M4   │ 从节点 S4(备份 M4)│
│ 主节点 M5   │ 从节点 S5(备份 M5)│
│ 主节点 M6   │ 从节点 S6(备份 M6)│
└─────────────┴──────────────┘

建议部署方式:

环境部署建议
云环境(K8s)每台机器部署一个 Pod,资源隔离
物理机或虚拟机每台部署两个实例(一个主一个从,非互为主从)
容器环境Docker + 网络固定映射(需注意端口)

2. 端口规划

每个 Redis 实例需要开放:

  • 主端口(默认 6379)

  • 集群总线端口(主端口 + 10000) → 16379

例如:

6379 / 6380 / 6381 ...   → 对应 Redis 实例
16379 / 16380 / 16381 ...→ 用于集群心跳、failover 等通信

3. 目录结构建议

/data/redis/└── 6379/├── redis.conf├── dump.rdb├── appendonly.aof├── logs/└── run/

每个端口一个独立目录。


4. 关键配置项(redis.conf)

最小配置示例(用于集群节点):

port 6379
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
appendfilename "appendonly.aof"
dbfilename dump.rdb
dir /data/redis/6379
bind 0.0.0.0
protected-mode no
daemonize yes
logfile "/data/redis/6379/logs/redis.log"

✅ 注意:Redis Cluster 模式下必须开启 AOF 或 RDB,否则迁移和重启数据可能丢失。


5. 启动集群节点

假设你启动了以下 6 个主节点和 6 个从节点:

redis-server /data/redis/6379/redis.conf
redis-server /data/redis/6380/redis.conf
...

6. 构建 Redis Cluster

使用 redis-cli --cluster 一键创建集群:

redis-cli --cluster create \192.168.0.1:6379 192.168.0.2:6379 192.168.0.3:6379 \192.168.0.4:6379 192.168.0.5:6379 192.168.0.6:6379 \192.168.0.1:6380 192.168.0.2:6380 192.168.0.3:6380 \192.168.0.4:6380 192.168.0.5:6380 192.168.0.6:6380 \--cluster-replicas 1

自动将 6 个主节点分配 slot、剩余作为从节点。


7. 高可用保障机制

1. 节点宕机自动 failover

  • Redis Cluster 采用内部 Gossip + 选举 协议

  • 若主节点宕机,从节点会在 cluster-node-timeout 后自动接管

  • 选举由剩余主节点投票完成(多数选举)

2. 客户端自动重定向(MOVED / ASK)

客户端支持 Redis Cluster 协议,自动更新路由映射表。


8. 安全与稳定性建议

项目建议配置
密码认证requirepass + masterauth
内存限制maxmemory + allkeys-lru
延迟监控latency-monitor-threshold 100
审计日志配置 logfile 和 rotate
Redis SentinelRedis Cluster 本身已自动选主,不需要 sentinel

9. 监控指标建议

工具说明
Prometheus + Redis Exporter监控内存、连接数、命中率、slot 分布等
Grafana可视化面板
自研监控重点监控 cluster_state, connected_slaves, instantaneous_ops_per_sec

10. 调优建议

1. 提前规划 slot 分布

使用 --cluster-slots 指定 slot 范围,避免集中热点。

2. Key 设计防跨 slot

使用 Hash Tag,如:

sign:{123}:20250609
order:{uid123}:create

确保 {} 内的内容一致即可定位到同一 slot,支持多 key 操作(如 Lua 脚本)。


11. 常用命令

命令说明
redis-cli -c -h host -p port连接 cluster 节点
cluster nodes查看节点状态
cluster slots查看 slot 分布
cluster info查看集群状态
redis-cli --cluster check检查集群一致性
redis-cli --cluster fix自动修复 slot 问题

12. 小结

模块推荐方案
集群拓扑6 主 6 从
数据结构Hash Tag 防跨 slot
部署方式容器化 / 多端口隔离
容灾机制自动选主 + AOF
管理工具redis-cli --cluster、Exporter
高并发分区热点、避免集中访问

二、Redis-Cluster集群中数据的读写流程

在 Redis Cluster 中,写入数据的查找过程是通过一种称为 "分片(sharding)+槽位(hash slot)+节点路由" 的机制完成的。这种机制既保证了分布式扩展能力,又保证了较高的效率。


1. 核心概念

1.1 集群槽位(Hash Slot)

  • Redis Cluster 将所有数据 key 映射到 0~16383(共 16384 个槽位)。

  • 每个节点负责若干个槽位的写入、查询和删除。

  • key 是通过 CRC16(key) mod 16384 算出来的。

1.2 节点

  • Redis Cluster 中的每个节点负责一部分槽位(比如节点 A 负责 0~5000)。

  • 集群中包含主节点(Master)和从节点(Slave),主节点负责写入操作,从节点用于备份与故障切换。


2. 写入流程详细剖析

假设我们写入一个 key:set user:123 "Tom",以下是详细过程:

步骤 1:客户端计算 key 的槽位

slot = CRC16("user:123") % 16384

比如计算结果是 4567,Redis 客户端会尝试去访问负责 slot 4567 的节点。

⚠️ Redis 允许使用“哈希标签”来固定 key 到同一个 slot,例如:set user:{123}:name Tomset user:{123}:age 20 会被 hash 到同一个槽位。


步骤 2:客户端从路由表中查找负责这个 slot 的节点

客户端(比如 JedisClusterLettuceRedisson)在初始化连接时,会从任一节点获取整张路由表:

> CLUSTER SLOTS

返回内容示例:

1) 1) (integer) 02) (integer) 54603) 1) "192.168.1.101"2) (integer) 7000
2) 1) (integer) 54612) (integer) 109223) 1) "192.168.1.102"2) (integer) 7001

说明:

  • 0 ~ 5460 的 slot 属于 192.168.1.101:7000

  • 5461 ~ 10922 属于 192.168.1.102:7001

  • ...其余依此类推

客户端将这个信息缓存起来,后续操作中直接路由到正确节点,减少中转。


步骤 3:客户端直接将命令发送到对应的节点

根据 slot 映射,客户端直接将命令 SET user:123 Tom 发送到对应节点(如 192.168.1.101:7000),该节点执行写入并返回结果。


步骤 4:数据写入节点内存 + AOF/RDB 机制(与单机一致)

在目标节点中,Redis 会:

  • 将 key 写入内存(dict)

  • 触发 AOF(Append Only File) 或 RDB(快照)机制持久化

  • 主节点还会异步将写入同步给从节点


步骤 5:容灾同步(副本机制)

每个主节点都有对应的从节点。写操作默认只写主节点,再由主节点异步复制到从节点(类似 Master-Slave)。

如:

Master A(slot 0~5460) <-- async replicate -- Slave A'

3. 特殊情况:重定向(MOVED、ASK)

3.1 MOVED 重定向

当客户端访问了错误的节点,节点会返回:

-MOVED 4567 192.168.1.102:7001

客户端收到后更新本地路由表,下次访问就直接访问正确节点。

3.2 ASK 重定向(迁移槽位期间)

在 slot 迁移过程中,为了不丢请求,源节点会返回:

-ASK 4567 192.168.1.103:7003

客户端必须先向目标节点发送:

ASKING
SET user:123 Tom

4. 完整流程图(逻辑视图)

客户端 ——> 计算 CRC16(key) % 16384 ——> 查本地槽位路由表│├─ 若命中:直接访问目标 Redis 节点│├─ 若失败:收到 -MOVED,刷新路由重试│└─ 若 slot 迁移中:收到 -ASK,发送 ASKING 命令临时重定向

5. 示例:在 Java 中查看 slot 分配

import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.ClusterSlotRange;Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("192.168.1.101", 7000));
JedisCluster cluster = new JedisCluster(nodes);List<Object> slots = cluster.clusterSlots();
for (Object slot : slots) {System.out.println(slot.toString());
}

6. 小结

步骤内容
1️⃣计算 key 的槽位:CRC16(key) % 16384
2️⃣查询本地槽位路由表(CLUSTER SLOTS)找到对应节点
3️⃣发送写入命令到目标节点
4️⃣数据写入内存 + AOF/RDB
5️⃣主从同步保证容灾
⚠️Slot 迁移时用 ASK;访问错误节点会返回 MOVED

三、深入剖析客户端重定向请求流程(Jedis为例)

在 Redis Cluster 中,当客户端访问了不属于当前连接节点的 slot,会收到 Redis 返回的重定向指令(如 MOVED,客户端需自动处理重定向并缓存 slot 的正确节点信息,以避免重复跳转,提高性能。


1. 重定向响应类型

Redis Cluster 有两种重定向响应:

类型场景响应格式说明
MOVEDslot 被分配到其他节点MOVED <slot> <ip:port>永久性跳转,需要更新本地 slot 映射表
ASK临时迁移 slot 过程ASK <slot> <ip:port>临时跳转,只适用于这一次请求

2. 客户端重定向处理流程

以客户端发送以下命令为例:

jedisCluster.set("user:{123}", "OK");

假设客户端连接的是节点 A,但 user:{123} 的 slot 属于节点 B,则:

  1. 客户端向节点 A 发送请求

  2. 节点 A 响应:

    MOVED 12182 192.168.0.2:6379
    
  3. 客户端收到 MOVED 后,更新 slot -> 节点映射缓存

  4. 下一次再访问 slot 12182,客户端直接将请求发送到 B,无需再跳转


3. JedisCluster 重定向缓存机制(源码级剖析)

JedisCluster 内部维护一个结构如下的路由表:

Map<Integer, JedisPool> slotCache; // slot → JedisPool(节点连接池)

重定向更新流程:

try {Jedis jedis = slotCache.get(slot).getResource();return jedis.set(key, value);
} catch (JedisMovedDataException movedEx) {// 提取跳转目标节点HostAndPort targetNode = movedEx.getTargetNode();// 更新 slotCache 映射slotCache.put(movedEx.getSlot(), new JedisPool(poolConfig, targetNode.getHost(), targetNode.getPort()));// 重新发起请求return this.set(key, value);
}

JedisMovedDataException 会触发客户端更新 slot → 节点 的映射缓存


4. 举例说明:缓存更新演示

假设:

  • 初始 slot 12345 → 映射到 192.168.0.1:6379

  • 实际应该为 → 192.168.0.5:6379

访问:

jedisCluster.get("user:{u001}");

Redis 响应:

MOVED 12345 192.168.0.5:6379

Jedis 内部处理:

slotCache.put(12345, JedisPool(192.168.0.5:6379)); // 更新缓存

之后再次访问 slot 12345,将直接命中正确节点。


5. 与 ASK 的区别

  • MOVED:客户端更新缓存,永久跳转

  • ASK:客户端不更新缓存,仅用于迁移期间:

处理方式(Lettuce 示例):

> ASK 12345 192.168.0.3:6379// 客户端执行:
client.send("ASKING"); // 声明一次临时跳转
client.send("GET", "user:{123}");

6. 缓存失效机制

大多数客户端(Jedis、Lettuce)都会:

  • 定期刷新 slot 映射(如每 60 秒)

  • 在检测到多次 MOVED 后,触发主动更新(防止 slot 分配变化)


7. 小结

步骤客户端行为
请求到错误节点Redis 返回 MOVED
客户端收到异常解析出 slot 和目标地址
更新 slot → 节点缓存存入 slotCache 映射表
重发请求访问新的目标节点
下一次请求直接命中缓存节点,无需重定向

四、深入剖析 Redis 的两种持久化机制:RDB与 AOF

1. RDB(Redis DataBase Snapshot)

✅ 1. 原理概览

RDB 是 Redis 在某一时刻生成整个内存数据快照,持久化为 .rdb 文件。它是基于 fork 的冷快照机制,效率高、数据压缩好。

✅ 2. 触发方式

触发方式描述
自动配置如 save 900 1(900 秒至少 1 次写)
手动命令:SAVE(阻塞),BGSAVE(异步)
主从同步主机执行 RDB 并传给从机

✅ 3. BGSAVE 背后流程(关键)

客户端发起 BGSAVE →
Redis 主进程 fork 子进程 →
子进程将内存快照写入临时文件 →
写入完成后 rename 为 dump.rdb →
主进程继续处理请求

⚠️ fork 会导致主进程短暂阻塞(复制页表),但不影响服务


✅ 4. 优缺点

优点缺点
高压缩比,恢复速度快恢复时精确到某时间点,不是实时
CPU 负载低(周期执行)fork 时内存消耗大(COW)
更适合冷备份和主从同步数据可能丢失几分钟

2. AOF(Append Only File)

✅ 1. 原理概览

AOF 是将 Redis 所有写命令按顺序记录到日志中,恢复时重放这些命令即可还原数据。

如:SET key1 value1 → 写入 aof 文件

✅ 2. 持久化策略

通过 appendfsync 参数控制写入频率:

模式说明
always每次写操作都 fsync,最安全但最慢
everysec(默认)每秒 fsync 一次,最佳平衡
no不主动 fsync,依赖操作系统调度,性能高但风险大

✅ 3. AOF 重写机制(rewrite)

随着 AOF 文件不断增长,Redis 会执行 AOF 重写,生成更紧凑版本(去除冗余命令):

原始:
SET x 1
SET x 2
SET x 3重写后:
SET x 3

流程:

  1. 子进程写入压缩后的 AOF 到新文件

  2. 主进程仍接收新写入并缓存在 rewrite buffer

  3. 重写完成后,合并 rewrite buffer 并替换原始 AOF 文件


✅ 4. 优缺点

优点缺点
数据恢复完整性高(几乎不丢)文件增长较快,需定期 rewrite
可用于操作审计写入性能略低于 RDB

3. AOF 与 RDB 联合持久化(Redis 推荐)

Redis 允许两者同时启用,策略:

appendonly yes
save 900 1
  • 启动时默认优先恢复 AOF(数据更新更及时)

  • RDB 更适合全量冷备

  • 可通过配置 aof-use-rdb-preamble yes

    • AOF 文件前半部分是 RDB 快照,后半是增量命令(极大提升恢复速度)


4. 文件格式解析(底层结构)

✅ 1. RDB 文件格式

REDIS
├── Header: "REDIS0009"
├── 数据体(每个 Key 的类型、过期时间、值)
├── EOF
└── CRC64 校验和

RDB 使用自定义二进制格式压缩数据,恢复效率极高。


✅ 2. AOF 文件格式

纯文本命令格式,支持多命令协议:

*3
$3
SET
$4
key1
$5
value

即标准的 Redis 协议格式 RESP,便于重放。


5. 持久化过程分析:写入路径对比

RDB

[客户端] → [Redis Server 内存] → (fork 子进程) → [生成 dump.rdb]

AOF

[客户端] → [内存 + AOF 缓存] → [AOF Buffer] → [fsync 到磁盘]

6. 数据安全性对比(典型故障场景)

场景RDBAOF
Redis crash丢失上次 BGSAVE 后写入的数据最多丢 1 秒数据(everysec)
宿主机断电可能没有触发 save如果写入缓冲未 fsync,则丢失
文件损坏无法恢复可通过 redis-check-aof 修复

7. 实际应用建议

场景推荐
数据恢复速度要求高RDB
数据安全性高AOF
主从同步RDB(第一次同步)
日志审计AOF(可追踪所有操作)
推荐配置同时开启 AOF + RDB

8. 调优建议

✅ RDB 调优

save 900 1
save 300 10
save 60 10000

避免频繁 fork(每次触发需考虑内存)

✅ AOF 调优

appendonly yes
appendfsync everysec
aof-rewrite-percentage 100
aof-rewrite-min-size 64mb
aof-use-rdb-preamble yes

9. 总结对比表

特性RDBAOF
触发方式定期快照 / 手动实时写操作日志
恢复速度快(秒级)慢(命令多)
数据完整性可能丢失几乎不丢
文件大小小(压缩好)大(命令多)
性能影响低(周期)中(频繁写)
重启恢复优先级

五、深入剖析Redis都有哪些数据类型及其数据结构

Redis 的强大性能和灵活性,核心在于其丰富的数据类型和背后的高效数据结构。下面我们从底层实现出发,深入剖析 Redis 各数据类型的编码实现、核心数据结构、操作复杂度、使用场景与优化技巧


Redis 支持的数据类型总览

数据类型描述内部编码底层数据结构
String字符串 / 数值int / embstr / raw简单动态字符串(SDS)
List有序列表ziplist / quicklist压缩列表 / 快速链表
Hash字典表ziplist / hashtable压缩列表 / 哈希表
Set无序唯一集合intset / hashtable整数集合 / 哈希表
ZSet有序集合ziplist / skiplist压缩列表 / 跳表 + 哈希表
Bitmap位图bit array字节数组
HyperLogLog基数估计sparse/dense稀疏/密集编码
Geo地理位置sorted set跳表结构
Stream消息队列radix tree + listpack压缩字典树

1. String —— 基本类型,却极其强大

📦 编码方式

编码触发条件描述
int值可转为 long 且小于 44 字节使用 long 存储
embstr小于等于 44 字节分配连续内存块,更高效
raw大于 44 字节普通 SDS 分配堆内存

📚 底层结构:SDS(Simple Dynamic String)

struct sdshdr {int len;      // 实际长度int free;     // 多预分配空间char buf[];   // 字符数组
}

✅ 支持二进制安全、O(1) 获取长度、自动扩容缩容

⏱ 操作复杂度

  • GET / SET:O(1)

  • APPEND / INCR:O(1) 或 O(N)(扩容时)


2. List —— 双端队列,适合消息队列、任务堆栈等

📦 编码方式

编码条件描述
ziplist元素较少,元素较小连续内存,节省空间
quicklist(默认)统一使用多个 ziplist 的链表,兼顾空间与性能

📚 quicklist 结构(Redis 3.2 引入)

quicklist → ziplist → element
  • 快速插入/删除:O(1)

  • 更低碎片:每个节点存多个元素

⏱ 操作复杂度

  • LPUSH / RPUSH:O(1)

  • LPOP / RPOP:O(1)

  • LINDEX / LRANGE:O(N)


3. Hash —— 轻量级对象存储(如用户信息)

📦 编码方式

编码条件描述
ziplistkey/value 都很短,数量少节省内存
hashtable元素较多哈希表,高性能查询

📚 哈希表结构

dictEntry {void* key;void* value;dictEntry* next; // 链式冲突解决
}

⏱ 操作复杂度

  • HSET / HGET:O(1)

  • HGETALL:O(N)


4. Set —— 无序、唯一集合(如标签系统)

📦 编码方式

编码条件描述
intset所有元素为整数整数数组,无 hash 冲突
hashtable含字符串或数量大常规哈希表

📚 intset 优化

  • 有序数组 + 二分查找(插入成本稍高,查找快)

  • 自动升级类型:int16 → int32 → int64

⏱ 操作复杂度

  • SADD / SREM:O(1)

  • SINTER / SUNION:O(N)


5. ZSet(Sorted Set)—— 有序排行榜核心

📦 编码方式

编码条件描述
ziplist元素少,数据短节省空间
skiplist元素多支持范围查询、排名查询

📚 跳表结构

ZSet = 哈希表(member → score)+ 跳表(score 排序)
  • 跳表时间复杂度:

    • 插入 / 删除:O(log N)

    • 区间操作:O(log N + M)

  • 多层索引节点,快速跳跃查找


6. Bitmap —— 位级存储,节省空间(适合签到、活跃标记)

  • 实质:一个大数组的位操作(key 映射到 offset)

  • 每 bit 可表示一个状态(如签到 0/1)

  • 单条记录消耗 1 bit

⏱ 复杂度

  • SETBIT / GETBIT:O(1)

  • BITCOUNT / BITOP:O(N)


7. HyperLogLog —— 估算去重用户数

  • 原理:基于概率算法计算 基数估计(cardinality)

  • 精度误差约 ±0.81%

  • 每个 key 占用约 12 KB

应用场景

  • 日活用户数、IP 去重数估计

  • 替代 Set 的场景(当精度要求不高)


8. Geo —— 地理坐标存储与范围查询

  • 实现方式:使用 ZSet + Geohash 编码

  • 命令:GEOADD / GEODIST / GEORADIUS

使用场景

  • 附近的人/门店推荐

  • 范围定位(基于距离)


9. Stream —— 高性能消息队列(Redis 5.0+)

底层结构:Radix Tree(压缩字典树)+ Listpack(紧凑结构)

  • 每个 Stream 是一个结构紧凑的时间序列消息队列

  • 支持消息 ID、消费组、ack 等机制

应用场景

  • 日志收集、消息总线

  • 替代 Kafka 的轻量队列方案


10. 编码自动切换机制(重要!)

Redis 为节省内存,会自动选择编码结构,典型如下:

类型小数据结构大数据结构
Stringint / embstrraw
Hashziplisthashtable
Listziplistquicklist
Setintsethashtable
ZSetziplistskiplist

配置项可控制切换阈值,如:

hash-max-ziplist-entries 512
hash-max-ziplist-value 64

11. 常见使用场景匹配

场景推荐类型说明
用户属性信息Hashkey → field/value
活跃用户标记Bitmap节省空间
每日签到Bitmap / ZSet位图 or 排序签到
排行榜ZSet分值决定排名
消息队列List / Stream简单/强需求分别适配
用户标签Set无序唯一集合

12. 总结对比表

类型有序?可重复?底层结构适用场景
StringSDS缓存、计数器、配置项
Listquicklist队列、堆栈
Hashkey 唯一ziplist / dict对象字段存储
Setintset / dict标签、唯一集合
ZSet✔(按 score)skiplist + dict排行榜
Bitmap字节数组活跃标志、签到
HLL计数器去重统计
Streamradix tree消息队列

六、如何选择数据类型


1. 从业务语义出发选择数据类型

业务场景推荐数据类型说明
缓存页面内容 / JSONString适用于大段文本、序列化数据
计数器 / 限流器String支持原子自增 INCR/DECR
用户属性信息(如 name、age)Hash每个字段作为一个小 key
任务队列 / 消息队列List / Stream支持先进先出 / 多消费者
用户标签、兴趣点Set无序且唯一
排行榜、积分榜Sorted Set分数决定排名
每日签到 / 活跃用户标记Bitmap每 bit 表示一个用户
活跃 IP 数 / 去重统计HyperLogLog近似去重,占用小
附近的人 / 门店搜索Geo基于 ZSet 做地理计算

2. 从访问模式出发选择数据类型

🚀 1. 高频写入(如实时消息、统计)

  • 建议使用:List / Stream(推送)或 String(INCR)

  • 理由:原子操作 + 快速写入 + 空间压缩

🧠 2. 随机访问(如查用户属性)

  • 建议使用:Hash(如 HGET user:123 name

  • 理由:键值小、访问字段不固定、不必分多个 key

📊 3. 排名查询 / 区间检索

  • 建议使用:ZSetZRANGEZREVRANK

  • 理由:天然支持 score 排序,跳表效率高


3. 从数据结构体积出发选择

🚨 Redis 有以下编码切换机制:

  • Hash 使用 ziplist 时节省内存(小数据量)

  • 超过阈值后自动转成 hashtable(高性能)

建议如下:

类型小数据建议说明
Hash< 512 个 field使用 HSET紧凑、节省空间
List< 512 元素使用 LPUSH / RPUSH快速队列
Set元素为整数使用 Set 自动编码为 intset

客户端不需要手动管理编码切换,由 Redis 自动完成。


4. 从功能需求出发选择数据结构

功能推荐类型示例
查询是否存在Set / BitmapSISMEMBER / GETBIT
统计总数Set / HyperLogLog精确 vs 近似去重
区间统计Sorted SetZRANGEBYSCORE
多用户数据隔离前缀 + 数据类型user:1001:tags (Set), user:1001:info (Hash)

5. 客户端编码示例(Java)

以 Jedis 为例:

🎯 存储用户信息:Hash

Map<String, String> userInfo = new HashMap<>();
userInfo.put("name", "Alice");
userInfo.put("age", "30");
jedis.hmset("user:1001", userInfo);

🎯 排行榜:ZSet

jedis.zadd("scoreboard", 1000, "user1");
jedis.zadd("scoreboard", 2000, "user2");
Set<String> topUsers = jedis.zrevrange("scoreboard", 0, 9);

🎯 签到:Bitmap

int day = 5;
jedis.setbit("sign:user:1001:202506", day, true);
boolean signed = jedis.getbit("sign:user:1001:202506", day);

🎯 活跃用户去重统计:HyperLogLog

jedis.pfadd("uv:2025-06-09", "user1");
long count = jedis.pfcount("uv:2025-06-09");

6. 最佳实践建议

设计原则建议
业务模型和 Redis 数据结构强关联不要用 String 承载复杂对象
利用 key 结构分层管理(如 user:1001:xxx)避免 key 冲突
大数据分片或拆 key(如 per user / per day)避免过大 value 或 key 集合
使用 TTL 控制生命周期清理过期数据,防止内存泄漏
合理估算结构大小选择类型超过几百万用户用 Bitmap / HyperLogLog 更合适

7. 总结图:如何选择 Redis 数据类型?

              +-----------------------------+|       有顺序 &重复元素?    |+-----------------------------+|Yes     |     No↓             ↓+----------------+   +----------------+|     List       |   |      Set       |+----------------+   +----------------+↓                    ↓单队列(LPUSH/RPOP)       排名要求?↓                    ↓→ List                Yes → ZSet(score)No  → Set / Bitmap

七、基于Jedis客户端,如何操作各种数据类型


连接初始化(统一前缀)

Jedis jedis = new Jedis("localhost", 6379);  // or new JedisPool(...).getResource();
jedis.auth("your_password"); // 如果设置了密码
jedis.select(0); // 选择数据库

1. String:键值存储 / 计数器

// 设置和获取字符串
jedis.set("user:1001:name", "Alice");
String name = jedis.get("user:1001:name");// 自增计数器
jedis.incr("page:view:home"); // 每访问一次加1

2. Hash:存储对象(如用户信息)

// 存储用户信息
Map<String, String> user = new HashMap<>();
user.put("name", "Alice");
user.put("age", "30");
jedis.hmset("user:1001", user);// 获取单个字段或多个字段
String name = jedis.hget("user:1001", "name");
List<String> values = jedis.hmget("user:1001", "name", "age");// 遍历整个 Hash
Map<String, String> all = jedis.hgetAll("user:1001");

3. List:队列/堆栈(如任务、消息队列)

// 从左推入任务
jedis.lpush("task:queue", "task1", "task2");// 从右弹出执行任务
String task = jedis.rpop("task:queue");// 获取列表区间(分页)
List<String> tasks = jedis.lrange("task:queue", 0, 10);

4. Set:无序唯一集合(如标签、兴趣)

// 添加兴趣标签
jedis.sadd("user:1001:tags", "music", "travel");// 是否有某标签
boolean has = jedis.sismember("user:1001:tags", "music");// 获取所有标签
Set<String> tags = jedis.smembers("user:1001:tags");

5. ZSet(Sorted Set):排行榜 / 排名

// 添加分数
jedis.zadd("scoreboard", 1000, "user1");
jedis.zadd("scoreboard", 1200, "user2");// 获取前 N 名用户
Set<String> topUsers = jedis.zrevrange("scoreboard", 0, 9);// 获取某用户排名和分数
Long rank = jedis.zrevrank("scoreboard", "user1");
Double score = jedis.zscore("scoreboard", "user1");

6. Bitmap:签到、活跃标记

// 第5天签到(bit 位偏移)
jedis.setbit("sign:user:1001:202506", 5, true);// 判断是否签到
boolean signed = jedis.getbit("sign:user:1001:202506", 5);// 统计本月总签到天数
long signedDays = jedis.bitcount("sign:user:1001:202506");

7. HyperLogLog:去重统计(如日活)

// 添加用户ID
jedis.pfadd("uv:2025-06-09", "user1", "user2");// 获取近似去重值
long count = jedis.pfcount("uv:2025-06-09");

8. Geo:地理坐标/附近的人

// 添加地理位置
jedis.geoadd("city:store", 116.397128, 39.916527, "Beijing");
jedis.geoadd("city:store", 121.473701, 31.230416, "Shanghai");// 查询距离
Double dist = jedis.geodist("city:store", "Beijing", "Shanghai", GeoUnit.KM);// 附近5公里内地点
List<GeoRadiusResponse> results = jedis.georadius("city:store", 116.397128, 39.916527, 5, GeoUnit.KM);

9. Stream:日志/消息队列(Redis 5.0+)

// 添加一条消息
Map<String, String> message = new HashMap<>();
message.put("event", "login");
message.put("userId", "1001");
jedis.xadd("log:events", StreamEntryID.NEW_ENTRY, message);// 读取最新消息
List<Map.Entry<String, List<StreamEntry>>> streams = jedis.xread(1, 1000, new AbstractMap.SimpleEntry<>("log:events", StreamEntryID.UNRECEIVED_ENTRY));
for (Map.Entry<String, List<StreamEntry>> stream : streams) {for (StreamEntry entry : stream.getValue()) {System.out.println(entry.getID() + " " + entry.getFields());}
}

10. 附加建议

场景类型推荐注意事项
复杂对象Hashfield 层级比存 JSON 更高效
排行榜ZSetscore 控制排序
热点标记Bitmap空间效率极高
多端消费Stream支持消费组 / ack

八、分析上亿用户连续签到数据的场景,如何使用Redis实现


业务需求与技术挑战

目标技术挑战
支持上亿用户内存占用低,结构压缩高效
支持每日签到支持按天记录签到状态
支持连续签到计算需要快速判断连续天数
支持查询某天是否签到要求 O(1) 查询
高并发写入/查询高吞吐,热点控制
可扩展支持多节点,方便水平扩展
核心推荐方案:Redis BitMap + Hash 分区 + Lua 脚本

1. 数据结构选择:BitMap 存储每日签到

🧾 设计思路:

  • 每个用户一个 BitMap,每一位表示某天是否签到

  • 从第 0 位开始,bitpos = 日期 - 起始日期(如 2025-01-01)

  • 例如:user:sign:12345 的 bitmap

    010111001...↑第 n 天是否签到
    

2. Key 设计(分区压缩)

上亿用户数据建议分桶:

sign:{userId % 1024}:{userId}

这样可以:

  • 避免 Redis 集群时跨 slot 操作

  • 支持多 key 并发写入扩展性强


3. 每日签到命令:SETBIT

SETBIT sign:{userId % 1024}:{userId} offset 1
  • offset = days_since_start(userId, today)

  • 设置某天为签到状态

代码示例(Java):

int offset = (int) ChronoUnit.DAYS.between(startDate, LocalDate.now());
String key = String.format("sign:{%d}:%d", userId % 1024, userId);
redisTemplate.opsForValue().setBit(key, offset, true);

4. 查询某天是否签到:GETBIT

GETBIT sign:{userId % 1024}:{userId} offset

5. 统计连续签到天数:Lua 脚本 + BitField

可用 BITFIELD 或 Lua 脚本高效读取连续 N 天位图,例如连续 7 天:

BITFIELD sign:{userId % 1024}:{userId} GET u7 0

用位运算统计从最后一天往前连续 1 的个数:

-- 简化示例:从右往左找连续 1
local key = KEYS[1]
local len = tonumber(ARGV[1])
local count = 0for i = len - 1, 0, -1 doif redis.call('GETBIT', key, i) == 1 thencount = count + 1elsebreakend
end
return count

6. 数据优化:压缩存储

  • 每个用户 365 天只需要 365bit ≈ 46B

  • 1 亿用户:46B * 10^8 = 4.6 GB,非常小


7. 过期清理(可选)

  • 使用 ZSet 记录活跃用户(打卡用户)

  • 每天批量遍历 ZSet,设置 BITMAP 的 TTL 过期策略


8. 常见扩展功能

功能实现方式
签到排行榜用 ZSet 记录连续签到天数
连续签到奖励连续值计算后发奖
多端防重使用 SETBIT 幂等性保障
补签功能允许用户花币/广告后补位

9. 集群与高并发方案

方案 1:分布式集群分片存储(推荐)

  • 使用 Redis Cluster,将 sign:{bucket}:{userId} 映射到不同 slot

  • 保证高并发访问的负载均衡

方案 2:异步化

  • 用户签到先写入 Kafka / MQ

  • 异步批量落入 Redis,降低高峰写压


10. 小结

目标实现方案
存储节省使用 BitMap,每用户 46B 一年签到数据
查询高效GETBIT/SETBIT O(1) 操作
连续天数计算Lua 脚本或 BITFIELD 快速统计
高并发分桶 + Redis Cluster 分布式架构
扩展性支持排行榜、补签、领奖逻辑

相关文章:

  • 通过示例解释 C# 中强大的 LINQ的集运算
  • 力扣面试150题--实现Trie(前缀树)
  • c#和c++区别
  • uni-app项目实战笔记4--使用组件具名插槽slot定义公共标题模块
  • 偷懒一下下
  • Logic Error: 如何识别和修复逻辑错误
  • [MSPM0开发]之七 MSPM0G3507 UART串口收发、printf重定向,解析自定义协议等
  • day54 python对抗生成网络
  • 【Linux仓库】进程状态【进程·叁】
  • 数据结构——第二章 线性表之顺序表、单链表
  • navicat可视化页面直接修改数据库密码——mysql、postgresql、mangodb等
  • 华为云Flexus+DeepSeek征文 | 当大模型遇见边缘计算:Flexus赋能低延迟AI Agent
  • 2.3 ASPICE的架构与设计
  • 松胜与奥佳华按摩椅:普惠科技与医疗级体验的碰撞
  • 【Vue PDF】Vue PDF 组件初始不加载 pdfUrl 问题分析与修复
  • Mac电脑 系统监测工具 System Dashboard Pro
  • 在mac上安装sh脚本文件
  • Unity编辑器-获取Projectwindow中拖拽内容的路径
  • 科技新底座揭幕!2025 MWC上海锚定AI+、5G融合、双区创新三大引擎
  • 人工智能时代汽车营销如何创新突破?云徙科技汽车营销智能体助力车企立足数智化转型
  • 网站开发视频教学/游戏推广话术
  • 青岛做网站好的公司/成功的网络营销案例ppt
  • 江西教育网站建设/上海宝山网站制作
  • 如何做建材网站的线下推广/软文广告案例500字
  • 山西做网站多少钱/seo的工具有哪些
  • 济南网站建设需要多少钱/百度广告联盟app