当前位置:网站首页>It's easy to operate. ThreadLocal can also be used as a cache

It's easy to operate. ThreadLocal can also be used as a cache

2020-11-06 01:28:26 Yin Jihuan

The background that

A friend asked me a question about interface optimization , His optimization point is very clear , Because there are many internal calls in the interface service To form a completed business function . Every service The logic in is independent , As a result, many queries are duplicated , Look at the picture below and you will see .

 picture

The upper level query is passed on

For this scenario, the best thing is to query the required data from the upper layer , And then pass it on to the lower levels to consume . So you don't have to repeat the query .

 picture

If you start writing code like this, no problem , But a lot of times , I was independent when I wrote before , Or the old logic of reuse , There are independent queries in it .

If you want to do optimization, you can only overload one of the old methods , Pass on the information you need directly .

  
  1. public void xxx(int goodsId) {
  2. Goods goods = goodsService.get(goodsId);
  3. .....
  4. }
  5. public void xxx(Goods goods) {
  6. .....
  7. }

Cache

If your business scenario allows for a certain delay in data , So you can solve the problem of repeated calls by adding cache directly . This has the advantage of not repeatedly querying the database , Instead, it takes data directly from the cache .

The bigger advantage is that it has the least impact on the optimization class , The original code logic does not need to be changed , You can only add annotations to the query .

  
  1. public void xxx(int goodsId) {
  2. Goods goods = goodsService.get(goodsId);
  3. .....
  4. }
  5. public void xxx(Goods goods) {
  6. Goods goods = goodsService.get(goodsId);
  7. .....
  8. }
  9. class GoodsService {
  10. @Cached(expire = 10, timeUnit = TimeUnit.SECONDS)
  11. public Goods get(int goodsId) {
  12. return dao.findById(goodsId);
  13. }
  14. }

If your business scenario doesn't allow caching , The above method can't be used . So is it necessary to change the code , Pass on the required information one layer at a time ?

Customize the cache within the thread

Let's summarize the current problems :

  1. In the same request , Get the same query many times RPC And so on .
  2. High demand for real-time data , Not suitable for caching , The main reason is that it is not easy to set the expiration time by adding cache , Unless data changes are used to actively update the cache .
  3. Just cache in this request , It doesn't affect other places .
  4. Don't want to change existing code .

After summing up, it is found that this scene is suitable for use ThreadLocal To transfer data , Minimal changes to existing code , And it only works for the current thread , Does not affect other threads .

  
  1. public void xxx(int goodsId) {
  2. Goods goods = ThreadLocal.get();
  3. if (goods == null) {
  4. goods = goodsService.get(goodsId);
  5. }
  6. .....
  7. }

The code above uses ThreadLocal To get data , If there is one, use it directly , You don't have to search again , If you don't have it, you can check it out , It doesn't affect the old logic .

Although it can achieve the effect , But not so good , Not elegant enough . It's not universal enough , If you want to cache multiple types of data in one request, how to handle it ? ThreadLocal You can't store fixed types . What's more, the old logic has to be changed , Add a judgment .

Here's a more elegant way :

  1. Custom cache annotations , Add to the query method .
  2. Define facet cuts to methods with cache annotations , Get the return value for the first time ThreadLocal. The second time directly from ThreadLocal Return the value of .
  3. ThreadLocal Storage in Map,Key For a certain identification of a method , This can cache multiple types of results .
  4. stay Filter Lieutenant general ThreadLocal Conduct remove operation , Because threads are reusable , It needs to be emptied after use .

Be careful :ThreadLocal Can't cross thread , If there is a cross thread requirement , Please use Ali's ttl To decorate .

 picture

Annotation definition

  
  1. @Target({ ElementType.METHOD })
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface ThreadLocalCache {
  4. /**
  5. * cache key, Support SPEL expression
  6. * @return
  7. */
  8. String key() default "";
  9. }

Storage definition

  
  1. /**
  2. * In thread cache management
  3. *
  4. * @ author Yin Jihuan
  5. * @ Time 2020-07-12 10:47
  6. */
  7. public class ThreadLocalCacheManager {
  8. private static ThreadLocal<Map> threadLocalCache = new ThreadLocal<>();
  9. public static void setCache(Map value) {
  10. threadLocalCache.set(value);
  11. }
  12. public static Map getCache() {
  13. return threadLocalCache.get();
  14. }
  15. public static void removeCache() {
  16. threadLocalCache.remove();
  17. }
  18. public static void removeCache(String key) {
  19. Map cache = threadLocalCache.get();
  20. if (cache != null) {
  21. cache.remove(key);
  22. }
  23. }
  24. }

Section definition

  
  1. /**
  2. * In thread caching
  3. *
  4. * @ author Yin Jihuan
  5. * @ Time 2020-07-12 10:48
  6. */
  7. @Aspect
  8. public class ThreadLocalCacheAspect {
  9. @Around(value = "@annotation(localCache)")
  10. public Object aroundAdvice(ProceedingJoinPoint joinpoint, ThreadLocalCache localCache) throws Throwable {
  11. Object[] args = joinpoint.getArgs();
  12. Method method = ((MethodSignature) joinpoint.getSignature()).getMethod();
  13. String className = joinpoint.getTarget().getClass().getName();
  14. String methodName = method.getName();
  15. String key = parseKey(localCache.key(), method, args, getDefaultKey(className, methodName, args));
  16. Map cache = ThreadLocalCacheManager.getCache();
  17. if (cache == null) {
  18. cache = new HashMap();
  19. }
  20. Map finalCache = cache;
  21. Map<String, Object> data = new HashMap<>();
  22. data.put("methodName", className + "." + methodName);
  23. Object cacheResult = CatTransactionManager.newTransaction(() -> {
  24. if (finalCache.containsKey(key)) {
  25. return finalCache.get(key);
  26. }
  27. return null;
  28. }, "ThreadLocalCache", "CacheGet", data);
  29. if (cacheResult != null) {
  30. return cacheResult;
  31. }
  32. return CatTransactionManager.newTransaction(() -> {
  33. Object result = null;
  34. try {
  35. result = joinpoint.proceed();
  36. } catch (Throwable throwable) {
  37. throw new RuntimeException(throwable);
  38. }
  39. finalCache.put(key, result);
  40. ThreadLocalCacheManager.setCache(finalCache);
  41. return result;
  42. }, "ThreadLocalCache", "CachePut", data);
  43. }
  44. private String getDefaultKey(String className, String methodName, Object[] args) {
  45. String defaultKey = className + "." + methodName;
  46. if (args != null) {
  47. defaultKey = defaultKey + "." + JsonUtils.toJson(args);
  48. }
  49. return defaultKey;
  50. }
  51. private String parseKey(String key, Method method, Object[] args, String defaultKey){
  52. if (!StringUtils.hasText(key)) {
  53. return defaultKey;
  54. }
  55. LocalVariableTableParameterNameDiscoverer nameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
  56. String[] paraNameArr = nameDiscoverer.getParameterNames(method);
  57. ExpressionParser parser = new SpelExpressionParser();
  58. StandardEvaluationContext context = new StandardEvaluationContext();
  59. for(int i = 0;i < paraNameArr.length; i++){
  60. context.setVariable(paraNameArr[i], args[i]);
  61. }
  62. try {
  63. return parser.parseExpression(key).getValue(context, String.class);
  64. } catch (SpelEvaluationException e) {
  65. // I can't figure out SPEL Default to class name + Method name + Parameters
  66. return defaultKey;
  67. }
  68. }
  69. }

Filter definition

  
  1. /**
  2. * Thread cache filter
  3. *
  4. * @ author Yin Jihuan
  5. * @ Personal wechat jihuan900
  6. * @ WeChat official account Ape world
  7. * @GitHub https://github.com/yinjihuan
  8. * @ The authors introduce http://cxytiandi.com/about
  9. * @ Time 2020-07-12 19:46
  10. */
  11. @Slf4j
  12. public class ThreadLocalCacheFilter implements Filter {
  13. @Override
  14. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  15. filterChain.doFilter(servletRequest, servletResponse);
  16. // Clear cache after execution
  17. ThreadLocalCacheManager.removeCache();
  18. }
  19. }

Automatic configuration class

  
  1. @Configuration
  2. public class ThreadLocalCacheAutoConfiguration {
  3. @Bean
  4. public FilterRegistrationBean idempotentParamtFilter() {
  5. FilterRegistrationBean registration = new FilterRegistrationBean();
  6. ThreadLocalCacheFilter filter = new ThreadLocalCacheFilter();
  7. registration.setFilter(filter);
  8. registration.addUrlPatterns("/*");
  9. registration.setName("thread-local-cache-filter");
  10. registration.setOrder(1);
  11. return registration;
  12. }
  13. @Bean
  14. public ThreadLocalCacheAspect threadLocalCacheAspect() {
  15. return new ThreadLocalCacheAspect();
  16. }
  17. }

Use cases

  
  1. @Service
  2. public class TestService {
  3. /**
  4. * ThreadLocalCache Will cache , Only valid for the current thread
  5. * @return
  6. */
  7. @ThreadLocalCache
  8. public String getName() {
  9. System.out.println(" It's time to check ");
  10. return "yinjihaun";
  11. }
  12. /**
  13. * Support SPEL expression
  14. * @param id
  15. * @return
  16. */
  17. @ThreadLocalCache(key = "#id")
  18. public String getName(String id) {
  19. System.out.println(" It's time to check ");
  20. return "yinjihaun" + id;
  21. }
  22. }

Function code : https://github.com/yinjihuan/kitty

Case code : https://github.com/yinjihuan/kitty-samples

About author : Yin Jihuan , Simple technology enthusiasts ,《Spring Cloud Microservices - Full stack technology and case analysis 》, 《Spring Cloud Microservices introduction Actual combat and advanced 》 author , official account Ape world Originator . Personal wechat jihuan900 , Welcome to hook up with .

I have compiled a complete set of learning materials , Those who are interested can search through wechat 「 Ape world 」, Reply key 「 Learning materials 」 Get what I've sorted out Spring Cloud,Spring Cloud Alibaba,Sharding-JDBC Sub database and sub table , Task scheduling framework XXL-JOB,MongoDB, Reptiles and other related information .

版权声明
本文为[Yin Jihuan]所创,转载请带上原文链接,感谢