缓存策略基础初探

本文深入探讨缓存的基础知识,包括缓存的定义、作用,以及数据库缓存、应用程序缓存和Web服务器缓存的原理与应用场景。通过对MySQL的查询缓存、ORM框架如Mybatis的缓存机制,以及Apache和Nginx的缓存策略配置的讲解,阐述了缓存在提升系统性能中的重要角色。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


缓存策略基础初探

一、引言

由于我个人对于缓存这块内容基础极为薄弱,在不久前一次面试中被逮着问了,当时大脑空空如也,对于缓存只能蹦出几个零零散散的片段:ecache,redis,读取缓存数据代替内存来优化读写速度,Map存储。所以那次面试也毫无悬念的GG了。没办法,只能从头来学习下缓存这块内容。

二、缓存基础

1、什么是缓存

要想了解一个事物,就得从源头知道这东东是啥?为什么要有它?它存在的作用?

这些概念性的描述就借用现成的啦:

  • 缓存就是数据交换的缓冲区(称作:Cache),当某一硬件要读取数据时,会首先从缓存汇总查询数据,有则直接执行,不存在时从内存中获取。由于缓存的数据比内存快的多,所以缓存的作用就是帮助硬件更快的运行。
  • 缓存往往使用的是RAM(断电既掉的非永久存储),所以在用完后还是会把文件送到硬盘等存储器中永久存储。电脑中最大缓存就是内存条,硬盘上也有16M或者32M的缓存。
  • 高速缓存是用来协调CPU与主存之间存取速度的差异而设置的。一般CPU工作速度高,但内存的工作速度相对较低,为了解决这个问题,通常使用高速缓存,高速缓存的存取速度介于CPU与主存之间。系统将一些CPU在最近几个时间段经常访问的内容存在高速缓存,这样就在一定程度上缓解了由于主存速度低造成的CPU“停工待料”的情况。
  • 缓存就是把一些外存上的数据保存在内存上而已,为什么保存在内存上,我们运行的所有程序里面的变量都是存放在内存中的,所以如果想将值放入内存上,可以通过变量的方式存储。在JAVA中一些缓存一般都是通过Map集合来实现的。

上述内容很好地回答了前面的问题。至于缓存的作用,被我引用的那篇文章也描述的很好,下面再次不要脸地抄一段:

缓存在不同的场景下,作用是不一样的:

  1. 操作系统磁盘缓存 ——> 减少磁盘机械操作。
  2. 数据库缓存——>减少文件系统IO。
  3. 应用程序缓存——>减少对数据库的查询。
  4. Web服务器缓存——>减少应用服务器请求。
  5. 客户端浏览器缓存——>减少对网站的访问。

而我们平时做Java开发常接触到的应该就是2、3、4了吧,下面我就从这几个方面入手开始学习。

2、数据库缓存

下面先拿最常用的mysql数据库来说吧。mysql数据库是通过QC(Query Cache)来实现数据库缓存的,当开启QC(默认是开启的,要关的话,把query_cache_size/type的值设成0,永久关闭就到mysql的配置文件里该)后,数据库会在执行select时,将其结果存储到QC,之后若再调用相同的SQL,就不会再去访问数据库表。直接从QC取值,这样一来是不是快了很多呢?

不过,这样做会有个问题,如果在数据进行缓存之后,原来的数据库表数据发生了更改,那么从QC取出来的数据就是错误的了。因此QC有个对应的机制,如果数据库表发生更改,那么这个数据库表相关的所有缓存都会失效。

根据QC的特点可知,要做mysql的查询缓存,该数据表的数据变动频率应该要比较小,否则碰到经常变动的表,缓存频繁失效,那开不开QC其实区别不大。

顺便扩展下除了开查询缓存外mysql查询优化的几个长考虑到的点:

  1. 加索引(这个是最常用的了);

  2. 看慢查询日志,查看需要优化的SQL。加了索引开了QC之后还是觉得很慢,就需要查下慢查询日志了
    ,开启方法为:set global slow_query_log = 1; 1为开启 0为关闭,此外,再介绍几个相关的参数(slow_query_log_file 记录日志的文件名;log_queries_not_using_indexes 这个参数设置为ON,可以捕获到所有未使用索引的SQL语句;long_query_time 设置一个时间,超过该时间值的SQL会被记录下来);

  3. 分库表。当数据量非常庞大几千万甚至上亿的时候,就需要分库表。先说分表,一般分表的方式有两种:水平分表和垂直分表。顾名思义,一个横着分一个竖着分。水平分表一般是数据量很大,数据表装不下时,要拆到多个表中,可以根据id,时间戳,或者其他的字段来拆这时候方法通常有以下三种:

    1. hash取模法。就是在分表前预估下这张表涉及到的数据量,假设有4000W,每张表可以容纳1000W,那我们可以大致拆成四张表,为了使数据能均匀地存到这四张表里,避免热点问题(对数据库的操作集中到一张表中,其他的表基本不被操作到,这样子压力就都集中到一张表上了)。所以我们对某个字段,比如说ID对4取模,这样子存进来的数据就均摊到四张表上,避免了热点问题,不过缺点就是较难再扩容,哪天需要再加个表,那取模怎么取?原来的那些ID怎么办?
    2. 制定range范围。这个就很简单了,4000W数据嘛,那就ID是1~1000万的扔一张表里,后面几个表依次类推。那这种方法要扩容就很方便了,不过反过来看,在一个范围内的ID都集中在一张表上,那么在那个range范围内数据操作不就都集中在这张表上了,这个方案会有热点问题存在的缺陷;
    3. 前两种方案各有长处,各有短处,那么,将他们结合起来不就完美了吗?所以group组的方案应运而生。假设目前计划拆4张表,每张表1000W,那么可以这样,把这些表归为2组,即两个group,第一个group放2张表,第二个group也是,然后在group之间使用hash取模法,即group1的ID为1-2000万,group的ID也为0-2000万,至于不同group取出的ID根据他们所处的group不同也就能唯一标识了。然后在group内根据range来分表。这种方法在一定程度上能结合前两种方法做优劣互补。

接下来说说垂直分表。垂直分表不想水平分表和表的总数据量有密切的关系。垂直分表更多的是基于业务对一个字段较多的大表,或者,某张表里有个或者有一些字段存储的信息量比较大,但是一般又查不到的进行拆分,拆成多个表,以优化查询速度。

至于分库,我的理解就是根据数据库的数据量和业务场景,将各个数据表拆开放到位于不同服务器的数据库中,以缓解服务器压力,并优化数据库操作。这块内容接触较少,理解还比较浅,后续再继续学习。

3、应用程序缓存

主要分成对象缓存、插叙缓存和页面缓存。

  1. 对象缓存:由ORM框架,像Hibernate和Mybatis都有提供,透明性访问,细粒度缓存数据库查询结果,无需用代码显式编程,在缓存策略中较为方便。适合OLTP(联机事务处理)应用;
  2. 插叙缓存:缓存数据库查询结果,类似Mysql的QC,适用较为耗时且对时效性要求较低的场景,当记录发生修改后记得清除旧的缓存。与查询缓存适用场景不同,可以互补;
  3. 页面缓存:作用是可以减轻数据库和应用服务器的压力,可以极大提高页面渲染速度,难点在于过期缓存的清理。分为以下几类:
    1. 动态页面静态化:利用模板技术来把动态页面生成静态的html,同时修改链接,使得之后的请求直接访问静态页面;无法做权限验证也无法显示个性化信息,不过可以通过AJAX来弥补动态页面静态化的一些缺点。多用于互联网CMS/新闻类的Web应用,以及部分的BBS;
    2. Servlet缓存:对请求返回的页面结果做缓存,适合粗粒度的页面缓存,如:新闻发布;相较于上一点,好处是可以做权限检查,在OScache中有提供简单的Servlet缓存,只需在web.xml中做好相应配置即可。也可以自己写代码实现:可以参考:https://www.cnblogs.com/henuyuxiang/p/7486120.html里面简单地实现了一个自定义的缓存类。
    3. 页面内部缓存:可以针对动态页面的局部片段进行缓存,适用于不常更新的个性化页面:如博客。同上,OScache也提供了简单的页面内部缓存,可以拓展JSP Tag来实现页面局部缓存。

下面补充下常用的ORM框架怎么实现对象缓存。

Mybatis。Mybatis提供了一级缓存和二级缓存,如下图:

根据图示可以看出,一级缓存是基于SqlSession级别的缓存。因此要做缓存时需要构造一个SqlSession对象,存储的数据结构为HashMap。而二级缓存是基于Mapper的,由多个一级缓存共同作用,可以看出二级缓存是跨多个SqlSession的。

至于使用的话,一级缓存是默认开启的,二级缓存则需要手动开启,需要再mybatis的配置文件中加入

<!--开启二级缓存  -->
<settings>
	<setting name="cacheEnabled" value="true"/>
</settings>

然后在需要缓存的Mapper文件里加

<cache></cache>

不过故事到这里还没完,上述的二级缓存是Mybatis自带的,只能用于单体应用,碰到分布式服务就GG了。为了实现分布式应用的二级缓存,需要去整合分布式缓存框架,如:Ehcache(其他的框架又碰到再说,Ehcache支持多缓存管理器实例,以及一个实例多个缓存区域,此外,默认提供Hibernate的缓存实现)。
至于操作嘛,大概分为以下几步:
	1. 导入mybatis-ehcache整合包(就是相关的一些jar包啦);
	2. 配置Mybatis配置文件开启二级缓存;
	3. 在需要开缓存的Mapper文件中整合ehcache缓存:
	<!-- 开启本mapper的namespace下的二级缓存
		type:指定cache接口的实现类的类型,不写type属性,mybatis默认使用PerpetualCache
		要和ehcache整合,需要配置type为ehcache实现cache接口的类型
	-->
	<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
	4. 配置缓存参数,在classpath下新建ehcache.xml文件。配置如下:
	<?xml version="1.0" encoding="UTF-8"?>
	<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
	<diskStore path="【数据在磁盘中的存储位置】"/>
    <defaultCache
        maxElementsInMemory="10000"【内存中缓存element的最大数目】
        eternal="false"【设置true则缓存的element永不过期,若为false,则还需结合timeToLiveSeconds来判断】
        maxElementsOnDisk="10000000"【磁盘中缓存element的最大数目,0为无穷大】
		overflowToDisk="true"【缓存溢出时是否将过期的element缓存到磁盘上】 
		【下列为可选属性】
		timeToIdleSeconds="120"【数据闲置时间,即缓存数据前后两次访问的间隔时间超过此数值时,数据会被删掉,默认为0,即无穷大】
        timeToLiveSeconds="120"【缓存的element的有效时间,默认为0】
		diskSpoolBufferSizeMB="30"【设置磁盘缓存的缓冲区大小,默认30M】 
		diskPersistent="false"【VM重启时是否启用磁盘保存ehcache中的数据,默认为false】 
        diskExpiryThreadIntervalSeconds="120"【磁盘缓存清理线程执行间隔时间,默认120s】
        memoryStoreEvictionPolicy="LRU"【缓存满时,加入新element的处理策略,默认LRU(近期最少使用),此外还有LFU(最不常使用)和FIFO(先进先出)】>
    <persistence strategy="localTempSwap"/>
	</defaultCache>
 	</ehcache>

二级缓存的使用场景:适用于查询请求较多,粒度较粗,且对查询结果返回的实时性要求不高的情况,可以使用Mybatis自带的二级缓存来缓解数据库压力,提升性能。实际的业务场景一些查询数据较多,较为耗时的统计分析sql、账单查询的sql等。这些数据查询比较频繁,且通常发生变化的概率比较低,所以就先设置好mybatis的缓存清空时间,然后再根据具体的业务场景隔一段时间去刷新缓存。

4、Web服务器缓存

web服务器缓存的一般分为三类:浏览器缓存(客户端的缓存,前面提到的页面缓存是服务端缓存、个人感觉springmvc配置静态资源的缓存也是页面缓存的一种实现)、代理服务器缓存和网关缓存(也就是CDN或者叫反向代理缓存)。

先讨论代理服务器缓存。一般常用的代理服务器有两种:正向代理apache和反向代理nginx,下面逐一介绍它们的缓存策略配置:

  1. apache服务器的缓存策略配置。apache要配置缓存策略要用到配置文件里的两个模块,mod_expires和mod_headers。
    1. mod_expires,主要是用来自动生成报文头的Expires标签和Cache-Control标签,要配置它很简单,要配置的指令就3个:

      • ExpiresActive:打开或关闭产生的Expires标签和Cache-Control标签,值为On或者Off,如:ExpiresActive On
      • ExpiresByType:制定MIME类型文档(比如说:text/html)的过期时间,如:ExpiresByType image【文档类型,也可以是text、application或者其他】/* “access plus 10 years”
      • ExpiresDefault:所有文档的默认过期时间,如:ExpiresDefault “access plus 6 months”
      • PS:过期时间的写法:
        • “access plus 1 month”
        • “access plus 4 weeks”
        • “now plus 30 days”
        • “modification plus 5 hours 3 minutes”
        • A2592000 【A加时间,A同acces或now,过期时间从访问时开始计算】
        • M604800 【M加时间,M同modification,过期时间从最近的修改时间开始计算,所以它只对静态文件起作用,不对动态文件起作用】
    2. mod_headers。这个顾名思义,就是设置header的模块。示例如下:

       # YEAR
       Header set Cache-Control “max-age=2592000″ 	
       # WEEK
       Header set Cache-Control “max-age=604800″  
       # NEVER CACHE
       Header set Expires “Thu, 01 Dec 2003 16:00:00 GMT”
       Header set Cache-Control “no-store, no-cache, must-revalidate”
       Header set Pragma “no-cache”			
      
    可以看出,这个模块设置的内容其实和mod_expires差不多的,所以这两个模块设置其中一个就够了。
  2. nginx要配置的相关模块就一个HTTP Headers,可以在这个模块里设置任意的HTTP报文头,模块下有两个指令:add_header和expires。
    1. add_header,语法为:add_header 【name】 【value】。默认值为none。在http、server和location三处均可配置。示例如下:

       location /foo1 {
       	add_header foo1 1;
       	rewrite / /foo2;
       }
      
    不过要注意的是,它的处理阶段比location处理晚,虽然可以写在location中,但如果rewrite别的location,那么上一个location中尚未处理的add_header就会丢失。像示例里最终的header就没有foo1。
    1. expires,语法为:expires [time|epoch|max|off],默认值为:expires off。在http、server和location三处均可配置。示例如下:

       location /foo1 {
       	expires 1h;
       }
      

关于缓存的基础先简单理解到这里,后面有学习到新的再更。。。

参考内容来源:

https://blog.csdn.net/zhengzhaoyang122/article/details/82184029

https://blog.csdn.net/Srodong/article/details/88390671

https://mp.weixin.qq.com/s/I57Y5btF-FcL6pGnUU7LHw

https://www.cnblogs.com/shuzhongruyu/p/8485607.html

https://www.cnblogs.com/shuzhongruyu/p/8485607.html

https://www.cnblogs.com/ysocean/p/7342498.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值