【Java 进阶篇】Redis 缓存优化:提升应用性能的不二选择

news/2025/1/18 9:48:35/

在这里插入图片描述

在现代的软件开发中,性能一直是开发者们追求的目标之一。对于数据库访问频繁、数据读取较慢的场景,使用缓存是提升性能的有效手段之一。而 Redis 作为一款高性能的内存数据库,被广泛用作缓存工具。本文将围绕 Redis 缓存优化进行详解,为你揭示如何通过优化缓存提升应用性能的奥秘。

缓存的魅力

缓存,就像是一位贴心的助手,可以加速应用程序的许多操作。它通过将一些计算结果或者数据库查询结果保存在快速访问的地方,使得后续相同的请求可以更快地获取到数据,减轻数据库的压力。在这个过程中,Redis 这个“魔法盒子”就成了许多开发者心中的明星。

Redis 缓存基础

在使用 Redis 缓存之前,我们需要先理解 Redis 的基本概念和基础操作。Redis 是一款基于内存的键值存储系统,它提供了多种数据结构,如字符串、哈希、列表、集合、有序集合等。这些数据结构为我们提供了灵活的缓存选择。

字符串缓存

首先,我们来看一个简单的字符串缓存示例:

import redis.clients.jedis.Jedis;public class RedisStringCacheExample {public static void main(String[] args) {// 连接到本地的 Redis 服务器Jedis jedis = new Jedis("localhost", 6379);System.out.println("连接成功");// 缓存数据jedis.set("username:1001", "Alice");jedis.set("username:1002", "Bob");// 从缓存中获取数据String user1 = jedis.get("username:1001");String user2 = jedis.get("username:1002");// 打印结果System.out.println("用户1001:" + user1);System.out.println("用户1002:" + user2);// 关闭连接jedis.close();}
}

在这个示例中,我们使用了 Redis 的字符串数据结构。通过 set 方法缓存了两个用户的用户名,然后通过 get 方法从缓存中获取了这些数据。这是一个简单而直观的缓存例子。

哈希缓存

如果我们需要缓存一些更复杂的数据,比如用户的详细信息,可以使用 Redis 的哈希数据结构:

import redis.clients.jedis.Jedis;
import java.util.Map;public class RedisHashCacheExample {public static void main(String[] args) {// 连接到本地的 Redis 服务器Jedis jedis = new Jedis("localhost", 6379);System.out.println("连接成功");// 缓存用户详细信息String userId = "1001";jedis.hset("user:" + userId, "name", "Alice");jedis.hset("user:" + userId, "age", "25");jedis.hset("user:" + userId, "city", "New York");// 从缓存中获取用户详细信息Map<String, String> userInfo = jedis.hgetAll("user:" + userId);// 打印结果System.out.println("用户详细信息:" + userInfo);// 关闭连接jedis.close();}
}

在这个例子中,我们使用了 Redis 的哈希数据结构(Hash)。通过 hset 方法设置了用户详细信息的多个字段,然后通过 hgetAll 方法获取了整个哈希表。哈希缓存适用于需要存储结构化数据的场景。

列表缓存

如果我们需要缓存一些列表数据,比如用户的最近浏览记录,可以使用 Redis 的列表数据结构:

import redis.clients.jedis.Jedis;
import java.util.List;public class RedisListCacheExample {public static void main(String[] args) {// 连接到本地的 Redis 服务器Jedis jedis = new Jedis("localhost", 6379);System.out.println("连接成功");// 缓存用户最近浏览记录String userId = "1001";jedis.lpush("history:" + userId, "product1", "product2", "product3");// 从缓存中获取用户最近浏览记录List<String> history = jedis.lrange("history:" + userId, 0, -1);// 打印结果System.out.println("用户最近浏览记录:" + history);// 关闭连接jedis.close();}
}

在这个例子中,我们使用了 Redis 的列表数据结构。通过 lpush 方法将多个产品添加到用户的浏览记录中,然后通过 lrange 方法获取整个列表。列表缓存适用于需要按顺序存储多个元素的场景。

缓存的优化策略

缓存击穿的解决方案

缓存击穿是指一个不存在于缓存中但存在于数据库中的数据被大量并发访问,导致大量请求穿透缓存直接访问数据库,加重数据库负担。为了解决这个问题,我们可以使用互斥锁或者缓存空值。

互斥锁
import redis.clients.jedis.Jedis;
import redis.clients.jedis.exceptions.JedisConnectionException;public class CacheBreakdownSolution {public static void main(String[] args) {Jedis jedis = null;try {// 获取 Jedis 实例jedis = new Jedis("localhost", 6379);String key = "product:123";String value = jedis.get(key);if (value == null) {// 设置互斥锁String lockKey = "lock:" + key;String lockValue = "1";String result = jedis.set(lockKey, lockValue, "NX", "EX", 10);if ("OK".equals(result)) {// 查询数据库并设置缓存value = "queryFromDatabase";jedis.setex(key, 3600, value);// 释放锁jedis.del(lockKey);} else {// 其他线程持有锁,等待片刻后重试Thread.sleep(100);main(args); // 重新执行}}// 打印结果System.out.println("获取到的值: " + value);} catch (JedisConnectionException | InterruptedException e) {// 处理连接异常System.err.println("连接异常:" + e.getMessage());} finally {if (jedis != null) {jedis.close();}}}
}

在这个例子中,我们使用了 Redis 的 SET 命令的 NX(不存在时设置)和 EX(过期时间)选项来实现互斥锁。当一个线程获取到锁后,它将查询数据库并设置缓存,然后释放锁。其他线程需要等待锁的释放,避免了多个线程同时查询数据库的情况。

缓存空值
import redis.clients.jedis.Jedis;
import redis.clients.jedis.exceptions.JedisConnectionException;public class CacheBreakdownSolution {public static void main(String[] args) {Jedis jedis = null;try {// 获取 Jedis 实例jedis = new Jedis("localhost", 6379);String key = "product:123";String value = jedis.get(key);if (value == null) {// 查询数据库value = "queryFromDatabase";// 如果数据库中没有值,则设置缓存空值,防止缓存穿透if (value != null) {jedis.setex(key, 3600, value);} else {// 设置缓存空值,并设置较短的过期时间jedis.setex(key, 60, "");}}// 打印结果System.out.println("获取到的值: " + value);} catch (JedisConnectionException e) {// 处理连接异常System.err.println("连接异常:" + e.getMessage());} finally {if (jedis != null) {jedis.close();}}}
}

在这个例子中,当查询数据库后发现数据库中没有值时,我们通过 setex 方法设置了一个较短的过期时间的缓存空值。这样,即使下一次请求仍然查询数据库,但在这个短时间内,其他请求会直接从缓存中获取到缓存空值,避免了缓存穿透问题。

缓存雪崩的解决方案

缓存雪崩是指在某个时间点,缓存中的大量数据同时过期,导致数据库被大量请求直接打到,引起数据库压力过大。为了解决这个问题,我们可以采用多种手段,比如合理设置过期时间、使用不同的过期时间、采用滑动窗口过期等。

合理设置过期时间
import redis.clients.jedis.Jedis;public class CacheAvalancheSolution {public static void main(String[] args) {Jedis jedis = null;try {// 获取 Jedis 实例jedis = new Jedis("localhost", 6379);String key = "product:123";String value = jedis.get(key);if (value == null) {// 查询数据库value = "queryFromDatabase";// 设置合理的过期时间,避免缓存雪崩jedis.setex(key, 3600 + (int) (Math.random() * 600), value);}// 打印结果System.out.println("获取到的值: " + value);} finally {if (jedis != null) {jedis.close();}}}
}

在这个例子中,我们使用了 Math.random() 来生成一个随机数,将过期时间设置在 1 小时到 1 小时 10 分钟之间。这样做可以使得大量数据不会在同一时刻过期,从而分散了对数据库的请求,避免了缓存雪崩。

使用不同的过期时间
import redis.clients.jedis.Jedis;public class CacheAvalancheSolution {public static void main(String[] args) {Jedis jedis = null;try {// 获取 Jedis 实例jedis = new Jedis("localhost", 6379);String key = "product:123";String value = jedis.get(key);if (value == null) {// 查询数据库value = "queryFromDatabase";// 使用不同的过期时间,避免缓存雪崩int randomExpiry = (int) (Math.random() * 600); // 0到600秒之间的随机数jedis.setex(key, 3600 + randomExpiry, value);}// 打印结果System.out.println("获取到的值: " + value);} finally {if (jedis != null) {jedis.close();}}}
}

在这个例子中,我们通过生成一个 0 到 600 秒之间的随机数,将过期时间设置在 1 小时到 1 小时 10 分钟之间。这样可以使得不同的缓存数据具有不同的过期时间,降低了缓存同时失效的概率,从而避免了缓存雪崩。

采用滑动窗口过期
import redis.clients.jedis.Jedis;
import redis.clients.jedis.exceptions.JedisConnectionException;public class CacheAvalancheSolution {public static void main(String[] args) {Jedis jedis = null;try {// 获取 Jedis 实例jedis = new Jedis("localhost", 6379);String key = "product:123";String value = jedis.get(key);if (value == null) {// 查询数据库value = "queryFromDatabase";// 采用滑动窗口过期,避免缓存雪崩int window = 600; // 窗口大小为600秒int randomExpiry = (int) (Math.random() * window); // 0到600秒之间的随机数int expireTime = window - randomExpiry; // 设置过期时间jedis.setex(key, expireTime, value);}// 打印结果System.out.println("获取到的值: " + value);} catch (JedisConnectionException e) {// 处理连接异常System.err.println("连接异常:" + e.getMessage());} finally {if (jedis != null) {jedis.close();}}}
}

在这个例子中,我们定义了一个窗口大小为 600 秒的滑动窗口,通过生成 0 到 600 秒之间的随机数,计算出设置的过期时间。这样可以使得缓存数据的过期时间在一个窗口内,避免了同时失效的情况,有效降低了缓存雪崩的发生概率。

结语

通过本文的介绍,相信你已经对 Redis 缓存优化有了更深入的了解。缓存作为提升应用性能的得力工具,但也需要谨慎使用并结合实际业务场景进行合理的优化。通过解决缓存击穿和缓存雪崩等常见问题,我们可以更好地发挥 Redis 缓存的威力,提升应用的响应速度,提高用户体验。在实际应用中,根据业务场景和需求选择合适的缓存策略,将缓存融入系统架构中,助力应用高效运行。希望本文能够帮助你更好地应对实际开发中的缓存优化问题,让你的应用在性能上更上一层楼。

作者信息

作者 : 繁依Fanyi
CSDN: https://techfanyi.blog.csdn.net
掘金:https://juejin.cn/user/4154386571867191

http://www.ppmy.cn/news/1285504.html

相关文章

云计算:OpenStack 配置云主机实例的存储挂载并实现外网互通

目录 一、实验 1. 环境 2.配置存储挂载 3.云主机实例连接外部网络&#xff08;SNAT&#xff09; 4.外部网络连接云主机实例&#xff08;DNAT&#xff09; 二、问题 1.云主机 ping 不通外部网络 2.nova list 查看云主机列表报错 3.nova list 与 virsh list --all有何区…

海德堡UV灯电源维修eta Plus Elc PE22-400-210

uv灯电源维修故障包括&#xff1a; 1、电压不稳&#xff1a;检查uv打印机的电压&#xff0c;设置一个稳压箱即可。 2、温度过高&#xff1a;uv打印机温度过高也会影响uv灯&#xff0c;可以更换为水冷式循环降温。 3、水箱里的信号线接触不好&#xff1a;将两边的信号线对调&…

如果SSE推送不稳定,是不是可以考虑切换成WebSocket,各自有什么优缺点

面对 Server-Sent Events (SSE) 推送不稳定的情况时&#xff0c;可以考虑切换到 WebSocket。SSE 和 WebSocket 都是现代Web应用中用于实现实时通信的技术&#xff0c;但它们有各自的优缺点和最适用的场景。 Server-Sent Events (SSE) 优点 简单性&#xff1a;SSE 在使用上比…

【Linux】Ubuntu22.04版本下实现gcc版本的快速切换

本文将介绍如何在Ubuntu22.04版本下实现gcc版本的快速切换。 本文首发于 ❄️慕雪的寒舍 前言 有的时候&#xff0c;不同版本的gcc会造成一些细微的差异&#xff0c;导致相关的一些工具不兼容&#xff0c;比如用于单元测试覆盖率生成的gcov/lcov工具&#xff0c;在不同的gcc版…

基于电商场景的高并发RocketMQ实战-Broker写入读取流程性能优化总结、Broker基于Pull模式的主从复制原理

&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308; 【11来了】文章导读地址&#xff1a;点击查看文章导读&#xff01; &#x1f341;&#x1f341;&#x1f341;&#x1f341;&#x1f341;&#x1f341;&#x1f3…

Java虚拟机中的垃圾回收

2 垃圾回收 2.1 判断一个对象是否可回收 2.1.1 引用计数法 如果一个对象被另一个对象引用&#xff0c;那么它的引用计数加一&#xff0c;如果那个对象不再引用它了&#xff0c;那么引用计数减一。当引用计数为 0 时&#xff0c;该对象就应该被垃圾回收了。 但是下面这种互相…

HarmonyOS page生命周期函数讲解

下面 我们又要看一个比较重要的点了 页面生命周期 页面组件有三个生命周期 onPageShow 页面显示时触发 onPageHide 页面隐藏时触发 onBackPress 页面返回时触发 这里 我们准备两个组件 首先是 index.ets 参考代码如下 import router from ohos.router Entry Component struc…

从方程到预测:数学在深度学习中的作用

图片来源 一、说明 深度学习通常被认为是人工智能的巅峰之作&#xff0c;它的成功很大程度上归功于数学&#xff0c;尤其是线性代数和微积分。本文将探讨深度学习与数学之间的深刻联系&#xff0c;阐明为什么数学概念是该领域的核心。 二、数学框架 从本质上讲&#xff0c;深度…