参考:https://www.cnblogs.com/firejava/p/6256788.html
1 缓存分类
按存放地点
- 客户端缓存: 浏览器缓存,网关、代理服务器缓存
服务端缓存: 页面缓存,数据缓存,数据库缓存
按缓存分类CDN缓存、反向代理缓存(Nginx)、分布试Cache、本地应用缓存
缓存媒介CPU、内存、磁盘
主要问题过期策略
- 固定时间
- 相对时间:比如最近10分钟内没有访问的数据
- 同步机制(数据一致性)
- 实时写入(推)
- 异步刷新(推拉)
浏览器缓存
最靠近用户的缓存,如果启用缓存,用户在访问同一个页面时,将不再从服务器下载页面,而是从本机的缓存目录中读取页面,然后再浏览器中展现这个页面。
浏览器缓存的控制,可以设置meta标签,可以设置数字,也可以设置时间,如下:
<Meta http-equiv=”Expires” Content=”3600″><Meta http-equiv=”Expires” Content=”Wed, 26 Feb 1997 08:21:57 GMT”>
不过现在的网站为了保证用户访问到最新的内容,一般很少采用浏览器缓存,取而代之的是更加灵活的服务器缓存。
页面缓存
页面缓存是将动态页面直接生成静态的页面放在服务器端,用户调取相同页面时,静态页面将直接下载到客户端,不再需要通过程序的运行和数据库的访问,大大节约了服务器的负载。
早期的网站很多使用发布系统来完成这个功能,在后台发布时将数据和页面模板整合成静态页面,存放在硬盘中。但这样的缺陷很明显,一是后台的程序的编写很 复杂,二是缓存的控制只能通过人为的方式来控制,这对一些更新十分频繁的网站就是一个噩梦,网站可能在不停的做缓存的删除和重建。当然后来出现了一些自动 更新这些缓存的框架,比如PHP的SMARTY模板技术,可以定义缓存过期的时间,自动去更新这些缓存。这对一些信息发布类网站已经确实适用了。
除了整个页面的缓存技术,还有一种技术叫做“网页片段缓存技术”,将页面的部分而不是全部进行缓存。代表作有ESI cache。
数据缓存
但是当 WEB2.0 兴起的今天,信息的发布已经不再是管理员统一发布的了,而是所有的用户都在发布信息,用户发布完信息后当然是想看到这些信息,而不是等到缓存时间到刷新后才看到这些数据,于是数据缓存的相关技术也就应运而生了。
比较有名的数据缓存框架有ehcache和 memcached。
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 在系统中的位置。
2 目前缓存的做法分为两种模式
内存缓存:缓存数据存放在服务器的内存空间中。
优点:速度快
缺点:资源有限
文件缓存:缓存数据存放在服务器的硬盘空间中。
优点:容量大
缺点:速度偏慢,尤其在缓存数量巨大时
3 缓存的更新有两种方法
- 被动更新:先从缓存获取,没有则回源获取,再更新缓存;
主动更新:发现数据改变后直接更新缓存(在分布式场景下,不容易实现)
数据库缓存
数据库的缓存一般由数据库提供,比如 ORACLE,可以对表建立高速缓存,提高对经常访问的数据的访问速度。- 缓存技术选择
有持久化需求或者对数据结构和处理有高级要求的应用,选择 redis。其他简单的 key/value 存储,选择memcache
分布式缓存如:Redis,memcached(大数据量,加缓存的接口较多的项目)
本地缓存如:ehcache,GuavaCache。(数据量小,需要加缓存的接口较少)
4 Guava Cache 与 ConcurrentMap 的区别
com.google.common.cache;
refresh 和expire 刷新机制
刷新机制:包括 refresh 和 expire 刷新机制
expireAfterAccess: 当缓存项在指定的时间段内没有被读或写就会被回收。
expireAfterWrite:当缓存项在指定的时间段内没有更新就会被回收(数据写入缓存超过一定时间自动刷新)。
refreshAfterWrite:当缓存项上一次更新操作之后的多久会被刷新。
通过分析源码,guava cache 在限制只有1个加载操作时进行加锁,其他请求必须阻塞等待这个加载操作完成;而且,在加载完成之后,其他请求的线程会逐一获得锁,去判断是否已被加载完成,每个线程必须轮流地走一个“”获得锁,获得值,释放锁“”的过程,这样性能会有一些损耗。这里由于我们计划本地缓存1秒,所以频繁的过期和加载,锁等待等过程会让性能有较大的损耗。
- 跟 expire 的区别是,指定时间过后,expire 是 remove该key,下次访问是同步去获取返回新值
- 而 refresh 则是指定时间后,不会 remove 该 key,下次访问会触发刷新,新值没有回来时返回旧值
cache = CacheBuilder.newBuilder().expireAfterWrite(duration, timeUtil).build(new CacheLoader<K, V>() {@Overridepublic V load(K k) throws Exception{return loadData(k);}});
public static void main(String[] args) throws ExecutionException, InterruptedException{//缓存接口这里是LoadingCache,LoadingCache在缓存项不存在时可以自动加载缓存LoadingCache<Integer,Student> studentCache//CacheBuilder的构造函数是私有的,只能通过其静态方法newBuilder()来获得CacheBuilder的实例= CacheBuilder.newBuilder()//设置并发级别为8,并发级别是指可以同时写缓存的线程数.concurrencyLevel(8)//设置写缓存后8秒钟过期.expireAfterWrite(8, TimeUnit.SECONDS)//设置缓存容器的初始容量为10.initialCapacity(10)//设置缓存最大容量为100,超过100之后就会按照LRU最近虽少使用算法来移除缓存项.maximumSize(100)//设置要统计缓存的命中率.recordStats()//设置缓存的移除通知.removalListener(new RemovalListener<Object, Object>() {@Overridepublic void onRemoval(RemovalNotification<Object, Object> notification) {System.out.println(notification.getKey() + " was removed, cause is " + notification.getCause());}})//build方法中可以指定CacheLoader,在缓存不存在时通过CacheLoader的实现自动加载缓存.build(new CacheLoader<Integer, Student>() {@Overridepublic Student load(Integer key) throws Exception {System.out.println("load student " + key);Student student = new Student();student.setId(key);student.setName("name " + key);return student;}});for (int i=0;i<20;i++) {//从缓存中得到数据,由于我们没有设置过缓存,所以需要通过CacheLoader加载缓存数据Student student = studentCache.get(1);System.out.println(student);//休眠1秒TimeUnit.SECONDS.sleep(1);}System.out.println("cache stats:");//最后打印缓存的命中率等 情况System.out.println(studentCache.stats().toString());}
