【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 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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JMX使用详解

JMX简介JMX优缺点JMX的功能JMX的用法JMX和Activiti的区别JMX使用案例JMX架构JMX支持的协议JMX工作原理JMX主要方法JMX中的MBean的注册JMX拓展SNMP网络管理协议TMN网络管理协议CIM网络管理协议Activiti JMX简介 JMX&#xff08;Java Management Extensions&#xff0c;即Java管…

拍照就能建模!手机就能访问! 这个技术正成为宣传新手段!

随着人工智能技术的不断进步&#xff0c;现在可以通过拍摄照片结合AI技术来实现3D模型生成。这种技术的出现&#xff0c; 不仅能更加方便快捷地创建3D模型&#xff0c;而且还能真实复原现实中物件的质感、纹理等。同时&#xff0c;极大地降低了各行业对3D技术的应用门槛&#x…

Baumer工业相机堡盟工业相机如何通过NEOAPI SDK设置相机的固定帧率(C#)

Baumer工业相机堡盟工业相机如何通过NEOAPI SDK设置相机的固定帧率&#xff08;C#&#xff09; Baumer工业相机Baumer工业相机的固定帧率功能的技术背景CameraExplorer如何查看相机固定帧率功能在NEOAPI SDK里通过函数设置相机固定帧率 Baumer工业相机通过NEOAPI SDK设置相机固…

Scrapy使用案例——爬取豆瓣Top 250电影数据

文章目录 什么是Scrapy&#xff1f;创建Scrapy项目编写Scrapy Spider创建Item类配置数据存储运行Scrapy爬虫处理常见问题结论Python技术资源分享1、Python所有方向的学习路线2、学习软件3、入门学习视频4、实战案例5、清华编程大佬出品《漫画看学Python》6、Python副业兼职与全…

用通俗易懂的方式讲解大模型:使用 FastChat 部署 LLM 的体验太爽了

之前介绍了Langchain-Chatchat 项目的部署&#xff0c;该项目底层改用了 FastChat 来提供 LLM(大语言模型)的 API 服务。 出于好奇又研究了一下 FastChat&#xff0c;发现它的功能很强大&#xff0c;可以用来部署市面上大部分的 LLM 模型&#xff0c;可以将 LLM 部署为带有标准…

Sensor Demosaic IP 手册PG286笔记

《 UG1449 Multimedia User Guide》中包含了大量的多媒体IP简介。 本IP 用于对bayer RGB&#xff08;每个pixel只有单个R/G/B&#xff09;做去马赛克处理&#xff0c;恢复成每个pixel点都有完整的RGB值。通过axi接口配置IP内部erg。 1、算法手册中的描述 提到了几种插值算法&…

IPD-PDP产品开发流程-PDT产品开发计划Charter文档模板(word)3

今天继续为家分享PDT的产品开发计划Charter模板的内容。 Charter任务书模板内容7&#xff1a;人力资源和技能需求 在这一部分&#xff0c;列出项目在不同阶段所需要的不同人力资源需求、数量、能力要求&#xff0c;以及对于一些特殊人力资源的需求。 7.1不同阶段的人力资源汇…

QT上位机开发(乘法计算小软件)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面一篇文章&#xff0c;我们学习了怎么创建qt的第一个工程&#xff0c;怎么用designer给qt修改界面。虽然我们到目前为止&#xff0c;还没有编写…

雪花旅游网的前端html模板推荐

一、需求获取 该网站是一个社交网络平台&#xff0c;也是一个提供旅行攻略、游记、景点介绍、交通信息等旅行相关内容的网站。它为用户提供了丰富的旅行信息&#xff0c;包括国内外的旅游目的地、景点推荐、旅行攻略、游记分享等。用户可以在该网站上查找各地的旅游信息&#…

静物摄影在UE5里运用几点记要

被摄体&#xff0c;相机与光源的关系&#xff0c;要增强立体感&#xff0c;摄像机与光源的位置关系要错开&#xff1b;b的立体感要更强 漫反射与点光源&#xff0c;UE5太阳光属于漫反射&#xff0c;整体比较柔和&#xff0c;但是阴影处比较黑&#xff1b;摄影棚会用反光板来增亮…

使用LOTR合并检索提高RAG性能

RAG结合了两个关键元素:检索和生成。它首先使用语义搜索等高级技术来浏览大量数据&#xff0c;包括文本、图像、音频和视频。RAG的本质在于它能够检索相关信息&#xff0c;然后作为下一阶段的基础。生成组件利用大型语言模型的能力&#xff0c;解释这些数据块&#xff0c;制作连…

三个故事,谈谈小米汽车技术发布会

都说新年新气象&#xff0c;随着年末消费旺季到来&#xff0c;汽车市场越来越热闹了。 继蔚来12月23日公布旗舰车型ET9&#xff0c;华为26日发布问界M9&#xff0c;小米汽车首款量产车型SU7终于正式亮相。 12月28日&#xff0c;在小米汽车技术发布会上&#xff0c;小米创办人…

AIGC与计算机技术:人工智能生成内容的深度探索

AIGC与计算机技术&#xff1a;人工智能生成内容的深度探索 摘要&#xff1a;随着人工智能技术的快速发展&#xff0c;AIGC&#xff08;人工智能生成内容&#xff09;成为了计算机领域的前沿话题。本文将详细探讨AIGC的基本原理、技术应用和未来发展趋势&#xff0c;以及它对计…

【AIGC表情prompt】提示词练习技巧

表情类提示词练习技巧 医疗机器人&#xff0c;男人笑脸景深&#xff0c;数据&#xff0c;座标&#xff0c;12k,c4d渲染&#xff0c;高分辨率&#xff0c;,暖色调&#xff0c;高清对比 医疗机器人&#xff0c;男人微笑&#xff0c;景深&#xff0c;数据&#xff0c;座标&#xf…

nodejs+vue+微信小程序+python+PHP的药品销售管理系统的设计与实现-计算机毕业设计推荐

然后分析系统需要实现的功能并进行设计。梳理业务流程&#xff0c;并根据功能设计数据库&#xff0c;最后通过编码实现&#xff0c;药店药品出库入库管理&#xff1a;登记药店药品销售情况&#xff0c;记录药店药品出入库管理。能更好的掌握药品的一个销售情况&#xff0c;利于…

Python新手教程 —— Hello, World!

文章目录 Hello, World!作者自述关于本系列什么是编程语言什么是Python安装Python运行Python3解释器IDLE编写代码文件 本文复习Python技术资源分享1、Python所有方向的学习路线2、学习软件3、入门学习视频4、实战案例5、清华编程大佬出品《漫画看学Python》6、Python副业兼职与…

Java集合/泛型篇----第一篇

系列文章目录 文章目录 系列文章目录前言一、ArrayList和linkedList的区别二、HashMap和HashTable的区别三、Collection包结构,与Collections的区别四、泛型常用特点前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站…

EMQX开启MongoDB接入认证与订阅发布鉴权

背景 关于物联网平台设计一个最佳实践是&#xff1a;对接入平台的设备进行认证&#xff0c;并且对设备可以发布和订阅的主题进行权限控制。 MQTT Broker 开启对接入设备的认证与订阅发布鉴权的意义在于增强系统的安全性。通过认证&#xff0c;可以确保只有经过授权的设备可以连…

【IEEE解刊】IF4.4实力强劲,国人占比第一,好投吗?(附中科院高分区快刊)

计算机类 • 好刊解读 今天小编带来IEEE旗下计算机领域高分好刊&#xff0c;如您有投稿需求&#xff0c;可作为重点关注&#xff01;后文有相关领域真实发表案例&#xff0c;供您投稿参考~ 01 期刊简介 IEEE Systems Journal ✅出版社&#xff1a;IEEE ✅ISSN&#xff1a;1…

【Bootstrap学习 day4】

Bootstrap5 列表组 使用Bootstrap创建列表 可以创建三种不类型的HTML列表&#xff1a; 无序列表—顺序无关紧要的项目列表。无序列表中的列表标有项目符号&#xff0c;例如。、等ul>li有序列表—顺序确实很重要的项目列表。有序列表中的列表项用数字标记&#xff0c;例如1、…