Cache!聊聊缓存

Posted 架构思轩

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Cache!聊聊缓存相关的知识,希望对你有一定的参考价值。

建议阅读者:缓存、分布式设计

预估阅读时长:15min


缓存的原理

缓存在分布式系统中应用广泛,如何在架构设计中使用缓存来优化业务性能一直都是一个重要的话题,今天我们就来看看缓存的原理、分类、技术对比,以及应用场景(缓存一致性、缓存穿透、缓存雪崩、缓存并发、缓存热点、缓存监控)。


缓存对性能提升十分明显,特别是在分布式系统中,80%的业务访问集中在20%的数据上,如何使用好缓存是架构设计的必修课。先来说说缓存的主要原理。


1、将数据写入/读取速度更快的存储


缓存提升系统性能的本质就是“空间换时间”,利用分布式的不同介质等快速存储的设备,来替代部署数据库或者硬件磁盘文件,加快系统的数据处理和响应速度。从介质的角度,可以分为CPU,内存,磁盘等。从冯诺依曼模型来开,数据处理的过程,通过了不通的介质,缓存更多是需要内存+硬盘的结合。

Cache!聊聊缓存

2、将数据缓存到离用户最近的位置


互联网的典型架构可以分为三层模式,客户端前台层,后台应用层,数据存储层,每层又可继续进行分层。架构分层的本质就是“数据移动”的过程,过程中伴随着数据的“被处理”和“被呈现”。每层都可以使用缓存技术,整体的原则是尽可能将数据放到离用户更近的位置,减少对后台系统的穿透,加速响应时间。

Cache!聊聊缓存

3、将数据缓存到离应用最近的位置


从后台应用层本身进一步来看,需要平衡应用服务器和数据库服务器成本和性能之间的矛盾,因为关系型数据库的读写能力受限于磁盘,每秒能够接收的请求次数也是有限的,需要引入不通类型的缓存技术,让应用服务尽可能通过不同方式,把需要的数据缓存到本地或分布式缓存中,减少对DB的访问压力。

Cache!聊聊缓存

缓存的分类

Cache!聊聊缓存

从分布式系统角度,缓存可以分为浏览器缓存、代理服务器缓存、服务器本地缓存、分布式缓存、数据库缓存等。

Cache!聊聊缓存

浏览器缓存(HTTP/CDN)

从客户端角度,可以使用HTTP缓存机制或者CDN缓存,来加快用户的访问。

HTTP可以使用本地缓存、强制页面刷新、回车或跳转等方式来使用客户端浏览器自身缓存。

CDN(Content Delivery Network),即内容分发网络。简单来讲,就是把原服务器上数据复制到其他服务器上,用户就近访问服务器获取资源。


代理服务器缓存(反向代理缓存)

代理服务器也称反向代理,比如Ngnix,介于客户端和业务服务器之间的中间服务器,把请求路由到多个服务器上,也可以对静态页面请求直接缓存,在减少响应时间和带宽使用方面很有效,同一个缓存数据会被重复使用多次。


服务器本地缓存

服务器本地缓存中,应用和缓存在同一进程内部,优点是缓存节省了网络开销;缺点是受限于本地内存的大小,多个应用无法共享缓存,且难以保持进程缓存的一致性。因此,本地缓存适用于数据量不大,数据更新频率较低的情况。本地缓存常用技术比如Caffeine。


分布式缓存

分布式缓存,是应用分离的缓存组件或服务。优点是独立应用,多个应用可直接的共享缓存。常用技术有Memcache和Redis等。分布式缓存需要尽可能让服务无状态,这样可以让应用水平扩展。

 

数据库缓存

数据库的缓存,主要指数据库本身提供的对数据库表的高速缓存。数据库将同样的请求查询缓存起来,保持一段时间,提升效率。


分布式缓存技术对比

Cache!聊聊缓存

分布式系统中,常用的缓存技术有如下几种:


Redis:一个开源的、Key-Value型、基于内存运行并支持持久化的NoSQL数据库,是目前最常用的分布式缓存技术。

优点:支持丰富的数据结构(包括字符串、哈希、列表、集合、有序集合),支持高可用(主动复制,读写分离),支持持久化(RDB快照模式,AOF持久化模式),Value值支持最大512M。

缺点:数据全内存,占用内存过高,必须要考虑资源成本。


Memcached:一款完全开源、高性能、分布式的内存系统;功能上相当于Redis的子集。更适合于数据量大,并发量大的业务诉求,这个和memcache的底层实现原理有关。


Tair: 支持丰富的数据结构,读写性能高,部分类型相对慢,理论上容量可以无限扩充。如果服务需要放入缓存量的数据很大,对延迟不特别敏感,可以选择Tair。Tair来自阿里,在阿里中应用广泛。


Ehcache:Java缓存框架 EhCache是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。可以配置内存不足时,启用磁盘缓存。


Spring Cache:是Spring提供的对缓存功能的抽象:即允许绑定不同的缓存解决方案(如Ehcache、Redis、Memcache、Map等等),但本身不直接提供缓存功能的实现

缓存过期

Cache!聊聊缓存

 常见的缓存过期(缓存更新)策略有:

  •  固定时间:如指定缓存30分钟; 

  •  相对时间:如10分钟内没有访问的数据;

  •  动态判断:有用户请求时再判断是否过期。


缓存一致性

Cache!聊聊缓存

缓存的一致性主要是指缓存与数据库的数据是否一致,涉及缓存更新,分为两种方法:

方法1: 先删除缓存,再更新数据库(推荐)

方法2: 先更新数据库,再删除缓存

 

方法1和方法2哪个更好?


这个主要需要看具体的业务场景。我更倾向于方法1,因为如果删除缓存成功,更新数据库失败,最多只是会造成缓存穿透,引起一次Cache miss,后面还会更新缓存。而如果更新数据库成功,删除缓存失败,会引起比较严重的数据不一致情况。

 

我们这里讲删除缓存而不是更新缓存,原因是如果有多个并发的请求更新数据,不能保证更新数据库的顺序和更新缓存的顺序一致。所以对于缓存的操作使用删除。

 

提高缓存一致性也要注意设置合理的缓存更新时间,这个是保证最终一致性的解决方案。

 

另外,也可以通过分布式协调,比如zookeeper来协调各缓存实例节点,保证缓存和数据的数据发生顺序一致。

 

当然,对于方法2,可以使用其他的一些技术来保证最终一致性,如下图,通过将数据库操作写入binlog中,如果删除缓存失败,程序会将数据存储到消息队列中,并进行重试。

Cache!聊聊缓存

缓存穿透

Cache!聊聊缓存

缓存穿透是指查询的数据在数据库是没有的,那么在缓存中也没有,所以,在缓存中查不到就会去数据库取查询,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。也有可能有人利用这个不存在的key进行恶意攻击。

 

解决缓存穿透有两种方案。

 

方案1: NULL依旧缓存

如果一个查询返回的数据为NULL,也依然缓存。但注意过期时间设置短一些,不超过五分钟。

 

方案2: 采用bitmap

bitmap是布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,不存在的数据会被bitmap拦截,从而避免了对底层存储系统的压力。比如一个订单ID 明显是在一个范围1-1000,如果不是1-1000之内直接过滤。


缓存雪崩

Cache!聊聊缓存

缓存雪崩是指缓存不可用,所有的请求短时间内大量到达底层系统,导致数据库掉用量暴增,压力过大导致系统雪崩。

 

解决缓存雪崩主要有以下方法:

 

方法1: 加锁/排队

 加锁或排队来保证缓存的单线程/进程写,避免失效时大量的并发请求,当然支持缓解DB压力,本质上没有提高QPS。

 

方法2: 多级缓存,不同过期时间

 将缓存失效时间分散,比如增加一个随机值。

 

方法3: 缓存降级

 当非核心服务影响到核心流程的性能时,对非核心服务进行降级。

 

方法4: 增加缓存可用性

 通过监控关注缓存的健康程度,根据业务量适当的扩容缓存。



缓存并发

Cache!聊聊缓存

缓存并发是指在高并发场景中,当一个缓存过期时,因为访问这个缓存的请求量较大,缓存未命中,多个请求同时访问数据库并写缓存,造成应用和数据库的负载增加。

解决缓存并发主要有以下方法:

 

方法1:锁

使用本地/分布式锁,保证每个key只有一个线程去查询后端服务。比如mutex key,Redis的setNX等。

 

方法2:软过期

不使用缓存服务提供的过期时间,而是业务层在存储过期时间,由业务程序只派遣一个线程去数据库中获取最新的数据,其他线程使用延长了的过期时间,等派遣的线程获取最新数据后再更新缓存。

缓存热点

Cache!聊聊缓存

哪些数据需要缓存:  1.热点数据;2.静态资源


虽然缓存系统本身的性能比较高,但对于一些特别热点的数据,如果大部分甚至所有的业务请求都命中同一份缓存数据,则这份数据所在的缓存服务器的压力也很大。


缓存热点的解决方案就是复制多份缓存副本,代码中将 Key 打散在多个缓存服务器上平摊流量压力来解决,减轻缓存热点导致的单台缓存服务器压力。缓存副本不用设置统一的过期时间,可为指定范围内的随机值。


缓存监控

Cache!聊聊缓存

一般我们需要关注的指标有缓存大小、过期时间、缓存命中率、缓存响应时间等多方面,并通过各种监控、测试来调整和优化。

缓存误区

Cache!聊聊缓存

误区1: 将缓存层当做传递数据媒介

有人使用缓存来给两个服务通信,这种做法是错误的。缓存是用来存储数据的,并不是来传递数据、节藕服务的。恰恰相反,缓存会导致服务耦合。正确的办法是使用消息队列(MQ)来传递数据尽量。

 

误区2: 缓存不用考虑序列化

序列化容易被忽视,比如不同的JVM的序列化不同,过于复杂的KV对象系列化不支持,添加或删除某字段出现系列化错误。这个需要通过测试、开发框架、CICD等开发流程来加强。


Takeaways

1、缓存原理:将数据写入/读取速度更快的存储;将数据缓存到离用户最近的位置;将数据缓存到离应用最近的位置

2、缓存分类:从分布式系统角度,缓存可以分为浏览器缓存、代理服务器缓存、服务器本地缓存、分布式缓存、数据库缓存等

3、缓存技术:Redis、Memcached、Tair、Ehcache、SpringCache

4、缓存过期:固定时间、相对时间、动态判断

5、缓存一致性:先删除缓存,再更新数据库;合理过期时间

6、缓存穿透: NULL依旧缓存;采用bitmap

7、缓存雪崩:加锁/排队;多级缓存,不同过期时间;缓存降级;增加缓存可用性

8、缓存并发:锁/软过期

9、缓存误区:将缓存层当做传递数据媒介;不考虑序列化


王思轩,计算机专业博士,阿里巴巴架构师

获得加拿大卡尔顿大学计算机博士学位,并获得法国和哈尔滨工业大学双硕士,哈尔滨工业大学本科,发表过10余篇国际学术论文。多年从事于云计算和系统架构设计工作,曾就职于华为,Qlik,Honeywell。现任阿里巴巴技术架构师,负责并参与阿里云中间件解决方案设计,以及企业互联网架构的赋能培训工作。

 • end • 

作者 | 王思轩

 架构思轩 

架构师思考小屋

空·



以上是关于Cache!聊聊缓存的主要内容,如果未能解决你的问题,请参考以下文章

聊聊缓存回收策略跟缓存更新策略

三种缓存策略:Cache Aside 策略Read/Write Through 策略Write Back 策略

三种缓存策略:Cache Aside 策略Read/Write Through 策略Write Back 策略

Caffeine高性能本地缓存

从底层了解JVM的volatile实现,CPU Cache缓存一致性MESIStore BufferInvalidate Queue等知识

从底层了解JVM的volatile实现,CPU Cache缓存一致性MESIStore BufferInvalidate Queue等知识