缓存是程序员必须了解的技术,无论是前端、后端还是客户端,大到复杂的系统架构,小到 CPU 或是芯片,都少不了缓存的影子。

下面只需 5 分钟,带你入门缓存技术。

什么是缓存?

缓存(Cache)本意是指可以进行高速数据交换的存储器,通俗点来说,就是通过将数据提前存放到内存,以提高访问速度

在我们设计程序或算法时,有两个基本指标,即时间复杂度空间复杂度。有时,我们的程序无法同时满足二者,就只能以时间换空间,或者以空间换时间。缓存是以空间换时间思想的典型应用,牺牲一部分内存空间,能够得到数百倍的读取性能提升。

缓存最常见的数据结构是键值对(Key/Value),类似字典,即一个 Key 对应一个 Value,访问缓存时通过 Key 获取 Value。

为什么需要缓存?

最简单的后端系统只需要一个应用服务(比如 Tomcat)和持久化存储数据的数据库(如 MySQL),对于一个访问量很小的系统来说,这样的架构就足够了。

但随着系统的用户数和访问量的提升,数据库会收到越来越多的并发请求。由于数据库是从磁盘中读取数据,性能较低,随着请求数的增多,数据库受到的压力会越来越大。由于应用访问数据库的连接数有限,当数据库的处理能力跟不上请求数时,新的请求将排队等待,从而导致我们的后台程序也会阻塞。当并发请求数持续增大时,数据库甚至会挂掉!欧豁,完蛋。

因此,我们需要性能更高的读取数据方式,能够帮助数据库分担压力,缓存登场了

可以在数据库之上增加一层缓存,当后台程序首次读取数据时,将得到的数据存入缓存中,那么后续的请求要读取相同数据时,只需从缓存中读取即可。由于缓存是将数据存入内存中,读取速度非常快,在成功提升性能的同时,替数据库分担了大量的压力。

因此想要提升系统的整体性能,缓存是不可或缺的。

缓存类别

可以将缓存分为两种类型。

本地缓存

直接运行在应用程序本地的缓存组件。

比如 JVM 中的 Map 数据结构,可以作为最简单的数据缓存:

class LocalCache {private static Map<String, Object> cache = new HashMap<>();private LocalCache() {}public static void put(String key, Object value) {cache.put(key, value);}public static Object get(String key) {
return cache.get(key);}}

如果你的应用程序只需要运行在一台服务器上,并且多个应用程序之间不需要共享缓存的数据(比如用户 token),可以直接采用本地缓存,访问缓存时不需要通过网络传输,非常地方便迅速。

但是本地缓存会和你的应用程序强耦合,应用程序停止,本地缓存也就停止了。而且如果是在分布式场景下,多个机器都要使用缓存,此时如果在每个服务器上单独维护一份本地缓存,不仅无法共享数据,而且非常浪费内存(因为每台机器可能缓存了相同的数据)。

为解决缓存数据共享的问题,可以使用分布式缓存。

分布式缓存

分布式缓存是指独立的缓存服务,不和任何一个具体的应用耦合,可以独立运行并搭建缓存集群。类似数据库,所有的应用程序都可以连接同一个缓存服务以获取相同的缓存数据。

除了数据共享外,分布式缓存的优点还有很多。比如不需要每台机器单独维护缓存、可以集中管理缓存和整体管控分析、便于扩展和容错等。但是应用必须要通过网络访问分布式缓存服务,会产生额外的网络开销成本;并且每台机器都有可能会对整个分布式缓存服务产生影响,而一旦分布式缓存挂了,所有的应用都可能出现瘫痪(缓存雪崩)。

多级缓存

上述两种缓存没有绝对的优劣,要根据实际的业务场景进行选型。其实还可以将本地缓存与分布式缓存相结合,形成多级缓存服务,架构如下:

当首次查询时(不存在缓存),会同时将数据写入本地缓存和分布式缓存。之后的查询优先查询分布式缓存,而如果分布式缓存宕机,则从本地缓存获取数据。通过多级缓存机制,能够起到兜底的作用,即使缓存挂掉,也能支撑应用运行一段时间。

缓存淘汰策略

下面我们思考一个问题,如何去实现一个本地缓存呢?

刚刚提到的 Map 数据结构是一个思路,但是和我们自己的电脑存储文件、或者是和 JVM 存储对象一样,内存当然不是无限的。因此在实现缓存时,必须要设计一套缓存淘汰策略,按照某种机制回收缓存占用的内存,保证缓存数据不会无限地增长直到撑爆内存。

下面介绍几种常见的缓存淘汰策略,关键问题就是当缓存空间已满时,应该选择哪些缓存进行删除。

LRU 最近最少使用

LRU(Least Recently Used)是最经典的内存淘汰策略,其设计原则是 “如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小”。即根据数据的最近访问时间来进行淘汰。缺点是可能会由于一次冷数据的批量查询而误删除大量的热点数据。

近似 LRU 算法

类似 LRU 算法,只是每次随机选择一批数据进行 LRU 淘汰,而不是全量 LRU 运算,牺牲部分准确度,以提升算法执行效率。

Redis 3.0 之后对其进行了优化,维护了一个侯选池,将随机选择的数据放入侯选池中进行 LRU 运算。当侯选池放满后,新随机的数据会替换掉池中最近被访问的数据。

TTL 超时时间

TTL(Time To Live)是指用户为缓存设置的过期时间,当前时间到达过期时间时,将删除缓存;如果缓存空间已满,则优先淘汰最接近过期时间的数据。

LFU 最少使用

LFU(Least Frequently Used)策略会记录每个缓存数据的最近访问次数(频率),并优先清除使用次数较少的数据。这种算法存在的显著缺点是,最新写入的数据由于访问次数少,常常刚被缓存就删除了。

FIFO 先进先出

FIFO(First In First Out)先进先出策略会将数据按照写入缓存的顺序进行排队,当缓存空间不足时,最先进入缓存的数据会被优先删除。是一种比较死板的策略,不考虑数据热度,可能会淘汰大量的热点数据,但是实现起来相对容易。

Second-Chance

对 FIFO 算法的改进,给每个缓存数据添加一个引用标志,当数据被访问时,设置其标志位为 1。

当缓存空间不足时,会检测队列首部(最先写入缓存)数据的引用标志位,如果为 1,表示被访问过,则重置标志位为 0 并且重新加入队列尾部;如果为 0,表示未被访问过,直接淘汰。

相对 FIFO 算法来说,更加灵活,降低了淘汰热点数据的风险。

Random 随机

随机淘汰策略,没啥好说的,一般不建议使用。

还有一些其他的策略,比如优先淘汰占用空间最大的数据等,不再赘述。

缓存实现

要想自己设计一个优秀的缓存,还是有难度的,不止是用一个 Java 的数组或者集合类那么简单。幸运的是,有很多现成的类库、框架和中间件可供我们直接使用。下面介绍一些优秀的缓存实现。

Caffeine

Caffeine 是基于 Java8 的近似最佳的高性能本地缓存库。提供了灵活的构造器 API,能够轻松地创建缓存对象,并且组合各种特性。可谓是进程缓存之王。

地址:https://github.com/ben-manes/caffeine/

Guava Cache

Guava 是 Google 开源的 Java 工具包,而 Guava Cache 是其中的本地缓存实现,提供了 time-based、size-based 等多种缓存淘汰策略,使用起来非常方便。

地址:https://github.com/google/guava/

Ehcache

Ehcache 是 Java 实现的开源缓存框架,特性齐全,成熟轻量,并且支持通过 RMI 等方式实现可扩展的分布式缓存。

地址:http://www.ehcache.org/

Memcached

开源的高性能分布式内存 K/V 缓存系统,支持主流语言的 API,适合存储小块的数据,可以利用多核 CPU。缺点是不支持持久化。

地址:http://memcached.org/

Redis

知名的 K/V 存储系统,适用于分布式缓存,支持多种数据结构、读写性能极高,并且可以搭建高可用集群、易扩展、支持持久化。还能通过 Lua 脚本实现原子性。

Spring Cache

Spring Cache 提供了抽象的缓存接口,帮助我们更好地使用缓存,底层可以选择不同的缓存实现,比如 Caffine 或者 Guava Cache。

没有银弹

缓存虽然具有很多优点,但也不是万能的。引入缓存后,还要考虑到缓存删除、缓存更新、缓存穿透、缓存击穿、缓存雪崩等问题。引入的缓存级数越多,数据不一致的风险就越大。因此,在架构设计时,要根据实际的业务需求来是否使用缓存、选用何种缓存。

毕竟,没有银弹。

有道无术,术可成;有术无道,止于术

欢迎大家关注Java之道公众号

好文章,我在看❤️

小白都能看懂的缓存入门相关推荐

  1. 随机森林的特征 是放回抽样么_机器学习超详细实践攻略(10):随机森林算法详解及小白都能看懂的调参指南...

    一.什么是随机森林 前面我们已经介绍了决策树的基本原理和使用.但是决策树有一个很大的缺陷:因为决策树会非常细致地划分样本,如果决策树分得太多细致,会导致其在训练集上出现过拟合,而如果决策树粗略地划分样 ...

  2. 小白都能看懂的实战教程 手把手教你Python Web全栈开发(DAY 3)

    小白都能看懂的实战教程 手把手教你Python Web全栈开发 Flask(Python Web)实战系列之在线论坛系统 第三讲 这是小白都能看懂的实战教程 手把手教你Python Web全栈开发 的 ...

  3. c语言程序和plc程序的区别,一文告诉你PLC与计算机的本质区别在哪里!小白都能看懂!...

    原标题:一文告诉你PLC与计算机的本质区别在哪里!小白都能看懂! 你真的了解PLC吗?你知道PLC与计算机的本质区别吗?我来简单解释一下吧. 1.PLC可以工作在极其恶劣的电磁环境中 如果我们把计算机 ...

  4. 小白都能看懂的实战教程 手把手教你Python Web全栈开发(DAY 1)

    小白都能看懂的实战教程 手把手教你Python Web全栈开发 Flask(Python Web)实战系列之在线论坛系统 第一讲 博主博客文章内容导航(实时更新) 更多优质文章推荐: 收藏!最详细的P ...

  5. 红色买绿色出 简单易操作的买卖点公式 散户小白都能看懂

    好久没来csdn了,在最初,CSDN是用来记录我做前端写的笔记,后面在工作期间,接触了产品,就转岗了,慢慢的,csdn就很少写了,但是我觉得这里可以作为我记录心得的一个地方,还是挺好的. 因为产品规划 ...

  6. 兄弟,用大白话告诉你小白都能看懂的Hadoop架构原理

    本文来自:石杉的架构笔记 目录 一.前奏 二.HDFS的NameNode架构原理 一.前奏 Hadoop是目前大数据领域最主流的一套技术体系,包含了多种技术. 包括HDFS(分布式文件系统),YARN ...

  7. 兄弟,用大白话给你讲小白都能看懂的分布式系统容错架构

    点击上方"蓝字", 右上角选择"设为星标" 周一至周五早11点半!精品文章准时送上! 本文来自石杉的架构笔记 目录 (1)TB级数据放在一台机器上:难啊! (2 ...

  8. 一篇高中生都能看懂的MySQL入门博客(长文)

    写在前面: 本篇博客共一万五千字左右,是我自己对MySQL进行重新学习时写的,是一些偏基础的东西. 如果你对MySQL足够的了解,本篇博客最多只能起一个查漏补缺的作用. 博客的主要面向对象为:想学习了 ...

  9. 小白都能看懂的关于Mixins机制的理解

    前言 ​ 在学习Flutter源码的时候,看到各种复杂的mixin和on,为了便于后续Flutter的学习,这里有必要一起来份详细Dart 的 Mixin机制. 什么是mixins 首先看看官方文档的 ...

最新文章

  1. 内存泄露的原因找到了,罪魁祸首居然是 Java TheadLocal
  2. linux mysql卸载_Linux环境搭建
  3. Python 实现图片质量比较之PSNR和SSIM
  4. 英文论文中i.e.,e.g.,etc.的正确用法
  5. java动物乐园_基于jsp的动物园管理系统-JavaEE实现动物园管理系统 - java项目源码...
  6. java银行利率_Java-银行计算利息
  7. Android开发入门二之AndroidManfest.xml文件详细说明 .
  8. CAN笔记(10) 错误种类和输出
  9. 计算机分级无法度量视频,雨林木风win7旗舰版电脑评分时出现无法度量视频播放性能...
  10. Hbase的基于快照的表修复
  11. 大数据的价值与发展趋势
  12. 基本概念(名词解释)
  13. ffmpeg完全教程
  14. PPT修行之路(二)-素材库
  15. Java获取当前电脑的ip地址
  16. 使用mplayer做影片预览图
  17. 初三数学——正弦值,余弦值和正切值
  18. 计算机中可以由用户设置的文件属性,计算机考试模拟试题
  19. 谷歌浏览器不安全的连接牵扯的系统缺陷
  20. NBMA网络OSPF路由协议配置

热门文章

  1. ubuntu 18.04 ip固定
  2. python做插件应用_Python插件机制实现详解
  3. 测试学开发——第一课:环境搭建与页面开发介绍。
  4. 前端基础-html-图片标签
  5. 二叉树经典题之二叉树最近公共祖先(LeetCode)
  6. 1-5:学习shell之命令的使用
  7. SQL注入学习资料总结
  8. IDA工具各个功能总结
  9. python2 安装 pip
  10. matlab画图横纵轴刻度相关设置