如何设计缓存及Hibernate缓存机制

    缓存的设计一般要用到单例设计模式和资源设计模式,还需要注意多线程同步的问题,以下主要讨论如何设计缓存,什么是数据库连接池,以及Hibernate中的缓存机制。

如何设计缓存

缓存原理

    在Java中经常用到缓存,在SSh框架中也会用到一级缓存和二级缓存,到底缓存是怎么实现的呢?

    缓存就是利用本地参考原则:当CPU要读取一个数据时,首先从缓存中查找,找到就立即读取并送给CPU处理;没有找到,就用相对慢的速率从内存中读取并送给CPU处理,同时把这个数据所在的数据块调入缓存中,可以使得以后对整块数据的读取都从缓存中进行,不必再调用内存。缓存是一种典型的空间换时间的方案。

    缓存就相当于是一个临时内存:它有一个有限的空间量,但访问它比访问原始数据速度要快。

    在分布式系统设计中,如果在请求层节点上放置一个缓存,即可响应本地的存储数据。当对服务器发送一个请求时,如果本地存在所请求数据,那么该节点即会快速返回本地缓存数据。如果本地不存在,那么请求节点将会查询磁盘上的数据。请求层节点缓存即可以存在于内存中(这个非常快速)也可以位于该节点的本地磁盘上(比访问网络存储要快)。全局缓存是指所有节点都使用同一个缓存空间;分布式缓存即缓存在分布式系统各节点内存中的缓存数据。

缓存算法

常用的缓存算法有:先进先出FIFO(First In First Out),LRU(Least Recently Used),LFU(LeastFrequently Used),具体见http://developer.51cto.com/art/201212/372465_2.htm

开源缓存产品 

    一个非常流行的开源缓存产品:Memcached(即可以在本地缓存上工作也可以在分布式缓存上工作)。Memcached用于许多大型Web站点,其非常强大。Memcached基于一个存储键/值对的hashmap,优化数据存储和实现快速搜索(O(1))。

如何设计缓存

在Java中最常见的一种实现缓存的方式就是使用Map,基本的步骤是:

1)先到缓存里面查找,看看是否存在需要使用的数据

2)如果没有找到,那么就创建一个满足要求的数据,然后把这个数据设置回到缓存中,以备下次使用

3)如果找到了相应的数据,或者是创建了相应的数据,那就直接使用这个数据。

下面只是缓存的基本实现,还有很多功能都没有考虑,比如缓存的清除,缓存的同步等等。现在有很多专业的缓存框架。

缓存接口设计
public Interface<E,V> ICashe(){

   public void size(int max); //设置缓存池大小
   
   public void add(E e); //往缓存中添加数据
   
   public void remove(E e); //从缓存中移除数据
   
   public void update(E e); //更新缓存中的数据
   
   public E get(V v);//从缓存中获取符合条件的值
   
   public void clear(); //清除缓存中数据
   
   public boolean empty(); //判断缓存是否为空
   
   public boolean full(); //判断缓存是否已满
}//end Interface ICashe
缓存实现类举例(因为多个对象要共有一个缓存,所以缓存常实现为单例模式)
public class Cashe Implement ICashe{

   private volatile static Singleton uniqueInstance=null;
    private Map<String,Object> map = new HashMap<String,Object>(); //缓存数据的容器
   

   private Singleton(){
   }
   
   public static Singleton getInstance() {
        if(uniqueInstance == null){
		   synchronized (Singleton.class){ //note we only synchronize the first time through!
		      if(uniqueInstance == null)   //once in the block,check again and if still null, create an instance
			     uniqueInstance=new Singleton();
		   }//end synchronized	   
		}//end if
			
	    return uniqueInstance;
   }//end getInstance()
   
   //other useful methods here
   @override
   public E get(V v){
      //先从缓存里面取值
      E e = map.get(v);
	  
     //判断缓存里面是否有值
     if(e == null){
     //如果没有,那么就去获取相应的数据,比如读取数据库或者文件
     //这里只是演示,所以直接写个假的值
     e = v+",value";
     //把获取的值设置回到缓存里面
     map.put(v, e);
    } //end if
  
    //如果有值了,就直接返回使用
    return e;
   }//end get()
   
   @override
   public void add(E e){
   //TODO
   }
   
   @override
   public void remove(E e){
   //TODO
   }
   
   @override
   public void update(E e){
   //TODO
   }
   
   @override
   public void clear() {
   //TODO
   }
   
   @override
   public boolean empty(){
   //TODO
   } 
   
   @override
   public boolean full(){
     //TODO
   } 
   
}//end class Singleton


数据库连接池

为什么使用数据库连接池

    在传统的两层结构中,客户端程序在启动时打开数据库连接,在退出程序时关闭数据库连接。这样,在整个程序运行中,每个客户端始终占用一个数据库连接,即使在大量没有数据库操作的空闲时间,如用户输入数据时,从而造成数据库连接的使用效率低下。

    对于Connection这样的资源,初始化的开销是很大的,因为建立连接必须进行Socket连接,验证以及授权等繁杂的操作,代价是昂贵的,因此及早初始化一定量打开的连接,并且缓存起来是一个相当不错的策略。

    那么怎么做呢?对于共享资源,有一个很著名的设计模式:资源池 (Resource Pool)。该模式正是为了解决资源的频繁分配、释放所造成的问题。为解决上述问题,可以采用数据库连接池技术。数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接。更为重要的是我们可以通过连接池的管理机制监视数据库的连接的数量、使用情况,为系统开发、测试及性能调整提供依据。

    因此,在前面的两层结构中,加入一层-数据库连接池。在三层结构模式中,数据库连接通过中间层的连接池管理。只有当用户真正需要进行数据库操作时,中间层才从连接池申请一个连接,数据库操作完毕,连接立即释放到连接池中,以供其他用户使用。这样,不仅大大提高了数据库连接的使用效率,使得大量用户可以共享较少的数据库连接,而且省去了建立连接的时间。

如何设计一个数据库连接池

    通常用连接池管理类(DBConnectionManager)来使用数据库连接池( DBConnectionPool)。因为系统中只能有一个连接池管理类的实例,所以连接池管理类的实现是单例模式。

    连接池管理类主要用于对多个连接池对象的管理,具有以下功能:①装载并注册特定数据库的JDBC驱动程序;②根据属性文件给定的信息,创建连接池对象;③为方便管理多个连接池对象,为每一个连接池对象取一个名字,实现连接池名字与其实例之间的映射;④跟踪客户使用连接情况,以便需要是关闭连接释放资源。连接池管理类的引入主要是为了方便对多个连接池的使用和管理,如系统需要连接不同的数据库,或连接相同的数据库但由于安全性问题,需要不同的用户使用不同的名称和密码。

数据库连接池接口设计

//用数据库连接池接口设计 public interface DBConnectionPool<E> { //最大最小连接数 public synchronized void size(int min,int max); //新建一个数据库连接   public synchronized E newConnection(String name,String URL,String user,String password); //得到一个连接,timeout是等待时间   public synchronized E getConnection(long timeout); //断开所有连接,释放占用的系统资源   public synchronized void release(); //使用完毕之后,把连接返还给空闲池   public synchronized void freeConnection(Connection con); } //end interface DBConnectionPool
连接池管理类
public class DBConnectionManager {   static private DBConnectionManager instance;   //连接池管理类的唯一实例   static private int clients;//客户数量   private ArrayList drivers=new ArrayList();   //容器,存放数据库驱动程序   private HashMap pools = new HashMap();   //以name/value的形式存取连接池对象的名字及连接池对象   static synchronized public DBConnectionManager getInstance()   /**如果唯一的实例instance已经创建,直接返回这个实例;否则,调用私有构造函数,   创建连接池管理类的唯一实例*/   private DBConnectionManager()   //私有构造函数,在其中调用初始化函数init()   public void freeConnection(String name,Connection con)   //释放一个连接,name是一个连接池对象的名字   public Connection getConnection(String name)   //从名字为name的连接池对象中得到一个连接   public Connection getConnection(String name,long time)   //从名字为name的连接池对象中取得一个连接,time是等待时间   public synchronized void release()//释放所有资源   private void createPools(Properties props)   //根据属性文件提供的信息,创建一个或多个连接池   private void init()//初始化连接池管理类的唯一实例,由私有构造函数调用   private void loadDrivers(Properties props)//装载数据库驱动程序 } //end DBConnectionManager

Hibernate缓存机制

1. 一级缓存和二级缓存的比较

Hibernate 中提供了两级Cache,

1)第一级别的缓存是Session级别的缓存,它是属于事务范围的缓存,即Session对象的生命周期通常对应一个事务。这一级别的缓存由hibernate管理的,一般情况下无需进行干预,第一级缓存是必需的。

2)第二级别的缓存是SessionFactory级别的缓存,它是属于进程范围或集群范围的缓存。这一级别的缓存可以进行配置和更改,并且可以动态加载和卸载,是一个可插拔的的缓存插件。 Hibernate还为查询结果提供了一个查询缓存,它依赖于第二级缓存。

2. 缓存管理

2.1 一级缓存的管理

当应用程序调用Session的save()、update()、savaeOrUpdate()、get()或load(),以及调用查询接口的 list()、iterate()或filter()方法时,如果在Session缓存中还不存在相应的对象,Hibernate就会把该对象加入到第一级缓存中。当清理缓存时,Hibernate会根据缓存中对象的状态变化来同步更新数据库。 Session为应用程序提供了两个管理缓存的方法evict(Object obj):从缓存中清除参数指定的持久化对象。 clear():清空缓存中所有持久化对象。

2.2 二级缓存的管理

2.2.1 Hibernate的二级缓存策略的一般过程如下:

1) 条件查询的时候,总是发出一条select * from table_name where …. (选择所有字段)这样的SQL语句查询数据库,一次获得所有的数据对象。

2) 把获得的所有数据对象根据ID放入到第二级缓存中。

3) 当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。

4) 删除、更新、增加数据的时候,同时更新缓存。

    Hibernate的二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query Cache。


2.2.2 什么样的数据适合存放到第二级缓存中? 

1)很少被修改的数据 

2)不是很重要的数据,允许出现偶尔并发的数据 

3)不会被并发访问的数据 

4)参考数据,指的是供应用参考的常量数据,它的实例数目有限,它的实例会被许多其他类的实例引用,实例极少或者从来不会被修改。

2.2.3 不适合存放到第二级缓存的数据? 

1)经常被修改的数据 

2)财务数据,绝对不允许出现并发 

3)与其他应用共享的数据。

2.3 常用的缓存插件 

Hibernater 的二级缓存是一个插件,下面是几种常用的缓存插件:

EhCache:可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,对Hibernate的查询缓存提供了支持。

OSCache:可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,提供了丰富的缓存数据过期策略,对Hibernate的查询缓存提供了支持。

SwarmCache:可作为群集范围内的缓存,但不支持Hibernate的查询缓存。

JBossCache:可作为群集范围内的缓存,支持事务型并发访问策略,对Hibernate的查询缓存提供了支持。

配置二级缓存的主要步骤:

1) 选择需要使用二级缓存的持久化类,设置它的命名缓存的并发访问策略。这是最值得认真考虑的步骤。

2) 选择合适的缓存插件,然后编辑该插件的配置文件。如

如何设计缓存及Hibernate缓存机制_第1张图片


你可能感兴趣的:(如何设计缓存及Hibernate缓存机制)