• 性能优化之缓存篇


    1. 前言:为什么要用缓存?

    用户数增长,架构演变,数据量增大,开始考虑怎么去做性能优化。

    而性能优化的第一定律就是:优先考虑使用缓存。

    2. 缓存的基本原理

    2.1 缓存的作用

    1、加快数据访问速度;

    2、减轻后端应用和数据存储的负载压力。

    2.2 缓存的特征

    1、命中率:命中率 = 命中数 / 请求数。

    这是衡量缓存有效性的重要指标。命中率越高,表明缓存的使用率越高。

    2、最大元素(最大空间)。

    一旦缓存中元素数量超过这个值(或者缓存数据空间超过其最大支 持空间),将会触发淘汰策略

    3、淘汰策略。

    这个我前文其实已经说过。

    FIFO(First In First Out) 先进先出,淘汰最早数据。

    判断存储时间,离目前最远的数据优先淘汰。

    LRU (Least Recently Used)剔除最近最少使用。

    判断最近使用时间,离目前最远的数据优先淘汰。

    LFU (Least Frequently Used)剔除最近使用频率最低的数据。

    在一段时间内,数据被使用次数最少的,优先淘汰。

    具体可以看这篇文章常见的缓存剔除策略 & LRU与LFU的区别

    3. 缓存的分类

    缓存的主要手段有:浏览器缓存、CDN、反向代理、本地缓存、分布式缓存、数据库缓存。

    解读《大型网站技术架构》一文中,其实已经说到过。

    我们一般说做性能优化时是指后三个:本地缓存、分布式缓存、数据库缓存。

    前面三个缓存策略属于网站前端的范畴。

    从硬件介质上来看,缓存分为内存和硬盘两种。

    但从技术上,又可以分成内存、硬盘文件、数据库。

    我们通常意义上说的缓存一般都是基于内存的。

    因为只有内存,才足够快。

    数据库缓存一般也是基于内存的,但这个活一般是DBA在配置数据库的时候就设置好了。

    对于大部分开发人员来说,我们一般所说的缓存优化都是基于本地缓存(ocal cache)和远程缓存(remote cache)。

    而现在远程缓存这个词一般也被分布式缓存这个常用方案所代指。

    4. 什么时候使用缓存?

    4.1 缓存的使用判断

    什么时候使用缓存的判断其实比较简单,抓住两点就行了:

    1、是不是热点数据?

    所谓热点,一般是遵循二八定律,即百分之八十的访问集中在百分之二十的数据上。

    2、是不是读比写多?

    这个比例一般为2:1。

    4.2 什么时候不应该使用缓存?

    反过来就是了。

    1、没有热点数据不要使用缓存,也没什么意义。

    因为内存资源是比较宝贵的。

    2、频繁修改的数据不要使用缓存。

    因为可能写入后还来不及读取就已失效或被淘汰,并且容易产生脏读。

    4.3 合理使用缓存

    最后,最重要的是确认是否需要使用缓存?

    确定了后,再选择合适的缓存工具及使用缓存的方式。

    5. 缓存时常见的一些问题

    使用缓存优点很多,但也存在一些很常见的问题。双刃之剑,就看怎么用了。

    列举一些我们工作中常见的一些缓存问题,并给出至少一种解决方案。

    5.1 缓存更新带来的数据不一致与脏读

    缓存更新的常见策略有:

    1、先更新数据库再更新缓存;

    2、先更新数据库再删除缓存;

    3、先删除缓存再更新数据库;

    4、定时清理缓存;

    5、有请求访问数据时,判断缓存是否过期,过期从数据库中刷新缓存。

    在这几种方案中,如果修改缓存与数据库不在同一个事物中,就带来了数据不一致和脏读的问题。

    对应方案1:先删除缓存再更新数据库,并且在同一个事物中。

    对应方案2:缓存自动失效后,另外的异步线程进行缓存更新。

    对应方案3:缓存更新在并发、分布式要考虑锁,redis天生就是单线程,比较有优势。

    5.2 怎么做缓存预热

    缓存预热是指在用户可访问服务之前,将热点数据加载到缓存的操作,这样可以有效避免上线后瞬时大流量造成系统不可用。

    缓存预热的一般性策略:

    1、开发个缓存刷新功能,手工刷新;

    2、项目启动的时候自动进行加载(一般为字典表等数据量不大的数据);

    3、设置个定时器,自动刷新缓存;

    4、提前统计热点数据,事先批量加载到如redis这样缓存工具中。

    5.3 缓存重建

    缓存失效后,重建热点缓存,如果耗时较长,在重建过程中,性能、负载不好。

    对应方案:

    1、正常情况下,交错缓存失效时间,减轻缓存压力;

    2、崩溃失效的情况下,可以使用带持久化功能的缓存来恢复,比如Redis;

    3、如果是MongoDB则不太一样,它是采用mmap来将数据文件映射到内存中,所以当MongoDB重启时,这些映射的内存并不会清掉,不需要进行缓存重建与预热。

    5.4 缓存雪崩与可用性

    缓存雪崩:缓存在同一时间失效时,访问直达数据库层,可能导致DB挂掉、系统崩溃。

    对应方案1:交错缓存失效时间或随机缓存失效时间。

    对应方案2:主从热备(Redis Sentinel)。

    对应方案3:集群/水平切分(Redis Cluster、一致性哈希)。

    5.5 缓存穿透

    缓存穿透:持续高并发访问某个不存在的Key。

    对应方案1:空值缓存。

    对应方案2:布隆过滤器(bloom filter) + bitmap。穷举可能访问的数据放入bitmap中,使用hash访问。

    5.6 缓存击穿

    缓存击穿:热点Key失效,高并发请求,直击数据库。

    缓存击穿与缓存穿透很相似,不同点是是缓存击穿前访问的是真实的热点数据,只是在某一刹那失效了,造成了击穿的效果。

    这样看,它其实也是缓存雪崩的一个特例。与雪崩的区别即在于击穿是对于特定的热点数据,而雪崩是全部数据。

    对应方案:多级缓存及交错失效时间 + LRU 淘汰算法。

    对于热点数据进行二级或多级缓存,并对于不同级别的缓存设定不同的失效时间,缓解雪崩。

    此外可使用LRU的变种算法LRU-K缓存数据。

    5.7 缓存降级

    缓存降级是服务降级中的一环。

    在访问量剧增,导致服务出现问题时,为了保证核心服务可用,防止发生缓存雪崩,可进行服务降级。

    以redis为例,比较常见的做法就是,不去数据库查询,而是直接返回默认值给用户。

    缓存降级也可根据日志级别进行预案设置。

    6. 分布式缓存的选型

    说了这么多缓存的原理与策略,说说我们在实际工作中应该怎么去做缓存选型。

    以下就是常用的几种缓存工具。

    6.1 Ehcache

    Ehcache是纯Java开源的缓存框架,最早从hibernate发展而来,现在算是springboot中的官配缓存工具,整合简单。特点如下:

    • 快速,针对大型高并发系统场景,Ehcache的多线程机制有相应的优化改善;

    • 简单,很小的jar包,简单配置就可直接使用,单机场景下无需过多的其他服务依赖;

    • 支持多种的缓存策略,灵活;

    • 缓存数据有两级:内存和磁盘,与一般的本地内存缓存相比,有了磁盘的存储空间,将可以支持更大量的数据缓存需求;

    • 具有缓存和缓存管理器的侦听接口,能更简单方便的进行缓存实例的监控管理;

    • 支持多缓存管理器实例,以及一个实例的多个缓存区域。

    6.2 Guava Cache

    Guava Cache是Google开源的Java重用工具集库Guava里的一款缓存工具,特点如下:

    • 自动将entry节点加载进缓存结构中;

    • 当缓存的数据超过设置的最大值时,使用LRU算法移除;

    • 具备根据entry节点上次被访问或者写入时间计算它的过期机制;

    • 缓存的key被封装在WeakReference引用内;

    • 缓存的Value被封装在WeakReference或SoftReference引用内;

    • 统计缓存使用过程中命中率、异常率、未命中率等统计数据。

    6.3 Memcache

    memcache本身不支持分布式,是通过客户端的路由处理来达到分布式解决方案的目的。特点如下:

    • memcache使用预分配内存池的方式管理内存;

    • 所有数据存储在物理内存里;

    • 非阻塞IO复用模型,纯KV存取操作;

    • 多线程,效率高,会遇到锁等上下文切换问题;

    • 只支持简单KV数据类型;

    • 数据不支持持久化。

    6.4 Redis

    Redis是当前主流的高性能内存数据库,多用于存储缓存数据,并能实现轻量级的MQ功能。特点如下:

    • 临时申请空间,可能导致碎片;

    • 有VM机制,能存储更多数据,超过内存空间后会导致swap,降低效率;

    • 非阻塞IO复用模型,支持额外CPU计算:排序、聚合,会影响IO性能;

    • 单线程,无锁,无上下文切换,单实例无法利用多核性能;

    • 支持多种数据类型:string / hash / list / set / sorted set;

    • 数据支持持久化:AOF(语句增量)/RDB(fork全量);

    • 天然支持高可用分布式方案sentinel +;

    • cluster(故障自动转移+集群)。

    6.5 推荐

    通常情况下,单机我们会用Ehcache,甚至java自己的concurrenthashmap来实现缓存。

    分布式一般选择redis。

    07 附录:参考资料

    缓存那些事 | 美团明辉

    什么是缓存预热,缓存更新,缓存降级的概念?

  • 相关阅读:
    javascript类的封装『转』
    闭包概念
    继承
    理解面向对象
    cookie的使用
    开园啦。。。
    Myslq 之常用命令
    Myslq 之修改提示符
    Myslq 之登陆、退出
    Javascript 之 Number
  • 原文地址:https://www.cnblogs.com/yaomaomao/p/12195046.html
Copyright © 2020-2023  润新知