变通实现微服务的per request以提高IO效率(三)
效率
变通实现微服务的per request以提高IO效率(二)遗留一个问题,如何正确的释放存储在ThreadLocal中的缓存,最理由就是在我们请求的方法执行完成后去清除缓存。
Filter
由于我的项目是基于dubbo的,所以可以利用dubbo提供的Filter机制去完成这件事情,可以看下filter的地位:
最终的效果:
创建ThreadLocalCacheFilter
创建一个类让其实现Filter接口,就一个方法invoke,这个invoke方法的功能类似于AOP的Around方法,我们想清除缓存就有地方操作了,只需要在return的前面,invoker.invoke方法后面添加相应的清除逻辑即可达到目的。由于缓存是线程独有的,所以直接清空就可以。
由于Filter加载机制问题,在Filter中使用Spring的注解是有点问题的,暂时是通过手动获取Bean的方式来加载cacheManager,后面在看dubbo的filter加载机制时会有简单提到。大家如果有其它好的方案可以告诉我
@Activate
public class ThreadLocalCacheFilter implements Filter {
private Logger logger = LoggerFactory.getLogger(getClass().getName());
@Autowired
private CacheManager cacheManager;
private void clearCache(){
if(null==cacheManager){
ApplicationContext appCtx = ApplicationContextUtils.getApplicationContext();
cacheManager= appCtx.getBean(ThreadLocalCacheManager.class);
}
Collection<String> cacheNames= this.cacheManager.getCacheNames();
if(null!=cacheNames) {
for(String cacheName :cacheNames) {
this.cacheManager.getCache(cacheName).clear();
}
}
}
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
Result result=invoker.invoke(invocation);
this.logger.info("release cache start");
this.clearCache();
this.logger.info("release cache end");
return result;
}
}
@Active注解
要想激活filter,我们需要在创建的自定义filter类上加载@Active注解,看下它的相关参数,也可以不配置
- group,条件之一,指定是服务端还是消费端
- value,条件之一,一般就是这个filter的英文名称,在dubbo配置文件中使用的
- before,排序的信息,比如排在哪些filter之前
- after,排序的信息,比如排在哪些filter之后
- order,排序的信息,应该是值越小排在最前面
加载Filter
编写的扩展filter,dubbo需要加载成功后才能使用,dubbo总共从resource下面的三个目录中加载filter
- META-INF/services/
- META-INF/dubbo/
- META-INF/dubbo/internal/
创建纯文件文件com.alibaba.dubbo.rpc.Filter放入对应的目录,然后写入需要使用的filter信息
threadLocalCacheFilter=com.filter.ThreadLocalCacheFilter
应用Filter
在dubbo配置文件中增加如下内容:
<dubbo:provider filter="threadLocalCacheFilter" />
Dubbo Filter
dubbo有这样一个类ProtocolFilterWrapper,它负责加载项目中所有的filter,并负责链式调用。
想学习设计模式的可以看看这个类是如何使用职责链模式的
这里只看一个方法就可以了:
- ExtensionLoader加载所以实现了Filter接口的类
- 根据过滤条件过滤filter,里面有排序
- 循环调用所有符合条件且经过排序的filter
注意变量next,当前方法在执行invoke方法时,将调用传递到了next。这里应该会有最后一个终结器来处理实际方法的执行。
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
Invoker<T> last = invoker;
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
if (filters.size() > 0) {
for (int i = filters.size() - 1; i >= 0; i --) {
final Filter filter = filters.get(i);
final Invoker<T> next = last;
last = new Invoker<T>() {
public Class<T> getInterface() {
return invoker.getInterface();
}
public URL getUrl() {
return invoker.getUrl();
}
public boolean isAvailable() {
return invoker.isAvailable();
}
public Result invoke(Invocation invocation) throws RpcException {
return filter.invoke(next, invocation);
}
public void destroy() {
invoker.destroy();
}
@Override
public String toString() {
return invoker.toString();
}
};
}
}
return last;
}
总结
结过三篇笔记,从最初的Context问题,到缓存的释放,基本可以非常方便的使用请求级的缓存了。这里需要注意的是需要明确哪些方案是适合做请求级缓存的。比如查询用户,有些操作中先插入用户然后再查询,如果查询的是被标记了请求级缓存的方法就会有问题。