<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>

          簡(jiǎn)歷寫(xiě)著熟悉 Dubbo,居然連 Dubbo 線程池監(jiān)控都不知道?

          共 21171字,需瀏覽 43分鐘

           ·

          2022-07-09 06:52

          程序員的成長(zhǎng)之路
          互聯(lián)網(wǎng)/程序員/技術(shù)/資料共享 
          關(guān)注


          閱讀本文大概需要 8 分鐘。

          來(lái)自:網(wǎng)絡(luò)

          Dubbo 是一款優(yōu)秀的微服務(wù)框架,它以其高性能、簡(jiǎn)單易用、易擴(kuò)展等特點(diǎn),廣泛應(yīng)用于互聯(lián)網(wǎng)、金融保險(xiǎn)、科技公司、制造業(yè)、零售物流等多個(gè)領(lǐng)域。如今,Dubbo 框架已經(jīng)成了互聯(lián)網(wǎng)開(kāi)發(fā)中比較常用的技術(shù)框架。
          在Dubbo框架中,當(dāng)客戶端調(diào)用服務(wù)端的時(shí)候,請(qǐng)求抵達(dá)了服務(wù)端之后,會(huì)有專(zhuān)門(mén)的線程池去接收參數(shù)并且處理。所以如果要實(shí)現(xiàn)Dubbo的線程池監(jiān)控,就需要先了解下Dubbo底層對(duì)于業(yè)務(wù)線程池的實(shí)現(xiàn)原理。

          Dubbo底層對(duì)于線程池的查看

          這里我所使用的框架是 Dubbo 2.7.8 版本,它在底層對(duì)于線程池的管理是通過(guò)一個(gè)叫做ExecutorRepository 的類(lèi)處理的,這個(gè)類(lèi)負(fù)責(zé)創(chuàng)建并管理 Dubbo 中的線程池,通過(guò)該擴(kuò)展接口,我們可以獲取到Dubbo再實(shí)際運(yùn)行中的業(yè)務(wù)線程池對(duì)象。
          具體的處理邏輯部分如下所示:
          package org.idea.dubbo.monitor.core.collect;
          import org.apache.dubbo.common.extension.ExtensionLoader;
          import org.apache.dubbo.common.threadpool.manager.DefaultExecutorRepository;
          import org.apache.dubbo.common.threadpool.manager.ExecutorRepository;
          import java.lang.reflect.Field;
          import java.util.concurrent.ConcurrentMap;
          import java.util.concurrent.ExecutorService;
          import java.util.concurrent.ThreadPoolExecutor;
          /**
           * @Author idea
           * @Date created in 7:04 下午 2022/6/29
           */

          public class DubboThreadPoolCollector {
              /**
               * 獲取Dubbo的線程池
               * @return
               */

              public static ThreadPoolExecutor getDubboThreadPoolInfo(){
                  //dubbo線程池?cái)?shù)量監(jiān)控
                  try {
                      ExtensionLoader<ExecutorRepository> executorRepositoryExtensionLoader = ExtensionLoader.getExtensionLoader(ExecutorRepository.class);
                      DefaultExecutorRepository defaultExecutorRepository = (DefaultExecutorRepository) executorRepositoryExtensionLoader.getDefaultExtension();
                      Field dataField = defaultExecutorRepository.getClass().getDeclaredField("data");
                      dataField.setAccessible(true);
                      ConcurrentMap<String, ConcurrentMap<Integer, ExecutorService>> data = (ConcurrentMap<String, ConcurrentMap<Integer, ExecutorService>>) dataField.get(defaultExecutorRepository);
                      ConcurrentMap<Integer, ExecutorService> executorServiceConcurrentMap = data.get("java.util.concurrent.ExecutorService");
                      //獲取到默認(rèn)的線程池模型
                      ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executorServiceConcurrentMap.get(9090);
                      return threadPoolExecutor;
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
                  return null;
              }
          }
          好了,現(xiàn)在我們知道如何在代碼中實(shí)時(shí)查看Dubbo線程池的信息了,那么接下來(lái)要做的就是如何采集這些線程池的數(shù)據(jù),并且進(jìn)行上報(bào),最后將上報(bào)存儲(chǔ)的數(shù)據(jù)通過(guò)統(tǒng)計(jì)圖的方式展示出來(lái)。
          下邊我們按照采集,上報(bào),展示三個(gè)環(huán)節(jié)來(lái)展示數(shù)據(jù)。

          采集數(shù)據(jù)

          在采集數(shù)據(jù)這塊,有兩種思路去采集,分別如下:
          • 后臺(tái)開(kāi)啟一個(gè)定時(shí)任務(wù),然后每秒都查詢一下線程池的參數(shù)信息。
          • 每次有請(qǐng)求抵達(dá)provider的時(shí)候,就查看一些線程池的參數(shù)信息。

          采用兩種不同的模式采集出來(lái)的數(shù)據(jù),可能會(huì)有些差異,下邊是兩種方式的比對(duì):
          通過(guò)對(duì)實(shí)際的業(yè)務(wù)場(chǎng)景分析,其實(shí)第二種方式對(duì)應(yīng)用的性能損耗極微,甚至可以忽略,所以使用這種方式去采集數(shù)據(jù)的話會(huì)比較合適。
          下邊讓我們一起來(lái)看看這種方式采集數(shù)據(jù)的話,該如何實(shí)現(xiàn)。
          首先我們需要自己定義一個(gè)filter過(guò)濾器:
          package org.idea.dubbo.monitor.core.filter;
          import org.apache.dubbo.common.constants.CommonConstants;
          import org.apache.dubbo.common.extension.Activate;
          import org.apache.dubbo.rpc.*;
          import org.idea.dubbo.monitor.core.DubboMonitorHandler;
          import java.util.concurrent.ThreadPoolExecutor;
          import static org.idea.dubbo.monitor.core.config.CommonCache.DUBBO_INFO_STORE_CENTER;
          /**
           * @Author idea
           * @Date created in 2:33 下午 2022/7/1
           */

          @Activate(group = CommonConstants.PROVIDER)
          public class DubboRecordFilter implements Filter {
              @Override
              public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
                  ThreadPoolExecutor threadPoolExecutor = DubboMonitorHandler.getDubboThreadPoolInfo();
                  //請(qǐng)求的時(shí)候趣統(tǒng)計(jì)線程池,當(dāng)請(qǐng)求量太小的時(shí)候,這塊的數(shù)據(jù)可能不準(zhǔn)確,但是如果請(qǐng)求量大的話,就接近準(zhǔn)確了
                  DUBBO_INFO_STORE_CENTER.reportInfo(9090,threadPoolExecutor.getActiveCount(),threadPoolExecutor.getQueue().size());
                  return invoker.invoke(invocation);
              }
          }
          關(guān)于DUBBO_INFO_STORE_CENTER的代碼如下所示:
          并且在dubbo的spi配置文件中指定好它們:
          dubboRecordFilter=org.idea.dubbo.monitor.core.filter.DubboRecordFilter
          當(dāng)provider加入了這個(gè)過(guò)濾器以后,若有請(qǐng)求抵達(dá)服務(wù)端,則會(huì)通過(guò)這個(gè)filter觸發(fā)采集操作。
          package org.idea.dubbo.monitor.core.collect;
          import org.idea.dubbo.monitor.core.bo.DubboInfoStoreBO;
          import java.util.Map;
          import java.util.concurrent.ConcurrentHashMap;
          /**
           * Dubbo數(shù)據(jù)存儲(chǔ)中心
           *
           * @Author idea
           * @Date created in 11:15 上午 2022/7/1
           */

          public class DubboInfoStoreCenter {
              private static Map<Integer, DubboInfoStoreBO> dubboInfoStoreBOMap = new ConcurrentHashMap<>();
              public void reportInfo(Integer port, Integer corePoolSize, Integer queueLength) {
                  synchronized (this) {
                      DubboInfoStoreBO dubboInfoStoreBO = dubboInfoStoreBOMap.get(port);
                      if (dubboInfoStoreBO != null) {
                          boolean hasChange = false;
                          int currentMaxPoolSize = dubboInfoStoreBO.getMaxCorePoolSize();
                          int currentMaxQueueLength = dubboInfoStoreBO.getMaxCorePoolSize();
                          if (corePoolSize > currentMaxPoolSize) {
                              dubboInfoStoreBO.setMaxCorePoolSize(corePoolSize);
                              hasChange = true;
                          }
                          if (queueLength > currentMaxQueueLength) {
                              dubboInfoStoreBO.setMaxQueueLength(queueLength);
                              hasChange = true;
                          }
                          if (hasChange) {
                              dubboInfoStoreBOMap.put(port, dubboInfoStoreBO);
                          }
                      } else {
                          dubboInfoStoreBO = new DubboInfoStoreBO();
                          dubboInfoStoreBO.setMaxQueueLength(queueLength);
                          dubboInfoStoreBO.setMaxCorePoolSize(corePoolSize);
                          dubboInfoStoreBOMap.put(port, dubboInfoStoreBO);
                      }
                  }
              }
              public DubboInfoStoreBO getInfo(Integer port){
                  return dubboInfoStoreBOMap.get(port);
              }
              public void cleanInfo(Integer port) {
                  dubboInfoStoreBOMap.remove(port);
              }
          }
          注意這個(gè)采集類(lèi)只會(huì)采集一段時(shí)間的數(shù)據(jù),然后定期會(huì)清空重置。
          之所以這么做,是希望用這個(gè)map統(tǒng)計(jì)指定時(shí)間內(nèi)的最大線程數(shù)和最大隊(duì)列數(shù),接著當(dāng)這些峰值數(shù)據(jù)被上報(bào)到存儲(chǔ)中心后就進(jìn)行清空。
          關(guān)于DubboInfoStoreCenter對(duì)象的定義,我將它放置在了一個(gè)叫做CommonCache的類(lèi)里面,具體如下:
          package org.idea.dubbo.monitor.core.config;
          import org.idea.dubbo.monitor.core.store.DubboInfoStoreCenter;
          /**
           * @Author idea
           * @Date created in 12:15 下午 2022/7/1
           */

          public class CommonCache {
              public static DubboInfoStoreCenter DUBBO_INFO_STORE_CENTER = new DubboInfoStoreCenter();
          }
          所以在上邊的過(guò)濾器中,我們才可以直接通過(guò)靜態(tài)類(lèi)引用去調(diào)用它的采集接口。
          好了,現(xiàn)在整體來(lái)看,我們已經(jīng)實(shí)現(xiàn)了在過(guò)濾器中去實(shí)時(shí)采集線程池的數(shù)據(jù),并且將它暫存在了一個(gè)Map表中,這個(gè)map的數(shù)據(jù)主要是記錄了某段時(shí)間內(nèi)的線程池峰值,供采集器角色去使用。
          那么接下來(lái),我們就來(lái)看看上報(bào)器模塊主要做了哪些操作。

          上報(bào)數(shù)據(jù)

          上報(bào)數(shù)據(jù)前,最重要的就是選擇合適的存儲(chǔ)組件了。首先上報(bào)的數(shù)據(jù)本身體量并不大,我們可以將采集時(shí)間短設(shè)置為15秒,那么設(shè)計(jì)一個(gè)上報(bào)任務(wù),每隔15秒采集一次dubbo線程池的數(shù)據(jù)。那么一天的時(shí)間就需上報(bào)5760次,假設(shè)一次上報(bào)存儲(chǔ)一條記錄的話,那么一天下來(lái)所需要存儲(chǔ)的數(shù)據(jù)也并不是特別多。
          并且存儲(chǔ)下來(lái)的服務(wù)數(shù)據(jù)實(shí)際上也并不需要保留太長(zhǎng)的時(shí)間,一般存儲(chǔ)個(gè)一周時(shí)間也就足夠了,所以最終我選用啦Redis進(jìn)行這方面的存儲(chǔ)。


          我們實(shí)際每次關(guān)注的數(shù)據(jù)字段主要有三個(gè),關(guān)于它們的定義我整理成了下邊這個(gè)對(duì)象:
          package org.idea.dubbo.monitor.core.bo;
          /**
           * @Author idea
           * @Date created in 7:17 下午 2022/6/29
           */

          public class ThreadInfoBO {


              private Integer activePoolSize;
              private Integer queueLength;
              private long saveTime;
              public Integer getActivePoolSize() {
                  return activePoolSize;
              }
              public void setActivePoolSize(Integer activePoolSize) {
                  this.activePoolSize = activePoolSize;
              }
              public Integer getQueueLength() {
                  return queueLength;
              }
              public void setQueueLength(Integer queueLength) {
                  this.queueLength = queueLength;
              }
              public long getSaveTime() {
                  return saveTime;
              }
              public void setSaveTime(long saveTime) {
                  this.saveTime = saveTime;
              }
              @Override
              public String toString() {
                  return "ThreadInfoBO{" +
                          ", queueLength=" + queueLength +
                          ", saveTime=" + saveTime +
                          '}';
              }
          }
          接著會(huì)開(kāi)啟一個(gè)線程任務(wù),每間隔15秒就會(huì)執(zhí)行一輪上報(bào)數(shù)據(jù)的動(dòng)作:
          package org.idea.dubbo.monitor.core.report;
          import com.alibaba.fastjson.JSON;
          import org.idea.dubbo.monitor.core.bo.DubboInfoStoreBO;
          import org.idea.dubbo.monitor.core.bo.ThreadInfoBO;
          import org.slf4j.Logger;
          import org.slf4j.LoggerFactory;
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.boot.CommandLineRunner;
          import java.util.concurrent.ExecutorService;
          import java.util.concurrent.Executors;
          import static org.idea.dubbo.monitor.core.config.CommonCache.DUBBO_INFO_STORE_CENTER;
          /**
           * @Author idea
           * @Date created in 12:13 下午 2022/7/1
           */

          public class DubboInfoReportHandler implements CommandLineRunner {
              @Autowired
              private IReportTemplate reportTemplate;
              private static final Logger LOGGER = LoggerFactory.getLogger(DubboInfoReportHandler.class);
              public static ExecutorService executorService = Executors.newFixedThreadPool(1);
              public static int DUBBO_PORT = 9090;
              @Override
              public void run(String... args) throws Exception {
                  executorService.submit(new Runnable() {
                      @Override
                      public void run() {
                          while (true) {
                              try {
                                  Thread.sleep(10000);
                                  DubboInfoStoreBO dubboInfoStoreBO = DUBBO_INFO_STORE_CENTER.getInfo(DUBBO_PORT);
                                  ThreadInfoBO threadInfoBO = new ThreadInfoBO();
                                  threadInfoBO.setSaveTime(System.currentTimeMillis());
                                  if(dubboInfoStoreBO!=null){
                                      threadInfoBO.setQueueLength(dubboInfoStoreBO.getMaxQueueLength());
                                      threadInfoBO.setActivePoolSize(dubboInfoStoreBO.getMaxCorePoolSize());
                                  } else {
                                     //這種情況可能是對(duì)應(yīng)的時(shí)間段內(nèi)沒(méi)有流量請(qǐng)求到provider上
                                      threadInfoBO.setQueueLength(0);
                                      threadInfoBO.setActivePoolSize(0);
                                  }
                                  //這里是上報(bào)器上報(bào)數(shù)據(jù)到redis中
                                  reportTemplate.reportData(JSON.toJSONString(threadInfoBO));
                                  //上報(bào)之后,這里會(huì)重置map中的數(shù)據(jù)
                                  DUBBO_INFO_STORE_CENTER.cleanInfo(DUBBO_PORT);
                                  LOGGER.info(" =========== Dubbo線程池?cái)?shù)據(jù)上報(bào) =========== ");
                              } catch (Exception e) {
                                  e.printStackTrace();
                              }
                          }
                      }
                  });
              }
          }
          這類(lèi)要注意下,Dubbo應(yīng)用的線程池上報(bào)任務(wù)應(yīng)當(dāng)?shù)日麄€(gè)SpringBoot應(yīng)用啟動(dòng)成功之后再去觸發(fā),否則可能會(huì)有些許數(shù)據(jù)不準(zhǔn)確性。所以再定義Bean初始化線程的時(shí)候,我選擇了CommandLineRunner接口。
          細(xì)心查看代碼的你可能會(huì)看到這么一個(gè)類(lèi):
          org.idea.dubbo.monitor.core.report.IReportTemplate
          這個(gè)類(lèi)定義了數(shù)據(jù)上報(bào)器的基本動(dòng)作,下邊是它的具體代碼:
          package org.idea.dubbo.monitor.core.report;


          /**
           * 上報(bào)模版
           *
           * @Author idea
           * @Date created in 7:10 下午 2022/6/29
           */

          public interface IReportTemplate {
              /**
               * 上報(bào)數(shù)據(jù)
               *
               * @return
               */

              boolean reportData(String json);


          }
          實(shí)現(xiàn)類(lèi)部分如下所示:
          package org.idea.dubbo.monitor.core.report.impl;
          import org.idea.dubbo.monitor.core.report.IReportTemplate;
          import org.idea.qiyu.cache.redis.service.IRedisService;
          import org.springframework.stereotype.Component;
          import javax.annotation.Resource;
          import java.time.LocalDate;
          import java.util.concurrent.TimeUnit;
          /**
           * @Author idea
           * @Date created in 7:12 下午 2022/6/29
           */

          @Component
          public class RedisTemplateImpl implements IReportTemplate {
              @Resource
              private IRedisService redisService;
              private static String queueKey = "dubbo:threadpool:info:";
              @Override
              public boolean reportData(String json) {
                  redisService.lpush(queueKey + LocalDate.now().toString(), json);
                  redisService.expire(queueKey + LocalDate.now().toString(),7, TimeUnit.DAYS);
                  return true;
              }


          }
          這里面我采用的是list的結(jié)構(gòu)去存儲(chǔ)這些數(shù)據(jù)指標(biāo),設(shè)定了一個(gè)過(guò)期時(shí)間為一周,最終存儲(chǔ)到redis之后的格式如下所示:

          數(shù)據(jù)展示

          好了,現(xiàn)在我們已經(jīng)完成了對(duì)線程池的監(jiān)控,最后只需要設(shè)計(jì)一個(gè)管理臺(tái),從緩存中提取上報(bào)的數(shù)據(jù)并且進(jìn)行頁(yè)面的展示即可。
          實(shí)現(xiàn)的邏輯比較簡(jiǎn)單,只需要定義好統(tǒng)計(jì)圖所需要的數(shù)據(jù)結(jié)構(gòu),然后在controller曾返回即可,例如下圖所示:
          最終展現(xiàn)出來(lái)的效果如下圖:
          隨著請(qǐng)求dubbo接口的量發(fā)生變化,統(tǒng)計(jì)圖可以展示出dubbo線程池的數(shù)據(jù)變動(dòng)情況。如果希望統(tǒng)計(jì)圖以實(shí)時(shí)的方式展示數(shù)據(jù)的話,其實(shí)只需要在js中寫(xiě)一個(gè)定時(shí)調(diào)用的函數(shù)即可。
          這里我是使用的是echart插件做的圖表渲染,我選用的是最簡(jiǎn)單的統(tǒng)計(jì)圖類(lèi)型,大家也可以根據(jù)自己的具體所需在echart的官網(wǎng)上選擇合適的模型進(jìn)行渲染,下邊這是echart的官網(wǎng)地址:
          https://echarts.apache.org/examples/zh/index.html
          <END>

          推薦閱讀:

          被呼吁抵制,7-Zip偽開(kāi)源還留有后門(mén)?

          Lombok原理和同時(shí)使?@Data和@Builder 的坑

          互聯(lián)網(wǎng)初中高級(jí)大廠面試題(9個(gè)G)

          內(nèi)容包含Java基礎(chǔ)、JavaWeb、MySQL性能優(yōu)化、JVM、鎖、百萬(wàn)并發(fā)、消息隊(duì)列、高性能緩存、反射、Spring全家桶原理、微服務(wù)、Zookeeper......等技術(shù)棧!

          ?戳閱讀原文領(lǐng)??!                                  朕已閱 

          瀏覽 31
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  亚洲精品乱码久久久久久蜜桃不卡 | 九九激情网 | 小早川怜子爆乿护士 | 免费日批在线播放 | 欧美成人A猛片 |