【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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/308487.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

流量不够?腾讯云轻量应用服务器“月流量不够用”收费价格表

腾讯云轻量应用服务器流量价格&#xff1a;轻量应用服务器是限制月流量的&#xff0c;每台轻量服务器均自带月流量包&#xff0c;如果当月自带的免费月流量用完了&#xff0c;超额部分需要另外支付流量费&#xff0c;价格为0.8元/BG&#xff08;地域不同&#xff0c;流量价格也…

MySQL入门教程-触发器

9.触发器 什么是触发器 触发器(trigger)&#xff1a;监视某种情况&#xff0c;并进行某种操作&#xff0c;它的执行并不是程序调用&#xff0c;也不是手工启动&#xff0c;而是由事件来触发&#xff0c;例如&#xff1a;对一张表进行操作&#xff08;插入&#xff0c;更新&…

再见2023,你好2024

再见2023&#xff0c;你好2024 生活1月 悲伤与治愈2~4月 运动与偏爱5月 体验与美食6月 婚礼与热爱7~8月 就医与别离9~11月 陪伴与暖房12月 体验&新生 运动追剧读书总结 生活 生活是一个修罗场&#xff0c;来世间一场&#xff0c;要经历丰腴有趣的人生。去体验各种滋味&…

网络安全应急响应工具之-流量安全取证NetworkMiner

在前面的一些文章中&#xff0c;用了很多的章节介绍流量分析和捕获工具wireshark。Wireshark是一款通用的网络协议分析工具&#xff0c;非常强大&#xff0c;关于wireshark的更多介绍&#xff0c;请关注专栏&#xff0c;wireshark从入门到精通。本文将介绍一个专注于网络流量取…

C语言编写Windows程序:组合启用/禁用Telnet客户端,并Telnet指定ip和端口

本文程序是将启用/禁用Telnet客户端的命令进行组合&#xff0c;单个命令的解析可参考文章&#xff1a; 启用/禁用Windows功能中的Telnet客户端的命令_()命令将阻止使用telnintel-CSDN博客 源代码如下&#xff1a; #include <stdio.h> #include <stdlib.h> #include…

RabbitMQ消息存储JSON格式反序列化

如果发送消息消息体为实体类对象数据&#xff0c;交换机接收消息经由路由键发送给队列。需要实现数据反序列化操作。实现JSON格式的反序列化操作 Rabbitmq的反序列化接口 MessageConverter&#xff0c;它的实现类有 Jackson2JsonMessageConverter的反序列化实现类&#xff0c…

城市分站优化系统源码:提升百度关键排名 附带完整的搭建教程

城市分站优化已成为企业网络营销的重要手段&#xff0c;今天来给大家分享一款城市分站优化系统源码。 以下是部分代码示例&#xff1a; 系统特色功能一览&#xff1a; 1.多城市分站管理&#xff1a;该系统支持多个城市分站的管理&#xff0c;用户可以根据业务需求&#xff0c;…

PiflowX组件-WriteToKafka

WriteToKafka组件 组件说明 将数据写入kafka。 计算引擎 flink 有界性 Streaming Append Mode 组件分组 kafka 端口 Inport&#xff1a;默认端口 outport&#xff1a;默认端口 组件属性 名称展示名称默认值允许值是否必填描述例子kafka_hostKAFKA_HOST“”无是逗号…

【华为机试】2023年真题B卷(python)-分糖果

一、题目 题目描述&#xff1a; 小明从糖果盒中随意抓一把糖果&#xff0c;每次小明会取出一半的糖果分给同学们。 当糖果不能平均分配时&#xff0c;小明可以选择从糖果盒中&#xff08;假设盒中糖果足够&#xff09;取出一个糖果或放回一个糖果。 小明最少需要多少次&#xf…

电容器50ZLH56MEFC6.3X11

电容器 常用电子元器件类型 50ZLH56MEFC6.3X11 文章目录 电容器前言一、电容器二、50ZLH56MEFC6.3X11总结前言 电容器在电子电路中有许多重要的应用,如滤波、耦合、储能、定时等。不同类型的电容器具有不同的性能特点,例如电容量、工作电压、频率响应等。在选择和使用电容…

读算法霸权笔记07_筛选

1. 美国残疾人法案 1.1. 1990年 1.2. 公司在招聘时使用身体检查是非法的 1.3. 有某些心理健康问题的人被“亮了红灯”&#xff0c;他们因此没能找到一份正常的工作&#xff0c;过上正常的生活&#xff0c;这就使其进一步被社会孤立&#xff0c;而这正是《美国残疾人法案》要…

知识付费saas租户平台 :如何用最少的时间获得最大的收益

明理信息科技知识付费saas租户平台 随着互联网的快速发展&#xff0c;人们越来越重视知识的获取和价值的挖掘。在这个信息爆炸的时代&#xff0c;知识付费已经成为了一种新的商业模式&#xff0c;为知识的传播和价值的转化提供了更加高效和便捷的途径。本文将探讨知识付费的发…