<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          簡直騷操作,ThreadLocal還能當(dāng)緩存用

          共 7072字,需瀏覽 15分鐘

           ·

          2020-08-10 13:19


          背景說明

          有朋友問我一個關(guān)于接口優(yōu)化的問題,他的優(yōu)化點(diǎn)很清晰,由于接口中調(diào)用了內(nèi)部很多的 service 去組成了一個完成的業(yè)務(wù)功能。每個 service 中的邏輯都是獨(dú)立的,這樣就導(dǎo)致了很多查詢是重復(fù)的,看下圖你就明白了。

          上層查詢傳遞下去

          對于這種場景最好的就是在上層將需要的數(shù)據(jù)查詢出來,然后傳遞到下層去消費(fèi)。這樣就不用重復(fù)查詢了。

          如果開始寫代碼的時候是這樣做的沒問題,但很多時候,之前寫的時候都是獨(dú)立的,或者復(fù)用的老邏輯,里面就是有獨(dú)立的查詢。

          如果要做優(yōu)化就只能將老的方法重載一個,將需要的信息直接傳遞過去。

          public void xxx(int goodsId) {
          Goods goods = goodsService.get(goodsId);
          .....
          }
          public void xxx(Goods goods) {
          .....
          }

          加緩存

          如果你的業(yè)務(wù)場景允許數(shù)據(jù)有一定延遲,那么重復(fù)調(diào)用你可以直接通過加緩存來解決。這樣的好處在于不會重復(fù)查詢數(shù)據(jù)庫,而是直接從緩存中取數(shù)據(jù)。

          更大的好處在于對于優(yōu)化類的影響最小,原有的代碼邏輯都不用改變,只需要在查詢的方法上加注解進(jìn)行緩存即可。

          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);
          }
          }

          如果你的業(yè)務(wù)場景不允許有緩存的話,上面這個方法就不能用了。那么是不是還得改代碼,將需要的信息一層層往下傳遞呢?

          自定義線程內(nèi)的緩存

          我們總結(jié)下目前的問題:

          1. 同一次請求內(nèi),多次相同的查詢獲取 RPC 等的調(diào)用。
          2. 數(shù)據(jù)實(shí)時性要求高,不適合加緩存,主要是加緩存也不好設(shè)置過期時間,除非采用數(shù)據(jù)變更主動更新緩存的方式。
          3. 只需要在這一次請求里緩存即可,不影響其他地方。
          4. 不想改動已有代碼。

          總結(jié)后發(fā)現(xiàn)這個場景適合用 ThreadLocal 來傳遞數(shù)據(jù),對已有代碼改動量最小,而且也只對當(dāng)前線程生效,不會影響其他線程。

          public void xxx(int goodsId) {
          Goods goods = ThreadLocal.get();
          if (goods == null) {
          goods = goodsService.get(goodsId);
          }
          .....
          }

          上面代碼就是使用了 ThreadLocal 來獲取數(shù)據(jù),如果有的話就直接使用,不用去重新查詢,沒有的話就去查詢,不影響老邏輯。

          雖然能實(shí)現(xiàn)效果,但是不太好,不夠優(yōu)雅。也不夠通用,如果一次請求內(nèi)要緩存多種類型的數(shù)據(jù)怎么處理? ThreadLocal 就不能存儲固定的類型。還有就是老的邏輯還是得改,加了個判斷。

          下面介紹一種比較優(yōu)雅的方式:

          1. 自定義緩存注解,加在查詢的方法上。
          2. 定義切面切到加了緩存注解的方法上,第一次獲取返回值存入 ThreadLocal。第二次直接從 ThreadLocal 中取值返回。
          3. ThreadLocal 中存儲 Map,Key 為某方法的某一標(biāo)識,這樣可以緩存多種類型的結(jié)果。
          4. 在 Filter 中將 ThreadLocal 進(jìn)行 remove 操作,因?yàn)榫€程是復(fù)用的,使用完需要清空。

          注意:ThreadLocal 不能跨線程,如果有跨線程需求,請使用阿里的 ttl 來裝飾。

          注解定義

          @Target({ ElementType.METHOD })
          @Retention(RetentionPolicy.RUNTIME)
          public @interface ThreadLocalCache {
          /**
          * 緩存key,支持SPEL表達(dá)式
          * @return
          */
          String key() default "";
          }

          存儲定義

          /**
          * 線程內(nèi)緩存管理
          *
          * @作者 尹吉?dú)g
          * @時間 2020-07-12 10:47
          */
          public class ThreadLocalCacheManager {
          private static ThreadLocal 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);
          }
          }
          }

          切面定義

          /**
          * 線程內(nèi)緩存
          *
          * @作者 尹吉?dú)g
          * @時間 2020-07-12 10:48
          */
          @Aspect
          public 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 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默認(rèn)為類名+方法名+參數(shù)
          return defaultKey;
          }
          }
          }

          過濾器定義

          /**
          * 線程緩存過濾器
          *
          * @作者 尹吉?dú)g
          * @個人微信 jihuan900
          * @微信公眾號 猿天地
          * @GitHub https://github.com/yinjihuan
          * @作者介紹 http://cxytiandi.com/about
          * @時間 2020-07-12 19:46
          */
          @Slf4j
          public class ThreadLocalCacheFilter implements Filter {
          @Override
          public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
          filterChain.doFilter(servletRequest, servletResponse);
          // 執(zhí)行完后清除緩存
          ThreadLocalCacheManager.removeCache();
          }
          }

          自動配置類

          @Configuration
          public class ThreadLocalCacheAutoConfiguration {
          @Bean
          public 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;
          }
          @Bean
          public ThreadLocalCacheAspect threadLocalCacheAspect() {
          return new ThreadLocalCacheAspect();
          }
          }

          使用案例

          @Service
          public class TestService {
          /**
          * ThreadLocalCache 會緩存,只對當(dāng)前線程有效
          * @return
          */
          @ThreadLocalCache
          public String getName() {
          System.out.println("開始查詢了");
          return "yinjihaun";
          }
          /**
          * 支持SPEL表達(dá)式
          * @param id
          * @return
          */
          @ThreadLocalCache(key = "#id")
          public String getName(String id) {
          System.out.println("開始查詢了");
          return "yinjihaun" + id;
          }
          }

          功能代碼:https://github.com/yinjihuan/kitty[1]

          案例代碼:https://github.com/yinjihuan/kitty-samples[2]

          關(guān)于作者 :尹吉?dú)g,簡單的技術(shù)愛好者,《Spring Cloud 微服務(wù)-全棧技術(shù)與案例解析》, 《Spring Cloud 微服務(wù) 入門 實(shí)戰(zhàn)與進(jìn)階》作者, 公眾號 猿天地 發(fā)起人。個人微信 jihuan900 ,歡迎勾搭。


          往期推薦



          誰說Cat不能做鏈路跟蹤的,給我站出來

          恕我直言,我也是才知道ElasticSearch條件更新是這么玩的

          分布式ID生成服務(wù),真的有必要搞一個

          Dubbo服務(wù)調(diào)用隔離這么玩對么



          后臺回復(fù)?學(xué)習(xí)資料?領(lǐng)取學(xué)習(xí)視頻


          如有收獲,點(diǎn)個在看,誠摯感謝

          瀏覽 41
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美AAAAAA | 亚洲大胆人体视频 | 美国三级欧美一级 | 内射麻豆| 男人天堂最新网址 |