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

记录redis学习

Redis — 从数据结构到集群原理


一、为什么是 Redis

Redis(REmoteDIctionaryServer)是一个基于内存的单线程高性能 KV 数据库。

核心指标: 读写速度:10W+ QPS / 单节点 操作原子:单线程 + 事件驱动 → 天然线程安全 持久化: RDB(快照)+ AOF(日志)双保险
对比RedisMySQLMongoDB
存储内存磁盘磁盘
QPS10W+几千几千
数据结构KV + List/Set/Hash/ZSet…文档
持久化RDB + AOF原生原生
典型用途缓存/队列/计数/分布式锁核心业务数据日志/JSON 存储

二、基础数据类型与使用场景

Redis 比你想象的更强大 — 它不是简单的 KV,而是"数据结构服务器"
类型底层结构典型应用
StringSDS(动态字符串)缓存、计数器、分布式锁
HashDict / ListPack用户信息、购物车
ListQuickList消息队列、最新列表
SetHT / ListPack标签、共同好友
ZSetSkipList + HT排行榜、延迟队列
StreamRadixTree可靠消息队列(类似 Kafka)
BitmapString 位操作签到、布隆过滤器
HyperLogLog概率算法UV 统计(12KB 估亿级)
GeoZSet 封装LBS、附近的人

2.1 String — 万物皆可存

命令:GET / SET / INCR / SETNX / SETEX 底层:SDS(Simple Dynamic String),O(1) 取长度,不会溢出
// 缓存stringRedisTemplate.opsForValue().set("user:1001",json,30,TimeUnit.MINUTES);// 计数器Longcount=stringRedisTemplate.opsForValue().increment("pv:article:123");// 分布式锁Booleanlocked=stringRedisTemplate.opsForValue().setIfAbsent("lock:order:"+orderId,"1",10,TimeUnit.SECONDS);

2.2 Hash — 对象存储

命令:HSET / HGET / HGETALL / HINCRBY 底层:Dict(默认)+ ListPack(小数据量自动切换,省内存)
// 存储用户信息Map<String,String>user=Map.of("name","张三","age","28","city","北京");stringRedisTemplate.opsForHash().putAll("user:1001",user);// 只更新某个字段stringRedisTemplate.opsForHash().increment("user:1001","age",1);

对比 String 存 JSON:Hash 可以原子更新单个字段,不用读 → 改 → 写。

2.3 List — 有序队列

命令:LPUSH / RPUSH / LPOP / RPOP / LRANGE / BLPOP 底层:QuickList(Linked List + ListPack 混合,省内存)
// 消息队列 — 生产者stringRedisTemplate.opsForList().leftPush("order:queue",orderJson);// 消费者 — 阻塞等待(60 秒超时)Stringmsg=stringRedisTemplate.opsForList().rightPop("order:queue",60,TimeUnit.SECONDS);// 最新 10 条动态List<String>latest=stringRedisTemplate.opsForList().range("timeline:user:1001",0,9);

2.4 Set — 无序去重集合

命令:SADD / SREM / SINTER / SUNION / SDIFF 底层:Dict(大 Set)+ ListPack(小 Set)
// 共同好友Set<String>commonFriends=stringRedisTemplate.opsForSet().intersect("friends:user:1","friends:user:2");// 你可能认识的人(差集)Set<String>suggestions=stringRedisTemplate.opsForSet().difference("friends:user:2","friends:user:1");

2.5 ZSet (Sorted Set) — 排行榜神器

命令:ZADD / ZRANGE / ZREVRANGE / ZRANK / ZINCRBY 底层:SkipList(跳表)+ Dict(哈希映射),查询 O(logN)

跳表原理

传统链表查找: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 O(N) 跳表: 第3层: 1 ──────────→ 5 ──────────→ 8 第2层: 1 ───→ 3 ───→ 5 ───→ 7 ───→ 8 第1层: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 O(logN)

随机层高,高层跳跃,底层精确定位。

// 排行榜 — 新增分数stringRedisTemplate.opsForZSet().add("leaderboard","user:1001",9850);// 排行榜 — 前 10 名(倒序)Set<ZSetOperations.TypedTuple<String>>top10=stringRedisTemplate.opsForZSet().reverseRangeWithScores("leaderboard",0,9);// 延迟队列 — 取到期的任务Set<String>tasks=stringRedisTemplate.opsForZSet().rangeByScore("delay:queue",0,System.currentTimeMillis());

2.6 Stream — 可靠消息队列

消息队列演进: List BLPOP → 无 ACK,消费者崩溃消息丢失 Pub/Sub → 无持久化,断连就丢消息 Stream → 有 ACK + 持久化 + 消费者组(≈ 轻量级 Kafka)
// 发送消息Map<String,String>msg=Map.of("orderId","12345","amount","99.9");stringRedisTemplate.opsForStream().add("order:stream",msg);// 消费者组消费List<MapRecord<String,Object,Object>>records=stringRedisTemplate.opsForStream().read(Consumer.from("order-group","consumer-1"),StreamReadOptions.empty().count(2).block(Duration.ofSeconds(5)),StreamOffset.create("order:stream",ReadOffset.lastConsumed()));// 确认消费stringRedisTemplate.opsForStream().acknowledge("order:stream","order-group",recordId);

2.7 其余数据类型速览

类型关键命令Java 操作
BitmapSETBIT / GETBIT / BITCOUNTopsForValue().setBit()
HyperLogLogPFADD / PFCOUNTopsForHyperLogLog().add()
GeoGEOADD / GEORADIUSopsForGeo().add()

三、为什么要用三个节点搭建集群

3.1 不是三个节点,是"三个主节点 + 至少一个副本"

Redis Cluster 最少需要六个节点:3 主 + 3 从。

三个主节点不是生产建议,而是Cluster 协议的最低数学下限

3.2 为什么是 3?

原因一:哈希槽的边界条件

Redis Cluster 固定 16384 个哈希槽(Slot) 每个 Key 通过 CRC16(key) % 16384 落到某个 Slot Master-1 Master-2 Master-3 ┌──────────┐ ┌──────────┐ ┌──────────┐ │ 0~5460 │ │ 5461~10922│ │10923~16383│ │ slot │ │ slot │ │ slot │ └──────────┘ └──────────┘ └──────────┘

如果只有 2 个主节点,一个挂了 =一半数据不可用

原因二:Raft 共识协议要求奇数

3 节点集群:至少需要 floor(3/2) + 1 = 2 票才能达成共识 2 节点集群:至少需要 floor(2/2) + 1 = 2 票 → 一票也不能少 → 等于没有容错

这就是为什么分布式共识协议(Raft / Paxos / ZooKeeper)全都用奇数节点

原因三:脑裂防护

网络分区前: Master-1 ←→ Master-2 ←→ Master-3 (3 节点集群) 网络分区后(节点 3 被隔离): Master-1 ←→ Master-2 │ Master-3 (孤立) 2/3 → 达成共识,继续服务 │ 1/3 → 无法形成多数,自动降级 → 集群分区后只有一个分区能提供读写服务,避免脑裂

四、集群故障选举机制

4.1 故障检测流程

主观下线(PFAIL): Node-A 超过 cluster-node-timeout 收不到 Node-B 的 PONG → Node-A 标记 Node-B 为 PFAIL(主观怀疑,仅自己知道) 客观下线(FAIL): Node-A 通过 Gossip 协议向其他节点广播"我觉得 B 可能挂了" 半数以上主节点也认为 B 是 PFAIL → 集群标记 Node-B 为 FAIL(坐实判定,全集群广播)

4.2 故障转移完整过程

Step 1: PFAIL 检测 Master-B 无响应超过 15 秒 → 多节点标记 PFAIL Step 2: FAIL 确认 超过半数主节点认同 → Master-B 标记 FAIL Step 3: 从节点选举 Master-B 的从节点们发起选举: Slave-1: 我是最老的从节点,主从复制偏移量最大,选我! Slave-2: 我 offset 小,放弃 Step 4: Raft 投票 其他主节点投票 Slave-1 获得 majority(过半数)选票 → 当选为新 Master Step 5: 接管 新 Master 接管旧 Master 的 Slot 通过 Gossip 协议通知全集群:Slot 0~5460 的新主人是 Slave-1

4.3 选举优先级

Slave 竞选 Master 的优先级排序: 1. repl_offset(复制偏移量) → 数据最新的从节点优先(最重要) 2. cluster-slave-validity-factor × node-timeout → 与主断开时间太久的从节点自动弃权 3. slave-priority(手动设置的优先级) → 数字越小优先级越高,0 表示永不竞选 4. nodeId 字典序 → 以上全一样时,nodeId 字母序小的当选(确定性)

4.4 脑裂处理

网络分区场景: 原 Master 被隔离,但自己不知道 集群选举出新 Master 原 Master 回归后: 发现 Slot 已被接管 → 自动降级为 Slave 与新 Master 建立主从复制 → 丢弃自己的旧数据 结果:短暂的脑裂后,最终一致,旧 Master 自己修复

五、Spring Data Redis 配置

spring:data:redis:# 单机host:localhostport:6379# 或 集群cluster:nodes:-127.0.0.1:7001-127.0.0.1:7002-127.0.0.1:7003-127.0.0.1:7004-127.0.0.1:7005-127.0.0.1:7006max-redirects:3# 或 哨兵sentinel:master:mymasternodes:-127.0.0.1:26379-127.0.0.1:26380-127.0.0.1:26381lettuce:pool:max-active:16max-idle:8min-idle:4timeout:3000ms
@ConfigurationpublicclassRedisConfig{@BeanpublicRedisTemplate<String,Object>redisTemplate(RedisConnectionFactoryfactory){RedisTemplate<String,Object>template=newRedisTemplate<>();template.setConnectionFactory(factory);// Jackson 序列化(可读,兼容性好)Jackson2JsonRedisSerializer<Object>serializer=newJackson2JsonRedisSerializer<>(Object.class);template.setKeySerializer(RedisSerializer.string());template.setValueSerializer(serializer);template.setHashKeySerializer(RedisSerializer.string());template.setHashValueSerializer(serializer);returntemplate;}@BeanpublicCacheManagercacheManager(RedisConnectionFactoryfactory){// Redis 作为 Spring Cache 的缓存后端returnRedisCacheManager.builder(factory).cacheDefaults(RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(30)).serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.string())).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(newGenericJackson2JsonRedisSerializer()))).build();}}

六、Jedis vs Lettuce vs Redisson

JedisLettuceRedisson
定位客户端客户端(Spring 默认)高级框架
线程模型同步 + 连接池Netty 异步(单连接复用)Netty 异步
集群支持
分布式锁手写手写一行代码
读写分离
发布订阅
最适合简单项目Spring Boot 默认需要锁/队列/限流

Redisson 分布式锁(一行代码)

RLocklock=redissonClient.getLock("lock:order:"+orderId);// 自动续期(看门狗),不用担心业务超时锁被释放lock.lock();try{// 业务逻辑}finally{lock.unlock();}// 或 tryLock 带超时if(lock.tryLock(3,10,TimeUnit.SECONDS)){try{...}finally{lock.unlock();}}
看门狗(Watchdog)机制: 你 lock() 了,但业务跑了太久? → Redisson 自动每 10 秒续期一次 → 业务完成 unlock(),看门狗停止 → 永远不会出现"锁过期被其他线程抢走"的问题

七、持久化:RDB vs AOF

RDBAOF
全称Redis DatabaseAppend Only File
原理定时快照整个内存记录每次写操作
文件大小小(压缩二进制)大(文本命令)
恢复速度慢(逐条回放)
数据丢失两次快照之间几乎不丢(每秒 fsync)
适用备份、灾难恢复主持久化
生产建议:RDB + AOF 双开 RDB 每 1 小时一次 + AOF 每秒 fsync → 崩溃最多丢 1 秒数据 → 恢复时先加载 RDB 再回放 AOF

八、内存淘汰策略

当内存到达 maxmemory 时: noeviction — 不淘汰,写操作直接报错(默认,不推荐) allkeys-lru — 所有 Key 中淘汰最近最少使用的(推荐) allkeys-lfu — 所有 Key 中淘汰使用频率最低的 volatile-lru — 只淘汰设了过期时间的 Key volatile-ttl — 淘汰 TTL 最短的 缓存场景 → allkeys-lru 排行榜/计数 → noeviction(不能丢数据)

九、缓存三大经典问题

问题原因解决方案
缓存穿透查不存在的数据,每次都穿透到 DB布隆过滤器 / 空值缓存
缓存击穿热点 Key 过期,瞬间全部打到 DB互斥锁加载 / 永不过期 + 异步更新
缓存雪崩大量 Key 同时过期 / Redis 宕机TTL + 随机偏移 / 多级缓存 / 集群
// 缓存击穿 — 互斥锁方案publicUsergetUser(Longid){Stringkey="user:"+id;Usercached=(User)redisTemplate.opsForValue().get(key);if(cached!=null)returncached;// 只有一个线程去查 DBStringlockKey="lock:user:"+id;Booleanlocked=redisTemplate.opsForValue().setIfAbsent(lockKey,"1",10,TimeUnit.SECONDS);if(Boolean.TRUE.equals(locked)){try{Useruser=userMapper.selectById(id);redisTemplate.opsForValue().set(key,user,30,TimeUnit.MINUTES);returnuser;}finally{redisTemplate.delete(lockKey);}}else{Thread.sleep(100);returngetUser(id);// 重试}}

十、总结

数据类型 → 不止 KV,List/Set/ZSet/Stream 覆盖 90% 的业务场景 集群选举 → Gossip 故障检测 + Raft 投票 + Slot 接管,自动故障转移 三个节点 → 哈希槽分片 + 奇数共识 + 脑裂防护,不是随意选的 Java 操作 → Spring Data Redis(配置驱动)+ Redisson(高级抽象)
http://www.cnnetsun.cn/news/3067386.html

相关文章:

  • 小米手机投屏到电脑:小米互联+Phone Link+远程软件
  • VL822 USB3.1 Gen2 HUB芯片选型与Type-C扩展坞设计实战
  • 大模型MoE架构原理:稀疏激活与专家路由技术解析
  • XZ6215输入电压6.5V,输出电压1.2-5.0V,输出电流300mA,CMOS降压型电压稳压器
  • 智科毕设新颖的开题大全
  • Web身份验证漏洞攻防实战:从暴力破解到MFA绕过的全面防御指南
  • 【ANSYS Sherlock实战指南】第一步:ODB++文件导入与属性映射详解
  • AntiDupl.NET架构深度解析:现代图像去重技术的工程实现
  • 在openEuler 22.03 LTS上实战部署Docker:从源配置到避坑指南
  • LibreTranslate 1.9.6:三大架构突破实现边缘计算时代的离线翻译革命
  • 前端基础面试题及答案
  • 国内线下会话分析解决方案实施指南:企业级AIOT硬件选型与部署策略
  • 2026 AI营销机构选型指南:本土服务商塔米德数智科技的价值与路径
  • 国内首批《人工智能 智能体互联》国家标准发布——Agent 有了交通规则
  • 计算机毕业设计之大学生教务评教系统的设计与实现
  • 德思特工业级天线方案:助力头部AGV制造商成功打造北美超级工厂标杆项目
  • 还在为验布机效果担心?这五个常见顾虑,AI其实已经解决了
  • 【技术解析】SimpleNet:在特征空间“制造”异常,实现高效图像缺陷检测与定位
  • Vivado IBERT实战:从眼图分析到误码率调优的硬件调试指南
  • 2026实测|TRAE与Copilot选择建议:从踩坑到选型全指南
  • HunterPie:为《怪物猎人:世界》打造的专业级实时监控与数据可视化增强工具
  • 2026年9款CRM管理系统对比:覆盖初创、团队与行业定制
  • 陶瓷卫浴整厂输送线怎么规划合理?4 个核心设计要点与避坑指南
  • 对话希迪智驾CEO胡斯博:“重载具身智能”的本质是系统工程能力
  • 如何用SPT-AKI Profile Editor终极存档修改器掌控你的塔科夫离线体验
  • Flux、Mono、Reactor 核心操作符与高阶应用场景深度解析
  • 第一章Netty,Selector之cancel
  • 个人项目 UI 没配图?用 Pexels API + Claude Code 一键搞定
  • MateCloud 5.0.8 正式版:Spring Boot 4 + Spring AI 2.0,把微服务脚手架推进到 AI 原生工程底座
  • 攻克Win7离线壁垒:VMware vCenter Converter Standalone 6.2服务启动报错(Cannot Start Service)的深度解析与实战修复