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

JVM 面试突击

🔬 JVM 面试突击:运行时数据区、类加载、GC 与调优全解析 📌 前置知识:阅读本文需要具备 Java 基础语法知识、对 JVM 有初步概念(知道 JVM 是运行 Java 程序的虚拟机即可)。本文定位为面试突击速查手册,每个考点都按"面试怎么答"组织,命令部分附带完整的操作步骤和输出解读。 各模块面试频率参考 在开始具体考点之前,先了解各模块的面试出现频率,有助于合理分配复习时间: 模块 面试频率 关键程度 运行时数据区 ⭐⭐⭐⭐⭐ 每场必问,入门级考点 类加载机制 ⭐⭐⭐⭐⭐ 双亲委派模型高频出现 垃圾回收机制 ⭐⭐⭐⭐⭐ 区分候选人水平的关键 调优工具与实战 ⭐⭐⭐⭐ 考察实际动手能力 JMM + volatile ⭐⭐⭐⭐⭐ 并发底层原理 经典面试题 ⭐⭐⭐⭐ 综合应用能力 📌 一、JVM 运行时数据区:内存布局与职责 🧠 1.1 JVM 内存布局全景图 这是面试最常考的入门题,必须清楚每个区域的功能、是否为线程共享,以及各自可能抛出的异常。下面先用 Mermaid 展示 JVM 内存区域的整体分类: flowchart LR JVM(["🔷 JVM 运行时数据区"]) JVM --> SHARED["👥 线程共享区"] JVM --> PRIVATE["🔒 线程私有区"] SHARED --> HEAP["📦 Java堆\n对象实例 / 数组\nGC 主要区域"] SHARED --> METHOD["📋 方法区\n类信息 / 常量 / 静态变量\nJDK8+ 元空间实现"] PRIVATE --> PC["📍 程序计数器\n字节码行号指示器\n无OOM"] PRIVATE --> VMSTACK["📚 虚拟机栈\n栈帧: 局部变量表+操作数栈+动态链接\nStackOverflowError / OOM"] PRIVATE --> NATIVE["🔧 本地方法栈\nnative 方法服务\nStackOverflowError / OOM"] classDef root fill:#0f172a,stroke:#3b82f6,stroke-width:2px,color:#bfdbfe,font-weight:bold; classDef branch fill:#2d1a05,stroke:#f59e0b,stroke-width:2px,color:#fde68a,font-weight:bold; classDef leaf fill:#1e1e24,stroke:#6b7280,stroke-width:1.5px,color:#e5e7eb; classDef private fill:#1e293b,stroke:#0284c7,stroke-width:1.5px,color:#f8fafc; class JVM root class SHARED,PRIVATE branch class HEAP,METHOD leaf class PC,VMSTACK,NATIVE private 下面用 HTML+CSS 布局图精确展示 JVM 内存各区域的相对位置、大小关系和内部结构: ...

十月 18, 2022 · 12 分钟 · 2372 字 · yaomingye

高并发企业自营电商小程序系统架构设计

🏗️ 高并发企业自营电商小程序系统架构设计:从需求分析到部署监控的全链路方案 从一个真实的架构评审说起 某团队接到一个需求:为公司开发一款自营电商小程序,C 端消费者可以在小程序上购买公司产品。预估用户量 5 万 ~ 10 万,产品部门希望赶在活动上线前交付。 架构师在评审会上画出了第一版方案:Spring Boot 单体应用 + MySQL + Redis 缓存。评审进行到一半,有人提出了一个问题:“如果 1 万个用户同时抢一个秒杀商品,这套架构能撑住吗?” 这个问题让团队陷入了沉默。单体应用的库存扣减在数据库层面是一条 UPDATE ... SET stock = stock - 1 WHERE stock > 0,在高并发下这条 SQL 会成为瓶颈——数据库行锁(InnoDB 行级锁,对某一行数据的排他锁定)会让所有请求串行化,QPS(Queries Per Second,每秒请求数)直接降到数据库单行更新的极限:约 500 ~ 1000。 ⚠️ 新手提示:数据库行锁串行化的意思是,当 1000 个请求同时更新同一行数据(比如同一商品的库存),InnoDB 会让它们排队执行,每个请求必须等前一个提交后才能继续。这不是"慢",而是"一个一个来"——1000 个请求就是 1000 次串行操作。 这就是本文要解决的核心问题: 如何设计一套能支撑 2 万 QPS 的企业自营电商系统,同时保证库存不超卖、订单不丢失、支付不重复。 📌 前置知识:阅读本文需要了解 Spring Boot 基础、Redis 基本操作、MySQL 基本用法、消息队列(RocketMQ/Kafka)的基本概念。如果对微服务架构不熟悉,建议先了解服务注册与发现(Nacos)的基本概念。 🏗️ 一、需求分析与业务建模 🎯 1.1 业务范围界定 在设计任何系统之前,第一步是明确边界——知道什么要做什么不做。 维度 内容 业务形态 企业自营 B2C 电商小程序,企业是唯一商家,面向 C 端消费者 用户规模 预估 1 万 ~ 10 万注册用户 峰值 QPS 预估 1000 ~ 5000(设计目标:支撑 2 万 QPS) 核心功能 商品浏览、下单、支付、退款、物流查询 这里有一个关键决策: 设计目标(2 万 QPS)远大于预估峰值(5000 QPS) 。这不是过度设计,而是为以下场景预留缓冲: ...

十月 13, 2022 · 14 分钟 · 2934 字 · yaomingye

一次性讲明白 Filter、Interceptor、RequestAdvice、ResponseAdvice

一次性讲明白 Filter、Interceptor、RequestAdvice、ResponseAdvice:执行顺序与实际应用 一、从一个常见需求说起 开发一个 Web 接口,通常需要处理以下事情: 记录每个请求的耗时日志 校验登录态,未登录拒绝访问 对请求参数做预处理(比如解密、格式转换) 对响应结果做统一封装(比如统一返回 {code, msg, data} 格式) 这四个需求对应的正是四个组件: 需求 对应组件 执行位置 记录请求日志 Filter Servlet 容器层(最外层) 登录校验 Interceptor Spring MVC 层(Controller 前后) 请求参数预处理 RequestAdvice Controller 方法执行前 响应统一封装 ResponseAdvice Controller 方法执行后 这四个组件在一条请求链路中各自负责不同的阶段。先看一张总览图,建立位置感: flowchart TD %% 半暗底色 + 高亮描边:完美适配博客深色/浅色双主题 %% classDef process fill:#1e1e24,stroke:#6b7280,stroke-width:2px,color:#e5e7eb; classDef data fill:#052e16,stroke:#16a34a,stroke-width:2px,color:#bbf7d0,font-weight:bold; classDef startEnd fill:#701a4c,stroke:#e11d48,stroke-width:2.5px,color:#fce7f3,font-weight:bold; A[客户端请求] --> B[Filter] B --> C[DispatcherServlet] C --> D[Interceptor.preHandle] D --> E[RequestAdvice] E --> F[Controller] F --> G[ResponseAdvice] G --> H[Interceptor.postHandle] H --> I[Interceptor.afterCompletion] I --> J[Filter 返回] J --> K[客户端响应] class E,G data; class A,B,C,D,F,H,I,K process; class J startEnd; 这张图只需要记住一个核心原则: Filter 在最外层,Interceptor 在中间层,Advice 在最内层(紧贴 Controller)。 请求进来从外到内,响应出去从内到外。 ...

十月 9, 2022 · 7 分钟 · 1426 字 · yaomingye

学会够用就行

💼 学会够用就行:一个后端开发的技术学习反思 📌 一、让人崩溃的瞬间 一个典型的微服务项目,技术栈清单:注册中心、配置中心、网关、RPC 框架、熔断降级、消息队列、链路追踪、分布式事务——光把这些组件的名字列出来就能占满一页文档 📋。 很多开发者的第一反应不是"每个组件解决什么问题",而是"每个组件的底层原理是什么" 🔬。因为有一个根深蒂固的观念:只会用,就是调参工程师;看不懂源码,就永远停留在表面。 于是开始列学习计划 📝。每个组件背后对应一个庞大的知识体系——光是其中一个,就涉及网络协议、数据一致性、故障转移、存储引擎。密密麻麻的学习大纲列出来了,以为只要按计划来,半年后就能"通吃"。 结果可想而知。三个月过去,没有一个组件真正学完 ⏰。每次深入到一个点,就会牵出另外三个不熟悉的概念,每一个又对应着新的源码、新的论文、新的文档。计划不断膨胀,焦虑不断积累 😰,而真正沉淀下来的知识少得可怜。 更典型的反应是:每次看到新的技术文章、新的开源项目、新的最佳实践,第一反应不是"学到了新东西",而是"我又落后了" 📉。这种"松鼠病" 🐿️(不断囤积学习资料,却从不真正消化)带来的不是进步,是持续的自我怀疑。 🔍 二、试图"全部精通"的失败模式 这种失败的学习尝试有清晰的模式: 选定一个组件,找到官方文档和源码 从入口类开始,顺着调用链往下读 读到一个关键分支时,发现它又依赖另一个不熟悉的领域 于是开一个新坑,转弯去研究那个依赖 笔记越记越多,分支越开越散,但没有一个能完整收尾 这种"递归式深挖" 🔁 的学习方式,表面上看是在追求深度,实际上只是在不同的表层之间跳转——每一层都浅尝辄止,因为时间根本不够。 后果也很直接: 工作效率下降 :写简单功能时总觉得"还没彻底搞懂",犹豫不决,原本一小时能完成的拖了一下午 面试暴露出知识表面化 :简历上写了"熟悉 XX 原理",遇到追问细节时就露馅——因为每个都只看了一部分,没有形成体系 反馈循环崩溃 :投入了大量时间却没有可验证的产出,越学越不知道自己学了什么,越不知道就越焦虑 这就是"全部精通"的悖论 ⚡:越想全部搞懂,越是什么都搞不懂;越努力,越焦虑。 ⚙️ 三、转折点:接受一个现实 这个现实很简单,但接受它需要时间 ⏳: 一个人不可能精通所有东西。 微服务生态不是某一个公司设计的统一框架,它是几十个开源项目各自演进、互相适配之后形成的一张网 🕸️。每个项目背后都有几个全职维护者,他们花了几万小时才做到今天的程度。指望一个人在业余时间把这些都"深入掌握",这本身就是不切实际的。 关键认知转变在这里: “会用就行"不是放弃学习 ——它是把有限的精力从"全面深挖"转移到"按需深入"上 “会用就行"不是不追求原理 ——它是在遇到问题、需要答案的时候才去追求原理,而不是在不理解问题之前就试图背下所有实现细节 “会用就行"的核心是聚焦 ——你不可能在所有方向上都跑赢所有人,但可以在一个方向上走得更远 用一个表格来对比这两种状态: 维度 试图全部精通 接受"够用就行” 学习驱动力 焦虑(怕落后) 需求(解决问题) 学习范围 所有组件,全面铺开 按项目需要,按兴趣聚焦 深度标准 “源码每一行都看懂” “能定位问题、能做出决策” 时间投入 所有业余时间 有重点的投入 心理状态 持续焦虑、自我怀疑 可控、可持续 实际产出 一堆半成品笔记 能落地的方案和代码 真正让人内化这个认知的,不是某篇文章或某本书,而是一次又一次的实际工作经历 💼——大部分线上问题,需要的不是"精通源码”,而是"知道该去哪里查” 🔍。 ...

十月 8, 2022 · 1 分钟 · 191 字 · yaomingye

CoAP 受限应用协议

📡 CoAP 受限应用协议:报文格式、通信模型与物联网实战全解析 从一个智能灯控场景说起 假设你正在开发一套智能路灯系统。每个路灯上有一颗低功耗 MCU(微控制器),通过 NB-IoT(窄带物联网)蜂窝网络上报状态、接收开关指令。MCU 的 RAM 只有 64KB,Flash 只有 256KB,网络带宽不到 100kbps,每月流量限额 30MB。 你能在这颗 MCU 上跑 HTTP 吗? 不能。 原因有三: HTTP 基于 TCP,TCP 三次握手 + TLS 握手需要至少 5 ~ 7 个往返(RTT),在 100kbps 窄带网络上耗时数秒 HTTP 头部是纯文本,一个 GET /status HTTP/1.1\r\nHost: ... 请求头轻松超过 200 字节,而传感器上报的有效数据可能只有 4 字节(一个温度值) TCP 连接要保持状态,MCU 内存不足以维护大量连接 CoAP (Constrained Application Protocol,受限应用协议)就是为这种场景设计的。它用 UDP 替代 TCP、用 4 字节定长二进制头部替代 HTTP 的文本头、用简单的重传机制替代 TCP 的复杂拥塞控制,让一颗 64KB RAM 的 MCU 也能参与到互联网架构中。 下面先用一段概念性代码感受 CoAP 的编程模型: ...

十月 5, 2022 · 11 分钟 · 2332 字 · yaomingye

从单体到微服务

🏗️ 从单体到微服务:拆分决策、业务边界分析与中间件选型全指南 🏗️ 一、问题切入:一个电商系统的"临界点" 假设你接手了一个运行了两年的电商单体应用。它使用 Spring Boot + MyBatis + MySQL 开发,所有模块——用户、商品、订单、库存、支付、物流——都在一个 Git 仓库、一个进程、一个数据库里运行。 刚开始 3 个开发,CI/CD 流水线 3 分钟跑完。现在团队扩到了 18 人,一个订单功能的改动要等 UI 模块的测试先跑完才能部署。上周,运营活动模块的内存泄漏导致支付服务也一起挂了——整个系统 40 分钟不可用。 这种场景不是假设,它是大多数高速增长的业务最终都会撞上的临界点。接下来的问题是: 你该不该拆?如果拆,怎么拆?拆完各服务怎么通信?中间件怎么选? 这篇文章回答这四个问题。 flowchart TD %% ========================================== %% 样式定义 %% ========================================== classDef startEnd fill:#701a4c,stroke:#e11d48,stroke-width:2px,color:#fce7f3,font-weight:bold;; classDef problem fill:#450a0a,stroke:#dc2626,stroke-width:1.5px,color:#fecaca,font-weight:bold;; classDef question fill:#2a1147,stroke:#a855f7,stroke-width:1.5px,color:#ede9fe,font-weight:bold;; subgraph MONOLITH ["单体架构现状"] M[单个 Spring Boot 进程\n所有模块耦合在一起] --> S1[代码冲突频繁\n18 人改同一仓库] M --> S2[部署互相阻塞\n改订单要等 UI 构建] M --> S3[故障无隔离\n内存泄漏拖垮全站] end subgraph DECISION ["你需要回答四个问题"] Q1([该不该拆?]) --> Q2([怎么拆?]) Q2 --> Q3([怎么通信?]) Q3 --> Q4([中间件选什么?]) end S1 -.->|推动决策| Q1 S2 -.->|推动决策| Q1 S3 -.->|推动决策| Q1 class M startEnd; class S1,S2,S3 problem; class Q1,Q2,Q3,Q4 question; 🏗️ 二、什么时候该拆:六个关键信号 拆分的收益永远伴随着代价——分布式事务、网络延迟、运维复杂度。在讨论"怎么拆"之前,必须先确认"该不该拆"。以下六个信号同时出现 3 个以上时,才值得启动拆分。 ...

十月 3, 2022 · 8 分钟 · 1559 字 · yaomingye

MQTT 协议

📡 MQTT 协议:角色体系、Broker 原理与 QoS 分级机制全解析 问题切入:一个智能家居的消息困境 假设你要开发一个智能家居系统,包含以下设备: 10 个温湿度传感器,每 5 秒上报一次数据 5 个智能插座,需要接收开关指令并上报当前功率 1 个手机 App,需要实时看到所有设备的状态,并能下发控制指令 你的第一反应可能是用 HTTP:传感器 POST 数据到服务端,App 轮询拉取最新状态。但很快问题就来了: 传感器数量 × 上报频率 = 10 × (1 / 5s) = 2 QPS 的上报请求 App 轮询最新状态 = 1 × (1 / 2s) = 0.5 QPS 的查询请求 设备控制指令 = App POST 到服务端,服务端再推给设备... HTTP 是请求-响应模式,服务端无法主动向设备推送指令。如果让设备轮询指令,延迟高且浪费带宽。而且温湿度传感器是低功耗设备(电池供电的 ESP8266),HTTP 的 TCP 三次握手 + Header 开销太大。 这就是 MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议)解决的问题:它是一个 发布-订阅模式 的轻量级消息协议,专为低带宽、高延迟、不可靠网络下的物联网设备通信而设计。 MQTT 的角色体系 MQTT 协议定义了三种角色。大部分文章对它们的介绍含糊其词,这里逐个讲清楚。 ...

十月 1, 2022 · 14 分钟 · 2882 字 · yaomingye

SSO 与 JWT+Redis 的定位差异

SSO 与 JWT+Redis 的定位差异:Token格式、管理策略、认证架构三个层次 🤔 一、一个常见的学习困惑 很多开发者在学习鉴权体系时会遇到这样的困惑: 已经理解了 JWT 的三段式结构,知道它是无状态的 Token 格式 也理解了 JWT + Redis 混合方案,知道它能解决 Token 主动撤销的问题 然后听到"微服务用 SSO(单点登录)",去查资料后发现 SSO 也用 JWT 于是产生疑问:JWT + Redis 方案和 SSO 是什么关系?是不是同一个东西的不同叫法?如果不是,区别在哪? 这三个概念确实容易混淆,因为它们都围绕"鉴权"这个话题,但它们解决问题的层次完全不同。下面用三个明确的定义开篇: 概念 本质 解决什么问题 JWT Token 数据格式 Token 如何编码用户信息、如何防篡改 JWT + Redis Token 管理策略(单服务内部) 单个服务如何签发、验证、撤销 Token SSO(单点登录) 认证架构模式(跨服务) 多个服务之间如何共享登录状态 💼 二、从一个具体的业务场景理解差异 假设你所在的公司有三个系统: OA 办公系统(oa.company.com)—— 审批、考勤 CRM 客户系统(crm.company.com)—— 客户管理 BI 报表系统(bi.company.com)—— 数据分析 🏝️ 2.1 没有 SSO 时:每个系统各自鉴权 sequenceDiagram participant U as 用户 participant OA as OA系统 participant CRM as CRM系统 participant BI as BI系统 Note over U,BI: 用户需要分别登录3个系统 U->>OA: 打开OA → 输入用户名密码 OA-->>U: 登录成功 (OA的Token) U->>CRM: 打开CRM → 再次输入用户名密码 CRM-->>U: 登录成功 (CRM的Token) U->>BI: 打开BI → 第三次输入用户名密码 BI-->>U: 登录成功 (BI的Token) 每个系统都有自己独立的用户表、独立的登录接口、独立签发 Token。用户需要在三个系统之间各登录一次。这里的每个系统内部,可能各自使用了 JWT + Redis 管理自己的 Token——但这和"用户只需登录一次"是两个不同的问题。 ...

九月 21, 2022 · 9 分钟 · 1755 字 · yaomingye

Java NIO

Java NIO:epoll 多路复用、Buffer 机制与单线程高并发全解析 1 ⚡ 问题切入:一个线程如何管理 10000 个连接? 在经典的 BIO (Blocking I/O,阻塞 I/O)模型下,每个 Socket 连接需要分配一个独立线程。当 accept() 返回一个新连接,就启动一个线程去 read() —— 这个线程在数据到达之前会一直阻塞,CPU 时间被白白浪费在线程上下文切换上。 // 传统 BIO:一个连接一个线程(不可行) ServerSocket server = new ServerSocket(8080); while (true) { Socket client = server.accept(); // 阻塞等待连接 new Thread(() -> { InputStream in = client.getInputStream(); byte[] buf = new byte[1024]; in.read(buf); // 阻塞等待数据 // 处理数据... }).start(); } 问题:如果有 10,000 个连接,就需要 10,000 个线程。每个 Java 线程默认栈大小约 1MB,仅线程栈就消耗 10GB 内存,而且 CPU 绝大多数时间都在做线程切换而非真正处理数据。这就是著名的 C10K 问题 (Client 10,000 Problem)。 ...

九月 14, 2022 · 17 分钟 · 3496 字 · yaomingye
Cat Radio