参考:https://www.cnblogs.com/firejava/p/6256788.html

1 缓存分类

按存放地点

  • 客户端缓存: 浏览器缓存,网关、代理服务器缓存
  • 服务端缓存: 页面缓存,数据缓存,数据库缓存


    按缓存分类

  • CDN缓存、反向代理缓存(Nginx)、分布试Cache、本地应用缓存


    缓存媒介

  • CPU、内存、磁盘


    主要问题

  • 过期策略

  • 固定时间
  • 相对时间:比如最近10分钟内没有访问的数据
  • 同步机制(数据一致性)
  • 实时写入(推)
  • 异步刷新(推拉)

浏览器缓存
最靠近用户的缓存,如果启用缓存,用户在访问同一个页面时,将不再从服务器下载页面,而是从本机的缓存目录中读取页面,然后再浏览器中展现这个页面。

浏览器缓存的控制,可以设置meta标签,可以设置数字,也可以设置时间,如下:

  1. <Meta http-equiv=”Expires” Content=”3600″>
  2. <Meta http-equiv=”Expires” Content=”Wed, 26 Feb 1997 08:21:57 GMT>

不过现在的网站为了保证用户访问到最新的内容,一般很少采用浏览器缓存,取而代之的是更加灵活的服务器缓存。

页面缓存
页面缓存是将动态页面直接生成静态的页面放在服务器端,用户调取相同页面时,静态页面将直接下载到客户端,不再需要通过程序的运行和数据库的访问,大大节约了服务器的负载。

早期的网站很多使用发布系统来完成这个功能,在后台发布时将数据和页面模板整合成静态页面,存放在硬盘中。但这样的缺陷很明显,一是后台的程序的编写很 复杂,二是缓存的控制只能通过人为的方式来控制,这对一些更新十分频繁的网站就是一个噩梦,网站可能在不停的做缓存的删除和重建。当然后来出现了一些自动 更新这些缓存的框架,比如PHP的SMARTY模板技术,可以定义缓存过期的时间,自动去更新这些缓存。这对一些信息发布类网站已经确实适用了。

除了整个页面的缓存技术,还有一种技术叫做“网页片段缓存技术”,将页面的部分而不是全部进行缓存。代表作有ESI cache。

数据缓存
但是当 WEB2.0 兴起的今天,信息的发布已经不再是管理员统一发布的了,而是所有的用户都在发布信息,用户发布完信息后当然是想看到这些信息,而不是等到缓存时间到刷新后才看到这些数据,于是数据缓存的相关技术也就应运而生了。

比较有名的数据缓存框架有ehcachememcached

ehcache 有很多缓存的分支(包括页面缓存的模块),但最核心的模块还是它的数据缓存部分,比如,当ehcache和 hibernate 进行整合 时,能将查询出的对象集合放入内存中,下次如果再查询这个查询,将直接从内存中返回这个数据集合,不需要再进行数据库的查询,同时,你可以配置缓存的刷新 模式,有 read-only,nonstrict-read-write,read-write 几种模式,其中read-only表示缓存是不刷新的(要刷新就只有重启了),nonstrict-read-write表示刷新是不及时的,你可以设置 超时的时间去刷新,read-write 表示在数据发生变化时缓存都会发生刷新,具体怎么配置可能就要根据具体业务了。

Memcached 大致的原理也和 ehcache 相同,将数据采用键值的形式存放在内存中,使用时可以将查询的 md5 作为键,查询的结果作为值。相对 ehcache 而言,memcached是一个工 具,ehcache 是一个框架,memcached 更加底层更加灵活,当然你也要写相应的代码去使用它。

这是一张网上的 memcached 图,说明了 memcached 在系统中的位置。
image.png

2 目前缓存的做法分为两种模式

内存缓存:缓存数据存放在服务器的内存空间中。
优点:速度快
缺点:资源有限

文件缓存:缓存数据存放在服务器的硬盘空间中。
优点:容量大
缺点:速度偏慢,尤其在缓存数量巨大时

3 缓存的更新有两种方法

  • 被动更新:先从缓存获取,没有则回源获取,再更新缓存;
  • 主动更新:发现数据改变后直接更新缓存(在分布式场景下,不容易实现)

  • 数据库缓存
    数据库的缓存一般由数据库提供,比如 ORACLE,可以对表建立高速缓存,提高对经常访问的数据的访问速度。

  • 缓存技术选择
    有持久化需求或者对数据结构和处理有高级要求的应用,选择 redis。其他简单的 key/value 存储,选择memcache

分布式缓存如:Redis,memcached(大数据量,加缓存的接口较多的项目)
本地缓存如:ehcache,GuavaCache。(数据量小,需要加缓存的接口较少)

4 Guava Cache 与 ConcurrentMap 的区别

  1. com.google.common.cache;

refresh 和expire 刷新机制
刷新机制:包括 refresh 和 expire 刷新机制
expireAfterAccess: 当缓存项在指定的时间段内没有被读或写就会被回收。
expireAfterWrite:当缓存项在指定的时间段内没有更新就会被回收(数据写入缓存超过一定时间自动刷新)。
refreshAfterWrite:当缓存项上一次更新操作之后的多久会被刷新。

通过分析源码,guava cache 在限制只有1个加载操作时进行加锁,其他请求必须阻塞等待这个加载操作完成;而且,在加载完成之后,其他请求的线程会逐一获得锁,去判断是否已被加载完成,每个线程必须轮流地走一个“”获得锁,获得值,释放锁“”的过程,这样性能会有一些损耗。这里由于我们计划本地缓存1秒,所以频繁的过期和加载,锁等待等过程会让性能有较大的损耗。

  • 跟 expire 的区别是,指定时间过后,expire 是 remove该key,下次访问是同步去获取返回新值
  • 而 refresh 则是指定时间后,不会 remove 该 key,下次访问会触发刷新,新值没有回来时返回旧值
  1. cache = CacheBuilder.newBuilder()
  2. .expireAfterWrite(duration, timeUtil)
  3. .build(new CacheLoader<K, V>() {
  4. @Override
  5. public V load(K k) throws Exception
  6. {
  7. return loadData(k);
  8. }
  9. });
  1. public static void main(String[] args) throws ExecutionException, InterruptedException{
  2. //缓存接口这里是LoadingCache,LoadingCache在缓存项不存在时可以自动加载缓存
  3. LoadingCache<Integer,Student> studentCache
  4. //CacheBuilder的构造函数是私有的,只能通过其静态方法newBuilder()来获得CacheBuilder的实例
  5. = CacheBuilder.newBuilder()
  6. //设置并发级别为8,并发级别是指可以同时写缓存的线程数
  7. .concurrencyLevel(8)
  8. //设置写缓存后8秒钟过期
  9. .expireAfterWrite(8, TimeUnit.SECONDS)
  10. //设置缓存容器的初始容量为10
  11. .initialCapacity(10)
  12. //设置缓存最大容量为100,超过100之后就会按照LRU最近虽少使用算法来移除缓存项
  13. .maximumSize(100)
  14. //设置要统计缓存的命中率
  15. .recordStats()
  16. //设置缓存的移除通知
  17. .removalListener(new RemovalListener<Object, Object>() {
  18. @Override
  19. public void onRemoval(RemovalNotification<Object, Object> notification) {
  20. System.out.println(notification.getKey() + " was removed, cause is " + notification.getCause());
  21. }
  22. })
  23. //build方法中可以指定CacheLoader,在缓存不存在时通过CacheLoader的实现自动加载缓存
  24. .build(
  25. new CacheLoader<Integer, Student>() {
  26. @Override
  27. public Student load(Integer key) throws Exception {
  28. System.out.println("load student " + key);
  29. Student student = new Student();
  30. student.setId(key);
  31. student.setName("name " + key);
  32. return student;
  33. }
  34. }
  35. );
  36. for (int i=0;i<20;i++) {
  37. //从缓存中得到数据,由于我们没有设置过缓存,所以需要通过CacheLoader加载缓存数据
  38. Student student = studentCache.get(1);
  39. System.out.println(student);
  40. //休眠1秒
  41. TimeUnit.SECONDS.sleep(1);
  42. }
  43. System.out.println("cache stats:");
  44. //最后打印缓存的命中率等 情况
  45. System.out.println(studentCache.stats().toString());
  46. }