使用Memcached作为全网站分布式缓存

使用Memcached作为全网站分布式缓存,大体思路就是:

首先搭建一些Memcached服务器,作为全网站的分布式缓存服务器。当Controller层调用以get*开头的service查询方法时,首先进入配置的切面对象,执行环绕通知方法,查看Memcached中是否已经有了此查询方法执行后的结果,如果有数据,则直接返回Controller,如果没有数据,就返回service层执行查询方法,并且把执行结果放入到Memcached中缓存起来。

当Controller层调用以add*,delete*,update*开头的service方法时,因为这些这些方法的执行会导致数据库数据变更,因此要清理受到影响的那部分缓存的数据,以免造成数据延迟,即也要进入在spring中配置的切面对象,执行后置通知方法,清空受影响的部分缓存数据。


其中有个关键点,是把service查询的数据结果放入Memcached时的K怎么设置,为了保证唯一性,可以设定K生成规则如下:

K=包名+类名+方法名+参数(可以多个),其中,如果参数是javabean的话,要先转为json串,再与前面的包名+类名+方法名进行拼接。

其中包名+类名比如:com.core.serice.product.ProductServiceImpl.productList


①首先要在Linux服务器上搭建Memcache服务器,这个可以查看Memcached官方文档

②在java项目中加入memcache-xx.jar,又分为两个包:

com.danga.MemCached与

net.spy.memcached

因为danga包有连接池,更稳定,因此此次使用的是danga包的Memcached

③Spring引入Memcached.xml,在其中进行连接池,切面对象和aop配置,这样就能保证所有对系统所有以get*,add*,delete*,update*开头的service方法生效:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="
     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
     http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
     http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
     http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

	<!-- Memcached 配置 -->
	<bean id="memCachedClient" class="com.danga.MemCached.MemCachedClient">
		<constructor-arg>
			<value>sockIOPool</value>
		</constructor-arg>
	</bean>
	<!-- Memcached连接池 -->
	<bean id="sockIOPool" class="com.danga.MemCached.SockIOPool" factory-method="getInstance" init-method="initialize" destroy-method="shutDown">
		<constructor-arg>
			<value>sockIOPool</value>
		</constructor-arg>
		<property name="servers">
			<list>
				<!-- 此处可以配置多台安装了Memcached的服务器,Memcached自带路由功能,我们不需要知道数据具体存到了哪个数据库 -->
				<value>192.168.200.149:11211</value>
			</list>
		</property>
		<property name="weights">
			<list>
				<value>1</value>
			</list>
		</property>
	</bean>
	
	<!-- 配置切面对象,expiry是切面类里加的时间参数 -->
	<bean id="cacheInterceptor" class="com.common.web.aop.CacheInterceptor">
		<property name="expiry" value="4200000"/>
	</bean>
	
	<!-- Spring  Aop 配置   get*配置环绕通知 ,update*、add*、delete*配置后置通知-->
	<aop:config>
		<!-- 面 -->
		<aop:aspect ref="cacheInterceptor">
			<!-- 点 -->
			<aop:around method="doAround" pointcut="execution(* com.core.service.*.*.get*(..))"/>
			<!-- 变更  -->
			<aop:after method="doAfter" pointcut="execution(* com.core.service.*.*.update*(..))"/>
			<aop:after method="doAfter" pointcut="execution(* com.core.service.*.*.add*(..))"/>
			<aop:after method="doAfter" pointcut="execution(* com.core.service.*.*.delete*(..))"/>
		</aop:aspect>
	</aop:config>
</beans>


④先写个测试类测试下Memcached

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:application-context.xml")
public class MemcachedTest{

	@Autowired
	private MemCachedClient memCachedClient;
	@Test
	public void testAdd() throws Exception {
		Student student = new Student();
		student.setName("哈哈");
		//存数据
		//memCachedClient.set("aa",student);
		//取数据
		Object object = memCachedClient.get("aa");
		System.out.println(object);
	}
}


⑤写切面类com.common.web.aop.CacheInterceptor:

/**
 * 缓存Memcached中数据的切面对象
 */
public class CacheInterceptor {

	@Autowired
	private MemCachedClient memCachedClient;
	
	//缓存时间,单位是秒
	private int expiry = 60*60*24*3;
	
	//配置环绕通知方法,get*会执行,环绕通知使用ProceedingJoinPoint,其中包含了请求的一些参数,能够获取要执行的包名,类型,方法名,参数之类的数据
	public Object doAround(ProceedingJoinPoint pjp) throws Throwable{
		//去Memcached中查看有没有此get方法查找的数据  包名+ 类名 + 方法名 + 参数(多个)
		String cacheKey = getCacheKey(pjp);
		System.out.println(cacheKey);
		//如果Memcached连接不上
		if(memCachedClient.stats().isEmpty()){
			System.out.println("Memcached服务器可能不存在或是连接不上");
			//pjp.proceed()此处就是放开拦截,执行service方法查询
			return pjp.proceed();
		}
		
		//如果以前没有缓存过此get方法查询的数据
		if(null == memCachedClient.get(cacheKey)){
			//回Service
			Object proceed = pjp.proceed();
			//吧数据缓存到Memcached中一份
			memCachedClient.set(cacheKey, proceed,expiry);
		}
		return memCachedClient.get(cacheKey);
	}
	
	//add*,update*,delete*后置通知,由于数据库数据变更,因此要清理受到影响的那部分缓存的数据。后置通知使用的是JoinPoint,没有了.proceed()方法
	public void doAfter(JoinPoint jp){
		//获取包名+类名 
		String packageName = jp.getTarget().getClass().getName();
		//以包名+类名开始的缓存数据都清理掉
		//Memcached中获取所有key的方法比较复杂,getKeySet(memCachedClient)也是从网上找的
		Map<String, Object> keySet = MemCachedUtil.getKeySet(memCachedClient);
		Set<Entry<String, Object>> entrySet = keySet.entrySet();
		//遍历清除
		for(Entry<String, Object> entry : entrySet){
			if(entry.getKey().startsWith(packageName)){
				memCachedClient.delete(entry.getKey());
			}
		}
	}
	
	
	//使用包名+类名+方法名+参数(多个)的生成策略生成Memcached保存需要的Key
	public String getCacheKey(ProceedingJoinPoint pjp){
		StringBuilder key = new StringBuilder();
		//获取包名+类名,比如com.core.serice.product.ProductServiceImpl.productList
		String packageName = pjp.getTarget().getClass().getName();
		key.append(packageName);
		//获取方法名
		String methodName = pjp.getSignature().getName();
		key.append(".").append(methodName);
		//获取参数(可能是多个)
		Object[] args = pjp.getArgs();
		//因为参数可能是javabean,所以此处要转为json串才能进行拼接生成key
		ObjectMapper  om = new ObjectMapper();
		om.setSerializationInclusion(Inclusion.NON_NULL);
		//遍历参数,转json串
		for(Object arg : args){
			//流
			StringWriter str = new StringWriter(); 
			//对象转Json,写入流中
			try {
				om.writeValue(str, arg);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			//参数
			key.append(".").append(str);
		}
		return key.toString();
	}
	public void setExpiry(int expiry) {
		this.expiry = expiry;
	}
}


⑥接下来实现Memcached 集群的高可用(HA)架构可参考下面的博客:

http://blog.csdn.net/liu251890347/article/details/38414247


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值