一個基於Java執行緒池管理的開源框架Hippo4j實踐

2023-04-18 06:00:53

@

概述

定義

Hippo4j 官網地址 https://hippo4j.cn/ 最新版本1.5.0

Hippo4j 官網檔案地址 https://hippo4j.cn/docs/user_docs/intro

Hippo4j 原始碼地址 https://github.com/opengoofy/hippo4j

Hippo4j是一個動態可觀測執行緒池框架,通過對 JDK 執行緒池增強,以及擴充套件三方框架底層執行緒池等功能,為業務系統提高線上執行保障能力。

執行緒池痛點

執行緒池是一種基於池化思想管理執行緒的工具,使用執行緒池可以減少建立銷燬執行緒的開銷,避免執行緒過多導致系統資源耗盡。在高並行以及大批次的任務處理場景,執行緒池的使用是必不可少的。執行緒池常見痛點:

  • 執行緒池隨便定義,執行緒資源過多,造成伺服器高負載。
  • 執行緒池引數不易評估,隨著業務的並行提升,業務面臨出現故障的風險。
  • 執行緒池任務執行時間超過平均執行週期,開發人員無法感知。
  • 執行緒池任務堆積,觸發拒絕策略,影響既有業務正常執行。
  • 當業務出現超時、熔斷等問題時,因為沒有監控,無法確定是不是執行緒池引起。
  • 原生執行緒池不支援執行時變數的傳遞,比如 MDC 上下文遇到執行緒池就 GG。
  • 無法執行優雅關閉,當專案關閉時,大量正在執行的執行緒池任務被丟棄。
  • 執行緒池執行中,任務執行停止,懷疑發生死鎖或執行耗時操作,但是無從下手。

功能

  • 動態變更:應用執行時動態變更執行緒池引數,包括不限於核心、最大執行緒、阻塞佇列大小和拒絕策略等,支援應用叢集下不同節點執行緒池設定差異化。
  • 自定義報警:應用執行緒池執行時埋點,提供四種報警維度,執行緒池過載、阻塞佇列容量、執行超長以及拒絕策略報警,並支援自定義時間內不重複報警。
  • 執行監控:管理應用執行緒池範例;支援自定義時長執行緒池執行資料採集儲存,同時也支援 Prometheus、InfluxDB 等採集監控,通過 Grafana 或內建監控頁面提供視覺化大屏監控執行指標。實時檢視執行緒池執行時資料,最近半小時執行緒池執行資料圖表展示。
  • 功能擴充套件 - 支援執行緒池任務傳遞上下文;專案關閉時,支援等待執行緒池在指定時間內完成任務。
  • 多種模式 - 內建兩種使用模式:依賴設定中心和無中介軟體依賴。
  • 容器管理 - Tomcat、Jetty、Undertow 容器執行緒池執行時檢視和執行緒數變更。
  • 框架適配 - Dubbo、Hystrix、RabbitMQ、RocketMQ 等消費執行緒池執行時資料檢視和執行緒數變更。

框架概覽

[外連圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-4bRHnVlQ-1681738378627)(null)]

Hippo4j參考美團的設計,按照租戶、專案、執行緒池的維度劃分。再加上系統許可權,讓不同的開發、管理人員負責自己系統的執行緒池操作。比如一家公司的公共元件團隊,團隊中負責訊息、短連結閘道器等專案,公共元件是租戶,訊息或短連結就是專案。Hippo4j 可以動態修改執行緒池,也可以實時檢視執行緒池執行時指標、負載報警、設定紀錄檔管理等。

  • hippo4j-adapter:適配對第三方框架中的執行緒池進行監控,如 Dubbo、RocketMQ、Hystrix 等;
  • hippo4j-auth:使用者、角色、許可權等;
  • hippo4j-common:多個模組公用程式碼實現;
  • hippo4j-config:提供執行緒池準實時引數更新功能;
  • hippo4j-console:對接前端控制檯;
  • hippo4j-core:核心的依賴,包括設定、核心包裝類等;
  • hippo4j-discovery:提供執行緒池專案範例註冊、續約、下線等功能;
  • hippo4j-example :範例工程;
  • hippo4j-message :設定變更以及報警通知傳送;
  • hippo4j-monitor :執行緒池執行時監控;
  • hippo4j-server :Server 端釋出需要的模組聚合;
  • hippo4j-spring-boot:SpringBoot Starter。

架構

簡單來說,Hippo4j 從部署的角度上分為兩種角色:Server 端和 Client 端。Server 端是 Hippo4j 專案打包出的 Java 程序,功能包括使用者許可權、執行緒池監控以及執行持久化的動作。Client 端指的是我們 SpringBoot 應用,通過引入 Hippo4j Starter Jar 包負責與 Server 端進行互動。比如拉取 Server 端執行緒池資料、動態更新執行緒池設定以及採集上報執行緒池執行時資料等。總體功能架構如下圖

  • 基礎元件
    • 設定中心(Config):設定中心位於 Server 端,它的主要作用是監控 Server 端執行緒池設定變更,實時通知到 Client 範例執行執行緒池變更流程。程式碼設計基於 Nacos 1.x 版本的 長輪詢以及非同步 Servlet 機制 實現。
    • 註冊中心(Discovery):負責管理 Client 端(單機或叢集)註冊到 Server 端的範例,包括不限於範例註冊、續約、過期剔除 等操作,程式碼基於 Eureka 原始碼實現。
    • 控制檯(Console):對接前端專案,包括不限於以下模組管理:

  • 訊息通知:Hippo4j 內建了很多需要通知的事件,比如:執行緒池引數變更通知、執行緒池活躍度報警、拒絕策略執行報警以及阻塞佇列容量報警等。目前 Notify 已經接入了釘釘、企業微信和飛書,後續持續整合郵件、簡訊等通知渠道;並且Notify 模組提供了訊息事件的 SPI 方案,可以接受三方自定義的推播。
  • Hippo4j-Spring-Boot-Starter:Hippo4j 提供以 Starter Jar 包的形式巢狀在應用內,負責與 Server 端完成互動。

部署

Docker安裝

使用 Docker 執行伺服器端,預設使用內建 H2 資料庫,資料持久化到 Docker 容器儲存卷中。

docker run -d -p 6691:6691 --name hippo4j-server hippo4j/hippo4j-server

或者底層儲存資料庫切換為 MySQL。DATASOURCE_HOST 需要切換為本地 IP,不能使用 127.0.0.1localhost

docker run -d -p 6691:6691 --name hippo4j-server \
-e DATASOURCE_MODE=mysql \
-e DATASOURCE_HOST=192.168.3.200 \
-e DATASOURCE_PORT=3306 \
-e DATASOURCE_DB=hippo4j_manager \
-e DATASOURCE_USERNAME=root \
-e DATASOURCE_PASSWORD=root \
hippo4j/hippo4j-server

存取 Server 控制檯,路徑 http://hadoop3:6691/index.html,預設使用者名稱密碼:admin / 123456

二進位制安裝

# 下載hippo4j-server1.5.0最新版本二進位制檔案,
wget https://github.com/opengoofy/hippo4j/releases/download/v1.5.0/hippo4j-server-1.5.0.tar.gz
# 解壓檔案
tar -xvf hippo4j-server-1.5.0.tar.gz
# 進入目錄
cd hippo4j-server/
# 建立資料庫使用者並執行conf/hippo4j_manager.sql建立和初始hippo4j_manager資料庫,按需修改conf/application.properties資料庫連線資訊
# 授權startup.sh執行許可權後,啟動hippo4j-server
./bin/startup.sh

如果不下載二進位制也可以使用原始碼編譯的方式,修改resources目錄下的application.properties資料庫連線資訊,啟動 Hippo4j-Server/Hippo4j-Bootstrap模組下 ServerApplication 應用類。

修改範例專案hippo4j-spring-boot-starter-example的application.properties檔案中spring.dynamic.thread-pool.server-addr,啟動範例專案hippo4j-spring-boot-starter-example 模組下 ServerExampleApplication 應用類。存取 Server 控制檯,路徑 http://hadoop3:6691/index.html,預設使用者名稱密碼:admin / 123456

設定變更,存取控制檯動態執行緒池選單下執行緒池範例,修改動態執行緒池相關引數。

點選確認按鈕後可以看到控制檯輸出執行緒池變更的設定引數

執行模式

Hippo4j 分為兩種使用模式:輕量級依賴設定中心以及無中介軟體依賴版本。

  • Hippo4j config:輕量級動態執行緒池管理,依賴 Nacos、Apollo、Zookeeper、ETCD、Polaris、Consul 等三方設定中心(任選其一)完成執行緒池引數動態變更,支援執行時報警、監控等功能。

  • Hippo4j server:前面部署就是無中介軟體依賴版本,需要部署部署 Hippo4j server 服務,通過視覺化 Web 介面完成執行緒池的建立、變更以及檢視,不依賴三方中介軟體。相比較 Hippo4j config,功能會更強大,但同時也引入了一定的複雜性。需要部署一個 Java 服務,以及依賴 MySQL 資料庫。

Hippo4j config Hippo4j server
依賴 Nacos、Apollo、Zookeeper、ETCD、Polaris、Consul 設定中心(任選其一) 部署 Hippo4j server(內部無依賴中介軟體)
使用 設定中心補充執行緒池相關引數 Hippo4j server web 控制檯新增執行緒池記錄
功能 包含基礎功能:引數動態化、執行時監控、報警等 基礎功能之外擴充套件控制檯介面、執行緒池堆疊檢視、執行緒池執行資訊實時檢視、歷史執行資訊檢視、執行緒池設定叢集個性化等
  • 使用建議:根據公司情況選擇,如果基本功能可以滿足使用,選擇 Hippo4j config 使用即可;如果希望更多的功能,可以選擇 Hippo4j server。兩者在進行替換的時候,無需修改業務程式碼

依賴設定中心

接入流程

這裡以官方提供以Nacos為設定中心範例說明,其他的類似

  • 引入依賴
<dependency>
    <groupId>cn.hippo4j</groupId>
    <artifactId>hippo4j-config-spring-boot-starter</artifactId>
    <version>1.5.0</version>
</dependency>
  • 啟動類上新增註解 @EnableDynamicThreadPool
  • 建立Nacos組態檔。

在範例工程hippo4j-config-nacos-spring-boot-starter-example中設定Nacos的地址,並在Nacos對應的空間和組下建立hippo4j-nacos.properties檔案,將原來在bootstrap.properties中的設定轉移到Nacos中

spring.dynamic.thread-pool.enable=true
spring.dynamic.thread-pool.banner=true
spring.dynamic.thread-pool.check-state-interval=5
spring.dynamic.thread-pool.monitor.enable=true
spring.dynamic.thread-pool.monitor.collect-types=micrometer
spring.dynamic.thread-pool.monitor.thread-pool-types=dynamic,web
spring.dynamic.thread-pool.monitor.initial-delay=10000
spring.dynamic.thread-pool.monitor.collect-interval=5000

spring.dynamic.thread-pool.notify-platforms[0].platform=WECHAT
spring.dynamic.thread-pool.notify-platforms[0].token=ac0426a5-c712-474c-9bff-72b8b8f5caff
spring.dynamic.thread-pool.notify-platforms[1].platform=DING
spring.dynamic.thread-pool.notify-platforms[1].token=56417ebba6a27ca352f0de77a2ae9da66d01f39610b5ee8a6033c60ef9071c55
spring.dynamic.thread-pool.notify-platforms[2].platform=LARK
spring.dynamic.thread-pool.notify-platforms[2].token=2cbf2808-3839-4c26-a04d-fd201dd51f9e

spring.dynamic.thread-pool.executors[0].thread-pool-id=message-consume
spring.dynamic.thread-pool.executors[0].thread-name-prefix=message-consume
spring.dynamic.thread-pool.executors[0].core-pool-size=4
spring.dynamic.thread-pool.executors[0].maximum-pool-size=6
spring.dynamic.thread-pool.executors[0].queue-capacity=512
spring.dynamic.thread-pool.executors[0].blocking-queue=ResizableCapacityLinkedBlockingQueue
spring.dynamic.thread-pool.executors[0].execute-time-out=800
spring.dynamic.thread-pool.executors[0].rejected-handler=AbortPolicy
spring.dynamic.thread-pool.executors[0].keep-alive-time=6691
spring.dynamic.thread-pool.executors[0].allow-core-thread-time-out=true
spring.dynamic.thread-pool.executors[0].alarm=true
spring.dynamic.thread-pool.executors[0].active-alarm=80
spring.dynamic.thread-pool.executors[0].capacity-alarm=80
spring.dynamic.thread-pool.executors[0].notify.interval=8
spring.dynamic.thread-pool.executors[0].notify.receives=chen.ma
spring.dynamic.thread-pool.executors[1].thread-pool-id=message-produce
spring.dynamic.thread-pool.executors[1].thread-name-prefix=message-produce
spring.dynamic.thread-pool.executors[1].core-pool-size=2
spring.dynamic.thread-pool.executors[1].maximum-pool-size=4
spring.dynamic.thread-pool.executors[1].queue-capacity=1024
spring.dynamic.thread-pool.executors[1].blocking-queue=ResizableCapacityLinkedBlockingQueue
spring.dynamic.thread-pool.executors[1].execute-time-out=800
spring.dynamic.thread-pool.executors[1].rejected-handler=AbortPolicy
spring.dynamic.thread-pool.executors[1].keep-alive-time=6691
spring.dynamic.thread-pool.executors[1].allow-core-thread-time-out=true
spring.dynamic.thread-pool.executors[1].alarm=true
spring.dynamic.thread-pool.executors[1].active-alarm=80
spring.dynamic.thread-pool.executors[1].capacity-alarm=80
spring.dynamic.thread-pool.executors[1].notify.interval=8
spring.dynamic.thread-pool.executors[1].notify.receives=chen.ma

啟動hippo4j-config-nacos-spring-boot-starter-example工程的ConfigNacosExampleApplication,修改上面在Nacos設定hippo4j-nacos.properties檔案,可以看到紀錄檔輸出修改執行緒池資訊,

ThreadPoolExecutor 適配,新增執行緒池設定類,通過 @DynamicThreadPool 註解修飾。threadPoolId 為伺服器端建立的執行緒池 ID。這個也是前面設定的spring.dynamic.thread-pool.executors[0].thread-pool-id=message-consume

個性化設定

hippo4j-config 是依賴設定中心做執行緒池設定動態變更。這種模式有一種缺點:改動組態檔後,所有使用者端都會變更。希望 hippo4j-config 能夠像 hippo4j-server 一樣實現使用者端叢集個性化設定,能夠針對單獨的使用者端進行設定變更。

  • 容器及三方框架執行緒池自定義啟用

容器及三方框架執行緒池新增啟用設定,為了保持統一,動態執行緒池設定中也有該引數設定。設定項預設開啟。

spring:
  dynamic:
    thread-pool:
      tomcat:
        enable: true
      executors:
        - thread-pool-id: message-consume
          enable: false
      adapter-executors:
        - threadPoolKey: 'input'
          enable: true
  • 使用者端叢集個性化設定:分別在動態執行緒池、容器執行緒池以及三方框架執行緒池設定下增加 nodes 設定節點,通過該設定可匹配需要變更的節點。
spring:
  dynamic:
    thread-pool:
      tomcat:
        nodes: 192.168.1.5:*,192.168.1.6:8080
      executors:
      - thread-pool-id: message-consume
        nodes: 192.168.1.5:*
      adapter-executors:
        - threadPoolKey: 'input'
          nodes: 192.168.1.5:*

執行緒池監控

  • 新增依賴
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  • 新增設定,上面Nccos設定已新增
  • 專案啟動,存取 http://localhost:29999/actuator/prometheus 出現 dynamic_thread_pool_ 字首的指標,即為成功。

後續則可以通過部署、設定Prometheus和Grafana實現指標採集和視覺化監控,詳細可以檢視前面文章或者Hippo4j的官方檔案

無中介軟體依賴

接入流程

前面部署章節主要演示無中介軟體依賴的,大體流程和依賴設定中心相似。通過 ThreadPoolBuilder 構建動態執行緒池,只有 threadFactory、threadPoolId 為必填項,其它引數會從 hippo4j-server 服務拉取。專案中使用上述定義的動態執行緒池,如下所示:

@Resourceprivate ThreadPoolExecutor messageConsumeDynamicExecutor;messageConsumeDynamicExecutor.execute(() -> xxx);@Resourceprivate ThreadPoolExecutor messageProduceDynamicExecutor;messageProduceDynamicExecutor.execute(() -> xxx);

伺服器端設定

hippo4j.core.clean-history-data-enable

是否開啟執行緒池歷史資料淨化,預設開啟。

hippo4j.core.clean-history-data-period

執行緒池歷史資料保留時間,預設值:30,單位分鐘。

伺服器端會保留這個設定時間的資料,超過這個時間則會被清理。比如按照預設值 30 分鐘來說,12:00 收集到的資料,12:30 就會被清理刪除。

hippo4j.core.monitor.report-type

使用者端監控上報伺服器端型別,可選值:http、netty,預設 http。伺服器端開啟 netty 設定後,需要在使用者端對應開啟才可生效。用來應對大量動態執行緒池監控場景。

三方框架執行緒池適配

Hippo4j 目前已支援的三方框架執行緒池列表:

  • Dubbo
  • Hystrix
  • RabbitMQ
  • RocketMQ
  • AlibabaDubbo
  • RocketMQSpringCloudStream
  • RabbitMQSpringCloudStream

引入 Hippo4j Server 或 Core 的 Maven Jar 座標後,還需要引入對應的框架適配 Jar:

<dependency>    <groupId>cn.hippo4j</groupId>    <!-- Dubbo -->    <artifactId>hippo4j-spring-boot-starter-adapter-dubbo</artifactId>    <!-- Alibaba Dubbo -->    <artifactId>hippo4j-spring-boot-starter-adapter-alibaba-dubbo</artifactId>    <!-- Hystrix -->    <artifactId>hippo4j-spring-boot-starter-adapter-hystrix</artifactId>    <!-- RabbitMQ -->    <artifactId>hippo4j-spring-boot-starter-adapter-rabbitmq</artifactId>    <!-- RocketMQ -->    <artifactId>hippo4j-spring-boot-starter-adapter-rocketmq</artifactId>    <!-- SpringCloud Stream RocketMQ -->    <artifactId>hippo4j-spring-boot-starter-adapter-spring-cloud-stream-rocketmq</artifactId>    <!-- SpringCloud Stream RabbitMQ -->    <artifactId>hippo4j-spring-boot-starter-adapter-spring-cloud-stream-rabbitmq</artifactId>    <version>1.5.0</version></dependency>

如果省事僅需引入一個全量包,框架底層會根據條件判斷載入具體執行緒池介面卡。

<dependency>    <groupId>cn.hippo4j</groupId>    <artifactId>hippo4j-spring-boot-starter-adapter-all</artifactId>    <version>1.5.0</version></dependency>

在官方範例中也提供集中執行緒池適配範例

修改hippo4j-spring-boot-starter-adapter-rocketmq-example的spring.dynamic.thread-pool.server-addr和rocketmq.nameServer,啟動程式後修改框架執行緒池-RocketMQ的範例設定引數

可以看到控制檯已經輸入執行緒池修改的紀錄檔資訊

而在Hippo4j Config,Hippo4j Config 除了依賴上述適配 Jar 包外,還需要在設定中心新增以下設定項。

spring:  dynamic:    thread-pool:      # 省略其它設定      adapter-executors:        # threadPoolKey 代表執行緒池標識        - threadPoolKey: 'input'          # mark 為三方執行緒池框架型別,參見文初已支援框架集合          mark: 'RocketMQSpringCloudStream'          corePoolSize: 10          maximumPoolSize: 10

拒絕策略自定義

Hippo4j 通過 SPI 的方式對拒絕策略進行擴充套件,可以讓使用者在 Hippo4j 中完成自定義拒絕策略實現。自定義拒絕策略,實現 CustomRejectedExecutionHandler 介面,在hippo4j-example-core中新增MyDemoRejectedExecutionHandler.java,內容如下:

package cn.hippo4j.example.core.handler;import cn.hippo4j.common.executor.support.CustomRejectedExecutionHandler;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.util.concurrent.RejectedExecutionHandler;import java.util.concurrent.ThreadPoolExecutor;public class MyDemoRejectedExecutionHandler implements CustomRejectedExecutionHandler {    @Override    public Integer getType() {        return 15;    }    @Override    public String getName() {        return null;    }    @Override    public RejectedExecutionHandler generateRejected() {        return new CustomMyDemoRejectedExecutionHandler();    }    public static class CustomMyDemoRejectedExecutionHandler implements RejectedExecutionHandler {        @Override        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {            Logger logger = LoggerFactory.getLogger(this.getClass());            logger.error("執行緒池丟擲拒絕策略MyDemoRejected");        }    }}

在hippo4j-spring-boot-starter-example模組中src/main/resources/META-INF/services 目錄,建立 SPI 自定義拒絕策略檔案 cn.hippo4j.common.executor.support.CustomRejectedExecutionHandler, 檔案內僅放一行自定義拒絕策略全限定名即可,原本已有這個檔案,我們修改內容即可

cn.hippo4j.example.core.handler.MyDemoRejectedExecutionHandler

啟動hippo4j-spring-boot-starter-example,修改範例執行緒池設定

拒絕策略觸發時,完成上述程式碼效果,僅列印異常紀錄檔提示。

2023-04-17 19:17:33.324 ERROR 29977 --- [ateHandler.test] r$CustomMyDemoRejectedExecutionHandler : 執行緒池丟擲拒絕策略MyDemoRejected

- **本人部落格網站**[**IT小神**](http://www.itxiaoshen.com)   www.itxiaoshen.com