当前位置:网站首页>简直骚操作,ThreadLocal还能当缓存用
简直骚操作,ThreadLocal还能当缓存用
2020-11-06 01:28:00 【尹吉欢】
背景说明
有朋友问我一个关于接口优化的问题,他的优化点很清晰,由于接口中调用了内部很多的 service 去组成了一个完成的业务功能。每个 service 中的逻辑都是独立的,这样就导致了很多查询是重复的,看下图你就明白了。

上层查询传递下去
对于这种场景最好的就是在上层将需要的数据查询出来,然后传递到下层去消费。这样就不用重复查询了。

如果开始写代码的时候是这样做的没问题,但很多时候,之前写的时候都是独立的,或者复用的老逻辑,里面就是有独立的查询。
如果要做优化就只能将老的方法重载一个,将需要的信息直接传递过去。
public void xxx(int goodsId) {Goods goods = goodsService.get(goodsId);.....}public void xxx(Goods goods) {.....}
加缓存
如果你的业务场景允许数据有一定延迟,那么重复调用你可以直接通过加缓存来解决。这样的好处在于不会重复查询数据库,而是直接从缓存中取数据。
更大的好处在于对于优化类的影响最小,原有的代码逻辑都不用改变,只需要在查询的方法上加注解进行缓存即可。
public void xxx(int goodsId) {Goods goods = goodsService.get(goodsId);.....}public void xxx(Goods goods) {Goods goods = goodsService.get(goodsId);.....}class GoodsService {@Cached(expire = 10, timeUnit = TimeUnit.SECONDS)public Goods get(int goodsId) {return dao.findById(goodsId);}}
如果你的业务场景不允许有缓存的话,上面这个方法就不能用了。那么是不是还得改代码,将需要的信息一层层往下传递呢?
自定义线程内的缓存
我们总结下目前的问题:
- 同一次请求内,多次相同的查询获取 RPC 等的调用。
- 数据实时性要求高,不适合加缓存,主要是加缓存也不好设置过期时间,除非采用数据变更主动更新缓存的方式。
- 只需要在这一次请求里缓存即可,不影响其他地方。
- 不想改动已有代码。
总结后发现这个场景适合用 ThreadLocal 来传递数据,对已有代码改动量最小,而且也只对当前线程生效,不会影响其他线程。
public void xxx(int goodsId) {Goods goods = ThreadLocal.get();if (goods == null) {goods = goodsService.get(goodsId);}.....}
上面代码就是使用了 ThreadLocal 来获取数据,如果有的话就直接使用,不用去重新查询,没有的话就去查询,不影响老逻辑。
虽然能实现效果,但是不太好,不够优雅。也不够通用,如果一次请求内要缓存多种类型的数据怎么处理? ThreadLocal 就不能存储固定的类型。还有就是老的逻辑还是得改,加了个判断。
下面介绍一种比较优雅的方式:
- 自定义缓存注解,加在查询的方法上。
- 定义切面切到加了缓存注解的方法上,第一次获取返回值存入 ThreadLocal。第二次直接从 ThreadLocal 中取值返回。
- ThreadLocal 中存储 Map,Key 为某方法的某一标识,这样可以缓存多种类型的结果。
- 在 Filter 中将 ThreadLocal 进行 remove 操作,因为线程是复用的,使用完需要清空。
注意:ThreadLocal 不能跨线程,如果有跨线程需求,请使用阿里的 ttl 来装饰。

注解定义
@Target({ ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)public @interface ThreadLocalCache {/*** 缓存key,支持SPEL表达式* @return*/String key() default "";}
存储定义
/*** 线程内缓存管理** @作者 尹吉欢* @时间 2020-07-12 10:47*/public class ThreadLocalCacheManager {private static ThreadLocal<Map> threadLocalCache = new ThreadLocal<>();public static void setCache(Map value) {threadLocalCache.set(value);}public static Map getCache() {return threadLocalCache.get();}public static void removeCache() {threadLocalCache.remove();}public static void removeCache(String key) {Map cache = threadLocalCache.get();if (cache != null) {cache.remove(key);}}}
切面定义
/*** 线程内缓存** @作者 尹吉欢* @时间 2020-07-12 10:48*/@Aspectpublic class ThreadLocalCacheAspect {@Around(value = "@annotation(localCache)")public Object aroundAdvice(ProceedingJoinPoint joinpoint, ThreadLocalCache localCache) throws Throwable {Object[] args = joinpoint.getArgs();Method method = ((MethodSignature) joinpoint.getSignature()).getMethod();String className = joinpoint.getTarget().getClass().getName();String methodName = method.getName();String key = parseKey(localCache.key(), method, args, getDefaultKey(className, methodName, args));Map cache = ThreadLocalCacheManager.getCache();if (cache == null) {cache = new HashMap();}Map finalCache = cache;Map<String, Object> data = new HashMap<>();data.put("methodName", className + "." + methodName);Object cacheResult = CatTransactionManager.newTransaction(() -> {if (finalCache.containsKey(key)) {return finalCache.get(key);}return null;}, "ThreadLocalCache", "CacheGet", data);if (cacheResult != null) {return cacheResult;}return CatTransactionManager.newTransaction(() -> {Object result = null;try {result = joinpoint.proceed();} catch (Throwable throwable) {throw new RuntimeException(throwable);}finalCache.put(key, result);ThreadLocalCacheManager.setCache(finalCache);return result;}, "ThreadLocalCache", "CachePut", data);}private String getDefaultKey(String className, String methodName, Object[] args) {String defaultKey = className + "." + methodName;if (args != null) {defaultKey = defaultKey + "." + JsonUtils.toJson(args);}return defaultKey;}private String parseKey(String key, Method method, Object[] args, String defaultKey){if (!StringUtils.hasText(key)) {return defaultKey;}LocalVariableTableParameterNameDiscoverer nameDiscoverer = new LocalVariableTableParameterNameDiscoverer();String[] paraNameArr = nameDiscoverer.getParameterNames(method);ExpressionParser parser = new SpelExpressionParser();StandardEvaluationContext context = new StandardEvaluationContext();for(int i = 0;i < paraNameArr.length; i++){context.setVariable(paraNameArr[i], args[i]);}try {return parser.parseExpression(key).getValue(context, String.class);} catch (SpelEvaluationException e) {// 解析不出SPEL默认为类名+方法名+参数return defaultKey;}}}
过滤器定义
/*** 线程缓存过滤器** @作者 尹吉欢* @个人微信 jihuan900* @微信公众号 猿天地* @GitHub https://github.com/yinjihuan* @作者介绍 http://cxytiandi.com/about* @时间 2020-07-12 19:46*/@Slf4jpublic class ThreadLocalCacheFilter implements Filter {@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {filterChain.doFilter(servletRequest, servletResponse);// 执行完后清除缓存ThreadLocalCacheManager.removeCache();}}
自动配置类
@Configurationpublic class ThreadLocalCacheAutoConfiguration {@Beanpublic FilterRegistrationBean idempotentParamtFilter() {FilterRegistrationBean registration = new FilterRegistrationBean();ThreadLocalCacheFilter filter = new ThreadLocalCacheFilter();registration.setFilter(filter);registration.addUrlPatterns("/*");registration.setName("thread-local-cache-filter");registration.setOrder(1);return registration;}@Beanpublic ThreadLocalCacheAspect threadLocalCacheAspect() {return new ThreadLocalCacheAspect();}}
使用案例
@Servicepublic class TestService {/*** ThreadLocalCache 会缓存,只对当前线程有效* @return*/@ThreadLocalCachepublic String getName() {System.out.println("开始查询了");return "yinjihaun";}/*** 支持SPEL表达式* @param id* @return*/@ThreadLocalCache(key = "#id")public String getName(String id) {System.out.println("开始查询了");return "yinjihaun" + id;}}
功能代码: https://github.com/yinjihuan/kitty
案例代码: https://github.com/yinjihuan/kitty-samples
关于作者 :尹吉欢,简单的技术爱好者,《Spring Cloud 微服务-全栈技术与案例解析》, 《Spring Cloud 微服务 入门 实战与进阶》作者, 公众号 猿天地 发起人。个人微信 jihuan900 ,欢迎勾搭。
我整理了一份很全的学习资料,感兴趣的可以微信搜索 「猿天地」,回复关键字 「学习资料」获取我整理好了的Spring Cloud,Spring Cloud Alibaba,Sharding-JDBC分库分表,任务调度框架XXL-JOB,MongoDB,爬虫等相关资料。

版权声明
本文为[尹吉欢]所创,转载请带上原文链接,感谢
http://cxytiandi.com/blog/detail/36497
边栏推荐
- 基础知识点整理
- 7.3.1 file upload and zero XML registration interceptor
- 【事件中心 Azure Event Hub】Event Hub日誌種發現的錯誤資訊解讀
- Skywalking系列博客2-Skywalking使用
- OPTIMIZER_TRACE详解
- Skywalking系列博客5-apm-customize-enhance-plugin插件使用教程
- 聆听无声的话语:手把手教你用ModelArts实现手语识别
- 面经手册 · 第16篇《码农会锁,ReentrantLock之公平锁讲解和实现》
- 为了省钱,我用1天时间把PHP学了!
- JUC 包下工具类,它的名字叫 LockSupport !你造么?
猜你喜欢
随机推荐
TF flags的简介
VUEJS开发规范
为什么民营企业要做党建?——极客邦控股党支部专题学习
Working principle of gradient descent algorithm in machine learning
GBDT与xgb区别,以及梯度下降法和牛顿法的数学推导
一文带你了解 Jest 单元测试
8.1.1 handling global exceptions through handlerexceptionresolver
程序员自省清单
互联网 舆情系统的架构实践
字符串的常见算法总结
Using class weight to improve class imbalance
用Python构建和可视化决策树
DeepWalk模型的简介与优缺点
刚毕业不久,接私活赚了2万块!
6.7 theme resolver theme style parser (in-depth analysis of SSM and project practice)
Skywalking系列博客5-apm-customize-enhance-plugin插件使用教程
基于深度学习的推荐系统
python 保存list数据
【QT】 QThread部分原始碼淺析
梯度下降算法在机器学习中的工作原理








