Nacos 實現動態化線程池,真香!

来源:https://www.cnblogs.com/javastack/archive/2023/10/13/17761680.html
-Advertisement-
Play Games

在後臺開發中,會經常用到線程池技術,對於線程池核心參數的配置很大程度上依靠經驗。然而,由於系統運行過程中存在的不確定性,我們很難一勞永逸地規劃一個合理的線程池參數。 在對線程池配置參數進行調整時,一般需要對服務進行重啟,這樣修改的成本就會偏高。一種解決辦法就是,將線程池的配置放到平臺側,運行開發同學 ...


在後臺開發中,會經常用到線程池技術,對於線程池核心參數的配置很大程度上依靠經驗。然而,由於系統運行過程中存在的不確定性,我們很難一勞永逸地規劃一個合理的線程池參數。

在對線程池配置參數進行調整時,一般需要對服務進行重啟,這樣修改的成本就會偏高。一種解決辦法就是,將線程池的配置放到平臺側,運行開發同學根據系統運行情況對核心參數進行動態配置。

本文以Nacos作為服務配置中心,以修改線程池核心線程數、最大線程數為例,實現一個簡單的動態化線程池。

代碼實現

推薦一個開源免費的 Spring Boot 實戰項目:

https://github.com/javastacks/spring-boot-best-practice

1.依賴

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    <version>2021.1</version>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    <version>2021.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>

2.配置yml文件

Spring Boot 基礎就不介紹了,推薦看這個實戰項目:

https://github.com/javastacks/spring-boot-best-practice

bootstrap.yml:

server:
  port: 8010
  # 應用名稱(nacos會將該名稱當做服務名稱)
spring:
  application:
    name: order-service
  cloud:
    nacos:
      discovery:
        namespace: public
        server-addr: 192.168.174.129:8848
      config:
        server-addr: 192.168.174.129:8848
        file-extension: yml

application.yml:

spring:
  profiles:
    active: dev

為什麼要配置兩個yml文件?

springboot中配置文件的載入是存在優先順序順序的,bootstrap優先順序高於application。

nacos在項目初始化時,要保證先從配置中心進行配置拉取,拉取配置之後才能保證項目的正常啟動。

3.nacos配置

登錄到nacos管理頁面,新建配置,如下圖所示:

註意Data ID的命名格式為,${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension} ,在本文中,Data ID的名字就是order-service-dev.yml

這裡我們只配置了兩個參數,核心線程數量和最大線程數。

4.線程池配置和nacos配置變更監聽

@RefreshScope
@Configuration
public class DynamicThreadPool implements InitializingBean {
    @Value("${core.size}")
    private String coreSize;

    @Value("${max.size}")
    private String maxSize;

    private static ThreadPoolExecutor threadPoolExecutor;

    @Autowired
    private NacosConfigManager nacosConfigManager;

    @Autowired
    private NacosConfigProperties nacosConfigProperties;

    @Override
    public void afterPropertiesSet() throws Exception {
        //按照nacos配置初始化線程池
        threadPoolExecutor = new ThreadPoolExecutor(Integer.parseInt(coreSize), Integer.parseInt(maxSize), 10L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(10),
                new ThreadFactoryBuilder().setNameFormat("c_t_%d").build(),
                new RejectedExecutionHandler() {
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        System.out.println("rejected!");
                    }
                });

        //nacos配置變更監聽
        nacosConfigManager.getConfigService().addListener("order-service-dev.yml", nacosConfigProperties.getGroup(),
                new Listener() {
                    @Override
                    public Executor getExecutor() {
                        return null;
                    }

                    @Override
                    public void receiveConfigInfo(String configInfo) {
                        //配置變更,修改線程池配置
                        System.out.println(configInfo);
                        changeThreadPoolConfig(Integer.parseInt(coreSize), Integer.parseInt(maxSize));
                    }
                });
    }

    /**
     * 列印當前線程池的狀態
     */
    public String printThreadPoolStatus() {
        return String.format("core_size:%s,thread_current_size:%s;" +
                        "thread_max_size:%s;queue_current_size:%s,total_task_count:%s", threadPoolExecutor.getCorePoolSize(),
                threadPoolExecutor.getActiveCount(), threadPoolExecutor.getMaximumPoolSize(), threadPoolExecutor.getQueue().size(),
                threadPoolExecutor.getTaskCount());
    }

    /**
     * 給線程池增加任務
     *
     * @param count
     */
    public void dynamicThreadPoolAddTask(int count) {
        for (int i = 0; i < count; i++) {
            int finalI = i;
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(finalI);
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

    /**
     * 修改線程池核心參數
     *
     * @param coreSize
     * @param maxSize
     */
    private void changeThreadPoolConfig(int coreSize, int maxSize) {
        threadPoolExecutor.setCorePoolSize(coreSize);
        threadPoolExecutor.setMaximumPoolSize(maxSize);
    }
}

這個代碼就是實現動態線程池和核心了,需要說明的是:

  • @RefreshScope:這個註解用來支持nacos的動態刷新功能;
  • @Value("${max.size}")@Value("${core.size}"):這兩個註解用來讀取我們上一步在nacos配置的具體信息;同時,nacos配置變更時,能夠實時讀取到變更後的內容
  • nacosConfigManager.getConfigService().addListener:配置監聽,nacos配置變更時實時修改線程池的配置。

5.controller

為了觀察線程池動態變更的效果,增加Controller類。

@RestController
@RequestMapping("/threadpool")
public class ThreadPoolController {

    @Autowired
    private DynamicThreadPool dynamicThreadPool;

    /**
     * 列印當前線程池的狀態
     */
    @GetMapping("/print")
    public String printThreadPoolStatus() {
        return dynamicThreadPool.printThreadPoolStatus();
    }

    /**
     * 給線程池增加任務
     *
     * @param count
     */
    @GetMapping("/add")
    public String dynamicThreadPoolAddTask(int count) {
        dynamicThreadPool.dynamicThreadPoolAddTask(count);
        return String.valueOf(count);
    }
}

6.測試

啟動項目,訪問http://localhost:8010/threadpool/print列印當前線程池的配置。

可以看到,這個就是我們之前在nacos配置的線程數。

訪問http://localhost:8010/threadpool/add?count=20增加20個任務,重新列印線程池配置

可以看到已經有線程在排隊了。

為了能夠看到效果,我們多訪問幾次/add介面,增加任務數,在控制台出現拒絕信息時調整nacos配置。

此時,執行/add命令時,所有的線程都會提示rejected。

調整nacos配置,將核心線程數調整為50,最大線程數調整為100.

重新多次訪問/add介面增加任務,發現沒有拒絕信息了。這時,列印具體的線程狀態,發現線程池參數修改成功。

總結

這裡,只是簡單實現了一個可以調整核心線程數和最大線程數的動態線程池。具體的線程池實現原理可以參考美團的這篇文章:https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html,結合監控告警等實現一個完善的動態線程池產品。

優秀的輪子還有好多,比如Hippo4J ,使用起來和dynamic-tp差不多。Hippo4J 有無依賴中間件實現動靜線程池,也有預設實現Nacos和Apollo的版本,而dynamic-tp 預設實現依賴Nacos或Apollo。

來源:blog.csdn.net/m0_37558251/article/details/126542359

近期熱文推薦:

1.1,000+ 道 Java面試題及答案整理(2022最新版)

2.勁爆!Java 協程要來了。。。

3.Spring Boot 2.x 教程,太全了!

4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這才是優雅的方式!!

5.《Java開發手冊(嵩山版)》最新發佈,速速下載!

覺得不錯,別忘了隨手點贊+轉發哦!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Java學習筆記二 面向對象(Object Oriented) 屬性(成員變數)跟隨對象放在堆裡面,局部變數(如 p1)放在棧裡面。只有成員變數的前面能添加許可權修飾符,且成員變數自帶預設值。 在一個類中,一個方法可以調用這個類中的其餘方法(包括自身,即遞歸)以及成員變數,不能在方法中再定義方法。 方 ...
  • 本文全面深入地探討了Go非類型安全指針,特別是在Go語言環境下的應用。從基本概念、使用場景,到潛在風險和挑戰,文章提供了一系列具體的代碼示例和最佳實踐。目的是幫助讀者在保證代碼安全和效率的同時,更加精通非類型安全指針的使用。 關註【TechLeadCloud】,分享互聯網架構、雲服務技術的全維度知識 ...
  • 一、業務場景 在多線程併發情況下,假設有兩個資料庫修改請求,為保證資料庫與redis的數據一致性,修改請求的實現中需要修改資料庫後,級聯修改Redis中的數據。 請求一:A修改資料庫數據 B修改Redis數據 請求二:C修改資料庫數據 D修改Redis數據 併發情況下就會存在A —> C —> D ...
  • 如果企業提供 IT 線上服務,那麼可觀測性能力是必不可少的。“可觀測性” 這個詞近來也越發火爆,不懂 “可觀測性” 都不好意思出門了。但是可觀測性能力的構建卻著實不易,每個企業都會用到一堆技術棧來組裝建設。比如數據收集,可能來自某個 exporter,可能來自 telegraf,可能來自 OTEL, ...
  • 1、策略模式 1.1、概述 策略模式是一種行為設計模式,它允許在運行時選擇演算法的行為。它將演算法封裝在獨立的策略類中,使得它們可以相互替換,而不影響客戶端代碼。這種模式通過將演算法的選擇從客戶端代碼中分離出來,提供了更大的靈活性和可維護性。 在Java中,策略模式的設計理念可以通過以下步驟實現: 定義一 ...
  • 斐波那契數列在代碼中的應用是比較常見的,下麵讓我們來瞭解下一個數學上的數列在代碼中會有哪些應用。瞭解斐波那契,可以給我們提供解決某些問題的思路,優化解決問題的方法。 ...
  • 查詢企業聯繫人郵箱API介面是一種非常實用的API介面,它可以幫助我們快速獲取企業聯繫人的郵箱地址。這個介面可以在各種需要進行營銷活動或者聯繫客戶的場合中使用,例如郵件廣告、市場調研等。 這篇博文將詳細介紹這個API介面的原理和功能,並提供示例代碼來演示如何使用這個介面。 一. API介面原理 查詢 ...
  • Nginx修改演示: vim /usr/local/nginx/conf/vhost/example.com server{ listen 80; server_name 192.168.1.180; location / { root /Code/project/public; index ind ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...