写在前面

taobao-pamirs-proxycache 是一款开源缓存代理框架, 它将 缓存代码 与 业务代码 解耦。让开发专注coding业务, 缓存通过xml配置即可实现。本文先从此工具如何使用讲起,给大家带来点感知~再从源码剖析它的实现原理。

一、proxycache工具的感知

1.1 使用场景

假设我有这样的一个场景,在访问UserWhiteReadService.getUserWhiteByAppAndWhiteCode时,希望先从缓存获取,结果为空,则走原生方法,再把原生方法返回的结果put到缓存。传统的做法,会写一堆取缓存再判空等代码。方法多了的话,每个要缓存的方法需要重复上述coding。结合这种场景,使用taobao-pamirs-proxycache 能给我们带来什么好处。从下面的代码来看,业务代码中去除了缓存的相关代码。只需要配置下xml即可达到传统做法的目的。管理更加集中了。

public ResultSupport<List<UserWhiteEventDTO>> getUserWhiteByAppAndWhiteCode(String appName, String userWhiteCode) throws Exception {

		ResultSupport<List<UserWhiteEventDTO>> res = new ResultSupport<List<UserWhiteEventDTO>>();
try {
List<UserWhiteEventDO> r = userWhiteEventDAO.selectUserWhitesByAppAndWhiteCode(appName, userWhiteCode);
res.setModule(TransferUtils.convert2UserWhiteEventDTOList(r));
res.setSuccess(Boolean.TRUE);
} catch (Exception e) {
res.setMessage("异常 : " + e);
throw new Exception("UserWhiteReadServiceImpl.getUserWhiteByAppAndWhiteCode error : " + e);
} return res;
}

缓存、清理方法配置 biz-cache.xml

<?xml version="1.0" encoding="gb2312"?>
<cacheModule>
<!-- 缓存bean list -->
<cacheBeans>
<cacheBean>
<beanName>userWhiteReadService</beanName>
<cacheMethods>
<methodConfig>
<methodName>getUserWhiteByAppAndWhiteCode</methodName>
<expiredTime>2592000</expiredTime><!-- 指定缓存生命周期 -->
</methodConfig>
<methodConfig>
<methodName>getUserWhitesByUserId</methodName>
<expiredTime>2592000</expiredTime><!-- 指定缓存生命周期 -->
</methodConfig>
</cacheMethods>
</cacheBean>
</cacheBeans> <!-- 清缓存bean list -->
<cacheCleanBeans>
<cacheCleanBean>
<beanName>userWhiteReadService</beanName>
<methods>
<cacheCleanMethod>
<methodName>cleanByAppAndCode</methodName>
<cleanMethods>
<methodConfig>
<methodName>getUserWhiteByAppAndWhiteCode</methodName>
</methodConfig>
</cleanMethods>
</cacheCleanMethod>
<cacheCleanMethod>
<methodName>cleanByUserId</methodName>
<cleanMethods>
<methodConfig>
<methodName>getUserWhitesByUserId</methodName>
</methodConfig>
</cleanMethods>
</cacheCleanMethod>
</methods>
</cacheCleanBean>
</cacheCleanBeans>
</cacheModule>

cache配置 base-cache.xml


<?xml version="1.0" encoding="gb2312"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"
default-autowire="byName"> <bean id="tairManager" class="com.taobao.tair.impl.mc.MultiClusterTairManager"
init-method="init">
<property name="configID">
<value>${tair.configID}</value>
</property>
<property name="dynamicConfig">
<value type="java.lang.Boolean">true</value>
</property>
</bean> <bean id="cacheManager" class="com.taobao.pamirs.cache.load.impl.LocalConfigCacheManager"
init-method="init" depends-on="tairManager">
<property name="storeType" value="tair" />
<property name="tairNameSpace" value="${tair.namespace}" /><!-- 缓存tair空间 -->
<property name="storeRegion" value="${tair.store.region}" /> <!-- 缓存环境隔离 -->
<property name="configFilePaths">
<list>
<value>spring/cache/biz-cache.xml</value>
</list>
</property>
<property name="tairManager" ref="tairManager" />
</bean> <bean class="com.taobao.pamirs.cache.framework.aop.handle.CacheManagerHandle">
<property name="cacheManager" ref="cacheManager" />
</bean>
</beans>

二、proxy-cache 框架模块

  • 缓存配置信息加载模块

  • beanProxy(bean代理对象)生成模块

  • CacheProxy(缓存代理对象)生成模块

  • 日志监控模块(本文不讲)

三、实现原理

3.1 缓存配置信息加载架构图

从上图及结合源码, CacheManager 是缓存框架的加载入口。CacheManager 有两个关键实现细节 :

1、定义了初始化方法init( ), 由子类LocalConfigCacheManager实现loadConfig( )。这是加载缓存配置信息,组装成缓存组件的入口。

2、实现了ApplicationListener 接口,重写了监听事件方法。

/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(ApplicationEvent event) { if (event instanceof ContextRefreshedEvent) {
// 2. 自动填充默认的配置
autoFillCacheConfig(cacheConfig); // 3. 缓存配置合法性校验
verifyCacheConfig(cacheConfig); // 4. 初始化缓存
initCache();
}}

initCache()方法, 主要是对缓存适配key的构造、生成所有需缓存的方法对应的"缓存代理" -- CacheProxy, 及缓存的定时清理任务。下面对上述各个细节点一一讲解。

3.1.1缓存适配器key的构造

缓存适配器的key格式 : region@beanName#methodName#{String|Long}



public static String getCacheAdapterKey(String region, String beanName,
MethodConfig methodConfig) {
Assert.notNull(methodConfig); // 最终的key
StringBuilder key = new StringBuilder(); // 1. region
if (StringUtils.isNotBlank(region))
key.append(region).append(REGION_SPLITE_SIGN); // "@" // 2. bean + method + parameter
String methodName = methodConfig.getMethodName();
List<Class<?>> parameterTypes = methodConfig.getParameterTypes(); key.append(beanName).append(KEY_SPLITE_SIGN); // "#"
key.append(methodName).append(KEY_SPLITE_SIGN); // "#"
key.append(parameterTypesToString(parameterTypes)); return key.toString(); }

3.1.2 缓存处理适配CacheProxy的组装

CacheProxy :包含了适配器Key、缓存类型(如 tair缓存 or Map本地缓存)、 缓存对应的对象bean及method、缓存空间(tair要用到)等。

ICache : 则是缓存基础接口。提供了get 、 put、clean等通用方法。目前支持tair 、 Map本地 两种缓存类型

3.2 beanProxy 代理对象生成结构图

CacheManagerHandle : 这个缓存处理类很关键,它实现了AbstractAutoProxyCreator接口,重写了getAdvicesAndAdvisorsForBean方法,实现了自己的AOP切面CacheManagerAdvisor。CacheManagerAdvisor,依赖了CacheManagerRoundAdvice拦截器, CacheManagerRoundAdvice 通过实现 MethodInterceptor接口的invoke 方法,实现了在访问目标方法时植入缓存访问、清缓存切面 。具体可以看下下面这一小段源码 :


protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass,
String beanName, TargetSource targetSource) throws BeansException { log.debug("CacheManagerHandle in:" + beanName); if (ConfigUtil.isBeanHaveCache(cacheManager.getCacheConfig(), beanName)) { log.warn("CacheManager start... ProxyBean:" + beanName); return new CacheManagerAdvisor[] { new CacheManagerAdvisor(
cacheManager, beanName) };
} return DO_NOT_PROXY;
}

CacheManagerRoundAdvice 重写的invoke方法 : 访问目标方法前进行拦截,如果是访问缓存的操作, 则植入缓存代理切面,优先从缓存结果中取,取不到再从原生方法取数据,并且put 到 缓存。 如果是清理缓存的操作, 则在原生方法访问后,清理原生方法历史缓存数据。



public Object invoke(MethodInvocation invocation) throws Throwable {
MethodConfig cacheMethod = null;
List<MethodConfig> cacheCleanMethods = null;
String storeRegion = "";
Method method = invocation.getMethod();
String methodName = method.getName();
try {
CacheConfig cacheConfig = cacheManager.getCacheConfig();
storeRegion = cacheConfig.getStoreRegion();
List<Class<?>> parameterTypes = Arrays.asList(method
.getParameterTypes());
cacheMethod = ConfigUtil.getCacheMethod(cacheConfig, beanName, methodName, parameterTypes);
cacheCleanMethods = ConfigUtil.getCacheCleanMethods(cacheConfig,
beanName, methodName, parameterTypes); } catch (Exception e) {
log.error("CacheManager:切面解析配置出错:" + beanName + "#"
+ invocation.getMethod().getName(), e);
return invocation.proceed();
}
String fromHsfIp = "";// hsf consumer ip
try {
fromHsfIp = (String) invocation.getThis().getClass()
.getMethod("getCustomIp").invoke(invocation.getThis());
} catch (NoSuchMethodException e) {
log.debug("接口没有实现HSF的getCustomIp方法,取不到Consumer IP, beanName="
+ beanName);
}
try {
// 1. 走缓存
if (cacheManager.isUseCache() && cacheMethod != null) {
String adapterKey = CacheCodeUtil.getCacheAdapterKey(
storeRegion, beanName, cacheMethod);
CacheProxy<Serializable, Serializable> cacheAdapter = cacheManager
.getCacheProxy(adapterKey);
String cacheCode = CacheCodeUtil.getCacheCode(storeRegion,
beanName, cacheMethod, invocation.getArguments());
return useCache(cacheAdapter, cacheCode,
cacheMethod.getExpiredTime(), invocation, fromHsfIp);
}
// 2. 清理缓存
if (cacheCleanMethods != null) {
try {
return invocation.proceed();
} finally {
cleanCache(beanName, cacheCleanMethods, invocation,
storeRegion, fromHsfIp);
}
}
// 3. 走原生方法
return invocation.proceed();
} catch (Exception e) {
// log.error("CacheManager:出错:" + beanName + "#"
// + invocation.getMethod().getName(), e);
throw e;
}
}

四、那些踩过的坑

原生方法,不要随意捕获异常;或者在捕获异常后,要手动throw异常出来。因为使用了该缓存工具,只要调用此方法不抛出异常,原生方法的结果(不排除异常结果)会被框架缓存住。记得有一次在断网演练的时候,由于断网导致连接DB出问题,异常信息还是被我catch掉了,结果就悲剧了,异常信息结果被缓存住了。导致应用恢复时,再次调用此方法,返回的结果一直都是exception~

写在最后

我的新博客

CSDN博客经常打不开, 老博客继续维护一段时间吧~~

taobao-pamirs-proxycache开源缓存代理框架实现原理剖析的更多相关文章

  1. Nginx多进程高并发、低时延、高可靠机制在缓存代理中的应用

    1. 开发背景 现有开源缓存代理中间件有twemproxy.codis等,其中twemproxy为单进程单线程模型,只支持memcache单机版和redis单机版,都不支持集群版功能. 由于twemp ...

  2. Nginx多进程高并发、低时延、高可靠机制缓存代理中的应用

    1. 开发背景 现有开源缓存代理中间件有twemproxy.codis等,其中twemproxy为单进程单线程模型,只支持memcache单机版和redis单机版,都不支持集群版功能. 由于twemp ...

  3. 【Android开源项目分析】android轻量级开源缓存框架——ASimpleCache(ACache)源代码分析

    转载请注明出处:http://blog.csdn.net/zhoubin1992/article/details/46379055 ASimpleCache框架源代码链接 https://github ...

  4. ACache【轻量级的开源缓存框架】

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 官方介绍 ASimpleCache 是一个为android制定的 轻量级的 开源缓存框架.轻量到只有一个java文件(由十几个类精简 ...

  5. Android轻量级的开源缓存框架ASimpleCache

    点击查看原文 先上方法调用,写最经常使用的.其它不一一写 保存数据: ACache mACache=ACache.get(this); mACache.put("数据名称", js ...

  6. 开源缓存框架之ASimpleCache

    ASimpleCache 是一个为android制定的 轻量级的 开源缓存框架.轻量到只有一个java文件(由十几个类精简而来). 1.它可以缓存什么东西? 普通的字符串.JsonObject.Jso ...

  7. 分布式开源调度框架TBSchedule原理与应用

    主要内容: 第一部分 TBSchedule基本概念及原理 1. 概念介绍 2. 工作原理 3. 源代码分析 4. 与其它开源调度框架对照 第二部分 TBSchedule分布式调度演示样例 1. TBS ...

  8. varnish4.0缓存代理配置

    防伪码:你必须非常努力,才能看起来毫不费力. 一.varnish原理: 1)Varnish简介: varnish缓存是web应用加速器,同时也作为http反向缓存代理.你可以安装varnish在任何h ...

  9. 【开源】OSharp框架解说系列(1):总体设计及系列导航

    系列文章导航 [开源]OSharp框架解说系列(1):总体设计 [开源]OSharp框架解说系列(2.1):EasyUI的后台界面搭建及极致重构 [开源]OSharp框架解说系列(2.2):EasyU ...

随机推荐

  1. 用keychain这个特点来保存设备唯一标识。

    由于IOS系统存储的数据都是在sandBox里面,一旦删除App,sandBox也不复存在.好在有一个例外,那就是keychain(钥匙串). 通常情况下,IOS系统用NSUserDefaults存储 ...

  2. (C#)xml的简单理解创建和读取

    xml知识点清理:一.文档规则 1.区分大小写. 2.属性值必须加引号(单引号.双引号都可以),一般情况下建议使用使用双引号. 3.所有标记必须有结束符号. 4.所有空标记必须关闭. 5.必须有且仅有 ...

  3. 游戏UI框架设计(二) : 最简版本设计

    游戏UI框架设计(二) --最简版本设计 为降低难度决定先讲解一个最简版本,阐述UI框架的核心设计理念.这里先定义三个核心功能: 1:UI窗体的自动加载功能. 2:缓存UI窗体. 3:窗体生命周期(状 ...

  4. hadoop-hdfs体系结构

    HDFS作为Hadoop的核心技术之一,HDFS(Hadoop Distributed File System, Hadoop分布式文件系统)是分布式计算中数据存储管理的基础.具有高容错高可靠性.高可 ...

  5. 新注册第一帖----------------------乱码新手自学.net 之Linq 入门篇

    作为一个业余开发,断断续续学.net/c#也有不少日子了, 学习过程中,不断忘了学,学了忘,这让我很苦恼. 以前学习过程中,我总是在笔记本中记录下来知识要点,这么久下来,笔记本都写了四五本了. 然而, ...

  6. BeautifulSoup简述

    网页解析器 从网页中提取有价值数据的工具 网页解析器种类 正则表达式 (模糊匹配) html.parser (结构化解析) BeautifulSoup第三方插件 (结构化解析,相对比较强大) lxml ...

  7. 从源码解析LinkedList集合

         上篇文章我们介绍了ArrayList类的基本的使用及其内部的一些方法的实现原理,但是这种集合类型虽然可以随机访问数据,但是如果需要删除中间的元素就需要移动一半的元素的位置,效率低下.并且它内 ...

  8. 学习JVM--垃圾回收(二)GC收集器

    1. 前言 在上一篇文章中,介绍了JVM中垃圾回收的原理和算法.介绍了通过引用计数和对象可达性分析的算法来筛选出已经没有使用的对象,然后介绍了垃圾收集器中使用的三种收集算法:标记-清除.标记-整理.标 ...

  9. 解决项目中找不到Maven Dependencies

    项目中找不到Maven Dependencies 正常的Maven项目应该是这样的 自己的项目中却没有Maven Dependencies,自己百度了, 发现解决不了,最后发现在.classpath和 ...

  10. Linux Tomcat安装,Linux配置Tomcat,Linux Tomcat修改内存,Linux tomcat修改端口

    Linux Tomcat安装,Linux配置Tomcat,Linux Tomcat修改内存,Linux tomcat修改端口 >>>>>>>>>& ...