十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Redis为什么这么火?三大核心秘密与实战指南

Redis为什么这么火?三大核心秘密与实战指南 开篇先从疑问切入。很多人第一次接触 Redis可能都是从“面试题”或“项目缓存优化”开始的。但真正使用一段时间后你会发现 Redis 能做的事情远不止缓存。它可以是分布式锁的承载体可以做消息队列可以扛住海量计数场景甚至可以作为轻量级数据库使用。一个中间件能火这么多年而且在面试、实战、系统设计里反复出现背后一定有值得深挖的设计理念。本文就围绕“Redis 凭什么这么火”这个问题拆解它的 3 个核心秘密为什么它快、为什么它并发能力强、为什么它这么好用。同时会给出环境搭建、Java 客户端操作、分布式锁实战、缓存设计与常见坑点帮助你从会用走向会用。先说清楚一个概念Redis 是开源的、基于内存的数据结构存储系统通常被归类为 NoSQL 数据库也可以叫内存数据库或缓存中间件。它的官方定义是“Redis is an open source, BSD licensed, advanced key-value store”但纯 key-value 已经不足以概括它的能力。它支持字符串、哈希、列表、集合、有序集合、位图、HyperLogLog、地理坐标、流Stream等多种数据结构每种结构都有对应的原子操作。正是这些数据结构让 Redis 从“缓存工具”变成了“业务工具箱”。很多开发者第一次被 Redis 吸引就是因为它快。单机 Redis 的读性能可以达到每秒十万次级别写性能也在万级到十万级之间。这个“快”并不只是因为内存而是内存、数据结构、网络模型、IO 模型共同作用的结果。也正因为快Redis 才能承担缓存、计数器、排行榜、分布式锁、信号量等对延迟敏感的场景。如果数据库查询需要几十毫秒Redis 读操作往往在亚毫秒级别差距非常明显。但 Redis 的魅力不只是“快”。它最反直觉的设计是单线程模型。在 Java 并发编程中我们想尽办法用多线程提升吞吐量Redis 却反过来用单线程加事件驱动模型规避了锁竞争、上下文切换、线程安全问题让性能在并发场景下依然稳定。这个设计思路值得每一个后端开发者认真理解。本文适合这些读者刚入门 Redis、被面试题里“Redis 为什么快、为什么单线程还这么快”困扰的初级开发者想用 Redis 做缓存、分布式锁、消息队列的 Java 后端工程师以及那些已经在用 Redis但还想把性能调优、缓存一致性、生产排错做扎实的技术同学。读完之后你会获得四样东西对 Redis 设计理念的系统认知一套可复制到本机的 Redis 环境搭建流程一个基于 Jedis 的完整 Java 操作示例以及一份覆盖缓存穿透、缓存击穿、缓存雪崩、分布式锁误删、连接池配置等高频问题的实战排查手册。1. 背景与核心概念1.1 Redis 到底是什么Redis 的全程是 Remote Dictionary Server即远程字典服务。字典Dictionary这个词很形象因为 Redis 的数据模型本质上就是一个大的哈希表通过 key 找到 value而 value 可以是多种数据结构。它的核心特性可以归纳为 5 点基于内存存储数据读写极快。支持持久化可以把内存数据保存到磁盘RDB、AOF。数据结构丰富不限于字符串。支持过期策略适合缓存场景。提供发布订阅、事务、Lua 脚本、管道等进阶能力。在系统架构中Redis 最常见的角色是处于应用和关系型数据库之间的缓存层。应用先查 Redis没有数据再查 MySQL并把查询结果回填到 Redis。这样做能大幅降低数据库压力提升接口响应速度。典型场景包括首页热点数据、商品详情、用户会话、验证码、排行榜等。除了缓存Redis 还被广泛用于分布式锁利用 SETNX EXPIRE 或 Redisson 实现多实例互斥。排行榜使用有序集合 ZSet。计数器使用 INCR、DECR 做 PV、UV、库存扣减。消息队列使用 List、Stream 实现轻量级消息通信。签到、去重统计使用 Bitmap、HyperLogLog。分布式 ID 或不重复号段使用 INCR 或 Lua 脚本。这也是为什么 Redis 火的原因之一它不是单一工具的定位而是可以用一套系统解决多个问题。1.2 Redis 与普通缓存 / 本地 Map 的区别很多新手会问我在 Java 里用 ConcurrentHashMap 做缓存不也行吗为什么要用 Redis区别不在“能不能存”而在“存给谁用”。本地缓存 Map 是 JVM 内存数据只在当前进程内可见。如果服务部署了多个实例用户请求落在不同实例上就会各自维护一份缓存互相不可见。而且 JVM 内存有限服务重启缓存就没了。Redis 是独立部署的中间件所有实例共享同一份缓存数据。它提供了网络访问接口、持久化、过期策略、数据淘汰策略、主从复制、集群分片这些能力是一个“可横向扩展、可持久化、可集中管理”的缓存服务。简单对比对比项Java 本地 MapRedis存储位置JVM 堆内独立进程内存多实例共享不共享共享持久化无RDB / AOF过期策略需要自己实现内置 EXPIRE、TTL数据结构基本类型、对象String、Hash、List、Set、ZSet 等并发控制JVM 锁单线程 原子命令 / Lua客户端访问本地方法调用网络协议多语言 SDK掌握了这个区别你就知道哪些数据该放本地缓存哪些该放 Redis。1.3 “Redis 火”的底层原因从工程角度看一个中间件能被大规模使用通常要满足三个条件能解决真实痛点。Redis 解决了数据库读压力大的问题。使用门槛低。Redis 命令简单学习成本比搜索引擎、消息队列低很多。生态成熟。官方支持多种语言客户端有主从、哨兵、集群方案能从小项目用到大型系统。这三点 Redis 全部满足。而接下来要讲的 3 个核心秘密分别对应它的性能优势、并发优势、功能优势。2. 环境准备与版本说明2.1 本文使用的环境为了保证示例能实际运行先说明我的演示环境。你可以结合自己的系统调整区别主要在安装方式上。操作系统LinuxCentOS 7 以上或 Ubuntu 18.04 以上Redis 版本以 6.x 稳定版为例说明重点演示命令和配置思路JDK 版本Java 8 及以上构建工具Maven 3.6客户端Jedis 4.x可视化工具Redis Desktop ManagerRDM或 Another Redis Desktop Manager可自行选择如果你的操作系统是 Windows建议使用 WSL2 安装 Linux 环境或者使用 Redis 官方提供的 Windows 移植版本。需要注意Redis 官方并不正式支持 Windows生产环境绝大多数部署在 Linux 上。版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路。2.2 安装 Redis 服务下面以 Linux 源码编译方式安装 6.2.x 为例。源码编译虽然步骤多一点但能让你理解 Redis 的安装结构。# 下载并解压 wget https://download.redis.io/releases/redis-6.2.14.tar.gz tar -xzf redis-6.2.14.tar.gz cd redis-6.2.14 # 编译 make # 如果 make 报错缺少 gcc先安装 # yum install -y gcc编译完成后会在 src 目录生成 redis-server、redis-cli、redis-sentinel 等可执行文件。也可以执行 make install 把它们安装到 /usr/local/bin。启动 Redis 服务# 前台启动方式方便看日志 src/redis-server # 指定配置文件后台启动 src/redis-server /path/to/redis.conf验证是否启动成功src/redis-cli ping如果输出 PONG说明服务正常。生产环境通常会设置 daemonize yes让 Redis 后台运行设置 requirepass 开启访问密码设置 bind 限制监听地址。下面是一份最小安全配置参考内容放到 redis.conf# 后台运行 daemonize yes # 监听地址本机访问用 127.0.0.1远程访问按需修改不要直接配 0.0.0.0 bind 127.0.0.1 # 认证密码生产环境必须设置 requirepass your-strong-password # 设置日志文件 logfile /var/log/redis/redis.log # 开启持久化 appendonly yes修改配置后重启 Redissrc/redis-cli shutdown src/redis-server /path/to/redis.conf开启密码后命令行访问需要带密码src/redis-cli -a your-strong-password这里要提醒一句命令行带 -a 密码会出现在 shell 历史中演示环境可以生产环境建议先登录客户端再执行 AUTH 命令或使用 REDISCLI_AUTH 环境变量。2.3 可视化客户端选择如果你习惯用图形界面操作 Redis可以安装 Redis Desktop Manager。这款工具曾用过另一个名字 Another Redis Desktop Manager新版官方名称为 Redis Desktop Manager。它可以查看 key 列表、查看数据结构、执行命令适合日常调试。也有不少团队使用 Redis Insight它是 Redis 官方推出的桌面客户端功能更现代自带性能监控。选择哪款取决于个人习惯不影响 Redis 本身的使用。3. 核心秘密一纯内存与高效数据结构3.1 内存让延迟从“量变”到“质变”Redis 的第一个核心秘密是它的数据主要放在内存中。传统的 Web 请求链路通常是应用查询 MySQLMySQL 需要做磁盘 IO、解析 SQL、走索引、回表、返回数据。一次普通查询耗时 5ms 到 30ms 很常见。如果并发上来数据库的连接数、CPU、磁盘 IO 都会成为瓶颈。Redis 把数据放内存省去了磁盘寻道和块读取的时间。内存的随机访问延迟是纳秒级相比机械硬盘的毫秒级延迟差距在几个数量级。再加上 Redis 的网络协议简洁、命令处理逻辑高效一次简单读写往往只需不到 1ms。但要注意“内存快”只是基础。如果实现得很粗糙比如为每个 key 都复制一份完整字符串、每操作一次做一次序列化性能一样上不去。所以 Redis 在数据结构的实现上也下了很大功夫。3.2 针对数据结构的特殊编码Redis 的值并不是简单用字符串表示。同样的类型会根据数据规模使用不同的底层编码目的是节省内存、提高操作效率。例如 String 类型底层可以是 int、raw、embstr 三种编码如果 value 是整数且范围合适Redis 直接用整数存储节省空间。如果是短字符串Redis 使用 embstr 编码一次分配内存。如果是长字符串则使用 raw 编码。Hash 类型在字段少、值小的情况下采用 ziplist 压缩列表字段多了会转换为 hashtable。ZSet 在数据量小的时候使用 ziplist数据量大了使用 skiplist 与 dict 的组合。这种“小数据用紧凑结构大数据用高效结构”的做法让 Redis 在小数据量场景下内存占用极低。这也是为什么 Redis 被称为“数据结构服务器”而不是简单的 KV 存储。下面用命令做一个小实验先设置一个整数再设置一个长字符串观察不同类型127.0.0.1:6379 SET counter 100 OK 127.0.0.1:6379 STRLEN counter 3 127.0.0.1:6379 TYPE counter string实际内存占用可以通过 OBJECT ENCODING 查看127.0.0.1:6379 OBJECT ENCODING counter int这条命令会返回底层编码。你可以在自己环境中多设置几个 key看看不同类型在不同数据规模下的编码变化。这是理解 Redis 内存优化的一个入口。3.3 缓存淘汰与过期策略既然 Redis 基于内存就不能无限存数据。Redis 提供了多种内存淘汰策略常见的有noeviction不淘汰内存不够时写入返回错误。allkeys-lru对所有 key 使用 LRU最近最少使用淘汰。volatile-lru只对设置了过期时间的 key 使用 LRU 淘汰。allkeys-lfu / volatile-lfu使用 LFU最不经常使用淘汰。random随机淘汰。缓存场景通常选择 allkeys-lru 或 volatile-lru。你需要根据业务判断哪些数据是热点哪些数据可以丢哪些数据不能丢。生产环境建议在 redis.conf 中显式配置maxmemory 2gb maxmemory-policy allkeys-lru过期策略则依赖 key 的 TTL。TTL 到期后Redis 并不会立即删除这个 key而是采用惰性删除加定期删除结合的方式。惰性删除是在每次读取时判断 key 是否过期定期删除是周期性地抽样检查并删除过期 key。理解这一点就能解释为什么某些 key 过期后内存并没有立刻下降。3.4 常见误区Redis 快就不需要优化了吗很多人觉得 Redis 天生快随手用就行。其实 Redis 的性能高低和你的使用习惯密切相关使用大量 bigkey比如存了几 MB 的字符串会导致单次操作耗时变长。在循环里逐条调用 Redis 命令网络 RTT 会放大耗时。对 Set、ZSet 做交集、并集、范围操作数据量大时 CPU 开销不小。慢查询会影响后续命令的执行速度因为 Redis 是单线程处理一个慢操作会阻塞其他命令。所以“Redis 快”是它的能力上限能不能发挥出来取决于你是否会用命令、是否会拆 key、是否设置合理的过期时间。这个主题放到后面的章节继续展开。4. 核心秘密二单线程模型与 I/O 多路复用4.1 为什么单线程反而快Redis 在 6.0 之前核心命令执行是单线程的。听起来很违反直觉现在服务器动不动几十核单线程不是浪费 CPU 吗答案在于 Redis 的瓶颈通常不在 CPU而在网络 IO 和内存大小。单线程带来的好处非常明显没有锁竞争不存在并发修改问题。没有线程切换的开销。实现简单不用考虑死锁、竞态条件。命令的执行顺序是确定的天然串行化。对于纯内存操作单线程执行命令的速度已经非常快。真正耗时的往往是网络读写。因此 Redis 使用了 I/O 多路复用机制配合事件循环在同一线程内处理多个客户端连接。可以用一个比喻理解传统多线程模型就像开很多窗口每个窗口一个服务员Redis 单线程模型就像一个服务员在多个窗口间来回服务哪个窗口有请求就先处理哪个。如果每个请求处理都很快这个服务员完全忙得过来还省去了协调多个服务员的成本。当然这个比喻忽略了系统调度的细节但核心思想是一致的Redis 选择用单线程避开并发复杂度用事件驱动提升 IO 吞吐量。4.2 I/O 多路复用与事件循环I/O 多路复用是指一个线程通过内核机制同时监控多个文件描述符当某个描述符可读或可写时内核通知应用程序去处理。Linux 上常见的机制包括 select、poll、epollRedis 会根据系统选择最合适的实现。事件循环可以简化成下面这个流程接收客户端连接请求。将连接注册到事件循环中。等待事件发生有数据可读、有空间可写、新连接到来。事件触发后调用对应处理器。处理完继续回到等待步骤。这个模型保证了单个线程能够高效服务大量连接。Redis 官方数据提到单实例可以支撑数万乃至十万级连接实际并发量还会受网络带宽、命令复杂度、内存分配影响。4.3 命令原子性与无锁设计单线程执行还有一个额外好处每个命令天然是原子的。在任意时刻Redis 只会执行一个命令所以多个客户端同时执行 INCR 操作时不会出现“读-改-写”的中间态竞争。我们来做一个小实验。启动多个 redis-cli同时对一个 key 执行 INCR# 终端1 INCR click_count # 终端2 INCR click_count # 终端3 INCR click_count无论发多少次最终结果都是精确递增不会因为并发而丢失计数。这正是 Redis 适合做计数器、秒杀库存扣减的原因。对于复杂的“读-判断-写”操作Redis 也提供了 Lua 脚本支持把多条命令打包成一个原子操作。后面分布式锁部分会用到这个能力。4.4 Redis 6.x 之后的变化很多文章把“单线程”当作 Redis 的固定标签但 Redis 6.0 引入了多线程 IO。这里必须说清楚多线程只用于网络数据的读取、解析和写回核心命令执行仍然由主线程串行完成。这样做既保留了命令执行的简单性和原子性又利用多核心加速了网络 IO避免大量客户端在高吞吐场景下出现网络处理瓶颈。所以现在的说法更准确的是Redis 命令执行仍然是单线程但网络 IO 可以多线程处理。了解了这一点你就不会在面试中说错“Redis 完全不使用多线程”也不会误解“Redis 6.0 变成多线程了”。4.5 单线程模型对使用者的启示单线程模型带给我们几个工程启示避免慢查询。凡是 O(N) 的命令如 KEYS、SMEMBERS、HGETALL在大 key 上执行会阻塞主线程引发全库卡顿。控制单次操作的数据量。一次写入超大 value 会造成内存分配阻塞。不要在大事务中执行大量命令。MULTI/EXEC 中的命令会排队执行事务期间其他客户端请求会等待。使用连接池。客户端反复创建和销毁连接不仅浪费网络资源也会让 Redis 频繁处理建立连接事件。“快”不是无条件的Redis 单线程的高性能建立在每个命令都很快的前提上。这是你在使用时必须时刻记住的原则。5. 核心秘密三丰富的数据类型与原子操作5.1 一个 Redis多个工具箱第三个核心秘密是 Redis 不满足于做一个 KV 缓存而是提供了一整套高性能数据结构。下面把最常用的数据类型梳理一遍。String字符串String 是 Redis 最基础的类型。它可以存字符串、整数、浮点数、二进制数据。除了 GET、SET还有 INCR、DECR、SETNX、SETEX 等命令。常用场景缓存对象序列化后的 JSON、计数器、验证码、分布式锁 value 标记。SET user:1001 {name:tom,age:18} GET user:1001 INCR page_view EXPIRE user:1001 300Hash哈希Hash 适合表示一个对象。它内部是一组 field-value比如用户信息包含 name、age、email。相比把整个对象序列化成 JSONHash 支持只修改某个字段节约网络流量。HSET user:1001 name tom age 18 email tomexample.com HGET user:1001 name HGETALL user:1001List列表List 底层是链表结构支持从头部或尾部压入弹出元素。适合简单消息队列、最新列表、最近浏览历史等场景。LPUSH notify:queue msg1 RPUSH notify:queue msg2 LPOP notify:queue LRANGE notify:queue 0 -1Set集合Set 保证元素唯一支持交集、并集、差集运算适合标签、好友关系、去重、抽奖等场景。SADD tag:java spring redis SADD tag:backend redis mysql SINTER tag:java tag:backendZSet有序集合ZSet 在 Set 基础上给每个元素增加 score可以按分数排序。适合排行榜、延迟队列、最近热播排序等场景。ZADD ranking:game 100 player1 ZADD ranking:game 200 player2 ZINCRBY ranking:game 5 player1 ZREVRANGE ranking:game 0 -1 WITHSCORESBitmap、HyperLogLog、Geo、StreamBitmap 适合签到、在线状态、布隆过滤器的位级操作。HyperLogLog 用来做海量数据的基数统计误差可控内存占用极小。Geo 用来存储地理位置并计算距离。Stream 是 Redis 5.0 引入的消息队列模型支持消息持久化和消费者组。这些类型覆盖了很多业务常见场景让开发者在大多数情况下不用再引入额外的存储组件。5.2 原子操作不只是快更是安全Redis 的原子性不仅体现在单个命令还可以通过 Lua 脚本把多条命令组合成原子操作。一个典型例子是“扣减库存”。不安全的写法是先 GET 库存判断是否大于 0再 DECR。这个过程在并发下会出现超卖。如果在多线程代码里你会想到加锁但在 Redis 里直接使用 Lua 脚本可以一次性完成判断和扣减local stock tonumber(redis.call(GET, KEYS[1])) if stock and stock 0 then redis.call(DECR, KEYS[1]) return 1 end return 0Java 侧通过 Jedis 执行这段脚本时整个脚本会被 Redis 原子执行不会插入其他命令。这比“先查再扣”要安全得多。5.3 消息队列与 StreamRedis 用作消息队列时很多人会问它和 Kafka、RabbitMQ 有什么区别结论是如果业务量不大、对消息丢失要求不高、不想引入额外中间件Redis 的 List 或 Stream 可以撑起轻量级队列场景。但如果需要消息回溯、严格的分区顺序、大规模堆积、多种消费模式则更适合使用专业的消息队列。使用 List 实现简单队列很直观# 生产者 LPUSH task:queue job-1 # 消费者 BRPOP task:queue 5BRPOP 是阻塞式弹出没有消息时会阻塞等待避免轮询空转。Redis 5.0 的 Stream 提供了 XADD、XREAD、XGROUP、XACK 等命令支持消费者组。实现消息确认、消费组分配、消息历史读取是比 List 更完整的队列方案。你可以把 Stream 理解为一个能存消息的内存日志适合在 Redis 生态内做消息发布订阅。5.4 分布式锁Redis 高价值场景Redis 能火很大一部分原因是它能实现简单可靠的分布式锁。在分布式系统中多个服务实例需要互斥地执行某个操作比如定时任务只允许一个节点执行、防止用户重复下单这时可以用 Redis 的 SETNX 命令。核心思想利用 Redis 单线程执行命令的原子性通过 SET key value NX EX timeout 实现“只有 key 不存在时才能设置成功”。谁设置成功谁就获得锁释放时删除 key。下面给出一个 Java 版本的示例实现。需要注意锁的 value 要能标识持有者避免误删他人锁过期时间必须设置防止持有者宕机导致死锁。释放锁时使用 Lua 脚本校验 value保证原子性。// 文件路径src/main/java/com/example/redis/RedisLockDemo.java import redis.clients.jedis.Jedis; public class RedisLockDemo { private static final String LOCK_SUCCESS OK; private static final String SET_IF_NOT_EXIST NX; private static final String SET_WITH_EXPIRE_TIME PX; private static final String LOCK_SCRIPT if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end; private final Jedis jedis; public RedisLockDemo(Jedis jedis) { this.jedis jedis; } /** * 获取分布式锁 * * param lockKey 锁的 key * param requestId 请求标识用于释放锁时校验 * param expireMs 过期时间毫秒 * return 是否获取成功 */ public boolean lock(String lockKey, String requestId, long expireMs) { String result jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireMs); return LOCK_SUCCESS.equals(result); } /** * 释放分布式锁 * 只有 value 匹配时才删除防止误删其他线程持有的锁 */ public boolean unlock(String lockKey, String requestId) { Object result jedis.eval(LOCK_SCRIPT, java.util.Collections.singletonList(lockKey), java.util.Collections.singletonList(requestId)); return Long.valueOf(1L).equals(result); } }这段代码体现了 Redis 分布式锁的三个基本原则使用 SET NX EX 保证原子设置锁和过期时间。value 使用唯一标识释放时先校验再删除。删除操作使用 Lua 脚本保证“判断和删除”的原子性。当然生产级分布式锁更推荐使用 Redisson它封装了锁续期、看门狗、公平锁、读写锁、红锁等能力避免自己造轮子踩坑。但如果只是理解原理上面的实现足够帮助你理解 Redis 为何在分布式场景中这么重要。6. 完整实战案例从安装到缓存应用6.1 项目结构下面用一个最小 Java 项目串联起来演示 Redis 的常见用法。项目只依赖 Jedis不需要引入 Spring Boot便于理解流程。redis-demo/ ├── pom.xml └── src/main/java/com/example/redis/ ├── RedisConnection.java ├── CacheService.java └── CacheApplication.java6.2 配置 Maven 依赖在 pom.xml 中引入 Jedisproject xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion groupIdcom.example/groupId artifactIdredis-demo/artifactId version1.0-SNAPSHOT/version properties maven.compiler.source8/maven.compiler.source maven.compiler.target8/maven.compiler.target /properties dependencies dependency groupIdredis.clients/groupId artifactIdjedis/artifactId version4.4.6/version /dependency dependency groupIdorg.slf4j/groupId artifactIdslf4j-simple/artifactId version1.7.36/version /dependency /dependencies /project版本号以你本地仓库能拉到的稳定版为准上面是我测试时的版本。6.3 编写连接管理类连接管理类负责创建 Jedis 连接。生产环境要用连接池这里先演示直连。// 文件路径src/main/java/com/example/redis/RedisConnection.java import redis.clients.jedis.Jedis; public class RedisConnection { public static Jedis getJedis() { String host 127.0.0.1; int port 6379; String password your-strong-password; Jedis jedis new Jedis(host, port); if (password ! null !password.isEmpty()) { jedis.auth(password); } return jedis; } }如果你的 Redis 没有设置密码可以去掉 auth 调用。这里提醒一点即使只是本机学习也建议设置密码并限制 bind 地址避免 Redis 暴露到公网。6.4 编写缓存服务类缓存服务类模拟一个典型场景查询商品信息时先查 Redis没有则查数据库并回填缓存。// 文件路径src/main/java/com/example/redis/CacheService.java import redis.clients.jedis.Jedis; public class CacheService { private static final String PRODUCT_CACHE_KEY_PREFIX product:info:; /** * 模拟从数据库查询商品信息 */ private String queryFromDatabase(String productId) { System.out.println(查询数据库productId productId); return {\id\: productId ,\name\:\示例商品\,\price\:99.9}; } /** * 查询商品信息先查缓存再查数据库最后回填 */ public String getProductInfo(String productId) { try (Jedis jedis RedisConnection.getJedis()) { String cacheKey PRODUCT_CACHE_KEY_PREFIX productId; String cached jedis.get(cacheKey); if (cached ! null) { System.out.println(命中缓存); return cached; } String dbData queryFromDatabase(productId); jedis.setex(cacheKey, 300, dbData); System.out.println(未命中缓存已回填TTL 300s); return dbData; } } }这里使用 setex 在设置 value 的同时指定过期时间避免先 set 再 expire 两步操作中途失败导致的“永不过期”问题。6.5 编写启动类并验证// 文件路径src/main/java/com/example/redis/CacheApplication.java public class CacheApplication { public static void main(String[] args) { CacheService cacheService new CacheService(); String productId 1001; System.out.println(第一次查询); System.out.println(cacheService.getProductInfo(productId)); System.out.println(第二次查询); System.out.println(cacheService.getProductInfo(productId)); } }用 Maven 运行mvn clean compile exec:java -Dexec.mainClasscom.example.redis.CacheApplication预期输出带密码的记得先调整 RedisConnection第一次查询 查询数据库productId 1001 未命中缓存已回填TTL 300s {id:1001,name:示例商品,price:99.9} 第二次查询 命中缓存 {id:1001,name:示例商品,price:99.9}因为这个 demo 的 queryFromDatabase 是本地方法第二次输出仍然会打印“命中缓存”。如果在真实项目中第二次查询不会访问数据库这样可以显著降低数据库压力。6.6 缓存穿透、击穿、雪崩的简单模拟这三个概念是所有 Redis 缓存开发者必须掌握的。这里先快速说明后面常见问题部分会给出完整排查。缓存穿透查询一个不存在的 keyRedis 没有数据库也没有请求每次都打到数据库形成穿透。缓存击穿一个热点 key 过期恰好大量请求同时访问数据库瞬间被打满。缓存雪崩大量 key 在同一时刻过期导致数据库压力骤增。解决思路分别是穿透对空结果也做缓存但 TTL 设置短一些使用布隆过滤器过滤不存在的 key。击穿热点 key 不设置过期时间或者使用互斥锁保证只有一个请求回填缓存。雪崩过期时间加随机值避免同时过期使用多级缓存热点数据预加载。这三个问题也是 Redis 面试题中的高频点值得单独写代码验证。6.7 Jedis 连接池改造直连模式适合学习真实项目必须使用连接池。Jedis 推荐 JedisPool可以复用连接降低创建销毁开销。// 文件路径src/main/java/com/example/redis/RedisPool.java import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; public class RedisPool { private static final JedisPool POOL; static { JedisPoolConfig config new JedisPoolConfig(); config.setMaxTotal(50); config.setMaxIdle(20); config.setMinIdle(5); config.setMaxWaitMillis(3000); config.setTestOnBorrow(true); String host 127.0.0.1; int port 6379; String password your-strong-password; if (password ! null !password.isEmpty()) { POOL new JedisPool(config, host, port, 2000, password); } else { POOL new JedisPool(config, host, port, 2000); } } public static Jedis getJedis() { return POOL.getResource(); } }注意连接池参数要结合业务设置不是越大越好。最大连接数过大会导致 Redis 端维持大量空闲连接占用资源。7. 常见问题与排查思路7.1 常见问题汇总表下面整理了 Redis 使用中的高频问题开发者可以对照排查。问题现象常见原因解决思路启动报 “Could not create server TCP listening socket *:6379: bind: Address already in use”端口被占用检查进程kill 或换端口启动后外部无法连接bind 只配置了 127.0.0.1或防火墙没放行按需修改 bind、配置安全组/防火墙AUTH 认证失败客户端密码与服务端 requirepass 不一致检查 redis.conf 和客户端参数使用 KEYS 命令导致 Redis 卡顿大量 key 遍历阻塞主线程使用 SCAN 命令分批遍历热点 key 过期瞬间数据库被压垮缓存击穿互斥锁重建缓存热点 key 不设置过期大量 key 同一时间过期缓存雪崩过期时间加随机值缓存里查询不到、数据库也没有缓存穿透空值缓存、布隆过滤器设置 key 后没有自动过期使用了 SET 而不是 SETEX或 EXPIRE 失败使用 setex / set expire并检查返回值分布式锁偶尔失效忘记设置过期时间、释放锁误删使用 SET NX EX释放时校验 valueRedis 内存占用高但没有多少数据大 key 或过期 key 未清理用 redis-cli --bigkeys 分析优化 key使用可视化工具连接超时密码错误、网络不通、bind 限制先用 redis-cli 命令行验证客户端大量 TIME_WAIT每次请求都新建连接使用连接池消息队列重复消费没有做消费确认或代码重复提交使用 Stream ACK业务侧做幂等7.2 慢查询排查Redis 提供了慢查询日志。在 redis-cli 中执行# 查看慢查询日志 SLOWLOG GET 10 # 查看慢查询阈值 CONFIG GET slowlog-log-slower-than # 设置阈值单位微秒 CONFIG SET slowlog-log-slower-than 10000如果发现很多命令超过 10ms需要进一步分析。常见的慢命令包括 KEYS、HGETALL、SMEMBERS、ZRANGEBYSCORE 等。建议使用 SCAN 代替 KEYS使用 HSCAN/SSCAN/ZSCAN 代替全量获取。7.3 大 key 排查大 key 会造成内存分配、网络传输、持久化阻塞。可以使用官方命令redis-cli --bigkeys这个命令会遍历 Redis 并输出各种类型中最大的 key。发现大 key 后可以考虑拆分 key、压缩 value、使用 List 分段存储等方式优化。7.4 Redis Desktop Manager 连接不上的排查步骤如果你使用 Redis Desktop Manager 或 Another Redis Desktop Manager 连接失败按下面顺序排查确认 Redis 进程运行中ps -ef | grep redis。确认端口开放ss -lntp | grep 6379。确认 bind 配置允许远程访问测试环境可先设为 0.0.0.0生产环境要谨慎。确认密码配置requirepass 与客户端输入的密码一致。检查防火墙和云安全组是否放行 6379 端口。在 Redis 所在机器上执行 redis-cli ping确认服务本身正常。记住一个原则先命令行验证再用工具排错不要一开始就怀疑图形客户端。8. 最佳实践与工程建议8.1 key 命名规范Redis key 建议使用业务前缀加冒号分层比如user:info:1001 order:list:20250101 product:detail:sku12345这样做的好处有三个可读性好可以按前缀分组搜索便于区分不同业务的数据。但要注意不要滥用冒号结构Redis 的 key 没有层级概念冒号只是约定。8.2 连接池参数设置JedisPool 参数没有标准答案需要根据 QPS、Redis 实例资源、业务响应时间调整。下面是一些经验值maxTotal按业务需要的峰值连接数估算不是越大越好。maxIdle保留空闲连接数避免频繁创建销毁。minIdle低峰期保留的最小连接数。maxWaitMillis获取连接的最大等待时间防止线程无限阻塞。testOnBorrow获取连接时做 ping 检测牺牲一点点性能换取连接可用性。8.3 缓存一致性方案缓存和数据库的一致性是老问题。没有一种方案能完美解决所有场景需要根据业务容忍度选择。常见的做法是 Cache Aside Pattern读操作先读缓存不命中则读数据库回填缓存。写操作先更新数据库再删除缓存。关于“先删缓存再更新数据库”和“先更新数据库再删缓存”哪个更好业界通常更推荐“先更新数据库再删除缓存”。原因是写操作后缓存中的旧值已经失效删除成本低而如果先删缓存再更新数据库在更新数据库期间可能有请求把旧数据回填到缓存造成脏数据。删除缓存时如果删除失败可以引入重试机制或者使用 Binlog 监听如 Canal异步删除。8.4 安全配置要求生产环境的 Redis 不允许裸奔。至少要做到以下几点设置强密码禁止空密码。bind 配置只监听内网或本机 IP。禁用危险命令CONFIG、FLUSHALL、FLUSHDB、KEYS 等通过 rename-command 重命名。使用非 root 用户运行 Redis 服务。定期备份 RDB 或 AOF 文件。监控内存、CPU、连接数、慢查询。下面是一个 rename 危险命令的配置示例rename-command CONFIG rename-command FLUSHALL rename-command FLUSHDB 要注意重命名命令后客户端或可视化工具可能无法直接使用这些命令需要同步调整。8.5 持久化选型Redis 持久化有两种主流方式RDB定期生成内存快照适合备份、恢复快但可能丢失最后一次快照后的数据。AOF记录每次写命令数据安全性高但文件大、重放慢。生产环境通常会同时开启 RDB 和 AOF或根据业务选择。如果服务波动不大可以使用 RDB如果对数据一致性要求高建议开启 AOF并设置 appendfsync everysec。在 redis.conf 中save 900 1 save 300 10 save 60 10000 appendonly yes appendfsync everysec需要理解RDB 和 AOF 写入磁盘的操作会有性能消耗配置需要结合业务容忍度调整。8.6 监控与容量规划Redis 不是无底洞。上线前要做好容量估算比如单个 key 平均大小、key 数量、过期时间、峰值并发量。运行中要监控redis-cli INFO 里的 used_memory、connected_clients、total_commands_processed。慢查询数量。命中率。每秒操作数。推荐使用官方工具 redis-cli 或可视化监控面板。生产环境更推荐 Prometheus Redis Exporter 做指标采集配合 Grafana 展示这样能提前发现容量和性能问题。9. 总结与学习路线写到这里我们可以回答标题的问题了Redis 之所以火不只是因为它是一个“缓存工具”而是因为它通过纯内存存储和精心设计的数据结构获得了极致性能通过单线程事件循环获得了高并发下的稳定性通过丰富的数据类型和原子操作把一个中间件变成了能覆盖缓存、锁、队列、计数、排行榜等众多场景的通用组件。如果要从零开始系统学习 Redis建议按这个顺序走掌握常用命令String、Hash、List、Set、ZSet 的基本操作。理解过期与淘汰机制EXPIRE、TTL、maxmemory、LRU。理解持久化RDB 与 AOF 的优劣。学 Java 客户端Jedis、Lettuce、Spring Data Redis。学习集群方案主从复制、哨兵、Cluster 分片。研究经典场景缓存穿透、击穿、雪崩分布式锁消息队列延迟队列。学会排查慢查询、大 key、热 key、内存分析。阅读源码可选事件循环、对象编码、RDB 文件格式。Redis 是一个“越用越有意思”的组件。刚入门时它只是一个方便的数据存储工具当你在分布式场景里遇到并发、一致性、性能问题再回头看 Redis 的设计会理解它为什么在竞争激烈的中间件生态中始终占据重要席位。希望这篇教程能帮你打好基础下一步可以亲手试试搭建 Redis 主从和哨兵集群或者在本地用 Spring Boot 写一个带缓存和分布式锁的完整项目真正把这些知识变成自己的实战经验。
返回列表