【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

相关文章

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

Elasticsearch 在 Java 中的使用教程

《Elasticsearch在Java中的使用教程》Elasticsearch是一个分布式搜索和分析引擎,基于ApacheLucene构建,能够实现实时数据的存储、搜索、和分析,它广泛应用于全文... 目录1. Elasticsearch 简介2. 环境准备2.1 安装 Elasticsearch2.2 J

Java中的String.valueOf()和toString()方法区别小结

《Java中的String.valueOf()和toString()方法区别小结》字符串操作是开发者日常编程任务中不可或缺的一部分,转换为字符串是一种常见需求,其中最常见的就是String.value... 目录String.valueOf()方法方法定义方法实现使用示例使用场景toString()方法方法

Java中List的contains()方法的使用小结

《Java中List的contains()方法的使用小结》List的contains()方法用于检查列表中是否包含指定的元素,借助equals()方法进行判断,下面就来介绍Java中List的c... 目录详细展开1. 方法签名2. 工作原理3. 使用示例4. 注意事项总结结论:List 的 contain

Java实现文件图片的预览和下载功能

《Java实现文件图片的预览和下载功能》这篇文章主要为大家详细介绍了如何使用Java实现文件图片的预览和下载功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... Java实现文件(图片)的预览和下载 @ApiOperation("访问文件") @GetMapping("

Spring Boot + MyBatis Plus 高效开发实战从入门到进阶优化(推荐)

《SpringBoot+MyBatisPlus高效开发实战从入门到进阶优化(推荐)》本文将详细介绍SpringBoot+MyBatisPlus的完整开发流程,并深入剖析分页查询、批量操作、动... 目录Spring Boot + MyBATis Plus 高效开发实战:从入门到进阶优化1. MyBatis

SpringCloud动态配置注解@RefreshScope与@Component的深度解析

《SpringCloud动态配置注解@RefreshScope与@Component的深度解析》在现代微服务架构中,动态配置管理是一个关键需求,本文将为大家介绍SpringCloud中相关的注解@Re... 目录引言1. @RefreshScope 的作用与原理1.1 什么是 @RefreshScope1.

Java并发编程必备之Synchronized关键字深入解析

《Java并发编程必备之Synchronized关键字深入解析》本文我们深入探索了Java中的Synchronized关键字,包括其互斥性和可重入性的特性,文章详细介绍了Synchronized的三种... 目录一、前言二、Synchronized关键字2.1 Synchronized的特性1. 互斥2.