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

news2024/11/22 14:03:23

在这里插入图片描述

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

Value = "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.coloradmin.cn/o/1343912.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《深入理解JAVA虚拟机笔记》并发与线程安全原理

除了增加高速缓存之外&#xff0c;为了使处理器内部的运算单元能尽量被充分利用&#xff0c;处理器可能对输入代码进行乱序执行&#xff08;Out-Of-Order Execution&#xff09;优化。处理器会在计算之后将乱序执行的结果重组&#xff0c;保证该结果与顺序执行的结果一致&#…

三台CentOS7.6虚拟机搭建Hadoop完全分布式集群(三)

这个是笔者大学时期的大数据课程使用三台CentOS7.6虚拟机搭建完全分布式集群的案例&#xff0c;已成功搭建完全分布式集群&#xff0c;并测试跑实例。 9 安装hbase 温馨提示&#xff1a;安装hbase先在master主节点上配置&#xff0c;然后远程复制到slave01或slave02 &#xf…

远程网络唤醒家庭主机(openwrt设置)

远程网络唤醒家庭主机&#xff08;openwrt设置&#xff09; 前提&#xff1a; 1.配置好主板bios的网络唤醒功能(网络教程自己百度一下找) 2.电脑开启网络唤醒功能(网络教程自己百度一下找) 3.路由器通过ddns实现域名和动态IP绑定内网穿透方法汇总_不修改光猫进行内网穿透-C…

【办公软件】Excel双坐标轴图表

在工作中整理测试数据&#xff0c;往往需要一个图表展示两个差异较大的指标。比如共有三个数据&#xff0c;其中两个是要进行对比的温度值&#xff0c;另一个指标是两个温度的差值&#xff0c;这个差值可能很小。 举个实际的例子&#xff1a;数据如下所示&#xff0c;NTC检测温…

JavaScript中实现页面跳转的几种常用方法

Hi i,m JinXiang ⭐ 前言 ⭐ 本篇文章主要介绍在JavaScript中实现页面跳转的几种常用方法以及部分理论知识 &#x1f349;欢迎点赞 &#x1f44d; 收藏 ⭐留言评论 &#x1f4dd;私信必回哟&#x1f601; &#x1f349;博主收将持续更新学习记录获&#xff0c;友友们有任何问题…

2024收入最高的编程语言

我的新书《Android App开发入门与实战》已于2020年8月由人民邮电出版社出版&#xff0c;欢迎购买。点击进入详情 1.Python Python 是最流行、用途最广泛的语言之一。它通常用于网络开发、数据科学、机器学习等。 以下是 Python 编程语言的一些主要用途&#xff1a; Web 开发&…

【AIGC科技展望】预测AIGC2025年的机会与挑战

2025年&#xff0c;AIGC的机会与挑战 在未来的五年里&#xff0c;AIGC&#xff08;AI Generated Content&#xff09;将会成为一个越来越重要的领域。但是&#xff0c;伴随着机会而来的是挑战。在这篇文章中&#xff0c;我们将一起探讨AIGC的机会与挑战&#xff0c;并预测2025…

WinForm开发 - C# RadioButton(单选框) 设置默认选中或取消默认选中

WinForm开发中RadioButton组件使用过程中的小技巧。 1、属性界面操作 如果有多个组件&#xff0c;希望不显示默认选中单选框只需要将其Checked属性全部设置为False即可&#xff0c; 如果希望默认多个组件中显示默认选中&#xff0c;将其Checked属性设置为True。 2、代码实…

GEE错误——‘xxx‘ did not match any bands.

这里我们在进行影像展示的时候会出现下面的错误,主要的原因是我们虽然进行了波段运算,但是依旧无法加载,主要原因是我们没有将计算过后的波段信息进行添加到我们的一个多波段影像,这里我们首先来看看代码出现的错误提示。当然这里只是给出了主要的问题,其实在进行波段运算…

Java——猫猫图鉴微信小程序(前后端分离版)

目录 一、开源项目 二、项目来源 三、使用框架 四、小程序功能 1、用户功能 2、管理员功能 五、使用docker快速部署 六、更新信息 审核说明 一、开源项目 猫咪信息点-ruoyi-cat: 1、一直想做点项目进行学习与练手&#xff0c;所以做了一个对自己来说可以完成的…

ES6的默认参数和rest参数

✨ 专栏介绍 在现代Web开发中&#xff0c;JavaScript已经成为了不可或缺的一部分。它不仅可以为网页增加交互性和动态性&#xff0c;还可以在后端开发中使用Node.js构建高效的服务器端应用程序。作为一种灵活且易学的脚本语言&#xff0c;JavaScript具有广泛的应用场景&#x…

PowerShell Instal 一键部署TeamCity

前言 TeamCity 是一个通用的 CI/CD 软件平台,可实现灵活的工作流程、协作和开发实践。允许在您的 DevOps 流程中成功实现持续集成、持续交付和持续部署。 系统支持 Centos7,8,9/Redhat7,8,9及复刻系列系统支持 Windows 10,11,2012,2016,2019,2022高版本建议使用9系列系统…

权重函数设计

1e4/(0.5*s^21*s1) 1.4125 抑制驱动器饱和 1e-4*(s1)/(0.001*s1) 10*(s10)/(s1000) 1e-6*(s1)/(0.001*s1) [0.01,0.1],[0.001,1] 0.5*(0.05*s1)/(0.0001*s0.01)

Android笔记(二十三):Paging3分页加载库结合Compose的实现分层数据源访问

在Android笔记&#xff08;二十二&#xff09;&#xff1a;Paging3分页加载库结合Compose的实现网络单一数据源访问一文中&#xff0c;实现了单一数据源的访问。在实际运行中&#xff0c;往往希望不是单纯地访问网络数据&#xff0c;更希望将访问的网络数据保存到移动终端的SQL…

windows高效的文件夹管理工具QTTabBar

1、介绍&#xff1a; 功能介绍视频https://www.bilibili.com/video/BV13y4y1V782/?spm_id_from333.880.my_history.page.click&vd_sourceef5003729aa0776908befb4d20108803 这个视频很坑&#xff0c;介绍了功能&#xff0c;但没介绍安装方法 2、下载&#xff1a; 下载链…

自检服务器,无需服务器、不用编程。

自检服务器&#xff0c;无需服务器、不用编程。 大家好&#xff0c;我是JavaPub. 这几年自媒体原来热&#xff0c;很多人都知道了个人 IP 的重要性。连一个搞中医的朋友都要要做一个自己的网站&#xff0c;而且不想学编程、还不想花 RMB 租云服务。 老读者都知道&#xff0c…

Spring Boot 基于Redisson实现注解式分布式锁

依赖版本 JDK 17 Spring Boot 3.2.0 Redisson 3.25.0 源码地址&#xff1a;Gitee 导入依赖 <properties><redisson.version>3.25.0</redisson.version> </properties><dependencies><dependency><groupId>org.projectlombok</…

OpenHarmony南向之Camera简述

Camera驱动框架 该驱动框架模型内部分为三层&#xff0c;依次为HDI实现层、框架层和设备适配层&#xff1a; HDI实现层&#xff1a;实现OHOS&#xff08;OpenHarmony Operation System&#xff09;相机标准南向接口。框架层&#xff1a;对接HDI实现层的控制、流的转发&#x…

ECMAScript 6 - 通过Promise输出题理解Promise

1 题目(1) 题目背景&#xff1a;分享洛千陨 珍藏题 const p1 () > (new Promise((resolve, reject) > {console.log(1);let p2 new Promise((resolve, reject) > {console.log(2);const timeOut1 setTimeout(() > {console.log(3);resolve(4);}, 0)resolve(5)…

js for和forEach 跳出循环 替代方案

1 for循环跳出 for(let i0;i<10;i){if(i5){break;}console.log(i) }在函数中也可以return跳出循环 function fn(){for(let i0;i<10;i){if(i5){return;}console.log(i)} } fn()for ... of效果同上 2 forEach循环跳出 break会报错 [1,2,3,4,5,6,7,8,9,10].forEach(i>…