自命为缓存之王的Caffeine(6)

news2024/11/17 11:53:50

您好,我是湘王,这是我的CSDN博客,欢迎您来,欢迎您再来~


之前用Caffeine替代Redis的时候,发现先保存KV,再获取key,过期时间为3秒。但即使过了3秒,还是能获取到保存的数据。这是为什么呢?因为之前在整合SpringBoot时,使用的是注解方式,在配置文件中已经定死了Caffeine的过期时间。

## Caffeine

spring.cache.cache-names=test

spring.cache.type=caffeine

spring.cache.caffeine.spec=initialCapacity=50,maximumSize=500,expireAfterWrite=300s

就是因为这里的expireAfterWrite=300s导致数据3秒后不能清除。经过测试,发现果然是300秒后Caffeine过期。

使用注解式的Caffeine,应用一旦启动,是无法动态调整过期时间的,必然与MongoDB时间不同步。

进一步延伸思考:Caffeine是没有持久化功能的,所以当应用重新启动的时候,上一次为Caffeine设置的过期时间会被重置。因此Caffeine + MongoDB替代Redis存储Token其实需要解决一个很关键的问题:MongoDB和Caffeine过期时间的同步问题,也就是Caffeine的过期时间要能够灵活调整的问题。

所以,需要放弃注解式Caffeine,使用自定义LoadingCache。当MongoDB保存时,就要同步到Caffeine。而当应用重启时,就要重新同步Caffeine。

修改CacheDao,增加LoadingCache定义:

private static LoadingCache<String, String> loadingCache = null;

/**
 * 自定义LoadingCache,指定过期时间expiretime
 *
 */
private LoadingCache<String, String> initCache(long expiretime) {
    return Caffeine.newBuilder()
            .initialCapacity(1)
            .maximumSize(100)
            .expireAfterWrite(expiretime, TimeUnit.MILLISECONDS)
            .build(key -> {
                // 没有数据或过期时返回null
                return null;
            });
}

注意:时间单位是TimeUnit.MILLISECONDS,搞错了就看不到效果了。

修改saveObject()方法:

/**
 * 保存时,需要增加过期时间,方便同步到Caffeine
 * 
 * @param key
 * @param value
 * @param expiretime
 * @return
 */
public boolean saveObject(final String key, final String value, final long expiretime) {
    Query query = new Query(Criteria.where("key").is(key));
    Update update = new Update();
    long time = System.currentTimeMillis();
    update.set("key", key);
    update.set("value", value);
    update.set("time", time);
    try {
        UpdateResult result = mongoTemplate.upsert(query, update, Cache.class);
        if (result.wasAcknowledged()) {
            // 同步到Caffeie
            loadingCache = initCache(expiretime * 1000);
            loadingCache.put(key, value);
            return true;
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return false;
}

注意其中的同步到Caffeine那两行。

最后,修改getObject()——重点来了!这是最关键的一步,应用重启之后还能否和MongoDB保持时间同步,就在于它了:

// expiretime指的是从存储到失效之间的时间间隔,单位毫秒
public String getObject(final String key, final long expiretime) {
    String result = null;
    // loadingCache不为空说明之前已经同步过了,可以直接读取它的值
    if (null != loadingCache) {
        result = loadingCache.get(key);
        if (null != result) {
            // 读取到值时,直接返回,读取不到就去mongodb读取
            return result;
        }
    }
    Query query = new Query(Criteria.where("key").is(key));
    Cache cache = (Cache) mongoTemplate.findOne(query, Cache.class);
    System.out.println("getObject(" + key + ", " + expiretime + ") from mongo");

    if (null != cache) {
        // -1表示永不过期
        if (-1 == expiretime) {
            return cache.getValue();
        }
        // 如果当前时间 - 存储cache时的时间 >= 过期间隔
        long currentTtime = System.currentTimeMillis();
        if (currentTtime - cache.getTime() >= expiretime * 1000) {
            // 删除key,并返回null
            removeObject(key);
        } else {
            /**
             * 需要计算出当前时间与过期时间之间的差值,并赋予Caffeine的失效时间
             * 计算过程分析:
             * 保存时间:00:00
             * 当前时间:00:03
             * 过期时间:10秒
             * 那么第一次读取时需要将剩余的7秒赋给Caffeine
             */
            if (null == loadingCache) {// loadingCache==null说明loadingCache需要同步
                loadingCache = initCache(expiretime * 1000 - (currentTtime - cache.getTime()));
                loadingCache.put(key, cache.getValue());
            }
            return cache.getValue();
        }
    }
    return null;
}

由于保存时增加了过期时间,Service和Controller也要修改:

/**
 * 缓存Service接口
 * 
 * @author 湘王
 */
@Service
public class CacheService {
   @Autowired
   private CacheDao<Cache> cacheDao;

   public String getObject(final String key, final long expiretime) {
      return cacheDao.getObject(key, expiretime);
   }

   /**
    * 增加了过期时间expiretime
    *
    * @param key
    * @param value
    * @param expiretime
    * @return
    */
   public boolean saveObject(final String key, final String value, final long expiretime) {
      return cacheDao.saveObject(key, value, expiretime);
   }

   public boolean removeObject(final String key) {
      return cacheDao.removeObject(key);
   }
}

/**
 * Cache控制器
 *
 * 湘王
 */
@RestController
public class CacheController {
    @Autowired
    private CacheService cacheService;

    /**
     * 增加了过期时间expiretime
     *
     * @param key
     * @param value
     * @param expiretime
     */
    @GetMapping("/cache/save")
    public void save(final String key, final String value, final int expiretime) {
        cacheService.saveObject(key, value, expiretime);
    }

    // 获取数据,过期时间为秒(会转换为毫秒)
    @GetMapping("/cache/get")
    public String get(final String key, final int expiretime) {
        String result = cacheService.getObject(key, expiretime);
        if (null == result) {
            return "expire value";
        }
        return result;
    }
}

修改后测试:

1、启动应用,通过save()保存,再通过get()读取,有效;

2、启动应用,通过get()读取,读取不到值(因为未设置),有效;

3、启动应用,通过save()保存,停止服务并稍后重启(可以在过期时间内重启,也可以在过期时间外重启):

3.1、通过get()读取,如果是在有效期内,能够读取到值,有效;

3.2、通过get()读取,如果超过有效期,就读取不到值了,有效。


感谢您的大驾光临!咨询技术、产品、运营和管理相关问题,请关注后留言。欢迎骚扰,不胜荣幸~

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

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

相关文章

网络爬虫简介

前言 没什么可以讲的所以就介绍爬虫吧 介绍 网络爬虫&#xff08;英语&#xff1a;web crawler&#xff09;&#xff0c;也叫网路蜘蛛&#xff08;spider&#xff09;&#xff0c;是一种用来自动浏览万维网的网络机器人。其目的一般为编纂网络索引。 网路搜索引擎等站点通过…

Windows 环境下,cmake工程导入OpenCV库

目录 1、下载 OpenCV 库 2、配置环境变量 3、CmakeLists.txt 配置 1、下载 OpenCV 库 OpenCV官方下载地址&#xff1a;download | OpenCV 4.6.0 下载完毕后解压&#xff0c;便可以得到下面的文件 2、配置环境变量 我们需要添加两个环境变量&#xff0c;一个是 OpenCVConfi…

小红书达人选择,投放指南4注意!

为什么达人投放后反响平平&#xff0c;别的品牌为什么曝光这么高&#xff0c;我投放的钱是打水漂了吗&#xff1f;怎么能达到高曝光投放&#xff1f;今天就跟大家来聊聊如何让小红书达人投放达到高曝光&#xff1f;随着消费升级&#xff0c;在这场营销中&#xff0c;平台日渐爆…

数据结构与算法】链表2:节点交换与删除 链表相交 环形链表

文章目录今日任务1.Leetcode24&#xff1a;两两交换链表中的节点&#xff08;1&#xff09;题目&#xff08;2&#xff09;思路&#xff08;3&#xff09;代码实现2.Leetcode19&#xff1a;删除链表的倒数第N个节点&#xff08;1&#xff09;题目&#xff08;2&#xff09;思路…

机试_6_数据结构(二)

本文介绍机试中考查的一些非线性数据结构&#xff0c;包括二叉树、二叉排序树、优先队列和散列表等较为高级的数据结构。 一、二叉树 树的结构有诸多变体&#xff0c;它们在各种应用中发挥着重要作用。 作为树的特例的二叉树(Binary Tree)&#xff0c;虽然看似简单&#xff0…

Ubuntu安装boost库

参考链接&#xff1a;https://blog.csdn.net/zeye5731/article/details/122413193 1、下载 boost库 boost 库各大版本下载&#xff1a;boost download | hisroy versions 下面就以安装 1.78.0的版本为例 2、安装boost库 我们将下载好的boost库上传到Ubuntu&#xff0c;并解…

C++设计模式(16)——责任链模式

亦称&#xff1a; 职责链模式、命令链、CoR、Chain of Command、Chain of Responsibility 意图 责任链模式是一种行为设计模式&#xff0c; 允许你将请求沿着处理者链进行发送。 收到请求后&#xff0c; 每个处理者均可对请求进行处理&#xff0c; 或将其传递给链上的下个处理…

关闭终端后在服务器上运行代码+将终端输出打印到文件中

解决方案 首先打开一个screen # name是你想给你的项目在screen中起的名字 screen -S name 然后&#xff0c;在你的运行命令后加入 | tee xxxx.txt&#xff0c;如 python run_mujoco.py --envWalker2d-v2 --tradeoff0.2 | tee Walker2d-v2.txt 这样就可以实现题目中的目的了…

华测导航GPCHC协议ROS驱动包,CGI610、410接收机,NavSatStatus、GPSFix和普通格式

目录一、消息类型1.1 sensor_msgs/NavSatFix1.2 sensor_msgs/NavSatStatus1.3 gps_common::GPSFix1.4 sensor_msgs::Imu二、部分源码2.1 相关的依赖和库2.2 文件结构2.3 字段分割函数2.4 定义消息话题Ubuntu 20.04 noetic 华测CGI 610——RS232-C——GPCHC 一、消息类型 1.1 …

从零编写linux0.11 - 第十一章 可执行文件

从零编写linux0.11 - 第十一章 可执行文件 编程环境&#xff1a;Ubuntu 20.04、gcc-9.4.0 代码仓库&#xff1a;https://gitee.com/AprilSloan/linux0.11-project linux0.11源码下载&#xff08;不能直接编译&#xff0c;需进行修改&#xff09; 本章目标 本章会加载并运行…

SpringCloud(PS)远程调用--Feign

远程调用RestTemplate远程调用RestTemplate方式调用存在的问题Http客户端Feign实现步骤自定义配置Feign优化Feign性能优化——连接池配置最佳实践RestTemplate远程调用 Bean // LoadBalancedpublic RestTemplate restTemplate(){return new RestTemplate();}Autowiredprivat…

linux基本功系列之fdisk命令实战

文章目录前言一. fdisk命令介绍二. 语法格式及常用选项三. 参考案例3.1 列出每个分区的大小3.2 分区操作3.2.1 添加硬盘3.2.2 开启虚拟机并分区3.3.3 分区完成后进行格式化挂载四 . 设置分区自动挂载前言 大家好&#xff0c;又见面了&#xff0c;我是沐风晓月&#xff0c;本文…

Elasticsearch7学习笔记(尚硅谷)

文章目录一、ElasticSearch概述1、ElasticSearch是什么2、全文搜索引擎3、ElasticSearch 和 Solr3.1 概述3.2 比较总结二、Elasticsearch入门1、Elasticsearch安装1.1 下载使用1.2 数据格式2、索引操作3、文档操作&#xff08;了解&#xff09;3.1 创建文档3.2 文档查询3.3 文档…

外贸谷歌优化,外贸google SEO优化费用是多少?

本文主要分享关于做外贸网站的谷歌seo成本到底需要投入多少这一件事。 本文由光算创作&#xff0c;有可能会被剽窃和修改&#xff0c;我们佛系对待这种行为吧。 那么外贸google SEO优化费用是多少&#xff1f; 答案是&#xff1a;2w~25w。 好&#xff0c;看到这个答案先别激…

0.1opencv库VS环境配置

opencv环境配置 感谢大家学习这门教程。本系列文章首发于公众号【周旋机器视觉】。 这个这门课程的第一篇文章&#xff0c;主要是opencv环境配置。 本教程的环境为 Visual Studio 2019CMake 3.22.3opencv 4.6.0windows 10 1、opencv的源码下载与安装 直接访问opencv官网&…

Docker入门教程

文章目录一、Docker概述1. 什么是容器技术&#xff1f;2. 什么是Docker3. 为什么要使用Docker4. Docker和虚拟机的对比5. Docker相关概念6. DockerHub7. Docker架构二、安装Docker1. 安装Docker2. 配置阿里云镜像加速三、Docker常用命令1. 帮助命令2. 镜像操作命令3. 容器操作命…

mysql 8.0 忘记root密码-linux

vim /etc/my.cnf 在[mysqld]最后加上如下语句&#xff1a;skip-grant-tables 并保存退出有的配置是分开的&#xff0c;/etc/my.cnf.d/mysql-server.cnf重启mysql服务 : service mysqld restart免密码登陆: mysql -u root -ppassword校验直接回车select host, user, authenticat…

C#:Krypton控件使用方法详解(第七讲) ——kryptonHeader

今天介绍的Krypton控件中的kryptonHeader&#xff0c;下面开始介绍这个控件的属性&#xff1a;控件的样子如上图所示&#xff0c;从上面控件外观来看&#xff0c;这个控件有三部分组成。第一部分是前面的图片&#xff0c;第二部分是kryptonHeader1文本&#xff0c;第三部分是控…

前端学习第一阶段-第7章 品优购电商项目

7-1 品优购项目介绍及准备工作 01-品优购项目导读 02-网站制作流程 03-品优购项目规划 04-品优购项目搭建 05-品优购项目-样式的模块化开发 06-品优购项目-favicon图标制作 07-品优购项目-TDK三大标签SEO优化 7-2 首页Header区域实现 08-品优购首页-快捷导航shortcut结构搭建 0…

MySQL(三):切分,主从复制,读写分离

文章目录一、切分水平切分垂直切分水平切分策略二、主从复制三、读写分离一、切分 水平切分 水平切分又称为sharding,它是将同一个表中的记录拆分到多个结构相同的表中。当一个表的数据不断增多的时候&#xff0c;sharding是必然的选择&#xff0c;它可以将数据分布到集群的不…