Java缓存技术总结

1缓存为什么要存在?

2缓存可以存在于什么地方?

3缓存有哪些属性?

4缓存介质,及其优缺点

5Java本地缓存设计及实现

搞清楚这4个问题,那么我们就可以随意的通过应用的场景来判断使用何种缓存了.

1. 缓存为什么要存在?

一 般情况下,一个网站,或者一个应用,它的一般形式是,浏览器请求应用服务器,应用服务器做一堆计算后再请求数据库,数据库收到请求后再作一堆计算后把数据 返回给应用服务器,应用服务器再作一堆计算后把数据返回给浏览器.这个是一个标准流程.但是随着互连网的普及,上网的人越来越多,网上的信息量也越来越 多,在这两个越来越多的情况下,我们的应用需要支撑的并发量就越来越多.然后我们的应用服务器和数据库服务器所做的计算也越来越多,但是往往我们的应用服 务器资源是有限的,数据库每秒中接受请求的次数也是有限的(谁叫俺们的硬盘转速有限呢).如果利用有限的资源来提供尽可能大的吞吐量呢,一个办法:减少计 算量,缩短请求流程(减少网络io或者硬盘io),这时候缓存就可以大展手脚了.缓存的基本原理就是打破上图中所描绘的标准流程,在这个标准流程中,任何 一个环节都可以被切断.请求可以从缓存里取到数据直接返回.这样不但节省了时间,提高了响应速度,而且也节省了硬件资源.可以让我们有限的硬件资源来服务 更多的用户.

2 缓存可以存在于什么地方?

理 论上来将,请求的任何一个环节都是缓存可以作用的地方.第一个环节,浏览器,如果数据存在浏览器上,那么对用户来说速度是最快的,因为这个时候根本无需网 络请求.第二个环节,浏览器和app之间,如果缓存加在这个地方,那么缓存对app来说是透明的.而且这个缓存中存放的是完整的页面.第三个节点,app 中本身就有几个层次,那么缓存也可以放在不同的层次上,这一部分是情况或者场景比较复杂的部分.选择缓存时需要谨慎.第四个环节,数据库中也可以有缓存, 比如说mysql的querycache.

那么也就是说在整个请求流程的任何一点,我们都可以加缓存.但是是所有的数据都可以放进缓存的吗.当然不是,需要放进缓存的数据总是有一些特征的,要清楚的判断数据是否可以被缓存,可以被怎样缓存就必须要从数据的变化特征下手.

数 据有哪些变化特征?最简单的就是两种,变和不变.我们都知道,不会变化的数据不需要每次都进行计算.问题是难道所有的数据理论上来讲都会变化,变化是世界 永恒的主题.也就是说我们把数据分为变和不变两种是不对的,那么就让我们再加一个条件:时间.那么我们就可以把数据特征总结为一段时间内变或者不变.那么 根据这个数据特征,我们就可以在合适的位置和合适的缓存类型中缓存该数据.

3缓存有哪些属性

从面向对象的角度来看,缓存就是一个对象,那么是对象,必然有属性.那么下面我们来探讨一下缓存有哪些属性.以下列举我们常用到的3个属性.

(1) 命中率

命中率是指请求缓存次数和缓存返回正确结果次数的比例.比例越高,就证明缓存的使用率越高.

命中率问题是缓存中的一个非常重要的问题,我们都希望自己缓存的命中率能达到100%,但是往往事与愿违,而且缓存命中率是衡量缓存有效性的重要指标.

(2) 最大元素

缓存中可以存放得最大元素得数量,一旦缓存中元素数量超过这个值,那么将会起用缓存清空策略,根据不同的场景合理的设置最大元素值往往可以一定程度上提高缓存的命中率.从而更有效的时候缓存.

(3) 清空策略

1 FIFO ,first in first out ,最先进入缓存得数据在缓存空间不够情况下(超出最大元素限制时)会被首先清理出去

2 LFU , Less Frequently Used ,一直以来最少被使用的元素会被被清理掉。这就要求缓存的元素有一个hit 属性,在缓存空间不够得情况下,hit 值最小的将会被清出缓存。

2 LRU ,Least Recently Used ,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。

4缓存介质

从硬件介质上来将无非就是两种,内存和硬盘(对应应用层的程序来讲不用考虑寄存器等问题).但是往往我们不会从硬件上来划分,一般的划分方法是从技术上划分,可以分成几种,内存,硬盘文件.数据库.

(1) 内存.将缓存放在内存中是最快的选择,任何程序直接操作内存都比操作硬盘要快的多,但是如果你的数据要考虑到break down的问题,因为放在内存中的数据我们称之为没有持久话的数据,如果硬盘上没有备份,机器down机之后,很难或者无法恢复.

(2) 硬盘.一般来说,很多缓存框架会结合使用内存和硬盘,比如给内存分配的空间有满了之后,会让用户选择把需要退出内存空间的数据持久化到硬盘.当然也选择直 接把数据放一份到硬盘(内存中一份,硬盘中一份,down机也不怕).也有其他的缓存是直接把数据放到硬盘上.

(3) 数据库.说到数据库,可能有的人会想,之前不是讲到要减少数据库查询的次数,减少数据库计算的压力吗,现在怎么又用数据库作为缓存的介质了呢.这是因为数 据库又很多种类型,比如berkleydb,这种db不支持sql语句,没有sql引擎,只是key和value的存储结构,所以速度非常的快,在当代一 般的pc上,每秒中十几w次查询都是没有问题的(当然这个是根据业务特征来决定的,如果您访问的数据在分布上是均匀的,那ahuaxuan可不能保证这个 速度了).

除了缓存介质之外,ahuaxuan根据缓存和应用的耦合程度将其划分为local cache和remote cache.

Local cache是指包含在应用之中的缓存组件.而remote cache指和应用解耦在应用之外的缓存组件.典型的local cache有ehcache,oscache,而remote cache有大名鼎鼎的memcached.

Localcache 最大的优点是应用和cache的时候是在同一个进程内部,请求缓存非常快速,完全不需要网络开销等.所以单应用,不需要集群或者集群情况下cache node不需要相互通知的情况下使用local cache比较合适.这也是java中ehcache和oscache这么流行的原因.

但是 Local cache是有一定的缺点的,一般这种缓存框架(比如java中的ehcache或者oscache)都是local cache.也就是跟着应用程序走的,多个应用程序无法直接共享缓存,应用集群的情况下这个问题更加明显,当然也有的缓存组件提供了集群节点相互通知缓存 更新的功能,但是由于这个是广播,或者是环路更新,在缓存更新频繁的情况下会导致网络io开销非常大,严重的时候会影响应用的正常运行.而且如果缓存中数 据量较大得情况下使用localcache意味着每个应用都有一份这么大得缓存,着绝对是对内存的浪费.

所以这个情况下,往往我们会 选择remote cache,比如memcached.这样集群或者分布式的情况下各个应用都可以共享memcached中的数据,这些应用都通过socket和基于 tcp/ip协议上层的memcached协议直接连接到memcached,有一个app更新了memcached中的值,所有的应用都能拿到最新的 值.虽然这个时候多了很多了网络上的开销,但是往往这种方案要比localcache广播或环路更新cache节点要普遍的多,而且性能也比后者高.由于 数据只需要保存一份,所以也提高了内存的使用率.

通过以上分析可以看出,不管是local cache,还是remote cache在缓存领域都有自己的一席之地,所以ahuaxuan建议在选择或者使用缓存时一定要根据缓存的特征和我们的业务场景准确判断使用何种缓存.这样才能充分发挥缓存的功能.

Ahuaxuan 认为,缓存的使用是架构师的必备技能,好的架构师能够根据数据的类型,业务的场景来准确的判断出使用何种类型的缓存,并且如何使用这种类型的缓存.在缓存 的世界里也没有银弹,目前还没有一种缓存可以解决任何的业务场景或者数据类型,如果这种技术出现了,那架构师就又更不值钱了.呵呵.

OSCache

  

  OSCache是个一个广泛采用的高性能的J2EE缓存框架,OSCache能用于任何Java应用程序的普通的缓存解决方案。

  

  OSCache有以下特点:

  

  缓存任何对象,你可以不受限制的缓存部分jsp页面或HTTP请求,任何java对象都可以缓存。

  

  拥有全面的API--OSCache API给你全面的程序来控制所有的OSCache特性。

  

  永久缓存--缓存能随意的写入硬盘,因此允许昂贵的创建(expensive-to-create)数据来保持缓存,甚至能让应用重启。

  

  支持集群--集群缓存数据能被单个的进行参数配置,不需要修改代码。

  

  缓存记录的过期--你可以有最大限度的控制缓存对象的过期,包括可插入式的刷新策略(如果默认性能不需要时)。

  

  官方网站 http://www.opensymphony.com/oscache/

  

  Java Caching System

  

  JSC(Java Caching System)是一个用分布式的缓存系统,是基于服务器的java应用程序。它是通过提供管理各种动态缓存数据来加速动态web应用。

  

  JCS和其他缓存系统一样,也是一个用于高速读取,低速写入的应用程序。

  

  动态内容和报表系统能够获得更好的性能。

  

  如果一个网站,有重复的网站结构,使用间歇性更新方式的数据库(而不是连续不断的更新数据库),被重复搜索出相同结果的,就能够通过执行缓存方式改进其性能和伸缩性。

  

  官方网站 http://jakarta.apache.org/turbine/jcs/

  

  EHCache

  

  EHCache 是一个纯java的在进程中的缓存,它具有以下特性:快速,简单,为Hibernate2.1充当可插入的缓存,最小的依赖性,全面的文档和测试。

  

  官方网站 http://ehcache.sourceforge.net/

  

  JCache

  

  JCache是个开源程序,正在努力成为JSR-107开源规范,JSR-107规范已经很多年没改变了。这个版本仍然是构建在最初的功能定义上。

  

  官方网站 http://jcache.sourceforge.net/

  

  ShiftOne

  

  ShiftOne Java Object Cache是一个执行一系列严格的对象缓存策略的Java lib,就像一个轻量级的配置缓存工作状态的框架。

  

  官方网站 http://jocache.sourceforge.net/

  

  SwarmCache

  

  SwarmCache是一个简单且有效的分布式缓存,它使用IP multicast与同一个局域网的其他主机进行通讯,是特别为集群和数据驱动web应用程序而设计的。SwarmCache能够让典型的读操作大大超过写操作的这类应用提供更好的性能支持。

  

  SwarmCache使用JavaGroups来管理从属关系和分布式缓存的通讯。

  

  官方网站 http://swarmcache.sourceforge.net

  

  TreeCache / JBossCache

  

   JBossCache是一个复制的事务处理缓存,它允许你缓存企业级应用数据来更好的改善性能。缓存数据被自动复制,让你轻松进行JBoss服务器之间 的集群工作。JBossCache能够通过JBoss应用服务或其他J2EE容器来运行一个MBean服务,当然,它也能独立运行。

  

  JBossCache包括两个模块:TreeCache和TreeCacheAOP。

  

  TreeCache --是一个树形结构复制的事务处理缓存。

  

  TreeCacheAOP --是一个“面向对象”缓存,它使用AOP来动态管理POJO(Plain Old Java Objects)

  

  注:AOP是OOP的延续,是Aspect Oriented Programming的缩写,意思是面向方面编程。

  

  官方网站 http://www.jboss.org/products/jbosscache

  

  WhirlyCache

  

  Whirlycache是一个快速的、可配置的、存在于内存中的对象的缓存。它能够通过缓存对象来加快网站或应用程序的速度,否则就必须通过查询数据库或其他代价较高的处理程序来建立。

5Java本地缓存设计及实现

Java本地缓存可分为二大类: 
一、通过文件缓存,顾名思义文件缓存是指把数据存储在磁盘上,不管你是以XML格式,序列化文件DAT格式还是其它文件格式;  
二、内存缓存,也就是实现一个类中静态Map,对这个Map进行常规的增删查. 
下面为一个简单的缓存代码 

Java代码  复制代码
  1. package lhm.hcy.guge.frameset.cache;   
  2.   
  3. import java.util.*;   
  4.   
  5. /**  
  6.  * <p>Title: </p>  
  7.  *  
  8.  * <p>Description: 管理缓存</p>  
  9.  * Deep blue 2008-11-28 think  
  10.  * 可扩展的功能:当chche到内存溢出时必须清除掉最早期的一些缓存对象,这就要求对每个缓存对象保存创建时间  
  11.  * <p>Copyright: Copyright (c) 2008</p>  
  12.  *  
  13.  * <p>Company: </p>  
  14.  *  
  15.  * @author Deepblue  2008-11-11  
  16.  * @version 1.0  
  17.  */  
  18. public class CacheManager {   
  19.     private static HashMap cacheMap = new HashMap();   
  20.   
  21.     //单实例构造方法   
  22.     private CacheManager() {   
  23.         super();   
  24.     }   
  25.     //获取布尔值的缓存   
  26.     public static boolean getSimpleFlag(String key){   
  27.         try{   
  28.             return (Boolean) cacheMap.get(key);   
  29.         }catch(NullPointerException e){   
  30.             return false;   
  31.         }   
  32.     }   
  33.     public static long getServerStartdt(String key){   
  34.         try {   
  35.             return (Long)cacheMap.get(key);   
  36.         } catch (Exception ex) {   
  37.             return 0;   
  38.         }   
  39.     }   
  40.     //设置布尔值的缓存   
  41.     public synchronized static boolean setSimpleFlag(String key,boolean flag){   
  42.         if (flag && getSimpleFlag(key)) {//假如为真不允许被覆盖   
  43.             return false;   
  44.         }else{   
  45.             cacheMap.put(key, flag);   
  46.             return true;   
  47.         }   
  48.     }   
  49.     public synchronized static boolean setSimpleFlag(String key,long serverbegrundt){   
  50.         if (cacheMap.get(key) == null) {   
  51.             cacheMap.put(key,serverbegrundt);   
  52.             return true;   
  53.         }else{   
  54.             return false;   
  55.         }   
  56.     }   
  57.   
  58.   
  59.     //得到缓存。同步静态方法   
  60.     private synchronized static Cache getCache(String key) {   
  61.         return (Cache) cacheMap.get(key);   
  62.     }   
  63.   
  64.     //判断是否存在一个缓存   
  65.     private synchronized static boolean hasCache(String key) {   
  66.         return cacheMap.containsKey(key);   
  67.     }   
  68.   
  69.     //清除所有缓存   
  70.     public synchronized static void clearAll() {   
  71.         cacheMap.clear();   
  72.     }   
  73.   
  74.     //清除某一类特定缓存,通过遍历HASHMAP下的所有对象,来判断它的KEY与传入的TYPE是否匹配   
  75.     public synchronized static void clearAll(String type) {   
  76.         Iterator i = cacheMap.entrySet().iterator();   
  77.         String key;   
  78.         ArrayList<String> arr = new ArrayList<String>();   
  79.         try {   
  80.             while (i.hasNext()) {   
  81.                 java.util.Map.Entry entry = (java.util.Map.Entry) i.next();   
  82.                 key = (String) entry.getKey();   
  83.                 if (key.startsWith(type)) { //如果匹配则删除掉   
  84.                     arr.add(key);   
  85.                 }   
  86.             }   
  87.             for (int k = 0; k < arr.size(); k++) {   
  88.                 clearOnly(arr.get(k));   
  89.             }   
  90.         } catch (Exception ex) {   
  91.             ex.printStackTrace();   
  92.         }   
  93.     }   
  94.   
  95.     //清除指定的缓存   
  96.     public synchronized static void clearOnly(String key) {   
  97.         cacheMap.remove(key);   
  98.     }   
  99.   
  100.     //载入缓存   
  101.     public synchronized static void putCache(String key, Cache obj) {   
  102.         cacheMap.put(key, obj);   
  103.     }   
  104.   
  105.     //获取缓存信息   
  106.     public static Cache getCacheInfo(String key) {   
  107.   
  108.         if (hasCache(key)) {   
  109.             Cache cache = getCache(key);   
  110.             if (cacheExpired(cache)) { //调用判断是否终止方法   
  111.                 cache.setExpired(true);   
  112.             }   
  113.             return cache;   
  114.         }else  
  115.             return null;   
  116.     }   
  117.   
  118.     //载入缓存信息   
  119.     public static void putCacheInfo(String key, Cache obj, long dt,boolean expired) {   
  120.         Cache cache = new Cache();   
  121.         cache.setKey(key);   
  122.         cache.setTimeOut(dt + System.currentTimeMillis()); //设置多久后更新缓存   
  123.         cache.setValue(obj);   
  124.         cache.setExpired(expired); //缓存默认载入时,终止状态为FALSE   
  125.         cacheMap.put(key, cache);   
  126.     }   
  127.     //重写载入缓存信息方法   
  128.     public static void putCacheInfo(String key,Cache obj,long dt){   
  129.         Cache cache = new Cache();   
  130.         cache.setKey(key);   
  131.         cache.setTimeOut(dt+System.currentTimeMillis());   
  132.         cache.setValue(obj);   
  133.         cache.setExpired(false);   
  134.         cacheMap.put(key,cache);   
  135.     }   
  136.   
  137.     //判断缓存是否终止   
  138.     public static boolean cacheExpired(Cache cache) {   
  139.         if (null == cache) { //传入的缓存不存在   
  140.             return false;   
  141.         }   
  142.         long nowDt = System.currentTimeMillis(); //系统当前的毫秒数   
  143.         long cacheDt = cache.getTimeOut(); //缓存内的过期毫秒数   
  144.         if (cacheDt <= 0||cacheDt>nowDt) { //过期时间小于等于零时,或者过期时间大于当前时间时,则为FALSE   
  145.             return false;   
  146.         } else { //大于过期时间 即过期   
  147.             return true;   
  148.         }   
  149.     }   
  150.   
  151.     //获取缓存中的大小   
  152.     public static int getCacheSize() {   
  153.         return cacheMap.size();   
  154.     }   
  155.   
  156.     //获取指定的类型的大小   
  157.     public static int getCacheSize(String type) {   
  158.         int k = 0;   
  159.         Iterator i = cacheMap.entrySet().iterator();   
  160.         String key;   
  161.         try {   
  162.             while (i.hasNext()) {   
  163.                 java.util.Map.Entry entry = (java.util.Map.Entry) i.next();   
  164.                 key = (String) entry.getKey();   
  165.                 if (key.indexOf(type) != -1) { //如果匹配则删除掉   
  166.                     k++;   
  167.                 }   
  168.             }   
  169.         } catch (Exception ex) {   
  170.             ex.printStackTrace();   
  171.         }   
  172.   
  173.         return k;   
  174.     }   
  175.   
  176.     //获取缓存对象中的所有键值名称   
  177.     public static ArrayList<String> getCacheAllkey() {   
  178.         ArrayList a = new ArrayList();   
  179.         try {   
  180.             Iterator i = cacheMap.entrySet().iterator();   
  181.             while (i.hasNext()) {   
  182.                 java.util.Map.Entry entry = (java.util.Map.Entry) i.next();   
  183.                 a.add((String) entry.getKey());   
  184.             }   
  185.         } catch (Exception ex) {} finally {   
  186.             return a;   
  187.         }   
  188.     }   
  189.   
  190.     //获取缓存对象中指定类型 的键值名称   
  191.     public static ArrayList<String> getCacheListkey(String type) {   
  192.         ArrayList a = new ArrayList();   
  193.         String key;   
  194.         try {   
  195.             Iterator i = cacheMap.entrySet().iterator();   
  196.             while (i.hasNext()) {   
  197.                 java.util.Map.Entry entry = (java.util.Map.Entry) i.next();   
  198.                 key = (String) entry.getKey();   
  199.                 if (key.indexOf(type) != -1) {   
  200.                     a.add(key);   
  201.                 }   
  202.             }   
  203.         } catch (Exception ex) {} finally {   
  204.             return a;   
  205.         }   
  206.     }   
  207.   
  208. }   
  209.   
  210.   
  211. package lhm.hcy.guge.frameset.cache;   
  212.   
  213. /**  
  214.  * <p>Title: </p>  
  215.  *  
  216.  * <p>Description: 缓存DTO</p>  
  217.  *  
  218.  * <p>Copyright: Copyright (c) 2008</p>  
  219.  *  
  220.  * <p>Company: </p>  
  221.  *  
  222.  * @author Deepblue  2008-11-11  
  223.  * @version 1.0  
  224.  */  
  225. public class Cache {   
  226.         private String key;//缓存ID   
  227.         private Object value;//缓存数据   
  228.         private long timeOut;//更新时间   
  229.         private boolean expired; //是否终止   
  230.         public Cache() {   
  231.                 super();   
  232.         }   
  233.   
  234.         public Cache(String key, Object value, long timeOut, boolean expired) {   
  235.                 this.key = key;   
  236.                 this.value = value;   
  237.                 this.timeOut = timeOut;   
  238.                 this.expired = expired;   
  239.         }   
  240.   
  241.         public String getKey() {   
  242.                 return key;   
  243.         }   
  244.   
  245.         public long getTimeOut() {   
  246.                 return timeOut;   
  247.         }   
  248.   
  249.         public Object getValue() {   
  250.                 return value;   
  251.         }   
  252.   
  253.         public void setKey(String string) {   
  254.                 key = string;   
  255.         }   
  256.   
  257.         public void setTimeOut(long l) {   
  258.                 timeOut = l;   
  259.         }   
  260.   
  261.         public void setValue(Object object) {   
  262.                 value = object;   
  263.         }   
  264.   
  265.         public boolean isExpired() {   
  266.                 return expired;   
  267.         }   
  268.   
  269.         public void setExpired(boolean b) {   
  270.                 expired = b;   
  271.         }   
  272. }   
  273.   
  274. //测试类,   
  275. class Test {   
  276.     public static void main(String[] args) {   
  277.         System.out.println(CacheManager.getSimpleFlag("alksd"));   
  278. //        CacheManager.putCache("abc", new Cache());   
  279. //        CacheManager.putCache("def", new Cache());   
  280. //        CacheManager.putCache("ccc", new Cache());   
  281. //        CacheManager.clearOnly("");   
  282. //        Cache c = new Cache();   
  283. //        for (int i = 0; i < 10; i++) {   
  284. //            CacheManager.putCache("" + i, c);   
  285. //        }   
  286. //        CacheManager.putCache("aaaaaaaa", c);   
  287. //        CacheManager.putCache("abchcy;alskd", c);   
  288. //        CacheManager.putCache("cccccccc", c);   
  289. //        CacheManager.putCache("abcoqiwhcy", c);   
  290. //        System.out.println("删除前的大小:"+CacheManager.getCacheSize());   
  291. //        CacheManager.getCacheAllkey();   
  292. //        CacheManager.clearAll("aaaa");   
  293. //        System.out.println("删除后的大小:"+CacheManager.getCacheSize());   
  294. //        CacheManager.getCacheAllkey();   
  295.   
  296.   
  297.     }   
  298. }  

猜你喜欢

转载自aoyouzi.iteye.com/blog/2211061
今日推荐