使用Memcached、Spring AOP構建數據庫前端緩存框架

http://my249645546.iteye.com/blog/1420778前端

上回說到Memcahed的安裝及java客戶端的使用(http://my249645546.iteye.com/blog/1420061),如今咱們使用memcached、Spring AOP技術來構建一個數據庫的緩存框架。java

數據庫訪問多是不少網站的瓶頸。動不動就鏈接池耗盡、內存溢出等。前面已經講到若是咱們的網站是一個分佈式的大型站點,那麼使用memcached實現數據庫的前端緩存是個很不錯的選擇;但若是網站自己足夠小隻有一個服務器,甚至是vps的那種,不推薦使用memcached,使用Hibernate或者Mybatis框架自帶的緩存系統就好了。spring

 

1、開啓memcached服務器端服務數據庫

若是已經安裝了memcached服務器端程序,請確認服務器端服務已開啓。apache

2、引入jarapi

1.  alisoft-xplatform-asf-cache-2.5.1.jar緩存

2.  commons-logging-1.0.4.jar服務器

3.  hessian-3.0.1.jarapp

4.  log4j-1.2.9.jar框架

5.  stax-api-1.0.1.jar

6.  wstx-asl-2.0.2.jar

3、建立memcached客戶端配置文件

<?xml version="1.0" encoding="UTF-8"?>

Xml代碼  
  1. <memcached>  
  2.     <!-- name 屬性是程序中使用Cache的惟一標識;socketpool 屬性將會關聯到後面的socketpool配置; -->  
  3.     <client name="mclient_0" compressEnable="true" defaultEncoding="UTF-8"  
  4.         socketpool="pool_0">  
  5.         <!-- 可選,用來處理出錯狀況 -->  
  6.         <errorHandler>com.alisoft.xplatform.asf.cache.memcached.MemcachedErrorHandler   
  7.         </errorHandler>  
  8.     </client>  
  9.   
  10.     <!--   
  11.         name 屬性和client 配置中的socketpool 屬性相關聯。   
  12.         maintSleep屬性是後臺線程管理SocketIO池的檢查間隔時間,若是設置爲0,則代表不須要後臺線程維護SocketIO線程池,默認須要管理。   
  13.         socketTO 屬性是Socket操做超時配置,單位ms。 aliveCheck   
  14.         屬性表示在使用Socket之前是否先檢查Socket狀態。   
  15.     -->  
  16.     <socketpool name="pool_0" maintSleep="5000" socketTO="3000"  
  17.         failover="true" aliveCheck="true" initConn="5" minConn="5" maxConn="250"  
  18.         nagle="false">  
  19.         <!-- 設置memcache服務端實例地址.多個地址用","隔開 -->  
  20.         <servers>127.0.0.1:11211</servers>  
  21.         <!--   
  22.             可選配置。代表了上面設置的服務器實例的Load權重. 例如 <weights>3,7</weights> 表示30% load 在   
  23.             10.2.224.36:33001, 70% load 在 10.2.224.46:33001   
  24.            
  25.         <weights>3,7</weights>  
  26.         -->  
  27.     </socketpool>  
  28. </memcached>  
<memcached>
	<!-- name 屬性是程序中使用Cache的惟一標識;socketpool 屬性將會關聯到後面的socketpool配置; -->
	<client name="mclient_0" compressEnable="true" defaultEncoding="UTF-8"
		socketpool="pool_0">
		<!-- 可選,用來處理出錯狀況 -->
		<errorHandler>com.alisoft.xplatform.asf.cache.memcached.MemcachedErrorHandler
		</errorHandler>
	</client>

	<!--
		name 屬性和client 配置中的socketpool 屬性相關聯。
		maintSleep屬性是後臺線程管理SocketIO池的檢查間隔時間,若是設置爲0,則代表不須要後臺線程維護SocketIO線程池,默認須要管理。
		socketTO 屬性是Socket操做超時配置,單位ms。 aliveCheck
		屬性表示在使用Socket之前是否先檢查Socket狀態。
	-->
	<socketpool name="pool_0" maintSleep="5000" socketTO="3000"
		failover="true" aliveCheck="true" initConn="5" minConn="5" maxConn="250"
		nagle="false">
		<!-- 設置memcache服務端實例地址.多個地址用","隔開 -->
		<servers>127.0.0.1:11211</servers>
		<!--
			可選配置。代表了上面設置的服務器實例的Load權重. 例如 <weights>3,7</weights> 表示30% load 在
			10.2.224.36:33001, 70% load 在 10.2.224.46:33001
		
		<weights>3,7</weights>
		-->
	</socketpool>
</memcached>

 

4、建立memcached客戶端程序

客戶端工具類:

 

Java代碼  
  1. package com.hl.usersmanager.memcached.client;   
  2.   
  3. import com.alisoft.xplatform.asf.cache.ICacheManager;   
  4. import com.alisoft.xplatform.asf.cache.IMemcachedCache;   
  5. import com.alisoft.xplatform.asf.cache.memcached.CacheUtil;   
  6. import com.alisoft.xplatform.asf.cache.memcached.MemcachedCacheManager;   
  7.   
  8. public class MemcachedCache {   
  9.     private ICacheManager<IMemcachedCache> manager;   
  10.     private IMemcachedCache cache;   
  11.        
  12.     public MemcachedCache(){   
  13.         manager = CacheUtil.getCacheManager(IMemcachedCache.class,   
  14.                 MemcachedCacheManager.class.getName());   
  15.         manager.setConfigFile("memcached.xml");   
  16.         manager.setResponseStatInterval(5*1000);   
  17.         manager.start();   
  18.         cache = manager.getCache("mclient_0");   
  19.     }   
  20.        
  21.     /**  
  22.      * 獲取緩存接口  
  23.      * @return  
  24.      */  
  25.     public IMemcachedCache getCache(){   
  26.         return cache;   
  27.     }   
  28.        
  29.     /**  
  30.      * 數據放入緩存  
  31.      * @param key  
  32.      * @param object  
  33.      */  
  34.     public void put(String key,Object object){   
  35.         cache.put(key, object);   
  36.     }   
  37.        
  38.     /**  
  39.      * 從緩存中讀取數據  
  40.      * @param key  
  41.      * @return  
  42.      */  
  43.     public Object get(String key){   
  44.         return cache.get(key);   
  45.     }   
  46. }  
package com.hl.usersmanager.memcached.client;

import com.alisoft.xplatform.asf.cache.ICacheManager;
import com.alisoft.xplatform.asf.cache.IMemcachedCache;
import com.alisoft.xplatform.asf.cache.memcached.CacheUtil;
import com.alisoft.xplatform.asf.cache.memcached.MemcachedCacheManager;

public class MemcachedCache {
	private ICacheManager<IMemcachedCache> manager;
	private IMemcachedCache cache;
	
	public MemcachedCache(){
		manager = CacheUtil.getCacheManager(IMemcachedCache.class,
				MemcachedCacheManager.class.getName());
		manager.setConfigFile("memcached.xml");
		manager.setResponseStatInterval(5*1000);
		manager.start();
		cache = manager.getCache("mclient_0");
	}
	
	/**
	 * 獲取緩存接口
	 * @return
	 */
	public IMemcachedCache getCache(){
		return cache;
	}
	
	/**
	 * 數據放入緩存
	 * @param key
	 * @param object
	 */
	public void put(String key,Object object){
		cache.put(key, object);
	}
	
	/**
	 * 從緩存中讀取數據
	 * @param key
	 * @return
	 */
	public Object get(String key){
		return cache.get(key);
	}
}

 

 

5、使用Spring AOP在數據查詢的Service層實現數據緩存及讀取

實現數據緩存的過程很簡單,就是在Service層查詢數據庫操做前判斷要查詢的數據在緩存中是否存在,若是不存在就到數據庫中查詢,查詢完成後將數據放入緩存系統;若是要查詢的數據在緩存中已經存在,則直接從緩存中讀取,不須要操做數據庫。這就大大下降了數據庫的鏈接次數。原理就是這麼簡單。

可是,若是直接對Service層代碼進行修改,就違背了「開放-封閉」原則,也會致使緩存系統的操做代碼散落到Service層的各處,不方便代碼的管理和維護。因此,Spring AOP華麗登場了。它使用非入侵式的來建立、管理這些緩存操做代碼。

關於Spring AOP自己的一些知識,咱們這裏不作講述。參考資料:

因爲首先要判斷查詢數據是否存在於緩存系統,若是存在直接從緩存中讀取,也就是說Service層的查詢代碼根本不會執行;另外一方面,若是數據在緩存系統中不存在,從數據庫查詢出的結果,咱們須要將其放入緩存系統中。

咱們來看Spring AOP的幾個裝備中哪一個適用呢?那就是最強大的環繞通知裝備@Around

 

下面以UserService爲例,其源代碼以下:

 

Java代碼  
  1. package com.hl.usersmanager.service.impl;   
  2.   
  3. import java.util.List;   
  4.   
  5. import org.springframework.beans.factory.annotation.Autowired;   
  6. import org.springframework.stereotype.Service;   
  7. import org.springframework.transaction.annotation.Transactional;   
  8.   
  9. import com.hl.usersmanager.dao.IUserMapper;   
  10. import com.hl.usersmanager.model.Users;   
  11. import com.hl.usersmanager.service.IUserService;   
  12.   
  13. //使用Service註解 不須要再在配置文件中配置bean   
  14. @Service  
  15. public class UserServiceImpl implements IUserService{   
  16.     @Autowired  
  17.     private IUserMapper userMapper;   
  18.        
  19.     @Override  
  20.     @Transactional  
  21.     public Users findUserByName(String name) {   
  22.         return userMapper.findUserByName(name);   
  23.     }   
  24.   
  25.     ……   
  26. }  
package com.hl.usersmanager.service.impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.hl.usersmanager.dao.IUserMapper;
import com.hl.usersmanager.model.Users;
import com.hl.usersmanager.service.IUserService;

//使用Service註解 不須要再在配置文件中配置bean
@Service
public class UserServiceImpl implements IUserService{
	@Autowired
	private IUserMapper userMapper;
	
	@Override
	@Transactional
	public Users findUserByName(String name) {
		return userMapper.findUserByName(name);
	}

	……
}

 

  findUserByName主要實現按照用戶名查詢用戶的功能,如今咱們使用Spring AOP來實現緩存:

 

Java代碼  
  1. package com.hl.usersmanager.aop.service;   
  2.   
  3. import org.apache.log4j.Logger;   
  4. import org.aspectj.lang.ProceedingJoinPoint;   
  5. import org.aspectj.lang.annotation.Around;   
  6. import org.aspectj.lang.annotation.Aspect;   
  7. import org.aspectj.lang.annotation.Pointcut;   
  8. import org.springframework.beans.factory.annotation.Autowired;   
  9.   
  10. import com.hl.usersmanager.memcached.client.MemcachedCache;   
  11. import com.hl.usersmanager.model.Users;   
  12.   
  13. @Aspect  
  14. public class UserServiceInterceptor {   
  15.     public static final Logger log = Logger   
  16.             .getLogger(UserServiceInterceptor.class);   
  17.   
  18. //將緩存客戶端工具類 MemcachedCache 織入進來   
  19.     @Autowired  
  20.     private MemcachedCache memcachedCache;   
  21.   
  22.     /*  
  23.      * 定義pointcunt  
  24.      */  
  25.     @Pointcut("execution(* com.hl.usersmanager.service.impl.UserServiceImpl.*(..))")   
  26.     public void aPointcut() {   
  27.   
  28.     }   
  29.   
  30.     /**  
  31.      * 環繞裝備 用於攔截查詢 若是緩存中有數據,直接從緩存中讀取;不然從數據庫讀取並將結果放入緩存  
  32.      *   
  33.      * @param call  
  34.      * @param name  
  35.      * @return  
  36.      */  
  37.     @Around("aPointcut()&&args(name)")   
  38.     public Users doFindUserByNameAround(ProceedingJoinPoint call, String name) {   
  39.         Users users = null;   
  40.         if (memcachedCache.getCache().containsKey("findUserByName_" + name)) {   
  41.             users = (Users) memcachedCache.get("findUserByName_" + name);   
  42.             log.debug("從緩存中讀取!findUserByName_" + name);   
  43.         } else {   
  44.             try {   
  45.                 users = (Users) call.proceed();   
  46.                 if (users != null) {   
  47.                     memcachedCache.put("findUserByName_" + name, users);   
  48.                     log.debug("緩存裝備被執行:findUserByName_" + name);   
  49.                 }   
  50.             } catch (Throwable e) {   
  51.                 e.printStackTrace();   
  52.             }   
  53.         }   
  54.         return users;   
  55.     }   
  56. }  
package com.hl.usersmanager.aop.service;

import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;

import com.hl.usersmanager.memcached.client.MemcachedCache;
import com.hl.usersmanager.model.Users;

@Aspect
public class UserServiceInterceptor {
	public static final Logger log = Logger
			.getLogger(UserServiceInterceptor.class);

//將緩存客戶端工具類 MemcachedCache 織入進來
	@Autowired
	private MemcachedCache memcachedCache;

	/*
	 * 定義pointcunt
	 */
	@Pointcut("execution(* com.hl.usersmanager.service.impl.UserServiceImpl.*(..))")
	public void aPointcut() {

	}

	/**
	 * 環繞裝備 用於攔截查詢 若是緩存中有數據,直接從緩存中讀取;不然從數據庫讀取並將結果放入緩存
	 * 
	 * @param call
	 * @param name
	 * @return
	 */
	@Around("aPointcut()&&args(name)")
	public Users doFindUserByNameAround(ProceedingJoinPoint call, String name) {
		Users users = null;
		if (memcachedCache.getCache().containsKey("findUserByName_" + name)) {
			users = (Users) memcachedCache.get("findUserByName_" + name);
			log.debug("從緩存中讀取!findUserByName_" + name);
		} else {
			try {
				users = (Users) call.proceed();
				if (users != null) {
					memcachedCache.put("findUserByName_" + name, users);
					log.debug("緩存裝備被執行:findUserByName_" + name);
				}
			} catch (Throwable e) {
				e.printStackTrace();
			}
		}
		return users;
	}
}

 

 環繞通知裝備須要一個ProceedingJoinPoint 類型的參數,它的強大之處在於能夠代理一個咱們的切入點,指定切入點方法是否執行,或者獲取執行後的返回結果!!

 

memcachedCache.getCache().containsKey("findUserByName_" + name)

能夠判斷緩存中是否有指定的數據。若是有則直接從緩存中讀取:

users = (Users) memcachedCache.get("findUserByName_" + name);

不然調用切入點UserServiceImpl的findUserByName方法:

users = (Users) call.proceed();

 call.proceed()表示執行切入點的方法。

 

 

使用Spring AOP之後,整個緩存系統代碼看起來 就是這麼優雅!UserServiceImpl根本不知道外界發了什麼,更不知道外界調用它的findUserByName的時候已經被攔截了!

那天不用緩存系統,只須要將Aop這塊的代碼去掉便可。

 

固然,咱們還須要在Spring 配置文件中註冊一個memcached客戶端工具類的bean:

 

Xml代碼  
  1. <!-- MemcachedCache緩存 -->  
  2. <bean id="MemcachedCache" class="com.hl.usersmanager.memcached.client.MemcachedCache"></bean>  
<!-- MemcachedCache緩存 -->
<bean id="MemcachedCache" class="com.hl.usersmanager.memcached.client.MemcachedCache"></bean>
相關文章
相關標籤/搜索