跳到主要内容
JackSparrow414
返回

Java中的锁(四)利用读写锁实现高性能网页缓存

本文导航

展开本文导航

背景

在我们的销售页面,有一个类似日历的控件,用户可以在该控件中查找当前月份中可以买票的活动。当多个用户访问该控件时,看到的内容是一样的,没必要每个用户发起的请求再去数据库查一遍。为了提高用户体验和避免大促期间该页面给服务器带来压力,我们决定对该控件的响应进行缓存。早期使用Ehcache2的SimplePageFragmentCachingFilter实现。

由于Ehcache2不再维护,我们决定将Ehcache2迁移到Ehcache3。在将Ehcache2迁移到Ehcache3的过程中,发现Ehcache2下的ehcache-web在Ehcache3中不再支持,现有项目中仍使用ehcache-web的SimplePageCachingFilter来缓存网页,于是决定copy SimplePageCachingFilter源码并使用 Ehcache3进行改写。在这个过程中发现了此SimplePageCachingFilter的实现利用里Java的读写锁。

Ehcache2源码解析-如何实现缓存网页

  1. 缓存网页的核心逻辑在于net.sf.ehcache.constructs.web.filter.CachingFilter#buildPageInfoEhcache CachingFilter.buildPageInfo 从 BlockingCache 获取或构建页面缓存
  2. blockingCache的get方法如下所示BlockingCache.get 获取读锁并在缓存未命中时获取写锁
  3. 先拿到一个锁,该锁是net.sf.ehcache.concurrent.ReadWriteLockSync的实例ReadWriteLockSync 封装 ReentrantReadWriteLock 的读锁和写锁 该类的内部有一个Java的ReentrantReadWriteLock
  4. 多线程并发时,拿到锁之后先去获取读锁,拿到之后立刻释放掉。然后去拿写锁,如果第一次拿到写锁则返回的Element对象是null,此时写锁未释放,其他并发的线程拿不到写锁就等待一定时间,此时间可配置。BlockingCache.attemptLock 根据超时配置尝试获取锁
  5. 拿到写锁之后则返回到第1步,调用buildPage,最后将该对象blockingCache.put进去,写锁在这里释放BlockingCache.put 写入缓存后在 finally 中释放写锁
  6. 总结一下Ehcache2的实现网页缓存的逻辑,通过配置Filter的url-pattern,在浏览器端请求该url时会对response进行缓存。具体的缓存逻辑是由Java的读写锁ReentrantReadWriteLock完成的,使用ReentrantReadWriteLock确实能解决大量并发请求一个网页内容时的性能问题

读写锁ReentrantReadWriteLock解析

读写锁的特性

直接看注释

  1. 多线程并发时,如果都去获取读锁,则不互斥;如果此时有线程拿到了写锁,再去获取读锁,则需要等待写锁释放ReentrantReadWriteLock.ReadLock.lock 的共享获取语义与源码
  2. 获取写锁时,如果此时没有其他线程拿到读锁或写锁,则直接获取;如果其他线程此时拿着读锁或写锁,则需等到锁的释放ReentrantReadWriteLock.WriteLock.lock 的互斥获取语义与源码

读写锁是如何实现的?

在Java中的锁(二)实现自定义锁中提到了,如果想要自定义锁,需要实现Lock接口+重写AQS的方法,并在实现的Lock接口中调用AQS的模板方法,这些方法最终会调用重写的AQS方法。

  1. 看一下它的主要结构,可以看到它的实现是按照上面我们所说的思路实现ReentrantReadWriteLock 内部 Sync、ReadLock 与 WriteLock 类结构

  2. 以读锁的lock方法为例,调用AQS的模板方法acquireShared,该方法会最终调用ReentrantReadWriteLock内部类Sync中的tryAcquireShared方法 AQS acquireShared 调用 tryAcquireShared 获取共享锁的源码

如何将Ehcach2-web的源码迁移到Ehcach3中?/ 如何自定义Filter实现高性能网页缓存?

看了上面的逻辑和读写锁的特性,实现起来就很简单了

  1. 自定义一个Filter,该Filter在缓存网页时使用Ehcache3。具体做法是将网页的Response放到Ehcache中
  2. 那怎么提高 高并发下对同一个网页的访问性能呢?使用读写锁
  3. 每个需要缓存的网页url中应该都有一个标识,此标识作为缓存的key
  4. 当多个并发请求都请求同一个网页时,拿到key去cache中找,找到则直接返回
  5. 找不到则开始获取读锁,拿到读锁什么都不做,直接释放;如果拿不到读锁,则等待
  6. 释放完读锁,立刻去竞争写锁,拿到写锁之后,先判断此时cache中有没有,因为当前线程可能不是第一个拿到写锁的线程,之前的写锁可能已经将内容写入cache中了,有则直接释放写锁;cache中没有则开始将网页的Response写入到cache中,然后释放写锁

分享本文:

继续阅读本系列

Java 中的锁

  1. JAVA 中的锁(一)锁分类
  2. Java中的锁(二)实现自定义锁
  3. Java中的锁(三)结合Condition实现等待通知
  4. Java中的锁(四)利用读写锁实现高性能网页缓存正在阅读

评论

欢迎提问、纠错和分享经验。发表评论需要登录 GitHub;中英文版本共享评论。

评论仅在正式网站开放。