资讯专栏INFORMATION COLUMN

猫头鹰的深夜翻译:为何需要缓存以及如何实现缓存

FuisonDesign / 3700人阅读

摘要:由于需要跨进程访问网络上的高速缓存,因此延迟,故障和对象序列化会导致性能下降。应用程序高速缓存会自动清除条目以保持其内存占用。缓存统计高速缓存统计信息可帮助识别高速缓存的运行状况并提供有关高速缓存行为和性能的信息。

前言

这篇文章探索了现有的各种JAVA缓存基数,它们对各种场景下提高应用的性能起着重要的作用。

近十年来,信息技术极高的提升了业务流程,它已经成为了全球企业的战略性方案。它从“可有可无”演变到现在的“不可或缺”。因此,响应时间变得越发的重要。数据获取时间对用户体验影响极大,它在所有的商业应用中几乎都是关键性需求。影响响应时间的因素很多,包括网络管道,协议,硬件,软件以及网速。庞大的IT基础设施和苛刻的系统性能要求严重影响了任何组织的战略目标。

本文旨在强调通过Java缓存机制提升应用的性能。

缓存的概念

缓存是指一块内存缓冲区,用来临时存储经常访问的数据。因为数据无需从原始来源重新获取,因此提升了性能。缓存这个概念应用在计算机/网络产业的各个领域,因此根据不同的用例,有不同的实现缓存的方法。事实上,像路由器,交换机,PC这样的设备使用缓存来加速内存访问。还有一个常见的场景,几乎存在于所有的PC,即浏览器缓存最近请求获取的对象,这样就无需多次获取同样的数据了。在一个分布式的JEE应用中,客户端/服务器端缓存对于提升性能也起着至关重要的作用。客户端缓存用来临时存储从服务器传来的静态数据,从而避免不必要的对服务器的方位。另一方面,服务器端的缓存会将它从别的地方获取的数据存储在内存中。

缓存可以建立在单点/多点JVM或是集群环境上。可以使用缓存来满足不同伸缩性程度的场景如下。

垂直伸缩

可以通过升级单个机器,赋予其更多的有效资源(CPU,RAM,HDD,SSD),并且开启缓存来实现。但是有个局限性,缓存升级的程度是有限的。在下面的用例中,可以通过增加内存并在应用层实现缓存来提升应用的性能。

水平伸缩

它可以通过添加更多的机器并在每台机器的应用层开启缓存来实现。但是它也存在局限,因为与之交互的下游应用并没有增加额外的机器。在下面的用例中,通过给每一个应用添加一个服务器/缓存来提高整体的性能。数据库可能会成为性能瓶颈,但是可以通过在缓存中存储静态/主数据来缓解。

进程中缓存

进程内缓存使对象可以与应用程序存储在同一实例中,即本地缓存可供应用程序使用,并共享相同的内存空间。
考虑进程缓存时的重点:

如果应用程序仅部署在一个节点中,即具有单个实例,那么进程内缓存是存储经常访问的数据以及快速访问数据的合适选择。

如果进程内高速缓存将部署在应用程序的多个实例中,那么在所有实例之间保持数据同步可能是一个挑战,并且会导致数据不一致。

如果服务器配置有限,那么这种类型的缓存会降低所有应用程序的性能,因为它共享相同的内存和CPU。垃圾收集器经常会被调用来清理可能导致性能开销的对象。如果不能有效管理缓存移除,则可能会发生内存不足报错。

内存中分布式缓存

分布式缓存(键/值对象)可以在支持从/向数据存储库读取/写入的应用程序的外部构建。它会频繁的从RAM中访问数据,避免从数据源获取数据。这样的缓存可以部署在集群的多个节点中,构成单一逻辑视图。缓存的客户端使用哈希算法来得出集群中对象的位置。

考虑分布式缓存的重点:

内存分布式缓存对于中型到大型,在集群中有多个实例,并且性能至关重要的应用是最佳的解决方案。数据不一致以及共享内存不再是性能的焦点,因为部署在集群中的分布式缓存可以展现出单一逻辑状态。

由于需要跨进程访问网络上的高速缓存,因此延迟,故障和对象序列化会导致性能下降。

实现的难度大于进程内缓存。

内存数据库

这种类型的数据库也称为主存数据库。数据存储在RAM中而不是硬盘上,以实现更快的响应。数据以压缩格式存储并且支持SQL。相关的数据库驱动程序可以用来代替现有的RDBMS。使用内存数据库替换RDBMS可以在不改变应用程序层的情况下提高应用程序的性能。只有垂直伸缩可用于扩展内存数据库。

内存数据网格

这种分布式缓存解决方案可以快速访问常用数据。数据可以在多个节点上缓存,复制和分区。

实现内存数据网格可以提高应用程序的性能并在不改变RDBMS的情况下扩展应用程序。

核心功能:

并行计算内存中的数据

在内存中搜索,聚合和排序数据

内存中的事务管理

事件处理

In-memory database vs In-memory data grid
内存中数据库通过替换和升级底层RDBMS的基础上提升性能,它将应用和底层数据库的变更隔离开来。
内存中数据网格通过调整应用程序来提升速度,将应用的变更和底层数据库隔离开来。
缓存用例

在任何一个企业级应用中都可以通过配置商业或者开源的框架来提升应用的性能。下面是几个常见的缓存用例。

应用缓存

应用程序缓存是应用程序保存在内存中用来频繁访问的数据的本地缓存。应用程序高速缓存会自动清除条目以保持其内存占用。

Level 1 (L1) 缓存

这是每个会话的默认事务缓存。它可以由任何Java持久性框架(JPA)或对象关系映射(ORM)工具来管理。L1缓存存储属于特定会话的实体对象,并在会话关闭后清除。如果一个会话内有多个事务,则所有这些事务都将被存储。

Level 2 (L2) 缓存

二级缓存可以配置为提供自定义缓存,可以保存要缓存的所有实体的数据。它可能与属性,关联和集合有关。它在会话工厂中配置,并且只要会话工厂可用,它就存在。

二级缓存可以配置为可在以下场景中共享:

应用的会话

具有相同数据库的在相同服务器上的应用程序

拥有同一数据库的在不同服务器上不同的应用程序

L1 / L2缓存的使用流程

标准ORM框架首先会在L1缓存中查找实体,然后在L2缓存中查找。L1缓存是查找开始的地方。如果找到一个实体的缓存,就会返回该实体。

如果在L1缓存中没有找到,就会去L2缓存查找

如果在L2缓存中找到,则会存到L1缓存,并且返回实体

如果在L1和L2中找不到实体,则它将从数据库中提取并存储在两个高速缓存中,然后再返回给调用方。

当任何会话在实体上机型任何修改时,L2缓存验证/刷新自身。

如果数据库完全由外部进程修改,即没有应用程序会话,则不能隐式刷新L2高速缓存,除非某些高速缓存刷新策略通过框架API或某个自定义API实现。

下面的通信图展现了使用L1/L2缓存:

混合高速缓存

混合高速缓存是标准ORM框架提供的高速缓存和开源/定制/ JDBC API实现的组合。应用可以使用混合高速缓存来调整局限于标准ORM框架的缓存能力。这种缓存用于响应时间至关重要的任务关键型应用。

缓存设计注意事项

缓存设计注意事项包括数据加载/更新,性能/内存大小,缓存移除策略,并发性和缓存统计信息。

数据加载和更新

将数据加载到缓存中是保持所有缓存内容一致性的重要设计决策。加载数据可以考虑以下方法:

使用标准框架(如hibernate,openJPA)提供的默认的功能或配置

使用开源缓存API(如Google Guava或是COTS的产品如Coherence, Ehcache或 Hazelcast.)实现键值映射。

利用编程自动或是显式插入加载实体

外部的应用可以通过同步或异步通信

性能/内存大小 32/64位

可用内存是实现性能SLA的一个重要因素,它取决于32/64位JRE,而JRE又依赖于32/64位机器的CPU架构。在32位的机器中,应用程序可用的堆大小大约是1.5G,而在64位机器中,堆的大小依赖于RAM的大小。

内存的高可用性确实会在运行时产生成本,并可能产生负面影响。

由于存储器布局,64位所需要的堆大小比32位多出30-50%。

保持更多的堆需要更多的GC任务来清理可能降低性能的未使用的对象。通过微调GC可以减少由GC导致的暂停运行。

缓存移除策略

缓存移除策略使缓存能够确保缓存的大小不超过最大限制。为了实现这一点,元素将根据缓存移除策略从缓存中删除,它还可以根据应用的需求自定义。缓存解决方案中有各种流行的缓存移除策略。

最近最少使用 (LRU):首先淘汰最长时间未被使用的缓存

最不常使用 (LFU):首先淘汰在一段时间内使用次数最少的缓存

先进先出 (FIFO)

并发

并发性是企业应用程序中的常见问题。它会引入冲突并且使系统位于不一致的状态中。当多个客户端尝试在缓存刷新期间同时更新相同的数据对象时,可能会发生这种情况。通常使用锁来解决,但是锁会影响性能。因此,需要针对这个考虑优化策略。

缓存统计

高速缓存统计信息可帮助识别高速缓存的运行状况并提供有关高速缓存行为和性能的信息。通常,以下属性可用于统计缓存:

Hit count:找到对象所需要的查找次数

Miss Count:没有找到对象所需要的查找次数

Load success count:成功加载的条目数

Total load time:加载元素的总时间

Load exception count:加载条目时抛出的异常数

Eviction count:从缓存中移除的条目数量

总结:各种缓存方案

有各种Java缓存解决方案可供选择 - 正确的选择取决于使用案例。以下是一些问题和比较,可以帮助找出最具成本效益和可行的缓存解决方案。

你需要一个轻量级还是全面的缓存解决方案?

你需要开源的,商业的或框架提供的缓存解决方案?

你需要进程中缓存还是分布式缓存?

一致性和延迟要求之间的折衷是什么?

你需要维护事务/主数据的缓存吗?

你需要一个缓存复制吗?

性能,可靠性,可伸缩性和可用性如何?

参考资料
In-memory database vs In-memory datagrid
LRU 和 LFU的区别


想要了解更多开发技术,面试教程以及互联网公司内推,欢迎关注我的微信公众号!将会不定期的发放福利哦~

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/61936.html

相关文章

  • 头鹰深夜翻译:Volatile原子性, 可见性和有序性

    摘要:有可能一个线程中的动作相对于另一个线程出现乱序。当实际输出取决于线程交错的结果时,这种情况被称为竞争条件。这里的问题在于代码块不是原子性的,而且实例的变化对别的线程不可见。这种不能同时在多个线程上执行的部分被称为关键部分。 为什么要额外写一篇文章来研究volatile呢?是因为这可能是并发中最令人困惑以及最被误解的结构。我看过不少解释volatile的博客,但是大多数要么不完整,要么难...

    Lionad-Morotar 评论0 收藏0
  • 头鹰深夜翻译:在JAVA中记录日志十个小建议

    摘要:是指可能导致程序终止的非常严重的时间。具有最高的级别,旨在关闭中的日志功能。因此为每一个消息选择一个合适的日志级别是非常重要的。日志的个小建议将日志访日代码块它能显著的减少因为字符串拼接而带来的性能的影响。 前言 首先,这篇文章没有进行任何的日志功能的详细介绍,而是对日志提出了几种最佳实践。适合对日志记录有所了解的同学阅读。下面是正文: JAVA日志管理既是一门科学,又是一门艺术。科学...

    venmos 评论0 收藏0
  • 头鹰深夜翻译:核心JAVA并发(一)

    摘要:简介从创建以来,就支持核心的并发概念如线程和锁。这篇文章会帮助从事多线程编程的开发人员理解核心的并发概念以及如何使用它们。请求操作系统互斥,并让操作系统调度程序处理线程停放和唤醒。 简介 从创建以来,JAVA就支持核心的并发概念如线程和锁。这篇文章会帮助从事多线程编程的JAVA开发人员理解核心的并发概念以及如何使用它们。 (博主将在其中加上自己的理解以及自己想出的例子作为补充) 概念 ...

    Richard_Gao 评论0 收藏0
  • 头鹰深夜翻译:从1000+JS项目中汇总10个最容易出现错误(以及如何解决)

    摘要:常出现的错误前十位为了可读性,错误名称进行了一定的简写。让我们深入了解每个错误发生的原因以及解决方法。这个问题很容易解决。当未捕获的错误跨越违法跨域策略的域边界时,会发生脚本错误。这是当你在中试图调用的方法时出现的错误。 JavaScript常出现的错误前十位 showImg(https://segmentfault.com/img/bV3Z1z?w=1116&h=691); 为了可读...

    eccozhou 评论0 收藏0
  • 头鹰深夜翻译:JDK Vs. JRE Vs. JVM之间区别

    摘要:什么是为执行字节码提供一个运行环境。它的实现主要包含三个部分,描述实现规格的文档,具体实现和满足要求的计算机程序以及实例具体执行字节码。该类先被转化为一组字节码并放入文件中。字节码校验器通过字节码校验器检查格式并找出非法代码。 什么是Java Development Kit (JDK)? JDK通常用来开发Java应用和插件。基本上可以认为是一个软件开发环境。JDK包含Java Run...

    blair 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<