【Ehcache技术专题】「入门到精通」带你一起从零基础进行分析和开发Ehcache框架的实战指南(9-页面缓存)

系列文章目录

本系列课程主要针对于Ehcache缓存框架功能的开发实践全流程技术指南!

  • 第一节:Ehcache缓存框架的基本概念和简介
  • 第二节:Ehcache缓存框架的配置分析和说明
  • 第三节:Ehcache缓存框架的缓存方式的探索
  • 第四节:Ehcache缓存框架的配置分析和说明
  • 第五节:Ehcache缓存框架的查询分析和说明
  • 第六节:Ehcache缓存框架的监听器功能扩展
  • 第七节:Ehcache缓存框架的并发功能的开发
  • 第八节:Ehcache缓存框架的同步阻塞的开发
  • 第九节:Ehcache缓存框架的页面缓存的开发
  • 第十节:Ehcache缓存框架之结合Spring整合

阻塞性缓存

BlockingCache就是为使用页面缓存而设计的,当多个线程同时请求一个页面时,如果缓存中存在对应的页面,则可以直接返回,Read锁之间不会阻塞;

如果对应的页面不存在,那么这个时候只有一个线程会返回null,其它线程都将被阻塞,返回值为null时,Ehcache将会把对应的页面put到BlockingCache中,此时该线程所持有的Write锁将释放,而其它被阻塞的线程也将可以顺利的获取到该页面。

这样一来就可以避免多个线程在get到的元素为null时,都同时往缓存中put对应的页面,造成不必要的资源浪费。如果有页面缓存这样的需求的话使用BlockingCache是再合适不过了。

该线程所持有的Write锁将释放,其它被阻塞的线程也将可以顺利的获取到该页面。如果再次有线程请求相同的页面,并且缓存中存在该页面,那么可以直接返回。如果缓存中不存在该页面,那么阻塞的线程将再次被阻塞,直到BlockingCache中有新的页面可用。

页面缓存

上面说的BlockingCache就是为页面缓存设计的。如果用户需要自己使用BlockingCache时注意在获取到的元素为null时要释放对应的Write锁。这个时候有两种方法。

读写缓存机制

  1. 调用BlockingCache的任意put方法,往其中存放一个对应key的元素;
  2. 自己定义一个类继承BlockingCache,然后开放一个释放锁的方法,对应逻辑可以参考BlockingCache的doAndReleaseLock()方法,这是因为其内部获取锁的方法getLockForKey()的访问类型是protected。

代码案例


public class MyBlockingCache<K, V> extends BlockingCache<K, V> {
   
    public MyBlockingCache(Cache<K, V> cache) {
   
        super(cache);
    }
    public void releaseLockForKey(K key) {
   
        Lock lock = getLockForKey(key);
        if (lock instanceof ReentrantLock) {
   
            ((ReentrantLock) lock).unlock();
        }
    }
}

releaseLockForKey方法中,我们通过调用getLockForKey方法获取对应 key 的锁,并使用unlock方法释放该锁。由于getLockForKey方法的访问权限是protected`,因此我们可以在子类中进行调用和重写。

public class BlockingCacheExample {
   
    public static void main(String[] args) {
   
        CacheManager cacheManager = EhcacheManager.newCacheManagerBuilder().build(true);
        Cache<String, Integer> cache = cacheManager.createCache("myCache",
                CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, Integer.class, ResourcePoolsBuilder.heap(10))
                        .withExpiry(Expirations.timeToLiveExpiration(Duration.ofMinutes(5)))
                        .build());
        cache.put("key1", 100); // 调用put方法存放一个key为"key1"的元素
        MyBlockingCache<String, Integer> myCache = new MyBlockingCache<>(cache);
        myCache.releaseLockForKey("key2"); // 调用自定义类中的释放锁的方法
    }
}

锁超时设置

BlockingCache在获取锁时如果被阻塞了,那么阻塞时间是不定的,它有可能会非常长。如果不希望阻塞时间太长的话,我们可以通过BlockingCache的setTimeoutMillis()方法设置最长阻塞时间,单位为毫秒,这样如果一个线程在timeoutMillis时间内还没有获取到对应的锁则将抛出LockTimeoutException。


ehcache-web浏览器缓存

Ehcache除了支持对象的缓存之外,还可以对Web页面进行缓存。这是通过ehcache-web模块下的Filter支持的。Ehcache会自动对Response进行gzip压缩,并且将压缩后的内容保存在缓存中。

缓存压缩

如果客户端是支持gzip的,那么服务端将直接返回gzip之后的Response,否则将从缓存中提取出来对应的Response进行解压缩,然后再返回给客户端。如果客户端是支持gzip的,那么它必须在请求的Header中包含“Accept-Encoding: gzip”。

SimplePageCachingFilter

ehcache-web模块下页面缓存Filter的一个简单实现,适用于可以压缩的Http响应(response),如HTML、XML、JSON等。它会使用通过CacheManager的静态方法create创建的单例CacheManager,这样如果之前已经存在CacheManager的实例了的话,这里就会直接拿来用,而不会再创建了。

这里一般默认情况下会取类根路径下的ehcache.xml文件或ehcache-failsafe.xml文件来创建CacheManager,但如果我们的项目中整合了Ehcache和Spring,且在Spring配置文件中指定的Ehcache的配置文件不是默认位置的话,Spring将使用指定的配置文件优先初始化CacheManager,这样SimplePageCachingFilter中要使用CacheManager时就不会再初始化了,而是直接使用Spring初始化的。

SimplePageCachingFilter适用于缓存整个页面的情况,如果只需要缓存某一个片段,如使用jsp:include包含的部分,请使用SimplePageFragmentCachingFilter。

calculateKey

Ehcache是以键值对的形式保存元素的,对于页面的缓存也不例外。页面缓存使用的key是通过SimplePageCachingFilter的calculateKey()方法获取的。其内部逻辑是获取请求时的URI及后面的查询字符串作为key进行返回,如“/user/index.jsp?name=abc”,这使得它的应用范围非常广。它不依赖于主机名和端口号,这将使得它同样适用于有多个域或多个端口请求同样内容的情况。如果有需要,我们可以对calculateKey方法进行重写,从而实现我们自己的计算key的逻辑。

可配置的初始化参数

对于SimplePageCachingFilter而言,可配置的初始化参数有两个,cacheName和blockingTimeoutMillis。

cacheName

在使用SimplePageCachingFilter时,我们需要通过初始化参数cacheName(init-param)指定当前Filter用于缓存的Cache的名称,对应于Ehcache配置文件中Cache的名称,默认为SimplePageCachingFilter。

Xml代码
<filter>  
   <filter-name>ehcacheFilter</filter-name>  
<filter-class>net.sf.ehcache.constructs.web.filter.SimplePageCachingFilter</filter-class>  
   <init-param>  
      <param-name>cacheName</param-name>  
      <param-value>webPageCache</param-value>  
   </init-param>  
</filter>  
<filter-mapping>  
   <filter-name>ehcacheFilter</filter-name>  
   <url-pattern>/index.jsp</url-pattern>  
   <dispatcher>FORWARD</dispatcher>  
   <dispatcher>INCLUDE</dispatcher>  
   <dispatcher>REQUEST</dispatcher>  
</filter-mapping> 
blockingTimeoutMillis

在多线程并发的情况下,为了避免多个线程请求同一个key而做重复的无用的工作, CachingFilter底层使用的是BlockingCache,如果提供的Cache不是BlockingCache,那么Ehcache将使用BlockingCache对当前Cache进行封装。BlockingCache允许并发读已经存在于cache中元素,但是如果进行读操作的时候对应的元素是不存在的,那么除第一个获取到锁(Lock)的线程之外的线程都会被阻塞起来,直到有线程往里面放置了对应的元素为止。

等待状态LockTimeoutException

默认情况下,如果第一个线程没有往Cache里面put对应的元素的话,这些线程会一直处于等待状态,这样时间长了之后服务端会因为拥有太多请求的连接没有返回而难堪重负。为此,我们可以在定义CachingFilter时通过初始化参数blockingTimeoutMillis来解决这一问题。通过设置blockingTimeoutMillis参数,BlockingCache最多只会阻塞指定的时间,即被阻塞的线程只会最多阻塞给定的时间,之后如果超时的话会抛出LockTimeoutException。

SimpleCachingHeadersPageCachingFilter

SimpleCachingHeadersPageCachingFilter继承自SimplePageCachingFilter,以提供HTTP缓存头信息。也就是说使用SimpleCachingHeadersPageCachingFilter与使用SimplePageCachingFilter几乎是一样的。所不同的是前者在构建返回信息的时候会设置“Last-Modified、Expires、Cache-Control、ETag”这四个缓存头信息,如果在设置之前这些信息已经存在的话,那么它们将会被忽略,而直接使用SimpleCachingHeadersPageCachingFilter重新生成过的。

由于浏览器和其它HTTP客户端都拥有包含在返回信息头中的过期信息,以至于在信息未过期前,它们不需要再次请求对应的页面。SimplePageCachingFilter是没有设置这些头信息的,那也就意味着使用SimplePageCachingFilter时每次客户端都需要向服务端请求页面。

SimplePageCachingFilter

那你为什么还要用SimplePageCachingFilter呢?考虑这样一种场景,我们有一个页面包含了动态数据,我们对它进行了缓存,如果使用SimpleCachingHeadersPageCachingFilter的话,返回头信息中会包括过期信息,这也就意味着在客户端本地缓存的页面过期之前它不会再向服务端请求对应的页面,而如果此时服务端更新了这个页面的缓存,或者是移除了这个缓存,之前在本地缓存过该页面的客户端只能等到其本地的信息正常超时后才能得到服务端更新的页面。而此时如果我们使用的是SimplePageCachingFilter的话,由于没有过期等缓存头信息,客户端每次都会向服务端发起请求,这样服务端页面有变化时客户端都能即时获取到更新。在这种情况下,缓存的作用是减少服务端的负载,而不是减少浏览器的请求。

SimpleCachingHeadersPageCachingFilter

SimpleCachingHeadersPageCachingFilter缓存的页面信息是包含“Last-Modified、Expires、Cache-Control、ETag”这四个头信息的。这样当缓存中存在对应的页面时,所有的请求获取到的页面的头信息都会是一样的。

SimplePageFragmentCachingFilter

SimplePageFragmentCachingFilter适用于对使用jsp:include包含的页面的缓存。除了不对Response进行gzip之SimplePageFragmentCachingFilter能做所有SimplePageCachingFilter能做的事情,这里就不对SimplePageFragmentCachingFilter做过多重复的介绍了。

最近更新

  1. TCP协议是安全的吗?

    2023-12-23 13:48:02       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-23 13:48:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-23 13:48:02       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-23 13:48:02       20 阅读

热门阅读

  1. Python实现逐行读取文本文件的几种方法

    2023-12-23 13:48:02       42 阅读
  2. centos 安装 Miniconda

    2023-12-23 13:48:02       45 阅读
  3. Python:Scrapy+Selenium相关依赖包记录

    2023-12-23 13:48:02       37 阅读
  4. 面向LLM的App架构——技术维度

    2023-12-23 13:48:02       33 阅读
  5. k8s中Chart的命名模板

    2023-12-23 13:48:02       38 阅读
  6. 19-二分-值域二分-有序矩阵中第 K 小的元素

    2023-12-23 13:48:02       38 阅读
  7. c# opencv 提取图片文字,如读取身份证号

    2023-12-23 13:48:02       42 阅读
  8. 各大高校科研工具链培训PPT汇总

    2023-12-23 13:48:02       38 阅读
  9. 嵌入式中的定时器概念

    2023-12-23 13:48:02       50 阅读
  10. 黑苹果安装经验总结2023-12

    2023-12-23 13:48:02       49 阅读