接口数据做缓存,响应飞快似天神

news2024/12/21 19:43:52

概述

在商业中 “现金为王”,而在互联网整个软件世界中,有一个与之相近的说法是“缓存为王”。

本文我们重点说明缓存方向:将方法的返回值缓存起来,下次再调用该方法时,如果方法的参数与之前调用时的参数相同,则会直接返回缓存中的结果,而不会再执行方法体。这样可以提高方法的执行效率

优点

  • 提高性能:缓存可以将方法的结果存储在内存中,后续对相同参数的方法调用可以直接从缓存中获取结果,避免重复计算,提高方法的执行效率。
  • 减轻数据库压力:对于需要频繁访问数据库的方法,可以将结果缓存在内存中,减轻数据库的压力,提高数据库的响应速度。
  • 简化代码逻辑:通过使用缓存,可以避免编写复杂的手动缓存代码或条件判断逻辑,使代码更简洁、可读性更好。

缺点

  • 内存占用:缓存的数据存储在内存中,因此如果缓存的数据量过大,会消耗大量的内存资源。在使用缓存时需要合理安排系统的内存和缓存容量。
  • 数据一致性:使用缓存后,需要注意维护数据的一致性。当数据发生变化时,需要及时更新缓存,以避免脏数据的问题。可通过合理设计缓存策略和使用缓存失效机制来解决这个问题。
  • 缓存失效:缓存的有效期限需要合理设置。如果缓存的有效期太长,可能导致数据更新不及时;如果缓存的有效期太短,可能增加重复执行代码的次数。需要根据具体业务场景来选择合适的缓存有效期。
  • 缓存击穿:当某个缓存条目在缓存失效时,同时有大量的并发请求到达时,可能会导致缓存击穿问题,即大量请求直接击穿到数据库。可以通过加锁或使用分布式锁等机制来避免缓存击穿。

注解介绍

Spring缓存机制通过 @EnableCaching开启,配合 @Cacheable、 @CachePut、 @CacheEvict等注解,为Java应用提供了一种声明式管理缓存的方式。这些注解使得缓存配置变得简洁明了,允许开发者轻松实现数据的自动缓存、更新和清除,从而优化应用性能,减少不必要的计算和数据访问开销。

1. 启用缓存支持

@EnableCaching 注解用于在Spring配置类上启用缓存管理功能。

  • 注解属性介绍

无属性。

  • 注解业务案例
1.  @Configuration
    
2.  @EnableCaching
    
3.  public class CacheConfig {
    
4.      // 配置其他Bean`
    
5.  }
    

2. 缓存结果

@Cacheable 注解用于声明一个方法的结果是可缓存的。

  • 注解属性介绍

  • valuecacheNames: 指定缓存名称,可以是单个或多个。

  • key: 指定缓存键的SpEL表达式。

  • condition: 指定缓存的条件SpEL表达式。

  • unless: 指定不缓存的条件SpEL表达式。

  • 注解业务案例 单一缓存名称和键:

1.  @Cacheable("books")
    
2.  public Book findBookById(String id) {
    
3.      // 业务逻辑
    
4.  }

多个缓存名称和条件:

1.  @Cacheable(value = {"books", "archivedBooks"}, condition = "#id.length() > 10")
2.  public Book findBookWithComplexKey(String id) {
3.      // 业务逻辑
4.  }
    
6.  @Service
7.  public class MyService {
    
9.      /**
    
10.       `* 一个使用 @Cacheable 所有属性的案例。`
    
11.       `*` 
    
12.       `* @param id 用户ID`
    
13.       `* @return 返回用户对象`
    
14.       `*/
    
15.      @Cacheable(
    
16.          value = "users",          // 缓存名称`
    
17.          key = "#id",              // 缓存键,使用SpEL表达式`
    
18.          condition = "#id.length() > 3",  // 缓存条件,只有当ID长度大于3时才缓存`
    
19.          unless = "#result == null" // 除非条件,如果结果为null,则不缓存`
    
20.      )
21.      public User findUserById(String id) {
    
22.          // 模拟数据库查询操作,这里假设ID长度小于3时没有结果`
    
23.          if (id == null || id.length() <= 3) {
    
24.              return null;
    
25.          }
    
26.          return performDatabaseQuery(id);
    
27.      }
    

29.      private User performDatabaseQuery(String id) {
    
30.          // 模拟数据库查询逻辑`
    
31.          return new User(id, "Name based on ID");
    
32.      }
    
33.  }

3. 更新缓存

@CachePut 注解用于在方法执行后更新缓存。

  • 注解属性介绍

@Cacheable相同。

  • 注解业务案例
 1.  @CachePut("books")

 2.  public Book updateBookDetails(String id, Book details) {

 3.  // 业务逻辑

 4.  }

 5.  

 6.  @Service

 7.  public class MyService {

 8.  

 9.  /**

 10. * 使用 @CachePut 所有属性的案例。

 11. *

 12. * @param user 用户对象,包含ID

 13. * @return 更新后的用户对象

 14. */

 15. @CachePut(

 16. 	value = "users", // 缓存名称

 17. 	key = "#user.id", // 缓存键,使用SpEL表达式

 18. 	condition = "#user.age > 18" // 条件,只有当用户年龄大于18时才更新缓存

 19. )

 20. public User updateUserProfile(User user) {

 21. // 模拟更新用户信息的业务逻辑

 22. return performUpdate(user);

 23. }

 24. 

 25. private User performUpdate(User user) {

 26. // 模拟更新逻辑

 27. user.setName("Updated Name");

 28. return user;

 29. }

 30. }

4. 清除缓存

@CacheEvict 注解用于在方法执行后清除缓存。

注解属性介绍

  • valuecacheNames: 指定缓存名称,可以是单个或多个。

  • allEntries: 清除所有缓存项。

  • condition: 指定清除缓存的条件SpEL表达式。

注解业务案例

清除特定缓存名称的条目:

1.  @CacheEvict("books")
    
2.  public void deleteBook(String id) {
    
3.      // 业务逻辑
    
4.  }

清除所有缓存名称的所有条目:

1.  @CacheEvict(allEntries = true)
    
2.  public void clearAllCaches() {
    
3.      // 业务逻辑
    
4.  }

5. 组合缓存操作

@Caching 注解用于组合多个缓存操作。

  • 注解属性介绍

value: 包含多个缓存操作的数组。

  • 注解业务案例

1.  @Caching(
    
2.      cacheable = {@Cacheable("books")},
    
3.      put = {@CachePut("books")},
    
4.      evict = {@CacheEvict("archivedBooks")}
    
5.  )
    
6.  public Book processBook(String id, Book details) {
    
7.      // 业务逻辑
    
8.  }
    

代码演示

redis依赖安装

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.21</version>
        </dependency>
  • redis配置文件
spring:
  redis:
    # 连接地址
    host: "127.0.0.1"
    # 端口
    port: 6379
    # 数据库
    database: 1
    password: helloworld
    # 连接超时
    connect-timeout: 5s
    # 读超时
    timeout: 5s

    # Lettuce 客户端的配置
    lettuce:
      # 连接池配置
      pool:
        # 最小空闲连接
        min-idle: 0
        # 最大空闲连接
        max-idle: 8
        # 最大活跃连接
        max-active: 8
        # 从连接池获取连接 最大超时时间,小于等于0则表示不会超时
        max-wait: -1ms

接口缓存配置类

框架给我们提供了 @Cacheable 注解用于缓存方法返回内容。但是 @Cacheable 注解 不能 定义缓存 有效期。这样的话在一些需要自定义缓存有效期的场景就不太实用。

框架给我们提供的 RedisCacheManager 类,是 准对这一块的 就配置类, 我们可以根据此类,自己实现 缓存有效期 的功能。

package com.xuzhongkj.configrations;


import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import javax.validation.constraints.NotNull;
import java.time.Duration;
import java.util.Objects;


@Configuration
@EnableCaching
public class CustomRedisCacheManager {

    private String keyPreFix = "whero";

    private RedisSerializer<String> keySerializer() {
        return new StringRedisSerializer();
    }

    private RedisSerializer<Object> valueSerializer() {
        // fastjson 中的类
        return new GenericFastJsonRedisSerializer();
    }


    /*
     * @description 配置自定义 缓存管理器: RedisCacheManager
     **/
    @Bean
    public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
        // 缓存注解 配置:
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                //设置 key 为String
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))
                //设置 value 为自动转Json的Object
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()))
                // 使用 prefixCacheNameWith 需要注意系统自动拼接的双”:“问题
                .computePrefixWith(cacheName -> keyPreFix + ":" + cacheName + ":");


        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(Objects.requireNonNull(redisTemplate.getConnectionFactory()));

        RedisCacheManager redisCacheManager = new CustomRedisCacheManagerToolsClass(redisCacheWriter, config);
        return redisCacheManager;
    }
}


/**
 * @Title: 自定义redis缓存管理器, 为了实现 缓存有效期的 动态性
 */
class CustomRedisCacheManagerToolsClass extends RedisCacheManager {

    public static final String SEPARATOR = "#";

    public CustomRedisCacheManagerToolsClass(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
        super(cacheWriter, defaultCacheConfiguration);
    }

    @Override
    protected RedisCache createRedisCache(@NotNull(message = "缓存名称不能为空") String name, RedisCacheConfiguration cacheConfiguration) {
        // 举例说明: name = "bookCourse:getCourseId#7D"
        if (name.contains(SEPARATOR)) {
            String[] spelStr = name.split(SEPARATOR);
            String key = spelStr[0];
            String valueStr = spelStr[1];

            int length = valueStr.length();

            if (length >= 2) {
                String cycleTimeStr = valueStr.substring(0, length - 1);

                if (cycleTimeStr.matches("\\d+")) {
                    long cycleTime = Long.parseLong(cycleTimeStr);

                    String cycleUnit = valueStr.substring(length - 1, length);

                    if (cycleUnit.equals("D")) {//表示天
                        return super.createRedisCache(key, cacheConfiguration.entryTtl(Duration.ofDays(cycleTime)));
                    }
                    if (cycleUnit.equals("H")) {//表示小时
                        return super.createRedisCache(key, cacheConfiguration.entryTtl(Duration.ofHours(cycleTime)));
                    }
                    if (cycleUnit.equals("M")) {//表示分钟
                        return super.createRedisCache(key, cacheConfiguration.entryTtl(Duration.ofMinutes(cycleTime)));
                    }
                    if (cycleUnit.equals("S")) {//表示秒
                        return super.createRedisCache(key, cacheConfiguration.entryTtl(Duration.ofSeconds(cycleTime)));
                    } else {
                        // 都不是则使用默认配置
                        return super.createRedisCache(name, cacheConfiguration);
                    }
                }
            }
        }
        return super.createRedisCache(name, cacheConfiguration);
    }
}

这里面简单对 RedisCacheConfiguration 缓存配置做一下说明:

  • serializeKeysWith():设置 Redis 的 key 的序列化规则。
  • erializeValuesWith():设置 Redis 的 value 的序列化规则。
  • computePrefixWith():计算 Redis 的 key 前缀。
  • cacheConfiguration.entryTtl(): 设置 @Cacheable 注解缓存的有效期。

测试使用

service层

package com.xuzhongkj.services.impls;

import com.xuzhongkj.pojo.Userswtt;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class WttService {

    @Cacheable(cacheNames = {"bookCourse:getCourseId#7M"}, key = "#name")
    public Userswtt getName (String name) {
        System.out.println("-------  创建 -----------"+name);
        Userswtt u = new Userswtt();
        u.setOpenid("qwejlksdfjkdf");
        u.setAge(11);
        u.setAvatar("https:/asdfj;askdf");
        u.setUid(123L);
        return  u;
    }

    @CachePut(cacheNames = {"bookCourse:getCourseId#7M"}, key = "#name")
    public Userswtt EdtName (String name) {
        System.out.println("---------- 更新 --------"+name);
        Userswtt u = new Userswtt();
        u.setOpenid("qwejlksdfjkdf");
        u.setAge(22);
        u.setAvatar("https:/asdfj;askdf");
        u.setUid(123L);
        return  u;
    }

    @CacheEvict(cacheNames = {"bookCourse:getCourseId#7M"}, key = "#name")
    public Userswtt DelName (String name) {
        System.out.println("--------- 删除 ---------"+name);
        Userswtt u = new Userswtt();
        return  u;
    }
}

controller 层

@RestController
@Slf4j
@RequestMapping("/test")
public class WttTest {

    @Autowired
    private WttService wttService;

    @GetMapping("/wtt")
    public Userswtt a1() {
            Userswtt name = wttService.getName("testResObj");
            return name;
    }

    @GetMapping("/wtt2")
    public Userswtt a2() {
            Userswtt name = wttService.EdtName("testResObj");
            return name;
    }

    @GetMapping("/wtt3")
    public Userswtt a3() {
            return wttService.DelName("testResObj");
    }

redis中键值对的存储情况:

  • key:

whero:bookCourse:getCourseId:testResObj

  • value

{“@type”:“com.xuzhongkj.pojo.Userswtt”,“age”:11,“avatar”:“https:/asdfj;askdf”,“openid”:“qwejlksdfjkdf”,“uid”:123}

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

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

相关文章

穷举vs暴搜vs深搜vs回溯vs剪枝系列一>找出所有子集的异或总和再求和

题目&#xff1a; 解析&#xff1a; 代码&#xff1a; private int ret;//返回周结果private int path;//枚举一个元素就异或进去public int subsetXORSum(int[] nums) {dfs(nums, 0);return ret;} private void dfs(int[] nums, int pos){ret path;for(int i pos; i <…

C# 从控制台应用程序入门

总目录 前言 从创建并运行第一个控制台应用程序&#xff0c;快速入门C#。 一、新建一个控制台应用程序 控制台应用程序是C# 入门时&#xff0c;学习基础语法的最佳应用程序。 打开VS2022&#xff0c;选择【创建新项目】 搜索【控制台】&#xff0c;选择控制台应用(.NET Framew…

创新实训——前端:配置问题及解决

在配置前端环境中&#xff0c;出现了一些问题&#xff1a; Error: The following dependencies are imported but could not be resolved: element-plus (imported by C:/Users/30753/Desktop/cxsx/Coarse-grained-information-processing-front/src/request.js) element-plus…

【解决方案】三方云仓与各电商平台的电子面单对接

为实现三方云仓与各电商平台的电子面单对接&#xff0c;您需要确保您的WMS&#xff08;仓库管理系统&#xff09;能够通过奇门接口&#xff08;假设这是一个特定的API或中间件服务用于连接不同的系统&#xff09;与各个电商平台进行数据交换。下面是一个简化的解决方案框架&…

属性描述符

什么是属性描述符 1 数据描述符 2 存取描述符 3 获取属性描述符 4 设置属性描述符 4.1 配置writable 4.2 配置configurable 4.3 configurable的特殊性 4.4 配置enumerable 5.同时设置多个属性的属性描述符 6.get和set的用法具体看访问器属性getter和setter帖子。 什…

PostgreSQL技术内幕21:SysLogger日志收集器的工作原理

0.简介 在前面文章中介绍了事务模块用到的事务日志结构和其工作原理&#xff0c;本文将介绍日志的另一个部分&#xff0c;操作日志&#xff0c;主要去描述SysLogger日志的工作原理&#xff0c;流程以及其中关键的实现&#xff1a;日志轮转&#xff0c;刷盘性能问题等&#xff…

Unity3D仿星露谷物语开发6之角色添加动画

1、目的 给角色添加素材中的动画&#xff0c;最终让角色动起来。 2、准备工作 当前的预设体中的Player对象还不够完善&#xff0c;需要删除掉再优化下。此时应当&#xff1a;Hierarchy中的Player对象切断和预设体的关联&#xff0c;同时删除Prefabs中的Player对象。 首先&a…

Go框架比较:goframe、beego、iris和gin

由于工作需要&#xff0c;这些年来也接触了不少的开发框架&#xff0c;Golang的开发框架比较多&#xff0c;不过基本都是Web"框架"为主。这里稍微打了个引号&#xff0c;因为大部分"框架"从设计和功能定位上来讲&#xff0c;充其量都只能算是一个组件&…

CS 144 check4: interoperating in the world

Lectures Note 略 Exercises 执行cmake --build build --target check_webget发现超出12s了。 1、回看check0的代码&#xff0c;似乎不需要关闭写入方向&#xff0c;于是注释掉&#xff08;关键&#xff09; 2、将request的变量类型从string转为string_view&#xff08;顺手…

现代风格VUE3易支付用户控制中心

适用系统 彩虹易支付 技术栈 vitevue3elementuiplusphp 亮点 独立前端代码,扩展开发,不改动系统文件,不影响原版升级 支持功能订制 界面预览

go语言zero框架中启动脚本.sh的编写与配置

在Go语言项目中&#xff0c;编写启动脚本 (.sh 文件) 通常用于自动化启动Go程序、配置环境变量、执行一些初始化任务或处理不同环境下的配置。下面是编写和配置启动脚本的步骤。 ### 1. 基本的 .sh 启动脚本 假设你已经在 Go 中编写了应用程序并编译为二进制文件&#xff0c;启…

React,Antd实现文本输入框话题添加及删除的完整功能解决方案

最终效果就是实现该输入框&#xff1a; 添加话题时&#xff0c;话题自动插入到输入框前面多文本输入框左侧间距为话题的宽度多行文本时&#xff0c;第二行紧接开头渲染删除文本时&#xff0c;如果删除到话题&#xff0c;再次删除&#xff0c;话题被删除 首先构造div结构 cons…

坑人 C# MySql.Data SDK

一:背景 1. 讲故事 为什么说这东西比较坑人呢?是因为最近一个月接到了两个dump,都反应程序卡死无响应,最后分析下来是因为线程饥饿导致,那什么原因导致的线程饥饿呢?进一步分析发现罪魁祸首是 MySql.Data,这就让人无语了,并且反馈都是升级了MySql.Data驱动引发,接下…

(五)FT2232HL高速调试器之--三步实现STM32的VSCODE在线仿真工程搭建

对于单片机开发&#xff0c;rtthread studios 与 vscode&#xff0c;鱼与熊掌可以兼得否&#xff0c;其实是可以的&#xff0c;下面通过三个步骤&#xff0c;实现基于FT2232HL高速调试器的&#xff0c;stm32的VSCODE在线仿真工程的搭建。 1、软件下载与VSCODE插件安装配置 软…

【计算机网络】lab2 Ethernet(链路层Ethernet frame结构细节)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;计算机网络_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前言 2.…

鸿蒙项目云捐助第十讲鸿蒙App应用分类页面二级联动功能实现

鸿蒙项目云捐助第十讲鸿蒙App应用分类页面二级联动功能实现 在之前的教程中完成了分类页面的左右两侧的列表结构&#xff0c;如下图所示。 接下来需要实现左侧分类导航项的点击操作&#xff0c;可以友好的提示用户选择了哪一个文字分类导航项。 一、左侧文字分类导航的处理 …

数字IC后端零基础入门基础理论(Day1)

数字IC后端设计导入需要用到的input数据如下图所示。 数字后端零基础入门系列 | Innovus零基础LAB学习Day9 Netlist: 设计的Gate level&#xff08;门级&#xff09;网表。下图所示为一个计数器设计综合后的门级netlist。 从这个netlist中我们看到这个设计顶层的名字叫counte…

如何编译Opencv +ffmpeg linux 明明安装了ffmpeg但是opencv就是找不到

想要编译opencvffmpeg&#xff1a; 1.安装ffmpeg 随便位置&#xff08;具体看ffmpeg 编译安装&#xff09; 执行下面命令&#xff0c;其中/usr/local/ffmpeg/lib/pkgconfig是你实际的ffmpeg路径 export PKG_CONFIG_PATH$PKG_CONFIG_PATH:/usr/local/ffmpeg/lib/pkgconfig2.下载…

三维视频融合在数字孪生中的应用

一、在提升监控效率与决策准确性方面的应用 改善监控视角与理解&#xff1a;在数字孪生场景下&#xff0c;三维视频融合技术能够将监控视频与三维环境相结合。例如在城市安防、工业生产、大型活动等场景中&#xff0c;形成连续、实时、动态的三维全景拼接图。这解决了传统监控…

语音识别失败 chrome下获取浏览器录音功能,因为安全性问题,需要在localhost或127.0.0.1或https下才能获取权限

环境&#xff1a; Win10专业版 谷歌浏览器 版本 131.0.6778.140&#xff08;正式版本&#xff09; &#xff08;64 位&#xff09; 问题描述&#xff1a; 局域网web语音识别出现识别失败 chrome控制台出现下获取浏览器录音功能&#xff0c;因为安全性问题&#xff0c;需要在…