2022.1 版本是 WebStorm 今年的第一个重大更新,该版本现已正式发布,更新的重点内容包括改进了对 Next.js 的支持、与 Volta 完全集成、能够从 Markdown 文件运行命令,以及对 Docker 的更新等。
v2022.1 的具体更新内容如下:
- 框架和技术:
- 更好地支持 Next.js、与 Volta 完全集成,以及改进 Vue 和 Docker
- 重新设计了 Docker 在 Services 工具窗口中的 UI,并增加了对 Docker Registry HTTP API V2 的支持,以便与 Docker 1.6+ 一起使用
- 更好地支持 Next.js、与 Volta 完全集成,以及改进 Vue 和 Docker
- 编辑器:
- 能够从 Markdown 文件中运行命令、更新了 Markdown 编辑器的浮动工具栏
- JavaScript 和 TypeScript:
- 结构视图的变化、改进的 Join Lines,以及枚举的可配置高亮颜色
- 用户体验:
- 一个新的通知工具窗口、一个改进的调试器用户界面,一个更新的 Structural Search 和 Replace 对话框
- 版本控制:
- 改进了 Git Blame 的注释功能,更新了 Git 工具窗口中的提交详情窗格,并支持拉取请求注释中的建议修改
更多详情可查看:https://blog.jetbrains.com/webstorm/2022/04/webstorm-2022-1/
Qt 6.3 发布了,与往常一样,该版本包含许多新功能以及大量 Bug 修复:自 Qt 6.2 发布以来修复了用户报告的总共 1750 个错误。下面摘录较为重要的新功能作介绍:
Qt Quick 编译器
新的 Qt 快速编译器是 Qt 6.3 中引入的重要新功能之一。 新的 QML 编译器由两个工具组成,QML 类型编译器 ( qmltc ) 和 QML 脚本编译器 ( qmlsc )。
- QML 类型编译器将 QML 类型编译为 C++,显着加快了 QML 类型的实例化。
- QML 脚本编译器在有意义的地方将函数和绑定编译到 C++,在 QML 中评估函数和绑定时能显著提高性能。
Qt 快速编译器旨在尽可能将 QML 中的函数和绑定编译成 C++ 代码。由于 QML 是一种动态类型的语言,如果不能在编译时确定所有类型,那编译行为也将变得无意义。在这种情况下,编译器会退回到将方法编译成类似于旧的 qmlcachegen 的字节码。
Qt 快速编译器对可以编译为本机代码的绑定和函数实现了显著的性能改进,绑定的评估速度比没有编译器的情况快 20% 到 35%。
有关该 Qt Quick Compiler 的更多细节,可在这篇 Qt 博客中细阅。
Qt Quick 和 Qt Quick 控件
Qt 6.3 添加了几个新的 Qt 快速控件,其中一些控件以前作为独立组件提供,比如控制树状视图的 TreeView
控制日历、日程表的 Calendar :
如今这些组件已被集成到 Qt 标准控件中。此外还添加了 FolderDialog 和MessageDialog 两个新对话框。 Qt Quick 中文本组件(Text、TextEdit、TextArea、TextInput)的性能也得到改进,以前,将非常大的文档传递给文本控件,可能会占用大量内存并导致绘图性能下降。该问题已在 Qt 6.3 中修复,确保后端只呈现可见的部分文本。
Qt Quick 3D
Qt Quick 3D 也又一些新特性,比如对反射的新支持。该反射使用ReflectionProbe QML 素实现,它的探针定位在场景中,捕捉周围环境并将其保存在立方体贴图中,然后其他素可以使用该地图来显示反射。
有关反射功能的更多详细信息,请查看介绍该功能的博客文章。
- 此外,新的 ResourceLoader 素可以更好地控制 Qt Quick 3D 中的资源管理,允许预加载大型资源,例如网格或纹理。
- 粒子系统也获得了一些新功能,在此处的博客文章中了解更多信息。
Qt PDF
6.3 版本的 Qt PDF 也获得了一些性能改进,现在与 Qt 5.15 LTS 版本的性能一致。
另外,官方透露 Qt 6.4 版本计划为 PDF 模块整一些新功能,但未对新内容作任何介绍。
其他改进
此外,该版本还有大量细小变化,包含支持 QLocale 中的 ISO639-2 语言标签、在 QDate、QTime 和 QLocale 中将时间转换为字符串时的 AM/PM 说明符,更容易在 JSON 和 CBOR 之间转换、新的 QtFuture::whenAll() 和 whenAny() 方法,以及许多其他较小的改进。
对 Qt Widgets 进行了许多改进,重点是高分辨率显示、样式、样式表和项目视图的样式。
在构建系统方面, Qt 6.3 对 CMake 的支持有了很多改进,值得注意的是新函数 qt-generate-deploy-app-script(),它极大地简化了为不同平台上的应用程序生成部署脚本。
下一步计划
Qt 6.3 是朝着下一个版本 Qt 6.4 和 Qt 6 系列的下一个 LTS 版本 Qt 6.5 迈出的一大步。
我们对这些版本有一些很棒的计划,其中包括对 WebAssembly 的全面支持、QHttpServer、gRPC 支持、基于 FFmpeg 的 Qt Multimedia 跨平台后端、Qt Speech、Qt Location,以及 Windows 11 上更好的原生 Look&Feel 和 iOS 支持
可以在 Qt 6.3 新功能页面查看该版本所有新内容,在发行公告中进一步了解该版本的所有变更。
Filename SHA-256 Git-2.35.2-64-bit.exe 8d33512f097e79adf7910de630b3a4446b25fe258f6c3a21bdbde410ca Git-2.35.2-32-bit.exe 4cac3338d1b637c7574a8ee0bacfd030925ea0f2c6bebc9 PortableGit-2.35.2-64-bit.7z.exe f15eb0ba8866da555cb8090fbf5aa6862b2e57169d2aeb88ed9c50f59522c8fb PortableGit-2.35.2-32-bit.7z.exe bfd53ed5aedca967ff84b65f53bc42162cdbd82131 MinGit-2.35.2-64-bit.zip 61f0f2d9abd7d54fbb81b30519d4aad8be66268e67cfc9ddc5 MinGit-2.35.2-32-bit.zip ddaa82fbd757fb65e31b1a0b85c537add MinGit-2.35.2-busybox-64-bit.zip 0ac509d2d2aeb35ce31c5afbcf4ef93a4edd80cea01a MinGit-2.35.2-busybox-32-bit.zip e8b6300acfcbfd8588e5ec022e5f46b635a00a8d77d0de1a7fc11ab43 Git-2.35.2-64-bit.tar.bz2 92a7e9f76682b2fba0a6198a91ee78d3c1dfb46737d899e Git-2.35.2-32-bit.tar.bz2 3bf358ee0bd3add315dc90201c4d3325b3f4a6866ecaaf5e6fcd99cfe4129faf
IntelliJ IDEA 2022.1 正式发布了,该版本的重点功能是:引入了 Dependency Analyzer 以促进依赖关系管理和冲突解决,一个更新的 New Project 向导来优化新项目的启动过程,以及 Notifications 通知工具窗口,它提供了一种新的、简化的方式来接收来自 IDE 的通知。2022.1 版本还包括许多其他值得注意的改进,下面摘录部分新功能作介绍。
关键更新
-
依赖分析器 (Dependency Analyzer)
为了促进依赖管理和冲突解决,IntelliJ IDEA 实现了依赖分析器,它提供项目和子项目中使用的所有依赖项(包括传递性依赖项)的广泛信息。
这个新功能允许轻松检测冲突的依赖关系并解决问题,比如可以过滤掉相同的依赖项,并查看它们在不同库中的存在,还可以快速浏览依赖项,以正确构建配置。
-
增强的 新项目 向导
重新设计了新项目 向导界面,以简化创建新项目的过程。可以快速启动一个空项目;使用 Java、Kotlin、Groovy 和 JavaScript 的预配置选项;或者有更复杂的项目,请使用生成器。
-
新的通知 工具窗口
事件日志实例已替换为新的 通知 工具窗口,更清楚地突出重要和有用的建议和通知,并将它们组织在专用工具窗口中。有关更多详细信息,请参阅博客文章。
用户体验
-
新建项目向导中的 Maven Archetype 优化
作为新建项目向导的 UI 改造的一部分,IntelliJ IDEA 重新设计了 Maven Archetype 项目生成器,2022.1 版本在浏览原型时引入了“键入时搜索”功能,以及在模块创建期间管理原型目录的能力。
此外,还可以按原型输入所需的属性:
-
均匀拆分选项卡
IntelliJ IDEA 2022.1 可以在编辑器选项卡之间平均分配工作空间,使它们具有相同的宽度。
-
将 UML 图导出为其他格式
现在可以将 UML 图导出为 yEd .graphml、JGraph .drawio、Graphviz .dot、带位置的 Graphviz .dot、Mermaid .md、Plantuml 和 IntelliJ IDEA .uml 文件,使得它们与第三方工具兼容。
-
更新了 结构搜索和替换 对话框
重新设计了结构搜索和替换对话框,提供所有模板的列表,以便更轻松地在它们之间导航。
安全
-
包检查器插件
ntelliJ IDEA 2022.1 现在可以通过检查 Checkmarx SCA 数据库和国家漏洞数据库,来检测项目中使用的 Maven 和 Gradle 依赖项中的漏洞。
Java 支持
-
支持 Java 18
IntelliJ IDEA 2022.1 支持 2022 年 3 月发布的 Java 18 的新功能。IDE 现在支持代码片段、开关表达式的模式匹配更改等功能。有关详细信息,请参阅此博客文章。
-
Java反编译器
Java 反编译器现在与 Java 17 版本更兼容。它支持现代语言构造函数,例如密封类型和模式匹配,具有更好的字符串反编译切换功能,提供类型注释并检测公共常量。
-
更好的 JUnit 5 支持
添加了对 JUnit 5.7 中引入的新功能的支持,包括对、和注释的支持。
使用 try/catch 模板更新 Surround
更新后的带有 try/catch模板的 Surround 现在重新抛出包装到 RuntimeException 中的异常,而不是吞下它。
Kotlin 支持
IntelliJ IDEA 2022.1 支持 Kotlin 1.6.20,因此最新的 Kotlin 语言功能,例如支持并行编译、上下文接收器原型以及跨所有 Kotlin 目标的更好的代码共享,现在都可以在 IDE 中使用。在此博客文章中了解有关新 Kotlin 更新的更多 信息。
-
改进了 Kotlin 的 IDE 性能
优化了包索引,大大提高了 IDE 在执行代码完成、突出显示和与参考搜索等相关操作时的速度,在代码更改后发生的重新索引案例的数量和范围也有所减少。
框架和技术
Go 微服务支持
- 添加了对 Go 微服务的支持,提供 URL 路径引用、端点、Search Everywhere 和装订线图标等功能。使用这些功能需要在 IntelliJ IDEA Ultimate 中安装 Go 插件,目前仅适用于标准库函数。
- IntelliJ IDEA Ultimate 现在为 Go 文件中的 HTTP 方法和标头提供补全,每个端点旁边都会出现一个地球图标,如果单击它,IntelliJ IDEA Ultimate 将建议几个选项。
Spring Data Mongo 的代码洞察改进
引入了许多更新来改善使用 Spring Data MongoDB 的用户体验。IntelliJ IDEA 现在突出显示 JSON 查询,完成运算符和文档字段,并提供从映射实体到 数据库 工具窗口的导航。
更好地支持 .proto 文件
为 .proto 文件引入了一个新的意图操作:为未解析的消息引用添加了缺失的导入语句,添加缺少的导入语句后, IDE 将提供消息引用的补全建议。
gRPC 反射支持
- 当项目中存在描述 gRPC 服务的 .proto 文件时,IntelliJ IDEA Ultimate 现在为服务名称、方法名称和请求正文选项提供代码补全。更重要的是,现在可以通过装订线图标运行请求。
- 如果项目没有 .proto 文件,但服务器支持 gRPC 反射,你将能够运行请求,并完成服务器运行实例的服务和方法名称。
Kubernetes 支持
Kubernetes
编辑集群上的资源
现在可以从编辑器选项卡中修改从集群加载的资源。
kubectl 的自定义路径
如果 kubectl 不在标准位置,现在可以手动配置路径。
转发端口
该版本为 pod 添加了端口转发功能。要转发端口,可以使用工具栏上的图标或选择上下文菜单项。
服务视图中的 描述资源操作
“服务”视图中的所有资源都有一个新的“描述资源”操作,可以从上下文菜单中调用它或使用工具栏按钮。
支持集群中的事件
集群事件现在显示在 服务 视图的单独节点中,提供有关系统中最近事件的数据。
要查看特定 pod 的事件,请在其上面调用 Describe Resource 并在操作结果中 查找 Events 部分
支持
为 werf.yaml 和相关 Helm 模板文件 ( https://werf.io ) 引入了有限的编辑器支持,包括代码补全功能、检查和快速修复建议、重构/重命名 . Values.werf.image.*,以及一些字段的验证,如 boolean 和 int。
对 Helm 的导入子值支持
支持通过 import-values 设置导入子值,这些设置影响模板中内置对象的完成/导航。尚未提供对 import-values 字段的增强编辑器支持。
请注意,Kubernetes 功能仅适用于 IntelliJ IDEA Ultimate,并且需要安装插件。
构建工具
-
更新 Gradle 的进度条
为 Gradle 进程实现了一个确定的进度条,例如下载依赖项和导入工件,允许跟踪文件发生的情况并估计该过程何时完成。
此版本还包含大量更新项,详情可在更新公告中细阅。
关注公众号
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
关注公众号
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
一、缓存简介
1.1 什么是缓存
缓存就是数据交换的缓冲区。缓存的本质是一个内存 Hash。缓存是一种利用空间换时间的设计,其目标就是更快、更近:极大的提高。
-
将数据写入/读取速度更快的存储(设备);
-
将数据缓存到离应用最近的位置;
-
将数据缓存到离用户最近的位置。
缓存是用于存储数据的硬件或软件的组成部分,以使得后续更快访问相应的数据。缓存中的数据可能是提前计算好的结果、数据的副本等。典型的应用场景:有 cpu cache, 磁盘 cache 等。本文中提及到缓存主要是指互联网应用中所使用的缓存组件。
缓存命中率是缓存的重要度量指标,命中率越高越好。
缓存命中率 = 从缓存中读取次数 / 总读取次数
1.2 何时需要缓存
引入缓存,会增加系统的复杂度。所以,引入缓存前,需要先权衡是否值得,考量点如下:
-
CPU 开销 – 如果应用某个计算需要消耗大量 CPU,可以考虑缓存其计算结果。典型场景:复杂的、频繁调用的正则计算;分布式计算中间状态等。
-
IO 开销 – 如果数据库连接池比较繁忙,可以考虑缓存其查询结果。
在数据层引入缓存,有以下几个好处:
-
提升数据读取速度。
-
提升系统扩展能力,通过扩展缓存,提升系统承载能力。
-
降低存储成本,Cache+DB 的方式可以承担原有需要多台 DB 才能承担的请求量,节省机器成本。
1.3 缓存的基本原理
根据业务场景,通常缓存有以下几种使用方式:
-
懒汉式(读时触发):先查询 DB 里的数据, 然后把相关的数据写入 Cache。
-
饥饿式(写时触发):写入 DB 后, 然后把相关的数据也写入 Cache。
-
定期刷新:适合周期性的跑数据的任务,或者列表型的数据,而且不要求绝对实时性。
1.4 缓存淘汰策略
缓存淘汰的类型:
1)基于空间:设置缓存空间大小。
2)基于容量:设置缓存存储记录数。
3)基于时间
-
TTL(Time To Live,即存活期)缓存数据从创建到过期的时间。
-
TTI(Time To Idle,即空闲期)缓存数据多久没被访问的时间。
缓存淘汰算法:
1)FIFO:先进先出。在这种淘汰算法中,先进入缓存的会先被淘汰。这种可谓是最简单的了,但是会导致我们命中率很低。试想一下我们如果有个访问频率很高的数据是所有数据第一个访问的,而那些不是很高的是后面再访问的,那这样就会把我们的首个数据但是他的访问频率很高给挤出。
2)LRU:最近最少使用算法。在这种算法中避免了上面的问题,每次访问数据都会将其放在我们的队尾,如果需要淘汰数据,就只需要淘汰队首即可。但是这个依然有个问题,如果有个数据在 1 个小时的前 59 分钟访问了 1 万次(可见这是个热点数据),再后一分钟没有访问这个数据,但是有其他的数据访问,就导致了我们这个热点数据被淘汰。
3)LFU:最近最少频率使用。在这种算法中又对上面进行了优化,利用额外的空间记录每个数据的使用频率,然后选出频率最低进行淘汰。这样就避免了 LRU 不能处理时间段的问题。
这三种缓存淘汰算法,实现复杂度一个比一个高,同样的命中率也是一个比一个好。而我们一般来说选择的方案居中即可,即实现成本不是太高,而命中率也还行的 LRU。
二、缓存的分类
缓存从部署角度,可以分为客户端缓存和服务端缓存。
客户端缓存
-
HTTP 缓存
-
浏览器缓存
-
APP 缓存(1、Android 2、IOS)
服务端缓存
-
CDN 缓存:存放 HTML、CSS、JS 等静态资源。
-
反向代理缓存:动静分离,只缓存用户请求的静态资源。
-
数据库缓存:数据库(如 MySQL)自身一般也有缓存,但因为命中率和更新频率问题,不推荐使用。
-
进程内缓存:缓存应用字典等常用数据。
-
分布式缓存:缓存数据库中的热点数据。
其中,CDN 缓存、反向代理缓存、数据库缓存一般由专职人员维护(运维、DBA)。后端开发一般聚焦于进程内缓存、分布式缓存。
2.1 HTTP 缓存
2.2 CDN 缓存
CDN 将数据缓存到离用户物理距离最近的服务器,使得用户可以就近获取请求内容。CDN 一般缓存静态资源文件(页面,脚本,图片,视频,文件等)。
国内网络异常复杂,跨运营商的网络访问会很慢。为了解决跨运营商或各地用户访问问题,可以在重要的城市,部署 CDN 应用。使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。
图片引用自:Why use a CDN
2.1.1 CDN 原理
CDN 的基本原理是广泛采用各种缓存服务器,将这些缓存服务器分布到用户访问相对集中的地区或网络中,在用户访问网站时,利用全局负载技术将用户的访问指向距离最近的工作正常的缓存服务器上,由缓存服务器直接响应用户请求。
1)未部署 CDN 应用前的网络路径:
-
请求:本机网络(局域网)=> 运营商网络 => 应用服务器机房
-
响应:应用服务器机房 => 运营商网络 => 本机网络(局域网)
在不考虑复杂网络的情况下,从请求到响应需要经过 3 个节点,6 个步骤完成一次用户访问操作。
2)部署 CDN 应用后网络路径:
-
请求:本机网络(局域网) => 运营商网络
-
响应:运营商网络 => 本机网络(局域网)
WebStorm激活2022.1
在不考虑复杂网络的情况下,从请求到响应需要经过 2 个节点,2 个步骤完成一次用户访问操作。与不部署 CDN 服务相比,减少了 1 个节点,4 个步骤的访问。极大的提高了系统的响应速度。
2.1.2 CDN 特点
优点
-
本地 Cache 加速:提升访问速度,尤其含有大量图片和静态页面站点;
-
实现跨运营商的网络加速:消除了不同运营商之间互联的瓶颈造成的影响,实现了跨运营商的网络加速,保证不同网络中的用户都能得到良好的访问质量;
-
远程加速:远程访问用户根据 DNS 负载均衡技术智能自动选择 Cache 服务器,选择最快的 Cache 服务器,加快远程访问的速度;
-
带宽优化:自动生成服务器的远程 Mirror(镜像)cache 服务器,远程用户访问时从 cache 服务器上读取数据,减少远程访问的带宽、分担网络流量、减轻原站点 WEB 服务器负载等功能。
-
集群抗攻击:广泛分布的 CDN 节点加上节点之间的智能冗余机制,可以有效地预防黑客入侵以及降低各种 D.D.o.S 攻击对网站的影响,同时保证较好的服务质量。
缺点
- 不适宜缓存动态资源
解决方案:主要缓存静态资源,动态资源建立多级缓存或准实时同步;
- 存在数据的一致性问题
1.解决方案(主要是在性能和数据一致性二者间寻找一个平衡)。
2.设置缓存失效时间(1 个小时,过期后同步数据)。
3.针对资源设置版本号。
2.2 反向代理缓存
反向代理(Reverse Proxy)方式是指以代理服务器来接受 internet 上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给 internet 上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。
2.2.1 反向代理缓存原理
反向代理位于应用服务器同一网络,处理所有对 WEB 服务器的请求。反向代理缓存的原理:
-
如果用户请求的页面在代理服务器上有缓存的话,代理服务器直接将缓存内容发送给用户。
-
如果没有缓存则先向 WEB 服务器发出请求,取回数据,本地缓存后再发送给用户。
这种方式通过降低向 WEB 服务器的请求数,从而降低了 WEB 服务器的负载。
反向代理缓存一般针对的是静态资源,而将动态资源请求转发到应用服务器处理。常用的缓存应用服务器有 Varnish,Ngnix,Squid。
2.2.2 反向代理缓存比较
常用的代理缓存有 Varnish,Squid,Ngnix,简单比较如下:
-
Varnish 和 Squid 是专业的 cache 服务,Ngnix 需要第三方模块支持;
-
Varnish 采用内存型缓存,避免了频繁在内存、磁盘中交换文件,性能比 Squid 高;
-
Varnish 由于是内存 cache,所以对小文件如 css、js、小图片的支持很棒,后端的持久化缓存可以采用的是 Squid 或 ATS;
-
Squid 功能全而大,适合于各种静态的文件缓存,一般会在前端挂一个 HAProxy 或 Ngnix 做负载均衡跑多个实例;
-
Nginx 采用第三方模块 ncache 做的缓冲,性能基本达到 Varnish,一般作为反向代理使用,可以实现简单的缓存。
三、进程内缓存
进程内缓存是指应用内部的缓存,标准的分布式系统,一般有多级缓存构成。本地缓存是离应用最近的缓存,一般可以将数据缓存到硬盘或内存。
-
硬盘缓存:将数据缓存到硬盘中,读取时从硬盘读取。原理是直接读取本机文件,减少了网络传输消耗,比通过网络读取数据库速度更快。可以应用在对速度要求不是很高,但需要大量缓存存储的场景。
-
内存缓存:直接将数据存储到本机内存中,通过程序直接维护缓存对象,是访问速度最快的方式。
常见的本地缓存实现方案:HashMap、Guava Cache、Caffeine、Ehcache。
3.1 ConcurrentHashMap
最简单的进程内缓存可以通过 JDK 自带的 HashMap 或 ConcurrentHashMap 实现。
-
适用场景:不需要淘汰的缓存数据。
-
缺点:无法进行缓存淘汰,内存会无限制的增长。
3.2 LRUHashMap
可以通过继承 LinkedHashMap 来实现一个简单的 LRUHashMap。重写 removeEldestEntry 方法,即可完成一个简单的最近最少使用算法。
缺点:
-
锁竞争严重,性能比较低。
-
不支持过期时间。
-
不支持自动刷新。
3.3 Guava Cache
解决了LRUHashMap 中的几个缺点。Guava Cache 采用了类似 ConcurrentHashMap 的思想,分段加锁,减少锁竞争。
Guava Cache 对于过期的 Entry 并没有马上过期(也就是并没有后台线程一直在扫),而是通过进行读写操作的时候进行过期处理,这样做的好处是避免后台线程扫描的时候进行全局加锁。直接通过查询,判断其是否满足刷新条件,进行刷新。
3.4 Caffeine
Caffeine 实现了 W-TinyLFU(LFU + LRU 算法的变种),其命中率和读写吞吐量大大优于 Guava Cache。其实现原理较复杂,可以参考你应该知道的缓存进化史。
3.5 Ehcache
EhCache 是一个纯 Java 的进程内缓存框架,具有快速、精干等特点,是 Hibernate 中默认的 CacheProvider。
优点
-
快速、简单;
-
支持多种缓存策略:LRU、LFU、FIFO 淘汰算法;
-
缓存数据有两级:内存和磁盘,因此无需担心容量问题;
-
缓存数据会在虚拟机重启的过程中写入磁盘;
-
可以通过 RMI、可插入 API 等方式进行分布式缓存;
-
具有缓存和缓存管理器的侦听接口;
-
支持多缓存管理器实例,以及一个实例的多个缓存区域;
-
提供 Hibernate 的缓存实现。
缺点
-
使用磁盘 Cache 的时候非常占用磁盘空间;
-
不保证数据的安全;
-
虽然支持分布式缓存,但效率不高(通过组播方式,在不同节点之间同步数据)。
3.6 进程内缓存对比
常用进程内缓存技术对比:
-
ConcurrentHashMap:比较适合缓存比较固定不变的素,且缓存的数量较小的。虽然从上面表格中比起来有点逊色,但是其由于是 JDK 自带的类,在各种框架中依然有大量的使用,比如我们可以用来缓存我们反射的 Method,Field 等等;也可以缓存一些链接,防止其重复建立。在 Caffeine 中也是使用的 ConcurrentHashMap 来存储素。
-
LRUMap:如果不想引入第三方包,又想使用淘汰算法淘汰数据,可以使用这个。
-
Ehcache:由于其 jar 包很大,较重量级。对于需要持久化和集群的一些功能的,可以选择 Ehcache。需要注意的是,虽然 Ehcache 也支持分布式缓存,但是由于其节点间通信方式为 rmi,表现不如 Redis,所以一般不建议用它来作为分布式缓存。
-
Guava Cache:Guava 这个 jar 包在很多 Java 应用程序中都有大量的引入,所以很多时候其实是直接用就好了,并且其本身是轻量级的而且功能较为丰富,在不了解 Caffeine 的情况下可以选择 Guava Cache。
-
Caffeine:其在命中率,读写性能上都比 Guava Cache 好很多,并且其 API 和 Guava cache 基本一致,甚至会多一点。在真实环境中使用 Caffeine,取得过不错的效果。
总结一下:如果不需要淘汰算法则选择 ConcurrentHashMap,如果需要淘汰算法和一些丰富的 API,推荐选择。
四、分布式缓存
分布式缓存解决了进程内缓存最大的问题:如果应用是分布式系统,节点之间无法共享彼此的进程内缓存。分布式缓存的应用场景:
-
缓存经过复杂计算得到的数据。
-
缓存系统中频繁访问的热点数据,减轻数据库压力。
不同分布式缓存的实现原理往往有比较大的差异。本文主要针对 Memcached 和 Redis 进行说明。
4.1 Memcached
Memcached 是一个高性能,分布式内存对象缓存系统,通过在内存里维护一个统一的巨大的 Hash 表,它能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。
简单的说就是:将数据缓存到内存中,然后从内存中读取,从而大大提高读取速度。
4.1.1 Memcached 特性
-
使用物理内存作为缓存区,可独立运行在服务器上。每个进程最大 2G,如果想缓存更多的数据,可以开辟更多的 Memcached 进程(不同端口)或者使用分布式 Memcached 进行缓存,将数据缓存到不同的物理机或者虚拟机上。
-
使用 key-value 的方式来存储数据。这是一种单索引的结构化数据组织形式,可使数据项查询时间复杂度为 O(1)。
-
协议简单,基于文本行的协议。直接通过 telnet 在 Memcached 服务器上可进行存取数据操作,简单,方便多种缓存参考此协议;
-
基于 libevent 高性能通信。Libevent 是一套利用 C 开发的程序库,它将 BSD 系统的 kqueue,Linux 系统的 epoll 等事件处理功能封装成一个接口,与传统的 select 相比,提高了性能。
-
分布式能力取决于 Memcached 客户端,服务器之间互不通信。各个 Memcached 服务器之间互不通信,各自独立存取数据,不共享任何信息。服务器并不具有分布式功能,分布式部署取决于 Memcached 客户端。
-
采用 LRU 缓存淘汰策略。在 Memcached 内存储数据项时,可以指定它在缓存的失效时间,默认为永久。当 Memcached 服务器用完分配的内时,失效的数据被首先替换,然后也是最近未使用的数据。在 LRU 中,Memcached 使用的是一种 Lazy Expiration 策略,自己不会监控存入的 key/vlue 对是否过期,而是在获取 key 值时查看记录的时间戳,检查 key/value 对空间是否过期,这样可减轻服务器的负载。
-
内置了一套高效的内存管理算法。这套内存管理效率很高,而且不会造成内存碎片,但是它最大的缺点就是会导致空间浪费。当内存满后,通过 LRU 算法自动删除不使用的缓存。
-
不支持持久化。Memcached 没有考虑数据的容灾问题,重启服务,所有数据会丢失。
4.1.2 Memcached 工作原理
1)内存管理
Memcached 利用 slab allocation 机制来分配和管理内存,它按照预先规定的大小,将分配的内存分割成特定长度的内存块,再把尺寸相同的内存块分成组,数据在存放时,根据键值 大小去匹配 slab 大小,找就近的 slab 存放,所以存在空间浪费现象。
这套内存管理效率很高,而且不会造成内存碎片,但是它最大的缺点就是会导致空间浪费。
2)缓存淘汰策略
Memcached 的缓存淘汰策略是 LRU + 到期失效策略。
当你在 Memcached 内存储数据项时,你有可能会指定它在缓存的失效时间,默认为永久。当 Memcached 服务器用完分配的内时,失效的数据被首先替换,然后是最近未使用的数据。
在 LRU 中,Memcached 使用的是一种 Lazy Expiration 策略:Memcached 不会监控存入的 key/vlue 对是否过期,而是在获取 key 值时查看记录的时间戳,检查 key/value 对空间是否过期,这样可减轻服务器的负载。
3)分区
Memcached 服务器之间彼此不通信,它的分布式能力是依赖客户端来实现。具体来说,就是在客户端实现一种算法,根据 key 来计算出数据应该向哪个服务器节点读/写。
而这种选取集群节点的算法常见的有三种:
-
哈希取余算法:使用公式:Hash(key)% N 计算出 哈希值 来决定数据映射到哪一个节点。
-
一致性哈希算法:可以很好的解决 稳定性问题,可以将所有的 存储节点 排列在 首尾相接 的 Hash 环上,每个 key 在计算 Hash 后会 顺时针 找到 临接 的 存储节点 存放。而当有节点 加入 或 退出 时,仅影响该节点在 Hash 环上 顺时针相邻 的 后续节点。
-
虚拟 Hash 槽算法:使用 分散度良好 的 哈希函数 把所有数据 映射 到一个 固定范围 的 整数集合 中,整数定义为 槽(slot),这个范围一般 远远大于 节点数。槽 是集群内 数据管理 和 迁移 的 基本单位。采用 大范围槽 的主要目的是为了方便 数据拆分 和 集群扩展。每个节点会负责 一定数量的槽。
4.2 Redis
Redis 是一个开源(BSD 许可)的,基于内存的,多数据结构存储系统。可以用作数据库、缓存和消息中间件。
Redis 还可以使用客户端分片来扩展写性能。内置了 复制(replication),LUA 脚本(Lua scripting),LRU 驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis 哨兵(Sentinel)和自动分区(Cluster)提供高可用性(high availability)。
4.2.1 Redis 特性
-
支持多种数据类型 – string、Hash、list、set、sorted set。
-
支持多种数据淘汰策略;
volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰;
volatile-ttl :从已设置过期时间的数据集中挑选将要过期的数据淘汰;
volatile-random:从已设置过期时间的数据集中任意选择数据淘汰;
allkeys-lru:从所有数据集中挑选最近最少使用的数据淘汰;
allkeys-random:从所有数据集中任意选择数据进行淘汰;
noeviction :禁止驱逐数据。
-
提供两种持久化方式 – RDB 和 AOF。
-
通过 Redis cluster 提供集群模式。
4.2.2 Redis 原理
1)缓存淘汰
Redis 有两种数据淘汰实现;
-
消极方式:访问 Redis key 时,如果发现它已经失效,则删除它
-
积极方式:周期性从设置了失效时间的 key 中,根据淘汰策略,选择一部分失效的 key 进行删除。
2)分区
-
Redis Cluster 集群包含 16384 个虚拟 Hash 槽,它通过一个高效的算法来计算 key 属于哪个 Hash 槽。
-
Redis Cluster 支持请求分发 – 节点在接到一个命令请求时,会先检测这个命令请求要处理的键所在的槽是否由自己负责,如果不是的话,节点将向客户端返回一个 MOVED 错误,MOVED 错误携带的信息可以指引客户端将请求重定向至正在负责相关槽的节点。
3)主从复制
- Redis 2.8 后支持异步复制。它有两种模式:
完整重同步(full resychronization) – 用于初次复制。执行步骤与 SYNC 命令基本一致。
部分重同步(partial resychronization) – 用于断线后重复制。如果条件允许,主服务器可以将主从服务器连接断开期间执行的写命令发送给从服务器,从服务器只需接收并执行这些写命令,即可将主从服务器的数据库状态保持一致。
-
集群中每个节点都会定期向集群中的其他节点发送 PING 消息,以此来检测对方是否在线。
-
如果一个主节点被认为下线,则在其从节点中,根据 Raft 算法,选举出一个节点,升级为主节点。
4)数据一致性
-
Redis 不保证强一致性,因为这会使得集群性能大大降低。
-
Redis 是通过异步复制来实现最终一致性。
4.3 分布式缓存对比
不同的分布式缓存功能特性和实现原理方面有很大的差异,因此他们所适应的场景也有所不同。
这里选取三个比较出名的分布式缓存(MemCache,Redis,Tair)来作为比较:
-
MemCache:只适合基于内存的缓存框架;且不支持数据持久化和容灾。
-
Redis:支持丰富的数据结构,读写性能很高,但是数据全内存,必须要考虑资源成本,支持持久化。
-
Tair:支持丰富的数据结构,读写性能较高,部分类型比较慢,理论上容量可以无限扩充。
总结:如果服务对延迟比较敏感,Map/Set 数据也比较多的话,比较适合 Redis。如果服务需要放入缓存量的数据很大,对延迟又不是特别敏感的话,那就可以选择 Memcached。
五、多级缓存
5.1 整体缓存框架
通常,一个大型软件系统的缓存采用多级缓存方案:
请求过程:
-
浏览器向客户端发起请求,如果 CDN 有缓存则直接返回;
-
如果 CDN 无缓存,则访问反向代理服务器;
-
如果反向代理服务器有缓存则直接返回;
-
如果反向代理服务器无缓存或动态请求,则访问应用服务器;
-
应用服务器访问进程内缓存;如果有缓存,则返回代理服务器,并缓存数据(动态请求不缓存);
-
如果进程内缓存无数据,则读取分布式缓存;并返回应用服务器;应用服务器将数据缓存到本地缓存(部分);
-
如果分布式缓存无数据,则应用程序读取数据库数据,并放入分布式缓存;
5.2 使用进程内缓存
如果应用服务是单点应用,那么进程内缓存当然是缓存的首选方案。对于进程内缓存,其本来受限于内存的大小的限制,以及进程缓存更新后其他缓存无法得知,所以一般来说进程缓存适用于:
-
数据量不是很大且更新频率较低的数据。
-
如果更新频繁的数据,也想使用进程内缓存,那么可以将其过期时间设置为较短的时间,或者设置较短的自动刷新时间。
这种方案存在以下问题:
-
如果应用服务是分布式系统,应用节点之间无法共享缓存,存在数据不一致问题。
-
由于进程内缓存受限于内存大小的限制,所以缓存不能无限扩展。
5.3 使用分布式缓存
如果应用服务是分布式系统,那么最简单的缓存方案就是直接使用分布式缓存。其应用场景如图所示:
Redis 用来存储热点数据,如果缓存不命中,则去查询数据库,并更新缓存。这种方案存在以下问题:
-
缓存服务如果挂了,这时应用只能访问数据库,容易造成缓存雪崩。
-
访问分布式缓存服务会有一定的 I/O 以及序列化反序列化的开销,虽然性能很高,但是其终究没有在内存中查询快。
5.4 使用多级缓存
单纯使用进程内缓存和分布式缓存都存在各自的不足。如果需要更高的性能以及更好的可用性,我们可以将缓存设计为多级结构。将最热的数据使用进程内缓存存储在内存中,进一步提升访问速度。
这个设计思路在计算机系统中也存在,比如 CPU 使用 L1、L2、L3 多级缓存,用来减少对内存的直接访问,从而加快访问速度。一般来说,多级缓存架构使用二级缓存已可以满足大部分业务需求,过多的分级会增加系统的复杂度以及维护的成本。因此,多级缓存不是分级越多越好,需要根据实际情况进行权衡。
一个典型的二级缓存架构,可以使用进程内缓存(如:Caffeine/Google Guava/Ehcache/HashMap)作为一级缓存;使用分布式缓存(如:Redis/Memcached)作为二级缓存。
5.4.1 多级缓存查询
多级缓存查询流程如下:
-
首先,查询 L1 缓存,如果缓存命中,直接返回结果;如果没有命中,执行下一步。
-
接下来,查询 L2 缓存,如果缓存命中,直接返回结果并回填 L1 缓存;如果没有命中,执行下一步。
-
最后,查询数据库,返回结果并依次回填 L2 缓存、L1 缓存。
5.4.2 多级缓存更新
对于 L1 缓存,如果有数据更新,只能删除并更新所在机器上的缓存,其他机器只能通过超时机制来刷新缓存。超时设定可以有两种策略:
-
设置成写入后多少时间后过期;
-
设置成写入后多少时间刷新。
对于 L2 缓存,如果有数据更新,其他机器立马可见。但是,也必须要设置超时时间,其时间应该比 L1 缓存的有效时间长。为了解决进程内缓存不一致的问题,设计可以进一步优化;
通过消息队列的发布、订阅机制,可以通知其他应用节点对进程内缓存进行更新。使用这种方案,即使消息队列服务挂了或不可靠,由于先执行了数据库更新,但进程内缓存过期,刷新缓存时,也能保证数据的最终一致性。
六、缓存问题
6.1 缓存雪崩
缓存雪崩是指缓存不可用或者大量缓存由于超时时间相同在同一时间段失效,大量请求直接访问数据库,数据库压力过大导致系统雪崩。
举例来说,对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。
解决缓存雪崩的主要手段如下:
-
增加缓存系统可用性(事前)。例如:部署 Redis Cluster(主从+哨兵),以实现 Redis 的高可用,避免全盘崩溃。
-
采用多级缓存方案(事中)。例如:本地缓存(Ehcache/Caffine/Guava Cache) + 分布式缓存(Redis/ Memcached)。
-
限流、降级、熔断方案(事中),避免被流量打死。如:使用 Hystrix 进行熔断、降级。
-
缓存如果支持持久化,可以在恢复工作后恢复数据(事后)。如:Redis 支持持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。
上面的解决方案简单来说,就是多级缓存方案。系统收到一个查询请求,先查本地缓存,再查分布式缓存,最后查数据库,只要命中,立即返回。
解决缓存雪崩的辅助手段如下:
-
监控缓存,弹性扩容。
-
缓存的过期时间可以取个随机值。这么做是为避免缓存同时失效,使得数据库 IO 骤升。比如:以前是设置 10 分钟的超时时间,那每个 Key 都可以随机 8-13 分钟过期,尽量让不同 Key 的过期时间不同。
6.2 缓存穿透
缓存穿透是指:查询的数据在数据库中不存在,那么缓存中自然也不存在。所以,应用在缓存中查不到,则会去查询数据库。当这样的请求多了后,数据库的压力就会增大。
解决缓存穿透,一般有两种方法:
1)缓存空值
对于返回为 NULL 的依然缓存,对于抛出异常的返回不进行缓存。
采用这种手段的会增加我们缓存的维护成本,需要在插入缓存的时候删除这个空缓存,当然我们可以通过设置较短的超时时间来解决这个问题。
2)过滤不可能存在的数据
制定一些规则过滤一些不可能存在的数据。可以使用布隆过滤器(针对二进制操作的数据结构,所以性能高),比如你的订单 ID 明显是在一个范围 1-1000,如果不是 1-1000 之内的数据那其实可以直接给过滤掉。
针对于一些恶意攻击,攻击带过来的大量 key 是不存在的,那么我们采用第一种方案就会缓存大量不存在 key 的数据。此时我们采用第一种方案就不合适了,我们完全可以先对使用第二种方案进行过滤掉这些 key。针对这种 key 异常多、请求重复率比较低的数据,我们就没有必要进行缓存,使用第二种方案直接过滤掉。而对于空数据的 key 有限的,重复率比较高的,我们则可以采用第一种方式进行缓存。
6.3 缓存击穿
缓存击穿是指,热点数据失效瞬间,大量请求直接访问数据库。例如,某些 key 是热点数据,访问非常频繁。如果某个 key 失效的瞬间,大量的请求过来,缓存未命中,然后去数据库访问,此时数据库访问量会急剧增加。
为了避免这个问题,我们可以采取下面的两个手段:
-
分布式锁:锁住热点数据的 key,避免大量线程同时访问同一个 key。
-
定时异步刷新:可以对部分数据采取失效前自动刷新的策略,而不是到期自动淘汰。淘汰其实也是为了数据的时效性,所以采用自动刷新也可以。
6.4 小结
上面逐一介绍了缓存使用中常见的问题。这里,从发生时间段的角度整体归纳一下缓存问题解决方案。
-
事前:Redis 高可用方案(Redis Cluster + 主从 + 哨兵),避免缓存全面崩溃。
-
事中:(一)采用多级缓存方案,本地缓存(Ehcache/Caffine/Guava Cache) + 分布式缓存(Redis/ Memcached)。(二)限流 + 熔断 + 降级(Hystrix),避免极端情况下,数据库被打死。
-
事后:Redis 持久化(RDB+AOF),一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。
分布式缓存 Memcached ,由于数据类型不如 Redis 丰富,并且不支持持久化、容灾。所以,一般会选择 Redis 做分布式缓存。
七、缓存策略
7.1 缓存预热
缓存预热是指系统启动后,直接查询热点数据并缓存。这样就可以避免用户请求的时候,先查询数据库,然后再更新缓存的问题。
解决方案:
-
手动刷新缓存:直接写个缓存刷新页面,上线时手工操作下。
-
应用启动时刷新缓存:数据量不大,可以在项目启动的时候自动进行加载。
-
定时异步刷新缓存。
7.2 如何缓存
7.2.1 不过期缓存
-
缓存更新模式:
-
开启事务;
-
写 SQL;
-
提交事务;
-
写缓存;
不要把写缓存操作放在事务中,尤其是写分布式缓存。因为网络抖动可能导致写缓存响应时间很慢,引起数据库事务阻塞。如果对缓存数据一致性要求不是那么高,数据量也不是很大,可以考虑定期全量同步缓存。
这种模式存在这样的情况:存在事务成功,但缓存写失败的可能。但这种情况相对于上面的问题,影响较小。
7.2.2 过期缓存
采用懒加载。对于热点数据,可以设置较短的缓存时间,并定期异步加载。
7.3 缓存更新
一般来说,系统如果不是严格要求缓存和数据库保持一致性的话,尽量不要将读请求和写请求串行化。串行化可以保证一定不会出现数据不一致的情况,但是它会导致系统的吞吐量大幅度下降。
一般来说缓存的更新有两种情况:
-
先删除缓存,再更新数据库;
-
先更新数据库,再删除缓存;
为什么是删除缓存,而不是更新缓存呢?
你可以想想当有多个并发的请求更新数据,你并不能保证更新数据库的顺序和更新缓存的顺序一致,那就会出现数据库中和缓存中数据不一致的情况。所以一般来说考虑删除缓存。
- 先删除缓存,再更新数据库;
对于一个更新操作简单来说,就是先去各级缓存进行删除,然后更新数据库。这个操作有一个比较大的问题,在对缓存删除完之后,有一个读请求,这个时候由于缓存被删除所以直接会读库,读操作的数据是老的并且会被加载进入缓存当中,后续读请求全部访问的老数据。
对缓存的操作不论成功失败都不能阻塞我们对数据库的操作,那么很多时候删除缓存可以用异步的操作,但是先删除缓存不能很好的适用于这个场景。先删除缓存也有一个好处是,如果对数据库操作失败了,那么由于先删除的缓存,最多只是造成 Cache Miss。
1)先更新数据库,再删除缓存(注:更推荐使用这种策略)。
如果我们使用更新数据库,再删除缓存就能避免上面的问题。
但是同样的引入了新的问题:假设执行更新操作时,又接收到查询请求,此时就会返回缓存中的老数据。更麻烦的是,如果数据库更新操作执行失败,则缓存中可能永远是脏数据。
2)应该选择哪种更新策略
通过上面的内容,我们知道,两种更新策略都存在并发问题。
但是建议选择先更新数据库,再删除缓存,因为其并发问题出现的概率可能非常低,因为这个条件需要发生在读缓存时缓存失效,而且同时有一个并发写操作。而实际上数据库的写操作会比读操作慢得多,而且还要锁表,而读操作必需在写操作前进入数据库操作,而又要晚于写操作更新缓存,所有的这些条件都具备的概率基本并不大。
如果需要数据库和缓存保证强一致性,则可以通过 2PC 或 Paxos 协议来实现。但是 2PC 太慢,而 Paxos 太复杂,所以如果不是非常重要的数据,不建议使用强一致性方案。更详细的分析可以参考:分布式之数据库和缓存双写一致性方案解析。
八、总结
最后,通过一张思维导图来总结一下本文所述的知识点,帮助大家对缓存有一个系统性的认识。
九、参考资料
1、《大型网站技术架构:核心原理与案例分析》
2、你应该知道的缓存进化史
3、如何优雅的设计和使用缓存?
4、理解分WebStorm激活2022.1布式系统中的缓存架构(上)
5、缓存那些事
6、分布式之数据库和缓存双写一致性方案解析
作者:vivo互联网服务器团队-Zhang Peng
2024最新激活全家桶教程,稳定运行到2099年,请移步至置顶文章:https://sigusoft.com/99576.html
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。 文章由激活谷谷主-小谷整理,转载请注明出处:https://sigusoft.com/170328.html