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

2023-12-30 05:12

本文主要是介绍【Java 进阶篇】Redis 缓存优化:提升应用性能的不二选择,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在这里插入图片描述

在现代的软件开发中,性能一直是开发者们追求的目标之一。对于数据库访问频繁、数据读取较慢的场景,使用缓存是提升性能的有效手段之一。而 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

这篇关于【Java 进阶篇】Redis 缓存优化:提升应用性能的不二选择的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/551805

相关文章

Java实现字节字符转bcd编码

《Java实现字节字符转bcd编码》BCD是一种将十进制数字编码为二进制的表示方式,常用于数字显示和存储,本文将介绍如何在Java中实现字节字符转BCD码的过程,需要的小伙伴可以了解下... 目录前言BCD码是什么Java实现字节转bcd编码方法补充总结前言BCD码(Binary-Coded Decima

Redis 的 SUBSCRIBE命令详解

《Redis的SUBSCRIBE命令详解》Redis的SUBSCRIBE命令用于订阅一个或多个频道,以便接收发送到这些频道的消息,本文给大家介绍Redis的SUBSCRIBE命令,感兴趣的朋友跟随... 目录基本语法工作原理示例消息格式相关命令python 示例Redis 的 SUBSCRIBE 命令用于订

SpringBoot全局域名替换的实现

《SpringBoot全局域名替换的实现》本文主要介绍了SpringBoot全局域名替换的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录 项目结构⚙️ 配置文件application.yml️ 配置类AppProperties.Ja

Java使用Javassist动态生成HelloWorld类

《Java使用Javassist动态生成HelloWorld类》Javassist是一个非常强大的字节码操作和定义库,它允许开发者在运行时创建新的类或者修改现有的类,本文将简单介绍如何使用Javass... 目录1. Javassist简介2. 环境准备3. 动态生成HelloWorld类3.1 创建CtC

JavaScript中的高级调试方法全攻略指南

《JavaScript中的高级调试方法全攻略指南》什么是高级JavaScript调试技巧,它比console.log有何优势,如何使用断点调试定位问题,通过本文,我们将深入解答这些问题,带您从理论到实... 目录观点与案例结合观点1观点2观点3观点4观点5高级调试技巧详解实战案例断点调试:定位变量错误性能分

Java实现将HTML文件与字符串转换为图片

《Java实现将HTML文件与字符串转换为图片》在Java开发中,我们经常会遇到将HTML内容转换为图片的需求,本文小编就来和大家详细讲讲如何使用FreeSpire.DocforJava库来实现这一功... 目录前言核心实现:html 转图片完整代码场景 1:转换本地 HTML 文件为图片场景 2:转换 H

Java使用jar命令配置服务器端口的完整指南

《Java使用jar命令配置服务器端口的完整指南》本文将详细介绍如何使用java-jar命令启动应用,并重点讲解如何配置服务器端口,同时提供一个实用的Web工具来简化这一过程,希望对大家有所帮助... 目录1. Java Jar文件简介1.1 什么是Jar文件1.2 创建可执行Jar文件2. 使用java

PHP应用中处理限流和API节流的最佳实践

《PHP应用中处理限流和API节流的最佳实践》限流和API节流对于确保Web应用程序的可靠性、安全性和可扩展性至关重要,本文将详细介绍PHP应用中处理限流和API节流的最佳实践,下面就来和小编一起学习... 目录限流的重要性在 php 中实施限流的最佳实践使用集中式存储进行状态管理(如 Redis)采用滑动

SpringBoot实现不同接口指定上传文件大小的具体步骤

《SpringBoot实现不同接口指定上传文件大小的具体步骤》:本文主要介绍在SpringBoot中通过自定义注解、AOP拦截和配置文件实现不同接口上传文件大小限制的方法,强调需设置全局阈值远大于... 目录一  springboot实现不同接口指定文件大小1.1 思路说明1.2 工程启动说明二 具体实施2

Java实现在Word文档中添加文本水印和图片水印的操作指南

《Java实现在Word文档中添加文本水印和图片水印的操作指南》在当今数字时代,文档的自动化处理与安全防护变得尤为重要,无论是为了保护版权、推广品牌,还是为了在文档中加入特定的标识,为Word文档添加... 目录引言Spire.Doc for Java:高效Word文档处理的利器代码实战:使用Java为Wo