性能优化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>() {@Overridepublic 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在线答疑。

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

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

相关文章

C#--核心

CSharp核心知识点学习 学习内容有&#xff1a; 绪论&#xff1a;面向对象的概念 Lesson1&#xff1a;类和对象 练习&#xff1a; Lesson2&#xff1a;封装--成员变量和访问修饰符 练习: Lesson3:封装--成员方法 Lesson4&#xff1a;封装--构造函数和析构函数 知识点四 垃圾回收…

在 .NET 中使用可以漫游的 Web 凭据

Windows 凭据管理器是一个内置在 Windows 操作系统中的功能&#xff0c;为用户提供一种安全的方式来存储和管理凭据。本文主要介绍如何在 .NET 中使用可以漫游的 Web 凭据&#xff0c;以及使用中的基本事项。 1. 引言 在前面的文章《试用 Windows Terminal 中的 Terminal Chat…

FPGA时序分析与时序约束(四)——时序例外约束

目录 一、时序例外约束 1.1 为什么需要时序例外约束 1.2 时序例外约束分类 二、多周期约束 2.1 多周期约束语法 2.2 同频同相时钟的多周期约束 2.3 同频异相时钟的多周期约束 2.4 慢时钟域到快时钟域的多周期约束 2.5 快时钟域到慢时钟域的多周期约束 三、虚假路径约…

Redis实现延迟任务队列(一)

业务需求 业务里面需要文章的定时发布功能&#xff0c;因此打算采用mq和redis来实现一下定时发布的功能。mq之前用过了。基于一些私信交换机地信息过期策略实现。所以这次采用redis。并且打算将这个延迟任务的服务集成在一个微服务里面&#xff0c;提供对外的feign的远程调用接…

线性代数基础【4】线性方程组

第四章 线性方程组 一、线性方程组的基本概念与表达形式 二、线性方程组解的基本定理 定理1 设A为mXn矩阵,则 (1)齐次线性方程组AX0 只有零解的充分必要条件是r(A)n; (2)齐次线性方程组AX0 有非零解(或有无数个解)的充分必要条件是r(A)&#xff1c;n 推论1 设A为n阶矩阵,则…

GPT2 GPT3

what is prompt 综述1.Pre-train, Prompt, and Predict: A Systematic Survey of Prompting Methods in Natural Language Processing(五星好评) 综述2. Paradigm Shift in Natural Language Processing(四星推荐) 综述3. Pre-Trained Models: Past, Present and Future Pro…

网络安全的威胁PPT

建议的PPT免费模板网站&#xff1a;http://www.51pptmoban.com/ppt/ 此PPT模板下载地址&#xff1a;https://file.51pptmoban.com/d/file/2023/03/20/1ae84aa8a9b666d2103f19be20249b38.zip 内容截图&#xff1a;

day07打卡

day07打卡 454. 四数相加 II 时间复杂度&#xff1a;O(N)&#xff0c;空间复杂度&#xff1a;O(N) 第一想法&#xff1a;创建一个哈希表&#xff0c;存下nums[i] nums[j]&#xff0c;再遍历nums3和nums4得到nums[k]nums[l]&#xff0c;从哈希表中找0-nums[k] - nums[l]即可…

Linux命令之服务器的网络配置hostname,sysctl,ifconfig,service,ifdown,ifup,route,ping的使用

1、查看当前主机名称&#xff0c;编辑配置文件修改主机名为你姓名拼音的首字母&#xff08;如张三&#xff0c;则为zs&#xff09; 2、查看本机网卡IP地址&#xff0c;编辑/etc/sysconfig/network-scripts/ifcfg-ens33&#xff0c;要求在一块物理网卡上绑定2个IP地址&#xff0…

Invalid bound statement (not found)(xml文件创建问题)

目录 解决方法&#xff1a; 这边大致讲一下我的经历&#xff0c;不想看的直接点目录去解决方法 今天照着老师视频学习&#xff0c;中间老师在使用动态SQL时&#xff0c;直接复制了一份&#xff0c;我想这么简单的一个&#xff0c;我直接从网上找内容创建一个好了&#xff0c;…

vue前端开发自学,借助KeepAlive标签保持组件的存活

vue前端开发自学,借助KeepAlive标签保持组件的存活&#xff01;如果不想让组件在切换的时候&#xff0c;被默认操作&#xff08;卸载掉了&#xff09;。他们需要使用这个这个表情哦。 下面给大家看看代码情况。 <template><h3>ComA</h3><p>{{ messag…

【Linux基础】Linux对时配置

Linux对时配置 ntp配置文件ntp.conf解析&#xff1a; &#xff08;1&#xff09;配置上层server 利用 server 关键字设定上层 NTP 服务器&#xff0c;上层 NTP 服务器的设定方式为&#xff1a; server [IP or hostname] [prefer]在 server 后端可以接 IP 或主机名&#xff…

谷歌裁员千人,搅动硅谷!终身编程终结,我们何以苟活?

新年第一个月&#xff0c;硅谷爆发了新一轮裁员潮。在这波浪潮中&#xff0c;有消息称谷歌计划裁员千人&#xff0c;另有Meta、Unity、Discord等多家公司也陆续放出了裁员的消息。就当前的就业环境来说&#xff0c;技术人员似乎面临着极其严峻的考验。 过去的一年间&#xff0c…

Qt第二周周二作业

代码&#xff1a; widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent nullptr);~Widget();void paintEvent(…

【下云】旧笔记本实现私人服务器

背景&缘由&想法 背景&#xff1a; 自己是做Java的&#xff0c;做互联网或者说学计算机的都知道&#xff0c;近几年大环境太差&#xff0c;人却越来越多&#xff0c;造成行业越来越卷&#xff1b;针对Java来说&#xff0c;被迫要学习多方面的知识&#xff0c;工作拧螺…

第七在线荣获百灵奖 Buylink Awards 2023零售圈年度卓越服务商品牌

1月11日&#xff0c;由零售圈主办、20零售连锁协会协办、30零售行业媒体支持的中国零售圈大会暨2024未来零售跨年盛典在西安落下帷幕&#xff0c;在这个零售行业盛典中&#xff0c;第七在线凭借其高精尖产品和卓越的服务质量成功入选&#xff0c;并荣获了“百灵奖 Buylink Awar…

腾讯云优惠券怎样领取?附最新优惠券领取教程

腾讯云优惠券是腾讯云推出的一种优惠活动&#xff0c;通常包含代金券和折扣券两种形式&#xff0c;可以在购买腾讯云产品结算时抵扣部分费用或享受特定折扣&#xff0c;帮助用户降低购买腾讯云产品的成本。 一、腾讯云优惠券类型 1、代金券&#xff1a;代金券可以在购买腾讯云…

workflow源码解析:ThreadTask

1、使用程序&#xff0c;一个简单的加法运算程序 #include <iostream> #include <workflow/WFTaskFactory.h> #include <errno.h>// 直接定义thread_task三要素 // 一个典型的后端程序由三个部分组成&#xff0c;并且完全独立开发。即&#xff1a;程序协议算…

Python 循环结构之for循环结合range()函数使用技巧

在Python中for循环经常结合range()函数一起使用。 range()函数是一个内置函数&#xff0c;用于生成一个整数序列&#xff0c;通常与for循环结合使用。它的常见用法是生成一系列连续的整数&#xff0c;可以指定起始值、结束值和步长。 range()函数的语法如下&#xff1a; ran…

【.NET Core】C#预处理器指令

【.NET Core】C#预处理器指令 文章目录 【.NET Core】C#预处理器指令一、概述二、可为空上下文&#xff08;#nullable&#xff09;三、条件编译2.1 定义DEBUG是编译代码2.2 未定义MYTEST时&#xff0c;将编译以下代码 四、定义符号五、定义区域六、错误和警告信息 一、概述 预…