当前位置: 首页 > news >正文

【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

相关文章:

  • Android笔记(二十三):Paging3分页加载库结合Compose的实现分层数据源访问
  • 2024年汉字小达人区级样题预测(基于近年真题)和备考建议
  • 如何使用Docker将.Net6项目部署到Linux服务器(三)
  • o2o生活通全开源尊享版+多城市切换+企业付款+交友IM+平台快报
  • C# json 转匿名对象及C#关键字的处理
  • 毫米波雷达:从 3D 走向 4D
  • 4. AOP
  • 单片机MCU堆栈概念与区别
  • 先序+中序还原二叉树【数据结构】
  • Prometheus通过consul实现自动服务发现
  • 搭建在线720虚拟VR展厅,不仅是展厅也是名片
  • 【SpringCloud】从实际业务问题出发去分析Eureka-Server端源码
  • 基于Freeswitch实现的Volte网视频通知应用
  • Git 使用规范:起名字、提交描述的最佳实践
  • Linux(ubuntu)下git / github/gitee使用
  • 【347天】每日项目总结系列085(2018.01.18)
  • 【译】理解JavaScript:new 关键字
  • 【跃迁之路】【669天】程序员高效学习方法论探索系列(实验阶段426-2018.12.13)...
  • CSS 专业技巧
  • flutter的key在widget list的作用以及必要性
  • Js基础知识(四) - js运行原理与机制
  • PAT A1092
  • Sequelize 中文文档 v4 - Getting started - 入门
  • Windows Containers 大冒险: 容器网络
  • 从零开始在ubuntu上搭建node开发环境
  • 高度不固定时垂直居中
  • 如何邀请好友注册您的网站(模拟百度网盘)
  • 深入浏览器事件循环的本质
  • 通过git安装npm私有模块
  • 源码之下无秘密 ── 做最好的 Netty 源码分析教程
  • 格斗健身潮牌24KiCK获近千万Pre-A轮融资,用户留存高达9个月 ...
  • # 安徽锐锋科技IDMS系统简介
  • #《AI中文版》V3 第 1 章 概述
  • #LLM入门|Prompt#1.7_文本拓展_Expanding
  • #我与Java虚拟机的故事#连载15:完整阅读的第一本技术书籍
  • (9)目标检测_SSD的原理
  • (C#)Windows Shell 外壳编程系列9 - QueryInfo 扩展提示
  • (delphi11最新学习资料) Object Pascal 学习笔记---第7章第3节(封装和窗体)
  • (JS基础)String 类型
  • (PyTorch)TCN和RNN/LSTM/GRU结合实现时间序列预测
  • (ZT)北大教授朱青生给学生的一封信:大学,更是一个科学的保证
  • (免费领源码)python#django#mysql公交线路查询系统85021- 计算机毕业设计项目选题推荐
  • (免费领源码)Python#MySQL图书馆管理系统071718-计算机毕业设计项目选题推荐
  • (算法)求1到1亿间的质数或素数
  • .net core Swagger 过滤部分Api
  • .NET Core实战项目之CMS 第十二章 开发篇-Dapper封装CURD及仓储代码生成器实现
  • .NET 中小心嵌套等待的 Task,它可能会耗尽你线程池的现有资源,出现类似死锁的情况
  • .NET/C# 中你可以在代码中写多个 Main 函数,然后按需要随时切换
  • .NET文档生成工具ADB使用图文教程
  • .Net下的签名与混淆
  • .NET与java的MVC模式(2):struts2核心工作流程与原理
  • .NET中使用Protobuffer 实现序列化和反序列化
  • /proc/vmstat 详解
  • @Async注解的坑,小心
  • @Transactional类内部访问失效原因详解