您的位置:首页 > 数据库 > Memcache

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

2015-10-30 14:51 585 查看
使用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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  缓存 java