推荐使用 HyperLogLog 数据结构,适合海量数据的基数统计:
添加用户:PFADD uv:20231001 user1 user2 ... 记录当天访问的用户 ID。
统计 UV:PFCOUNT uv:20231001 返回当天独立用户数。
合并统计:PFMERGE uv:202310 uv:20231001 uv:20231002 合并多天数据。
优势:占用空间小(约 12KB),支持百万级数据,误差率约 0.81%,远优于 Set 或 Bitmap(内存占用大)。
Geo 是 Redis 3.2 新增的地理位置数据结构,用于存储和查询经纬度信息,支持距离计算和范围查询。
核心命令:
GEOADD key longitude latitude member:添加地理位置(如 GEOADD cities 116.40 39.90 beijing)。
GEODIST key member1 member2 [unit]:计算两点距离(单位 m/km/mi/ft)。
GEORADIUS key longitude latitude radius unit:查询指定经纬度范围内的成员。
GEORADIUSBYMEMBER key member radius unit:查询指定成员周围的成员。
底层基于 Sorted Set 实现,通过 GeoHash 编码经纬度为分数,支持高效范围查询,适合附近的人、商家定位等场景。
常用 Redis 客户端:
Java:Jedis(简单轻量,需手动管理连接)、Redisson(功能丰富,支持分布式锁、自动续期等高级特性,适合复杂场景)、Lettuce(基于 Netty,异步非阻塞,Spring Boot 默认客户端)。
Python:redis-py(官方推荐,支持 Pipeline、事务)。
Go:go-redis(功能全面,支持集群)。
项目中可根据语言、性能需求和功能复杂度选择,如 Java 项目常用 Redisson 处理分布式锁,Lettuce 用于高并发场景。
Redis String 类型的最大值为 512MB。这是 Redis 对单个字符串值的硬性限制,无论底层是 SDS 还是其他编码方式,均不能超过此大小。实际使用中应避免存储过大字符串(如超过 100KB),以免影响 Redis 性能(如序列化耗时、网络传输慢)。
内存优化:
启用内存淘汰策略,删除无用数据。
压缩大键,拆分 Big Key。
合理设置过期时间,避免内存溢出。
并发优化:
使用 Pipeline 批量处理命令,减少网络往返。
启用 Redis 6.0+ 的多线程 I/O,提升网络处理能力。
避免长时间阻塞命令(如 KEYS、HGETALL),改用 SCAN 分批操作。
架构扩展:
主从复制 + 读写分离,分散读压力。
集群分片,分散数据和写压力。
增加节点数量,水平扩展。
硬件升级:使用 SSD 降低 I/O 延迟,增加内存提高缓存命中率。
原因:Redis 中字符串对象有两种编码:EMBSTR(嵌入式,字符串与对象元数据存在同一内存页)和 RAW(独立分配内存)。EMBSTR 阈值 44 字节是因为:
Redis 内存页为 64 字节,对象元数据(如 len、alloc 等)占 20 字节,剩余 44 字节用于存储字符串,确保字符串和元数据在同一页,减少内存碎片。
调整历史:
Redis 3.2 前阈值为 39 字节,因元数据结构不同。
后续版本优化元数据布局,阈值调整为 44 字节,更高效利用 64 字节内存页。
当字符串长度 ≤44 字节时用 EMBSTR,>44 字节时自动转为 RAW。
原生批处理命令(如 MSET、MGET):
单个命令处理多个键值对,原子执行(要么全成功,要么全失败)。
仅支持特定命令(如 MSET 只处理 String 类型的设置)。
服务器一次性解析并执行,返回合并结果。
Pipeline:
客户端批量发送多个任意命令,服务器按顺序执行,批量返回结果。
非原子执行(中间命令失败不影响后续),支持所有命令组合。
减少网络往返次数,但不保证原子性。
场景:原生批处理适合同类型命令的原子操作,Pipeline 适合多类型命令的批量执行。
一主一从:一个主节点搭配一个从节点,简单易维护,适合小规模场景。
一主多从:一个主节点搭配多个从节点,主节点写,从节点分担读压力,适合读多写少场景。
级联复制(树状结构):主节点 → 从节点(同时作为其他从节点的主节点)→ 从节点,减轻主节点复制压力,适合从节点数量多的场景。
主从 + 哨兵:在主从基础上增加哨兵节点,实现自动故障转移,提升高可用性,是生产环境常用架构。
插入:LPUSH key value(左插)、RPUSH key value(右插)、LINSERT key BEFORE/AFTER pivot value(指定元素前后插入)。
删除:LPOP key(左删并返回)、RPOP key(右删并返回)、LREM key count value(删除指定数量的 value)。
查询:LRANGE key start stop(获取范围内元素)、LINDEX key index(获取指定索引元素)、LLEN key(获取长度)。
修改:LSET key index value(设置指定索引元素)。
阻塞操作:BLPOP key timeout、BRPOP key timeout(阻塞式弹出,无元素时等待)。
队列(FIFO,先进先出):
用 LPUSH key value 从左侧插入元素,RPOP key 从右侧弹出元素。
或 RPUSH key value 从右侧插入,LPOP key 从左侧弹出。
阻塞队列:用 LPUSH + BRPOP(带超时的阻塞弹出),避免空轮询。
栈(LIFO,后进先出):
用 LPUSH key value 从左侧插入元素,LPOP key 从左侧弹出元素(后插入的先弹出)。
或 RPUSH key value 从右侧插入,RPOP key 从右侧弹出。
Redis List 基于双向链表或压缩列表实现,支持高效的两端操作,适合实现队列和栈。
Ziplist(压缩列表):
特点:连续内存块存储多个元素,无指针跳转,节省空间;元素紧密排列,适合小数据场景。
优势:内存利用率高,遍历速度快。
缺点:插入 / 删除元素可能引发连锁更新(重新分配内存),大数据量下性能下降。
应用:作为 Hash、List、Sorted Set 的底层实现(数据量小时)。
Quicklist(快速列表):
特点:由多个 Ziplist 组成的双向链表,每个 Ziplist 称为一个节点;平衡了空间和性能。
优势:解决了 Ziplist 连锁更新问题,支持高效的两端操作;通过配置节点大小(list-max-ziplist-size)控制内存占用。
应用:Redis 3.2+ 中替代普通链表作为 List 类型的底层实现。
网络问题:主从节点跨机房或网络带宽不足,导致 RDB/AOF 传输延迟。
主节点负载高:主节点写操作频繁,导致 BGSAVE 生成 RDB 缓慢,或命令缓冲区积压。
从节点同步慢:从节点硬件性能差(CPU / 内存不足),加载 RDB 或执行命令慢。
大键操作:主节点操作 Big Key 导致命令传播延迟,从节点执行耗时。
复制积压缓冲区不足:主节点 repl-backlog-size 过小,从节点断线重连时需全量同步。
并发写量大:主节点短时间内大量写操作,从节点跟不上同步节奏。


15.Redis 的 ListPack 数据结构是什么?
ListPack 是 Redis 5.0 引入的新数据结构,用于替代 Ziplist,优化存储效率和性能。
特点:
连续内存块存储元素,无冗余空间,比 Ziplist 更节省内存。
每个元素前存储长度信息,避免 Ziplist 的连锁更新问题。
支持快速定位元素,遍历效率高。
应用:Redis 6.2+ 中作为 Hash、Sorted Set 等类型的底层实现(替代部分场景的 Ziplist),未来计划逐步取代 Ziplist。
内存碎片化:Redis 频繁分配和释放内存(如频繁修改、删除键),导致内存空间被分割成大量不连续的小块,总空闲内存足够但无法分配连续大块内存的现象。
优化方法:
启用自动内存整理:Redis 4.0+ 支持 activerehashing yes,后台线程定期整理哈希表碎片。
合理设置数据结构:避免频繁修改大键,使用更紧凑的数据结构(如 Hash 替代多个 String)。
重启实例:极端情况下,重启 Redis 可重建内存布局,消除碎片(需配合持久化避免数据丢失)。
控制键生命周期:及时删除无用键,减少内存分配回收频率。
Redis 虚拟内存(VM)机制是早期版本(2.4 及之前)用于解决内存不足的方案,允许将部分不常用数据 swap 到磁盘,释放内存给活跃数据。
原理:将内存中的冷数据(不常访问)序列化后写入磁盘文件,内存中仅保留索引;访问冷数据时从磁盘加载回内存。
现状:Redis 2.6 后已废弃 VM 机制,原因是:
磁盘 I/O 延迟高,影响性能。
现代服务器内存成本降低,更推荐通过主从、集群扩展内存。
可通过持久化(RDB/AOF)+ 内存淘汰策略替代 VM 的功能。
Redis 集群通过哈希槽(Hash Slot)定位键,步骤如下:
集群共有 16384 个哈希槽,每个节点负责一部分槽位。
计算键的槽位:CRC16(key) % 16384,得到 0-16383 之间的槽位值。
节点通过 Gossip 协议交换槽位分配信息,每个节点知道所有槽位对应的节点。
客户端访问键时:
若连接的节点负责该槽位,直接处理。
否则,节点返回 MOVED 重定向信息,包含目标节点地址,客户端重新连接目标节点。
此机制实现了数据的分布式存储和访问路由。
单线程 + I/O 多路复用:用 epoll/kqueue 处理网络事件,避免多线程锁竞争,兼顾并发和简单性。
动态字符串(SDS):优化 C 字符串缺陷,支持 O (1) 长度计算、自动扩容和二进制安全。
跳表(Skiplist):Sorted Set 底层实现,平衡查找、插入效率,比红黑树实现更简单。
渐进式 rehash:哈希表扩容时,分多次迁移数据,避免单线程阻塞(如 dictRehashStep 函数)。
写时复制(COW):BGSAVE 和主从复制时,子进程共享主进程内存,修改时才复制页面,节省内存。
编码转换:根据数据量自动切换底层编码(如 String 从 int→embstr→raw),平衡空间和性能。
Redisson 分布式锁基于 Redis 实现,核心原理:
加锁:通过 Lua 脚本原子执行 hset 命令,存储锁的键、唯一标识(UUID + 线程 ID)和重入次数,同时设置过期时间(默认 30 秒)。支持可重入(同一线程重复加锁时重入次数 + 1)。
解锁:Lua 脚本验证唯一标识,重入次数 - 1,若为 0 则删除锁键,保证原子性。
续期:内置看门狗(Watch Dog)机制,若业务未完成,每隔 10 秒(过期时间的 1/3)自动延长锁过期时间,避免锁提前释放。
高可用:支持 Redis 集群、哨兵模式,通过 Red Lock 算法(多节点加锁)提升可靠性。
Redis Zset(有序集合)底层根据数据量大小选择两种实现:
压缩列表(Ziplist):当元素数量少(≤128 个)且元素体积小(≤64 字节)时使用。
存储结构:按分数升序存储,每个元素包含 “成员 + 分数”,通过压缩列表的连续内存存储。
跳表(Skiplist)+ 哈希表:数据量大时使用。
跳表:按分数排序存储元素,支持快速范围查询和排序。
哈希表:映射成员到分数,支持 O (1) 时间复杂度的分数查询和更新。
两种结构结合,兼顾了内存效率(小数据)和操作性能(大数据)。
相比红黑树:
跳表实现更简单,代码维护成本低,适合 Redis 简洁设计理念。
范围查询效率更高,跳表可直接遍历底层链表,红黑树需中序遍历,操作复杂。
插入时无需旋转平衡,平均性能更稳定。
相比 B + 树:
B + 树适合磁盘存储(多路平衡),Redis 是内存数据库,跳表的内存利用率更高。
跳表插入删除无需大规模调整节点,性能更优。
B + 树结构复杂,不适合 Redis 对简单性的追求。
Redisson 看门狗是分布式锁的自动续期机制,用于避免业务未完成时锁过期:
触发条件:当加锁时未指定过期时间(leaseTime = -1),自动启用看门狗。
工作原理:
加锁成功后,启动后台定时任务(默认间隔 10 秒)。
任务检查锁是否仍被当前线程持有,若持有则延长锁过期时间(默认延长至 30 秒)。
业务完成并解锁后,看门狗任务自动取消。
优势:无需手动续期,简化分布式锁使用,避免因业务执行时间超过锁过期时间导致的并发问题。
配置:可通过 lockWatchdogTimeout 参数调整看门狗超时时间。