今日日报:Redis Lua 原子脚本与三段式库存模型——从商品表拆出独立库存微服务

拆库存服务:库存不是商品的附属品 某天盯着商品表的字段列表,发现 quantity、remain_quantity、sale_count 这三个东西怎么看怎么和 name、price、cover_url 不是一家人。name 改了不频繁,库存每秒都在扣——高频写和低频读挤在同一行,互相锁着玩。 决定拆。新建了一个 mall-inventory 微服务,独立数据库 cloud_mall_inventory,三张表:inventory(主库存)、inventory_batch(批次追踪)、inventory_log(变动流水)。 最头疼的问题:扣库存的一致性 库存扣减最怕两个事:超卖和半截崩溃。 第一个做法是两条 Redis 命令: redisUtil.increment(key, -quantity); // 扣 available redisUtil.increment(frozenKey, quantity); // 加 frozen 问题很明显——第一条执行完、第二条还没跑的时候,机器崩了怎么办?available 扣了但 frozen 没加,库存"凭空消失"了。 解法是 Lua 脚本,把两条操作打包发给 Redis: local qty = -tonumber(ARGV[1]) local avail = redis.call('INCRBY', KEYS[1], qty) if avail < 0 then redis.call('INCRBY', KEYS[1], -qty) return -1 end redis.call('INCRBY', KEYS[2], -qty) return avail - qty Redis 内部保证整个脚本一次性原子执行,不存在中间状态。Spring Data Redis 的 StringRedisTemplate.execute(script, keys, args) 直接调用就行。 三段式库存模型 完整链路改成了"冻结 → 确定 → 释放": 下单 → frozen +1, available -1(冻结) ├─ 支付成功 → frozen -1, sale_count +1(确认扣减) └─ 超时/取消 → frozen -1, available +1(释放) 之前是下单直接扣 remain_quantity,30 分钟后超时取消还要回滚。但回滚依赖 MQ 消息,MQ 挂了库存就永远不恢复了。新模型不存在这个问题——「可用库存」只负责「卖」,frozen 只负责「锁」,职责拆开,逻辑自洽。 ...

七月 9, 2023 · 2 分钟 · 237 字 · yaomingye

Redis 高可用架构:主从、哨兵、集群与分片

主从、哨兵、集群与分片——高可用架构全解 一、问题切入:单机 Redis 能走多远 开发环境启动一个 Redis 实例,redis-cli 连上去,SET / GET 一切正常。然后某天线上出了问题: 促销活动期间,Redis 内存飙到 32GB 上限,新的写入被拒绝 服务器宕机,缓存全丢,所有请求直接穿透到 MySQL,服务雪崩 同一个 Key 被几百个并发请求同时修改,客户端频繁收到 READONLY 错误 这些问题指向同一个根因:单机 Redis 有三个硬伤。 硬伤 表现 后果 内存上限 一台机器最多几百 GB 内存,存不下全量数据 频繁淘汰 / OOM 单点故障 进程挂掉 → 整个缓存层不可用 请求穿透到 DB,服务雪崩 写吞吐瓶颈 单机只能处理几万 QPS 的写入,核心在主线程串行执行 促销期间扛不住 Redis 为了解决这三个问题,依次演进出了三种架构模式——主从复制、哨兵模式、集群模式。理解这三者之间的关系,是开发者对接云 Redis 服务的前提。 flowchart TD classDef startEnd fill:#701a4c,stroke:#e11d48,stroke-width:2px,color:#fce7f3,font-weight:bold; classDef process fill:#1e1e24,stroke:#6b7280,stroke-width:1.5px,color:#e5e7eb; classDef highlight fill:#450a0a,stroke:#dc2626,stroke-width:1.5px,color:#fecaca,font-weight:bold; S[📦 单机 Redis] --> M[📋 主从复制] M --> S2[🔍 哨兵模式] S2 --> C[🔗 集群模式] M --> M1["解决问题: 读扩展 + 数据冗余\n新增问题: 手动故障转移"] S2 --> S2A["解决问题: 自动故障转移\n新增问题: 写仍单点"] C --> C1["解决问题: 写扩展 + 海量数据\n新增问题: 跨槽限制"] class S,M,S2,C process; class M1,S2A,C1 highlight; class S startEnd; 这张演进路线图的含义:后一层架构不是替代前一层,而是叠加。集群模式内置了主从复制和哨兵的部分能力,但三者解决的问题域并不完全相同。下面逐一展开。 ...

一月 17, 2023 · 6 分钟 · 1251 字 · yaomingye

Redis + Caffeine 双层缓存:降级与容错

Redis + Caffeine 双层缓存 📖 前置阅读:本文是缓存架构的进阶级文章,假设读者已经掌握了 Redis 的基础操作和 Caffeine 本地缓存的 API。如果还不熟悉,建议先阅读: SpringBoot Redis 全操作指南 —— Redis 实战篇 Caffeine 核心与 SpringBoot 集成 —— Caffeine 入门篇 一、⚡ 问题切入:凌晨三点,Redis 挂了 凌晨三点,Redis 内存用满——大量的 TTL 同时到期 + 新一波定时任务写入,导致内存 OOM,Redis 进程被系统 kill。你的服务所有缓存请求全部报错,瞬间全部穿透到 MySQL,数据库连接池耗尽,整个系统不可用。 值班群炸了。你翻日志发现——服务启动时所有 @Cacheable 都配置了 Redis,Redis 一挂连个兜底的都没有。 Redis 是高可用的——有哨兵(Sentinel)、有集群(Cluster),官方说可用性能到 99.99%。但 99.99% 意味着一年有将近 1 小时的不可用时间。这 1 小时如果发生在双十一,后果就不是"维护了一次",而是"事故"。 本地缓存的价值不只是"快",更是 Redis 挂了时的最后一道防线。就算 Redis 是全宇宙最高可用的服务,网络也可能抖——交换机故障、机房间专线断掉、Kubernetes 网络策略变更——这些事情的发生概率比 Redis 自身故障高得多。 本篇要解决的问题:构建 Redis(远程)+ Caffeine(本地)双层缓存架构,把 Redis 的不可用当成"迟早会发生的事"来设计,而不是寄望于它不会发生。 📌 真实场景:数据字典——最简单的双层缓存 在进入复杂架构之前,先看一个真实项目里怎么用 Spring Cache + Caffeine + Redis 做双层缓存。不是所有场景都需要 200 行的 TieredCacheManager——有时候 3 行配置 + 1 个注解就够了。 ...

十月 31, 2022 · 11 分钟 · 2341 字 · yaomingye

Caffeine 本地缓存核心与 SpringBoot 集成

Caffeine 核心与 SpringBoot 集成 📖 前置阅读:本文假设读者已了解 Redis 基本操作和 Spring Cache 注解(@Cacheable/@CachePut/@CacheEvict)。如果还不熟悉 Redis 系列,建议先阅读 SpringBoot Redis 全操作指南。 一、⚡ 问题切入:Redis 再快也是远程调用 回顾一下,一个典型的 Redis 缓存查询是: // Redis 缓存读 User user = (User) redisTemplate.opsForValue().get("user:1001"); if (user != null) return user; // 缓存未命中,查 MySQL user = userMapper.selectById(1001L); redisTemplate.opsForValue().set("user:1001", user, 30, TimeUnit.MINUTES); return user; Redis 延迟一般在 0.5ms ~ 2ms——相比 MySQL 的 3ms ~ 10ms 已经快很多了。但这个延迟不是免费的:每次 Redis 查询都是一次网络往返(RTT)。同机房内 RTT 大约 0.1ms,跨机房可能到 2ms 甚至更久。 在高 QPS 下,这 0.5ms × 10 万次查询 = 50 秒的累计时间,还不算序列化/反序列化的 CPU 开销。而且 Redis 不是永远不会挂——网络抖动、内存满了、主从切换,任何一个都可能让 Redis 临时不可用。 ...

十月 30, 2022 · 6 分钟 · 1073 字 · yaomingye

Redis 缓存策略进阶:六大模式全解析

🚀 Redis 缓存策略进阶:六大模式全解析 📖 前置阅读:本文是 SpringBoot Redis 系列的进阶篇,假设读者已经掌握了 Redis 的基本数据结构和 SpringBoot 环境下的 RedisTemplate 操作。如果还没有,建议先阅读前两篇: Redis 核心架构:五大数据结构与常用命令全解析 —— 介绍篇 SpringBoot Redis 全操作指南 —— 实战篇 一、⚡ 问题切入:没有缓存策略会怎样? 先看一段日常开发中常见的业务代码: // 一个典型的"查缓存 → 查 DB → 写缓存"逻辑 public User getUserById(Long userId) { String cacheKey = "user:" + userId; // 1. 先查 Redis 缓存 User user = (User) redisTemplate.opsForValue().get(cacheKey); if (user != null) { return user; } // 2. 缓存未命中,查 MySQL user = userMapper.selectById(userId); if (user != null) { // 3. 写入 Redis 缓存,设置 30 分钟过期 redisTemplate.opsForValue().set(cacheKey, user, 30, TimeUnit.MINUTES); } return user; } public void updateUser(User user) { // 更新 DB userMapper.updateById(user); // 删除缓存(而非更新缓存) redisTemplate.delete("user:" + user.getId()); } 这段代码隐含了一个被广泛使用的模式—— Cache-Aside(旁路缓存) 。但这就是全部吗?当业务场景从"普通查询"扩展到"秒杀库存扣减"、“热点榜单刷新”、“写多读少日志落盘"时,上面这段代码会暴露出以下问题: ...

十月 21, 2022 · 16 分钟 · 3330 字 · yaomingye

SpringBoot Redis 全操作指南

🚀 SpringBoot Redis 全操作指南 📖 前置阅读:本文假设读者已了解 Redis 的五种核心数据结构(String / Hash / List / Set / ZSet)和基本命令。如果还不熟悉,建议先阅读 Redis 核心架构:五大数据结构与常用命令全解析。 🎯 第一步:目标说明 这篇文章的目标很明确:让读者在一篇文章内学会 SpringBoot 项目中所有常用的 Redis 操作,读完就能直接写到项目里。 具体来说,读完这篇文章会掌握: 用 StringRedisTemplate 和 RedisTemplate 操作 Redis 五种数据结构 用 Spring Cache 注解(@Cacheable、@CachePut、@CacheEvict)无侵入地加缓存 用 Redisson 实现分布式锁 Pipeline 批量操作和发布订阅 排行榜、计数器、消息队列等真实业务场景的完整代码 文中的所有代码都可以直接复制粘贴到项目里,只需要改包名和类名。 📋 第二步:前置条件 开始之前,确认以下知识储备和环境就绪: 前置项 具体要求 验证命令 JDK 17+(文中用 17,8+ 均兼容) java -version Maven 3.6+ mvn -v SpringBoot 3.x(文中用 3.2.0) mvn dependency:tree | grep spring-boot Redis 7.x(6.x 也兼容文中所有操作) redis-cli --version IDE IntelliJ IDEA / VS Code / Eclipse 均可 — 前置知识 SpringBoot 基础(依赖注入、application.yml)、SQL 基础、Linux 命令行基础 — 📌 前置知识:读者需要了解 SpringBoot 的 @Configuration、@Bean、@Autowired 基本用法,以及 application.yml 配置文件的写法。如果不熟悉 Maven 的 pom.xml 依赖管理,建议先补一下 SpringBoot 入门。 ...

十月 20, 2022 · 17 分钟 · 3462 字 · yaomingye

Redis 核心架构

Redis 核心架构:五大数据结构与常用命令全解析 一、⚡ 问题切入:MySQL 为什么不够? 先看一个典型的电商场景。商品详情页的 QPS(每秒请求数)在促销期间达到 5000,每个请求需要执行以下 SQL: -- 商品基本信息 SELECT * FROM product WHERE id = 10001; -- 商品 SKU 列表 SELECT * FROM product_sku WHERE product_id = 10001; -- 商品评价统计 SELECT COUNT(*), AVG(rating) FROM review WHERE product_id = 10001; MySQL 单机在简单查询下约能支撑 2000 ~ 3000 QPS,5000 QPS 直接打到数据库会导致连接池耗尽、响应超时,最终服务雪崩。 有人会说"加读写分离、分库分表",但这些方案在数据到达 MySQL 之前就有一个更直接的思路: 把热点数据放在内存里 。 这就是 Redis 存在的根本原因——将频繁访问的数据从磁盘(MySQL)迁移到内存,用空间换时间。一条 Redis GET 命令的延迟通常在 0.1ms 以内,而 MySQL 单条查询即使在索引命中、Buffer Pool 热数据全缓存的情况下,延迟也在 1ms ~ 5ms 之间。差距来自 存储介质 (内存 vs 磁盘)和 数据访问路径 (直接内存寻址 vs B+Tree 遍历)。 ...

十月 19, 2022 · 13 分钟 · 2659 字 · yaomingye
Cat Radio