从公众号转载,关注微信公众号掌握更多技术动态
---------------------------------------------------------------
一、缓存基础
1.缓存简介
缓存提升性能的幅度,不只取决于存储介质的速度,还取决于缓存命中率。为了提高命中 率,缓存会基于时间、空间两个维度更新数据。在时间上可以采用 LRU、FIFO 等算法淘汰 数据,而在空间上则可以预读、合并连续的数据。如果只是简单地选择最流行的缓存管理 算法,就很容易忽略业务特性,从而导致缓存性能的下降。
(1)命中率
命中率=命中数/(命中数+没有命中数)
当某个请求能够通过访问缓存而得到响应时,称为缓存命中。缓存命中率越高,缓存的利用率也就越高。
(2)最大空间
缓存中可以容纳最大元素的数量。当缓存存放的数据超过最大空间时,就需要根据淘汰算法来淘汰部分数据存放新到达的数据。
⼤容量缓存是能带来性能加速的 收益,但是成本也会更⾼,⽽⼩容量缓存不⼀定就起不到加速访问的效果。⼀般来说,建议把缓存容量 设置为总数据量的15%到30%,兼顾访问性能和内存空间开销。
(3)淘汰算法
缓存的存储空间有限制,当缓存空间被用满时,如何保证在稳定服务的同时 有效提升命中率?这就由缓存淘汰算法来处理,设计适合自身数据特征的淘汰算法能够有效提升缓存命中率。常见的淘汰算法有:
-
FIFO(first in first out)「先进先出」。最先进入缓存的数据在缓存空间不够的情况下(超出最大元素限制)会被优先被清除掉,以腾出新的空间接受新的数据。策略算法主要比较缓存元素的创建时间。「适用于保证高频数据有效性场景,优先保障最新数据可用」。
-
LFU(less frequently used)「最少使用」,无论是否过期,根据元素的被使用次数判断,清除使用次数较少的元素释放空间。策略算法主要比较元素的hitCount(命中次数)。「适用于保证高频数据有效性场景」。
-
LRU(least recently used)「最近最少使用」,无论是否过期,根据元素最后一次被使用的时间戳,清除最远使用时间戳的元素释放空间。策略算法主要比较元素最近一次被get使用时间。当遇到爬虫时,缓存的数据变成非热点数据。「比较适用于热点数据场景,优先保证热点数据的有效性。」
LRU策略更加关注数据的时效性,⽽LFU策略更加关注数据的访问频次。
(4)缓存的使用场景
-
经常需要读取的数据
-
频繁访问的数据 热点数据缓存
-
IO 瓶颈数据
-
计算昂贵的数据
-
无需实时更新的数据
-
缓存的目的是减少对后端服务的访问,降低后端服务的压力
(5)缓存更新策略
缓存中的数据会和数据源中的真实数据有一段时间窗口的不一致,需要利用某些策略进行更新,下面会介绍几种主要的缓存更新策略。
①LRU/LFU/FIFO算法剔除:剔除算法通常用于缓存使用量超过了预设的最大值时候,如何对现有的数据进行剔除。例如Redis使用maxmemory-policy这个配置作为内存最大值后对于数据的剔除策略。
②超时剔除:通过给缓存数据设置过期时间,让其在过期时间后自动删除,例如Redis提供的expire命令。如果业务可以容忍一段时间内,缓存层数据和存储层数据不一致,那么可以为其设置过期时间。在数据过期后,再从真实数据源获取数据,重新放到缓存并设置过期时间。例如一个视频的描述信息,可以容忍几分钟内数据不一致,但是涉及交易方面的业务,后果可想而知。
③主动更新:应用方对于数据的一致性要求高,需要在真实数据更新后,立即更新缓存数据。例如可以利用消息系统或者其他方式通知缓存更新。
有两个建议:
-
低一致性业务建议配置最大内存和淘汰策略的方式使用。
-
高一致性业务可以结合使用超时剔除和主动更新,这样即使主动更新出了问题,也能保证数据过期时间后删除脏数据。
(6)缓存粒度控制
缓存粒度问题是一个容易被忽视的问题,如果使用不当,可能会造成很多无用空间的浪费,网络带宽的浪费,代码通用性较差等情况,需要综合数据通用性、空间占用比、代码维护性三点进行取舍。
对于缓存的使用,需要对其存储的内容和数量严格限制,并对大小进行估算,通过文档进行维护。
2.缓存如何提高性能
缓存指的将数据存储在相对较高访问速度的存储介质中,以供系统处理。内存是半导体元件。对于内存而言,只要给出了内存地址,就可以直接访问该地址取出 数据。内存的访问速度很快但价格昂贵。而磁盘是机械器件。磁盘访问数据时,需要等磁盘盘片旋转到磁头下,才能读取相应的数 据。尽管磁盘的旋转速度很快,但是和内存的随机访问相比,性能差距非常大。一般来说,如果是随机读写,会有 10 万到 100 万倍左右的差距。但如果是顺序访问 大批量数据的话,磁盘的性能和内存就是一个数量级的。磁盘的最小读写单位是扇区,目前常见的磁盘扇区是 4K 个字节。操作系统一次会读写多个扇区,所以操作系统的最小读 写单位是块(Block),也叫作簇(Cluster)。当要从磁盘中读取一个数据时,操作系 统会一次性将整个块都读出来。因此,对于大批量的顺序读写来说,磁盘的效率会比随机读 写高许多。
一方面缓存访问速度快,可以减少数据访问的时间,另一方面如果缓存的数据是经过计算的处理得到的,那么被缓存的数据无需重复计算即可直接使用,因此缓存还起到减少计算时间的作用。例如,一个论坛需要在首页展示当前有多少用户同时在线,如果使用 MySQL 来存储当前用户状态,则每次获取这个总数都要“count(*)”大量数据,这样的操作无论怎么优化 MySQL,性能都不会太高。如果要实时展示用户同时在线数,则 MySQL 性能无法支撑。
缓存的本质是一个内存的Hash表,网站应用中,数据缓存以一对key、value的形式存储在内存Hash表中。Hash表数据读写的时间复杂度为O(1)。缓存主要用来存放读多写少、很少变化的数据,比如商品的类目信息,热门词的搜索列表信息,热门商品信息等。应用程序读取数据,先到缓存中读取,如果读取不到或数据已失效,再访问数据库并将数据写入到缓存。
网站数据访问通常遵循二八定律(80%访问落在20%数据上),因此利用Hash表和内存的高速访问特性,将这20%的数据缓存起来,可很好改善系统性能,提高数据读取速度,降低存储访问压力,提高吞吐量。以微博为例:一个明星发一条微博,可能几千万人来浏览。如果使用 MySQL 来存储微博,用户写微博只有一条 insert 语句,但每个用户浏览时都要 select 一次,即使有索引,几千万条 select 语句对 MySQL 数据库的压力也会非常大。
获取缓存的时候千万不用通过服务调用获取缓存,调用服务花费的时间远远大于获取缓存,这样意义不大
3.缓存失效的方式
-
被动失效,主要处理如模板变更和一些对时效性不太敏感数据的失效,采用设置一定时间 长度(如只缓存 3 秒钟)这种自动失效的方式。当然,你也要开发一个后台管理界面, 以便能够在紧急情况下手工失效某些 Cache。
-
主动失效,一般有 Cache 失效中心监控数据库表变化发送失效请求、系统发布也需要清 空 Cache 数据等几种场景。其中失效中心承担了主要的失效功能,这个失效中心的逻 图如下:
失效中心会监控关键数据表的变更(有个中间件来解析 MySQL 的 binglog,然后发现有 Insert、Update、Delete 等操作时,会把变更前的数据以及要变更的数据转成一个消息发 送给订阅方),通过这种方式来发送失效请求给 Cache,从而清除 Cache 数据。
二、缓存带来的复杂性
缓存避免再高峰刷新,避免连接数占满
1.合理使用缓存
①频繁修改的数据不应该使用缓存
当数据的读写的比例很大的时候才推荐使用缓存。
②对于访问频率低的数据不应该使用缓存
因为缓存以内存为存储,内存资源宝贵,不可能将所有数据都进行缓存。
③数据一致性要求的访问不应该使用缓存
一般会对缓存设置过期时间,而当缓存没到过期时间更新了数据这时可能出现数据不一致与脏读
缓存往往针对的是“资源”,当某一个操作是“幂等”的和“安全”的,那么这样的操作就可以被抽象为对“资源”的获取操作,那么它才可以考虑被缓存。有些操作不幂等、不安全,比银行转账