性能优化2.0,新增缓存后,程序的秒开率不升反降

在这里插入图片描述

目录

    • 一、前情提要
      • 经过4次优化,将页面的加载时间控制在了1秒以内,实打实的提升了程序的秒开率。
    • 二、先了解一下,什么是缓存
      • 1、缓存有哪些分类
      • 2、本地缓存与分布式缓存
    • 三、Guava Cache本地缓存
      • 1、Google Guava
      • 2、Loadingcache数据结构
      • 3、Loadingcache数据结构构建流程:
      • 4、判断缓存是否过期
      • 5、Loadingcache如何解决缓存穿透
        • (1)expireAfterAcess和expireAfterWrite同步加载
        • (2)refreshAfterWrite同步加载
        • (3)refreshAfterWrite异步加载
    • 四、Redis中如何解决缓存穿透
    • 五、使用loadingCache优化页面加载
      • 1、引入pom
      • 2、初始化LoadingCache
      • 3、优化5:通过LoadingCache缓存模板数据,在编辑模板后,更新缓存

大家好,我是哪吒。

一、前情提要

在上一篇文章中提到,有一个页面加载速度很慢,是通过缓冲流优化的。

在这里插入图片描述

查询的时候,会访问后台数据库,查询前20条数据,按道理来说,这应该很快才对。

追踪代码,看看啥问题,最后发现问题有三:

  1. 表中有一个BLOB大字段,存储着一个PDF模板,也就是上图中的运费模板;
  2. 查询后会将这个PDF模板存储到本地磁盘
  3. 点击线上显示,会读取本地的PDF模板,通过socket传到服务器。

大字段批量查询、批量文件落地、读取大文件并进行网络传输,不慢才怪,这一顿骚操作,5秒能加载完毕,已经烧高香了。

在这里插入图片描述

经过4次优化,将页面的加载时间控制在了1秒以内,实打实的提升了程序的秒开率。

  1. 批量查询时,不查询BLOB大字段;
  2. 点击运费查询时,单独查询+触发索引,实现“懒加载”;
  3. 异步存储文件
  4. 通过 缓冲流 -> 内存映射技术mmap -> sendFile零拷贝 读取本地文件;

在这里插入图片描述

有一个小伙伴在评论中提到,还可以通过缓存继续优化,确实是可以的,缓存也是复用优化的一种。

为了提高页面的加载速度,使用了单条查询 + 触发索引,提高数据库查询速度。

归根结底,还是查询了数据库,如果不查呢,访问速度肯定会更快。

这个时候,就用到了缓存,将运费模板存到缓存中。

二、先了解一下,什么是缓存

缓存就是把访问量较高的热点数据从传统的关系型数据库中加载到内存中,当用户再次访问热点数据时,是从内存中加载,减少了对数据库的访问量,解决了高并发场景下容易造成数据库宕机的问题。

我理解的缓存的本质就是一个用空间换时间的一个思想。

提供“缓存”的目的是为了让数据访问的速度适应CPU的处理速度,其基于的原理是内存中“局部性原理”。

CPU 缓存的是内存数据,用于解决 CPU 处理速度和内存不匹配的问题,比如处理器和内存之间的高速缓存,操作系统在内存管理上,针对虚拟内存 为页表项使用了一特殊的高速缓存TLB转换检测缓冲区,因为每个虚拟内存访问会引起两次物理访问,一次取相关的页表项,一次取数据,TLB引入来加速虚拟地址到物理地址的转换。

1、缓存有哪些分类

  1. 操作系统磁盘缓存,减少磁盘机械操作
  2. 数据库缓存,减少文件系统 I/O
  3. 应用程序缓存,减少对数据库的查询
  4. Web 服务器缓存,减少应用程序服务器请求
  5. 客户端浏览器缓存,减少对网站的访问

2、本地缓存与分布式缓存

本地缓存:在客户端本地的物理内存中划出一部分空间,来缓存客户端回写到服务器的数据。当本地回写缓存达到缓存阈值时,将数据写入到服务器中。

本地缓存是指程序级别的缓存组件,它的特点是本地缓存和应用程序会运行在同一个进程中,所以本地缓存的操作会非常快,因为在同一个进程内也意味着不会有网络上的延迟和开销。

本地缓存适用于单节点非集群的应用场景,它的优点是快,缺点是多程序无法共享缓存。

无法共享缓存可能会造成系统资源的浪费,每个系统都单独维护了一份属于自己的缓存,而同一份缓存有可能被多个系统单独进行存储,从而浪费了系统资源。

分布式缓存是指将应用系统和缓存组件进行分离的缓存机制,这样多个应用系统就可以共享一套缓存数据了,它的特点是共享缓存服务和可集群部署,为缓存系统提供了高可用的运行环境,以及缓存共享的程序运行机制。

下面介绍一个小编最常用的本地缓存 Guava Cache。

三、Guava Cache本地缓存

1、Google Guava

Google Guava是一个Java编程库,其中包含了许多高质量的工具类和方法。其中,Guava的缓存工具之一是LoadingCache。LoadingCache是一个带有自动加载功能的缓存,可以自动加载缓存中不存在的数据。其实质是一个键值对(Key-Value Pair)的缓存,可以使用键来获取相应的值。

Guava Cache 的架构设计灵感来源于 ConcurrentHashMap,它使用了多个 segments 方式的细粒度锁,在保证线程安全的同时,支持了高并发的使用场景。Guava Cache 类似于 Map 集合的方式对键值对进行操作,只不过多了过期淘汰等处理逻辑。

Guava Cache对比ConcurrentHashMap优势在哪?

  1. Guava Cache可以设置过期时间,提供数据过多时的淘汰机制;
  2. 线程安全,支持并发读写;
  3. 在缓存击穿时,GuavaCache 可以使用 CacheLoader 的load 方法控制,对同一个key,只允许一个请求去读源并回填缓存,其他请求阻塞等待;

2、Loadingcache数据结构

在这里插入图片描述

  1. Loadingcache含有多个Segment,每一个Segment中有若干个有效队列;
  2. 多个Segment之间互不打扰,可以并发执行;
  3. 各个Segment的扩容只需要扩自己,与其它的Segment无关;
  4. 设置合适的初始化容量与并发水平参数,可以有效避免扩容,但是设置的太大了,耗费内存,设置的太小,缓存价值降低,需要根据业务需求进行权衡;
  5. Loadingcache数据结构和ConcurrentHashMap很相似,ReferenceEntry用于存放key-value;
  6. 每一个ReferenceEntry都会存放一个双向链表,采用的是Entry替换的方式;
  7. 每次访问某个元素就将元素移动到链表头部,这样链表尾部的元素就是最近最少使用的元素,替换的复杂度为o(1),但是访问的复杂度还是O(n);
  8. 队列用于实现LRU缓存回收算法;

3、Loadingcache数据结构构建流程:

  1. 初始化CacheBuilder,指定参数(并发级别、过期时间、初始容量、缓存最大容量),使用build()方法创建LocalCache实例;
  2. 创建Segment数组,初始化每一个Segment;
  3. 为Segment属性赋值;
  4. 初始化Segment中的table,即一个ReferenceEntry数组(每一个key-value就是一个ReferenceEntry);
  5. 根据之前类变量的赋值情况,创建相应队列,用于LRU缓存回收算法。

4、判断缓存是否过期

  1. expireAfterWrite,在put时更新缓存时间戳,在get时如果发现当前时间与时间戳的差值大于过期时间戳,就会进行load操作;
  2. expireAfterAccess,在expireAfterWrite的基础上,不管是写还是读都会记录新的时间戳;
  3. refreshAfterWrite,调用get进行值的获取的时候才会执行reload操作,这里的刷新操作可以通过异步调用load实现。

5、Loadingcache如何解决缓存穿透

缓存穿透是指在Loadingcache缓存中,由于某些原因,缓存的数据无法被正常访问或处理,导致缓存失去了它的作用。

发生缓存穿透的原因有很多,比如数据量过大、数据更新频繁、数据过期、数据权限限制、缓存性能瓶颈等原因,这里不过多纠结。

(1)expireAfterAcess和expireAfterWrite同步加载

设置为expireAfterAcess和expireAfterWrite时,在进行get的过程中,缓存失效的话,会进行load操作,load是一个同步加载的操作,如下图:

如果发生了缓存穿透,当有大量并发请求访问缓存时,会有一个线程去同步查询DB,随即通过reeatrantLock进入loading等待状态,其它请求相同key的线程,一部分在waitforvalue,另一部分在reentantloack的阻塞队列中,等待同步查询完毕,所有请求都会获得最新值。

在这里插入图片描述

(2)refreshAfterWrite同步加载

如果采用refresh的话,会通过scheduleRefresh方法进行load,也是一个线程同步获取DB。

其它线程不会阻塞,性能比expireAfterWrite同步加载高,但是,可能返回新值、也可能返回旧值。

在这里插入图片描述

(3)refreshAfterWrite异步加载

当加载缓存的线程是异步加载的话,对于请求1,如果在异步结束前返回,就会返回旧值,反之是新值。

对于其他线程来说,不会被阻塞,直接返回,返回值可能是新值或者是旧值。
在这里插入图片描述

Loadingcache没使用额外的线程去做定时清理和加载的功能,而是依赖于get()请求。

在查询的时候,进行时间对比,如果使用refreshAfterWrite,在长时间没有查询时,查询有可能会得到一个旧值,我们可以通过设置refreshAfterWrite(写刷新,在get时可以同步或异步缓存的时间戳)为5s,将expireAfterWrite(写过期,在put时更新缓存的时间戳)设为10s,当访问频繁的时候,会在每5秒都进行refresh,而当超过10s没有访问,下一次访问必须load新值。

四、Redis中如何解决缓存穿透

如果发生了缓存穿透,可以针对要查询的数据,在Redis中插入一条数据,添加一个约定好的默认值,比如defaultNull。

比如你想通过某个id查询某某订单,Redis中没有,MySQL中也没有,此时,就可以在Redis中插入一条,存为defaultNull,下次再查询就有了,因为是提前约定好的,前端也明白是啥意思,一切OK,岁月静好。

在这里插入图片描述

五、使用loadingCache优化页面加载

1、引入pom

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.0.1-jre</version>
</dependency>

2、初始化LoadingCache

private static LoadingCache<String, String> loadCache;

public static void initLoadingCache() {
    loadCache = CacheBuilder.newBuilder()
            // 并发级别设置为 10,是指可以同时写缓存的线程数
            .concurrencyLevel(10)
            // 写刷新,在get时可以同步或异步缓存的时间戳
            .refreshAfterWrite(5, TimeUnit.SECONDS)
            // 写过期,在put时更新缓存的时间戳
            .expireAfterWrite(10, TimeUnit.SECONDS)
            // 设置缓存容器的初始容量为 10
            .initialCapacity(10)
            // 设置缓存最大容量为 100,超过之后就会按照 LRU 算法移除缓存项
            .maximumSize(100)
            // 设置要统计缓存的命中率
            .recordStats()
            // 指定 CacheLoader,缓存不存在时,可自动加载缓存
            .build(new CacheLoader<String, String>() {
                        @Override
                        public String load(String key) throws Exception {
                            // 自动加载缓存的业务
                            return "error";
                        }
                    }
            );
}

3、优化5:通过LoadingCache缓存模板数据,在编辑模板后,更新缓存

查询模板信息后,通过loadCache.put(uuid, pdf);加载到内存中,在编辑模板时,更新缓存过期时间,下次获取模板PDF时,直接从LoadingCache缓存中取,降低数据库访问压力,perfect!!!
在这里插入图片描述

然并卵,这种情况是不适合缓存的,因为模板pdf本来就是一个BLOB大数据,你把它放内存里缓存了,你告诉我,能放几个?内存扛得住吗?

优化炫技一时爽,BUG不断一直爽,一直爽


🏆文章收录于:100天精通Java从入门到就业

全网最细Java零基础手把手入门教程,系列课程包括:Java基础、Java8新特性、Java集合、高并发、性能优化等,适合零基础和进阶提升的同学。

🏆哪吒多年工作总结:Java学习路线总结,搬砖工逆袭Java架构师

华为OD机试 2023B卷题库疯狂收录中,刷题点这里

刷的越多,抽中的概率越大,每一题都有详细的答题思路、详细的代码注释、样例测试,发现新题目,随时更新,全天CSDN在线答疑。

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

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

相关文章

上海亚商投顾:创业板指冲高回落 光伏、航运股逆势走强

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 沪指1月12日冲高回落&#xff0c;创业板指午后跌近1%。北证50指数跌超6%&#xff0c;倍益康、华信永道、众诚科…

SpringBoot 入门教程

1.复习SSM项目中&#xff0c;用spring&#xff0c;mybatis,springmvc这三个框架整合的项目。 SSM项目的所有类&#xff0c;这是用SSM整合一个搜索书籍种类和呈现的前端和后端的ssm的小项目。 2.springboot如何去开发这个页面&#xff1a; 新建springboot项目&#xff0c;勾选对…

【Nuxt3】Nuxt3脚手架nuxi安装项目和项目目录介绍

简言 最近学了Nuxt3,并使用它创建了自己的小网站。记录下学习到的nuxt3内容。 Nuxt3官网 Nuxt 是一个免费的开源框架&#xff0c;可通过直观、可扩展的方式使用 Vue.js 创建类型安全、高性能、生产级的全栈 Web 应用程序和网站。 支持SSR、SPA、建立静态网站&#xff0c;也可以…

分布式限流的主流方案

本文已收录至我的个人网站&#xff1a;程序员波特&#xff0c;主要记录Java相关技术系列教程&#xff0c;共享电子书、Java学习路线、视频教程、简历模板和面试题等学习资源&#xff0c;让想要学习的你&#xff0c;不再迷茫。 常见的分布式限流方案 前面我们了解了什么是分布式…

【算法实验】实验1

实验1-1 斐波那契数 【问题描述】斐波那契数 &#xff08;通常用 F(n) 表示&#xff09;形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始&#xff0c;后面的每一项数字都是前面两项数字的和。 定义&#xff1a;F(0) 0, F(1) 1, F(n) F(n-1) F(n-2) 其中n>1 要求计…

11.云原生存储之TIDB

云原生专栏大纲 文章目录 为什么使用TIDB后端视角运维视角基础架构视角 TiDB Operator 简介软件版本要求部署tidbTIDB工具helm常用命令TIDB学习推荐资料 为什么使用TIDB 从后端视角、运维视角和基础架构视角来看&#xff0c;使用 TiDB 作为数据库系统可以获得分布式架构、高可…

Unity 踩坑记录 项目启动时获取目标子UI的位置相同

检查是否使用了 LayoutGroup ui控件控制位置 因为项目刚启动的时候 控件还没有工作所以他们都挤在一个位置 延迟两秒钟获取 就可以获取到 子UI 的正确坐标位置

【HarmonyOS4.0】第七篇-ArkUI系统组件(二)

鸿蒙开发系统组件详细剖析 五、进度条组件 进度条也是UI开发最常用的组件之一&#xff0c;ArkUI开发框架提供了两种类型的进度条&#xff1a; Progress 和LoadingProgress &#xff0c;前者可以精准指定进度&#xff0c;后者表示正在加载的状态&#xff0c;我们接下来对它们分…

对Transformer的理解。

要理解Transformer&#xff0c;需要先理解注意力机制&#xff0c;下面大部分内容来自台大教授李宏毅老师讲课资料。 注意力机制 之前使用的MLP&#xff0c;CNN&#xff0c;RNN模型可以解决一些简单序列问题&#xff0c;但当序列长度太长容易失去效果&#xff0c;原因是看了新…

python 列表的高级应用

当前版本&#xff1a; Python 3.8.4 简介 列表&#xff08;list&#xff09;是Python编程语言中的基本数据类型之一&#xff0c;也是一个非常重要的通用序列。在其它编程语言中&#xff0c;它们通常被称为“数组”。可以存储多个元素&#xff0c;包括数字、字符串、甚至其他列…

python 字符串的详细处理方法

当前版本&#xff1a; Python 3.8.4 简介 字符串是由字符组成的序列&#xff0c;可以用单引号、双引号或三引号&#xff08;单引号或双引号的连续使用&#xff09;括起来。一般用来表示和处理文本信息&#xff0c;可以是字母、数字、标点符号以及其他特殊字符&#xff0c;用于…

力扣每日一练(24-1-14)

做过类似的题&#xff0c;一眼就是双指针&#xff0c;刚好也就是题解。 if not nums:return 0p1 0 for p2 in range(1, len(nums)):if nums[p2] ! nums[p1]:p1 1nums[p1] nums[p2]return p1 1 根据规律&#xff0c;重复的数字必定相连&#xff0c;那么只要下一个数字与上一…

WeNet2.0:提高端到端ASR的生产力

摘要 最近&#xff0c;我们提供了 WeNet [1]&#xff0c;这是一个面向生产&#xff08;工业生产环境需求&#xff09;的端到端语音识别工具包&#xff0c;在单个模型中&#xff0c;它引入了统一的两次two-pass (U2) 框架和内置运行时&#xff08;built-in runtime&#xff09;…

SpringCloud.04.熔断器Hystrix( Spring Cloud Alibaba 熔断(Sentinel))

目录 熔断器概述 使用Sentinel工具 什么是Sentinel 微服务集成Sentinel 配置provider文件&#xff0c;在里面加入有关控制台的配置 实现一个接口的限流 基本概念 重要功能 Sentinel规则 流控规则 简单配置 配置流控模式 配置流控效果 降级规则 SentinelResource…

Linux/Traverxec

Enumeration nmap 使用nmap快速扫描目标&#xff0c;发现对外开放了22和80&#xff0c;第一个问题就是问80端口运行的是什么服务&#xff0c;针对这两个端口扫描对应的详细信息后就能得到答案 Nostromo 从nmap的扫描结果可以看到&#xff0c;目标开启了80端口&#xff0c;且…

一二三应用开发平台文件处理设计与实现系列之5——MinIO技术预研

背景 上篇介绍了文件读写框架设计与实现&#xff0c;同时顺便说明了本地磁盘存储模式的实现模式。 今天来说下基于文件读写框架&#xff0c;如何集成对象存储组件minio&#xff0c;集成之前&#xff0c;需要对minio进行必要的了解&#xff0c;本篇是minio的技术预研。 minio简…

Python - 深夜数据结构与算法之 AVL 树 红黑树

目录 一.引言 二.高级树的简介 1.树 2.二叉树 3.二叉搜索树 4.平衡二叉树 三.AVL 树 ◆ 插入节点 ◆ 左旋 ◆ 右旋 ◆ 左右旋 ◆ 右左旋 ◆ 一般形式 ◆ 实际操作 ◆ 总结 四.红黑树 ◆ 概念 ◆ 示例 ◆ 对比 五.总结 一.引言 前面我们介绍了二叉树、二叉…

论文阅读 Self-Supervised Burst Super-Resolution

这是一篇 ICCV 2023 的文章&#xff0c;主要介绍的是用自监督的方式进行多帧超分的学习 Abstract 这篇文章介绍了一种基于自监督的学习方式来进行多帧超分的任务&#xff0c;这种方法只需要原始的带噪的低分辨率的图。它不需要利用模拟退化的方法来构造数据&#xff0c;而且模…

爬虫验证码分析

声明&#xff1a; 该文章为学习使用&#xff0c;严禁用于商业用途和非法用途&#xff0c;违者后果自负&#xff0c;由此产生的一切后果均与作者无关 本文只做简单的验证码分析&#xff0c;不涉及扣代码等逆向 一、常见得验证码平台 易盾&#xff1a;https://dun.163.com/pr…

重学Java 5 idea详细使用和运算符

慢点跑&#xff0c;前面的路不好走 ——24.1.14 一、IDEA的使用 1.idea的介绍 1.概述&#xff1a;开发工具 2.特点&#xff1a; a、idea是java写的&#xff0c;所以本地上必须有正确的jdk环境 b、idea自动保存 c、不用我们打开dos命令窗口执行javac和java命令 d、idea有强大的…