Kafka——多線程開發消費者實例

引言

在分布式系統領域,Kafka憑借高吞吐量、低延遲的特性成為消息隊列的事實標準。隨著硬件技術的飛速發展,服務器多核CPU已成常態——一臺普通的云服務器動輒配備16核、32核甚至更多核心。然而,Kafka Java Consumer的設計卻長期保持著"單線程"的核心架構,這看似與硬件發展趨勢相悖的設計背后,隱藏著怎樣的考量?

當我們面對每秒數十萬條消息的處理需求時,單線程消費的瓶頸會愈發明顯:消息堆積、消費延遲飆升、CommitFailedException異常頻發……這些問題的根源往往在于消費能力與生產速度的不匹配。此時,多線程消費就成為突破性能瓶頸的關鍵手段。

本文將從Kafka Consumer的底層設計原理出發,系統解析兩種多線程消費方案的實現邏輯、優缺點及適用場景,結合生產環境的實戰經驗,提供一套完整的多線程消費優化指南。無論是需要嚴格保證消息順序的金融場景,還是追求極致吞吐量的日志處理系統,都能從中找到適配的解決方案。

Kafka Consumer的線程模型:單線程設計的底層邏輯

要理解多線程消費的實現方案,首先必須明確Kafka Java Consumer的線程模型設計。很多開發者誤以為Consumer是純粹的單線程架構,這其實是一種片面的認知——從Kafka 0.10.1.0版本開始,Consumer就已經演進為雙線程設計:用戶主線程與心跳線程并存。

雙線程架構的分工協作

用戶主線程:負責執行poll()方法獲取消息、處理業務邏輯及提交位移,是Consumer的核心工作線程。

心跳線程(Heartbeat Thread):獨立于用戶主線程運行,定期向Broker發送心跳請求,用于維持消費者與協調者(Coordinator)的會話活性。其核心作用是將"存活性檢測"與"消息處理"解耦,避免因消息處理耗時過長導致的不必要Rebalance。

這種設計的優勢在于:即使消息處理耗時超過session.timeout.ms,只要心跳線程正常工作,消費者就不會被判定為"死亡",從而減少Rebalance的發生。

單線程設計的深層原因

盡管引入了心跳線程,Kafka Consumer的核心工作流程(消息獲取與處理)仍以單線程為基礎。這種設計并非技術局限,而是社區基于多方面考量的刻意選擇:

  1. 非阻塞式消息獲取的需求 老版本的Scala Consumer采用多線程阻塞式設計,每個分區對應一個Fetcher線程,難以滿足流處理等場景的非阻塞需求。單線程+輪詢(Poll)機制可以靈活控制消息獲取的時機,更適配實時計算中的過濾、連接等操作。

  2. 簡化客戶端設計與線程安全 單線程模型避免了多線程共享Consumer實例帶來的線程安全問題。KafkaConsumer類明確標注為非線程安全(thread-safe=false),除wakeup()方法外,在多線程中調用任何方法都可能導致ConcurrentModificationException

  3. 跨語言移植的便利性 并非所有編程語言都像Java一樣原生支持多線程,單線程設計降低了客戶端移植的復雜度,有助于Kafka生態在多語言環境中的擴展。

  4. 業務邏輯與消費框架的解耦 單線程設計將消息處理的多線程策略交給開發者決定,避免框架對業務邏輯的過度侵入。例如,日志收集場景可能需要簡單的單線程處理,而實時風控場景則需要復雜的多線程并行計算。

單線程模型的性能瓶頸

單線程設計雖然帶來了簡化性,但在高并發場景下會暴露明顯短板:

  • 處理能力受限:單線程的消息處理速度無法充分利用多核CPU資源,當消息吞吐量超過單線程處理上限時,會導致消費延遲持續增加。

  • 風險集中:一旦單線程因異常阻塞,將導致整個消費者實例癱瘓,影響所有分區的消息消費。

  • 參數配置矛盾:為避免Rebalance,需平衡max.poll.interval.msmax.poll.records的配置,但單線程處理能力有限,難以在高吞吐量與低延遲間找到最優解。

正是這些瓶頸,推動開發者探索多線程消費方案,在保證線程安全的前提下充分釋放硬件性能。

多線程消費方案詳解:原理、實現與對比

基于KafkaConsumer的線程安全特性,社區形成了兩種主流的多線程消費方案。兩種方案各有側重,分別適用于不同的業務場景,需結合實際需求選擇。

方案1:多線程+多Consumer實例(粗粒度劃分)

核心思路

為每個線程分配一個獨立的KafkaConsumer實例,線程內部完整執行"消息獲取-業務處理-位移提交"的全流程。多個線程并行工作,各自負責部分分區的消費,形成"一個線程對應一個消費者實例"的架構。

實現架構

消費者組├─ 線程1 → KafkaConsumer實例1 → 負責分區1、2的消費├─ 線程2 → KafkaConsumer實例2 → 負責分區3、4的消費└─ 線程3 → KafkaConsumer實例3 → 負責分區5、6的消費

代碼實現

public class MultiConsumerThreadDemo {private final static String TOPIC_NAME = "order-topic";private final static String BOOTSTRAP_SERVERS = "kafka-1:9092,kafka-2:9092";private final static String GROUP_ID = "order-consumer-group";private final int threadNum; // 線程數量private final ExecutorService executorService;
?public MultiConsumerThreadDemo(int threadNum) {this.threadNum = threadNum;this.executorService = new ThreadPoolExecutor(threadNum,threadNum,0L, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<>(100),new ThreadPoolExecutor.CallerRunsPolicy());}
?public void start() {for (int i = 0; i < threadNum; i++) {executorService.submit(new ConsumerWorker());}}
?private class ConsumerWorker implements Runnable {private final KafkaConsumer<String, String> consumer;private final AtomicBoolean closed = new AtomicBoolean(false);
?public ConsumerWorker() {// 每個線程創建獨立的Consumer實例Properties props = new Properties();props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, BOOTSTRAP_SERVERS);props.put(ConsumerConfig.GROUP_ID_CONFIG, GROUP_ID);props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");this.consumer = new KafkaConsumer<>(props);// 訂閱目標主題consumer.subscribe(Collections.singletonList(TOPIC_NAME));}
?@Overridepublic void run() {try {while (!closed.get()) {// 1. 獲取消息ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1000));// 2. 處理消息for (ConsumerRecord<String, String> record : records) {processRecord(record); // 業務處理邏輯}// 3. 手動提交位移consumer.commitSync();}} catch (WakeupException e) {// 忽略關閉時的異常if (!closed.get()) throw e;} finally {consumer.close(); // 釋放資源}}
?public void shutdown() {closed.set(true);consumer.wakeup(); // 喚醒阻塞的poll()方法}
?private void processRecord(ConsumerRecord<String, String> record) {// 實際業務處理邏輯System.out.println("Thread: " + Thread.currentThread().getId() + ", Partition: " + record.partition() + ", Offset: " + record.offset());}}
?public static void main(String[] args) {MultiConsumerThreadDemo demo = new MultiConsumerThreadDemo(3); // 3個線程demo.start();}
}

方案優勢

  1. 實現簡單直觀 無需復雜的線程間通信機制,每個線程獨立完成消費流程,符合開發者對多線程的直觀理解。

  2. 線程安全有保障 每個線程使用專屬的KafkaConsumer實例,避免多線程共享資源導致的并發問題,無需額外的同步措施。

  3. 天然保證分區內順序 由于每個分區只被一個線程消費,消息在分區內的處理順序與存儲順序完全一致,適用于金融交易、訂單處理等對順序性要求嚴格的場景。

  4. 故障隔離性好 單個線程異常不會影響其他線程的正常運行,例如線程A因OOM崩潰后,線程B、C仍能繼續處理各自分區的消息。

方案劣勢

  1. 資源消耗較高 每個線程都需要維護獨立的TCP連接、緩沖區和元數據,在線程數較多時會占用大量內存和網絡資源。例如,100個線程將創建100個TCP連接,可能觸發Broker的連接數限制。

  2. 線程數受分區數限制 在消費者組中,分區數是線程數的上限(N個分區最多只能被N個線程消費)。若線程數超過分區數,多余的線程會處于空閑狀態,造成資源浪費。

  3. Rebalance風險較高 線程同時負責消息獲取與處理,若業務邏輯耗時過長,可能導致poll()間隔超過max.poll.interval.ms,觸發Rebalance。

方案2:單/多線程獲取消息+線程池處理(細粒度劃分)

核心思路

將消費流程拆分為"消息獲取"與"消息處理"兩個階段:

  • 消息獲取:由單線程或少量線程執行poll()方法,從Kafka拉取消息。

  • 消息處理:通過線程池并行處理消息,主線程不參與業務邏輯。

這種方案將消費鏈路解耦為"生產者-消費者"模型:獲取線程作為生產者將消息放入任務隊列,線程池中的工作線程作為消費者處理消息。

實現架構

消費者組├─ 獲取線程 → KafkaConsumer實例 → 拉取消息└─ 線程池(N個工作線程)→ 并行處理消息

代碼實現

public class ThreadPoolConsumerDemo {private final KafkaConsumer<String, String> consumer;private final ExecutorService workerPool;private final int workerNum;private final AtomicBoolean closed = new AtomicBoolean(false);
?public ThreadPoolConsumerDemo(int workerNum) {this.workerNum = workerNum;// 初始化ConsumerProperties props = new Properties();props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "kafka-1:9092,kafka-2:9092");props.put(ConsumerConfig.GROUP_ID_CONFIG, "thread-pool-group");props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");this.consumer = new KafkaConsumer<>(props);// 初始化線程池this.workerPool = new ThreadPoolExecutor(workerNum, workerNum,0L, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<>(1000),new ThreadPoolExecutor.CallerRunsPolicy() // 隊列滿時由提交線程執行任務);// 訂閱主題consumer.subscribe(Collections.singletonList("log-topic"));}
?public void start() {// 啟動消息獲取線程new Thread(this::pollAndProcess).start();}
?private void pollAndProcess() {try {while (!closed.get()) {// 1. 獲取消息(單線程執行)ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1000));if (records.isEmpty()) continue;
?// 2. 提交任務到線程池處理List<Future<?>> futures = new ArrayList<>();for (ConsumerRecord<String, String> record : records) {futures.add(workerPool.submit(() -> processRecord(record)));}
?// 3. 等待所有任務處理完成后提交位移(保證位移與處理結果一致)for (Future<?> future : futures) {try {future.get(); // 阻塞等待任務完成} catch (ExecutionException e) {// 處理任務執行異常log.error("Task failed", e.getCause());}}// 手動提交位移consumer.commitSync();}} catch (WakeupException e) {if (!closed.get()) throw e;} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {consumer.close();workerPool.shutdown();}}
?private void processRecord(ConsumerRecord<String, String> record) {// 消息處理邏輯,如日志解析、數據清洗等System.out.println("Worker thread: " + Thread.currentThread().getId() + ", Record: " + record.value());}
?public void shutdown() {closed.set(true);consumer.wakeup();}
?public static void main(String[] args) {ThreadPoolConsumerDemo demo = new ThreadPoolConsumerDemo(10); // 10個工作線程demo.start();}
}

方案優勢

  1. 資源利用率更高 消息獲取線程數量少(通常1-3個),大幅減少TCP連接和內存占用。線程池可靈活調整大小,充分利用多核CPU資源。

  2. 擴展性更強 消息獲取與處理能力可獨立擴容:若拉取速度慢,可增加獲取線程;若處理速度慢,可增加線程池大小,無需受限于分區數。

  3. 適合CPU密集型處理 線程池并行處理能顯著提升CPU密集型任務的效率,例如大數據量的JSON解析、正則匹配等場景。

方案劣勢

  1. 實現復雜度高 需要處理線程池管理、任務異常、位移提交時機等問題,尤其是保證位移與處理結果的一致性難度較大。

  2. 破壞分區內順序性 線程池并行處理可能導致后獲取的消息先被處理(如消息A先于消息B被拉取,但線程2先處理完消息B),適用于日志分析、數據采集等對順序不敏感的場景。

  3. 位移提交風險大 若采用"全部任務完成后提交位移"的策略,單個任務超時會阻塞整個位移提交流程,可能觸發Rebalance;若采用"按分區提交",則需要復雜的分區-任務映射管理。

  4. 異常處理復雜 工作線程的異常無法直接傳遞給獲取線程,需通過Future或回調函數捕獲,容易因處理不當導致消息丟失或重復消費。

兩種方案的對比與選擇指南

維度方案1(多Consumer實例)方案2(線程池處理)
線程安全天然安全(無共享資源)需額外同步(位移提交、異常處理)
順序性保證分區內嚴格有序可能破壞順序
資源消耗高(多連接、多緩沖區)低(少連接、共享線程池)
擴展性受分區數限制無限制(線程池可動態調整)
實現復雜度
適用場景金融交易、訂單處理(順序敏感)日志分析、數據采集(高吞吐)
Rebalance風險較高(處理耗時影響poll間隔)較低(獲取線程輕量)

選擇建議:

  • 優先方案1:當業務要求消息嚴格有序、實現復雜度需最低化,或分區數較少(如≤20)時。

  • 優先方案2:當追求高吞吐量、可接受消息亂序,或業務處理為CPU密集型時。

  • 混合方案:對于超大規模集群(如1000+分區),可結合兩種方案——按主題分片,每個分片內使用方案1,分片間并行處理。

多線程消費的進階實踐:優化與避坑

線程數與線程池參數的科學配置

方案1的線程數配置

  • 基本原則:線程數 ≤ 訂閱主題的總分區數,建議設置為分區數的1~1.5倍(預留部分線程應對臨時峰值)。

  • 示例:若主題有10個分區,線程數可設為10(充分利用分區)或8(避免資源浪費)。

方案2的線程池配置

  • 核心線程數:根據CPU核心數設置(CPU密集型任務:核心數+1;IO密集型任務:核心數*2)。

  • 隊列容量:避免過大(導致內存溢出)或過小(觸發拒絕策略),建議根據max.poll.records設置(如隊列容量=2*max.poll.records)。

  • 拒絕策略:優先選擇CallerRunsPolicy(讓提交線程處理任務,避免消息丟失),而非默認的AbortPolicy

位移提交的最佳實踐

位移提交是多線程消費中最容易出錯的環節,需根據方案特性選擇合適策略:

方案1的位移提交

  • 推薦手動提交:在消息處理完成后調用commitSync(),確保位移與處理結果一致。

  • 優化:可按分區批量提交(commitSync(offsets)),減少提交次數。

方案2的位移提交

  • 禁止自動提交:自動提交可能導致"已提交位移但消息未處理"的情況,引發消息丟失。

  • 按分區分組提交:將同一分區的消息分配給固定線程,處理完成后按分區提交位移,示例:

    // 按分區分組任務
    Map<TopicPartition, List<ConsumerRecord>> partitionRecords = new HashMap<>();
    for (ConsumerRecord record : records) {TopicPartition tp = new TopicPartition(record.topic(), record.partition());partitionRecords.computeIfAbsent(tp, k -> new ArrayList<>()).add(record);
    }
    ?
    // 按分區提交任務
    Map<TopicPartition, OffsetAndMetadata> offsets = new HashMap<>();
    for (Map.Entry<TopicPartition, List<ConsumerRecord>> entry : partitionRecords.entrySet()) {TopicPartition tp = entry.getKey();List<ConsumerRecord> tpRecords = entry.getValue();// 提交分區任務到線程池futures.add(workerPool.submit(() -> {for (ConsumerRecord record : tpRecords) {processRecord(record);}// 記錄分區最大位移long maxOffset = tpRecords.get(tpRecords.size() - 1).offset() + 1;offsets.put(tp, new OffsetAndMetadata(maxOffset));}));
    }
    ?
    // 等待所有任務完成后提交位移
    for (Future<?> f : futures) f.get();
    consumer.commitSync(offsets);

避免Rebalance的關鍵優化

Rebalance是多線程消費中的主要性能殺手,需從參數配置與代碼邏輯兩方面優化:

  1. 參數調優

    • max.poll.interval.ms:根據單批消息處理的最大耗時設置(建議≥處理耗時*1.5)。

    • max.poll.records:控制單批消息數量,確保處理時間≤max.poll.interval.ms

    • session.timeout.ms:建議設置為heartbeat.interval.ms的3~5倍(如心跳3秒,會話超時10秒)。

  2. 代碼優化

    • 方案1中,避免在消費線程中執行耗時操作(如遠程調用、大文件IO),可異步化處理。

    • 方案2中,為線程池任務設置超時時間(future.get(timeout, unit)),避免單個任務阻塞位移提交。

多線程消費的監控與調優

關鍵監控指標

  • 消費延遲(Consumer Lag):通過kafka-consumer-groups.sh或Prometheus監控,確保延遲穩定在可接受范圍。

    # 查看消費延遲
    bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 \--group test-group --describe
  • 線程池指標:活躍線程數、隊列積壓數、任務拒絕數(方案2關鍵指標)。

  • Rebalance頻率:通過Broker日志或rebalance_latency_avg指標監控,理想狀態為0。

調優案例

某電商平臺采用方案2處理訂單日志,初期因線程池隊列容量過小(100)導致任務頻繁被拒絕,調整為ArrayBlockingQueue(10000)并配合CallerRunsPolicy后,拒絕數降為0,吞吐量提升30%。

多線程VS多進程:消費架構的終極選擇

除多線程外,啟動多個Consumer進程也是常見的消費擴展方式。兩種架構各有優劣,需根據業務場景綜合評估:

多進程方案的特點

優勢:

  • 隔離性更強:進程間內存完全隔離,單個進程崩潰不會影響其他進程。

  • 資源控制更精細:可通過cgroups等工具為每個進程分配獨立的CPU、內存配額。

  • 適合超大規模集群:在K8s等容器化環境中,多進程可通過水平擴展實現動態擴縮容。

劣勢:

  • 資源消耗極高:每個進程需加載獨立的JVM、類庫,內存占用是多線程的數倍。

  • 通信成本高:進程間通信需依賴網絡(如Redis、MQ),遠慢于線程間的內存通信。

  • 運維復雜度高:需管理多個進程的生命周期、日志與監控,不利于問題排查。

多線程與多進程的對比

場景多線程方案多進程方案
資源效率高(共享內存、JVM)低(獨立資源)
隔離性低(線程崩潰可能影響進程)高(進程間完全隔離)
擴展方式垂直擴展(增加線程數)水平擴展(增加實例數)
適用規模中小規模(單機多核)大規模(分布式集群)
典型應用單體應用、邊緣計算云原生應用、微服務架構

混合架構:多進程+多線程

在超大規模場景中,可結合兩種方案的優勢:

  • 進程維度:按服務器節點部署多個Consumer進程,實現水平擴展。

  • 線程維度:每個進程內部采用方案1或方案2,充分利用單機多核資源。

例如,某支付平臺在10臺服務器上部署Consumer進程,每臺進程內啟動8個線程(對應8個分區),總消費能力達到80個分區的處理上限。

常見問題與解決方案

方案2中如何安全提交位移?

問題:線程池處理消息時,位移提交時機難以把握,易出現"位移已提交但消息處理失敗"的情況。 解決方案

  • 采用"至少一次"語義:確保消息處理完成后再提交位移,失敗時重試任務。

  • 結合冪等設計:通過消息唯一ID(如訂單號)避免重復消費的業務影響。

  • 使用事務提交:在支持事務的存儲系統(如MySQL)中,將消息處理與位移提交放入同一事務。

如何處理方案1中的線程數超過分區數的問題?

問題:線程數超過分區數時,多余線程空閑,浪費資源。 解決方案

  • 動態調整線程數:通過監控分區數變化,動態增減線程(如使用ThreadPoolExecutorsetCorePoolSize())。

  • 按主題分片:將多個主題的分區分配給不同線程,避免單個主題分區數不足的問題。

多線程消費中的數據傾斜如何處理?

問題:部分分區消息量過大,導致對應線程負載過高,整體消費延遲增加。 解決方案

  • 分區拆分:將熱點分區拆分為多個子分區,均衡負載。

  • 動態負載均衡:方案1中,可自定義分區分配策略(如StickyAssignor),避免分區集中分配。

  • 流量控制:在消費線程中增加限流邏輯,避免熱點分區壓垮單個線程。

如何應對方案2中的順序性問題?

問題:線程池并行處理破壞消息順序,導致業務邏輯錯誤(如"訂單取消"先于"訂單創建"執行)。 解決方案

  • 按Key分組處理:將同一業務Key(如訂單ID)的消息路由到同一工作線程,保證局部順序。

    // 按Key的哈希值分配線程
    int threadIndex = Math.abs(record.key().hashCode() % workerNum);
    workerPool.submit(new WorkerTask(record), threadIndex);
  • 引入本地隊列:為每個分區創建獨立的阻塞隊列,工作線程按分區順序消費隊列中的消息。

總結

Kafka多線程消費的核心目標是在保證數據一致性的前提下,最大化利用硬件資源。通過本文的分析,我們可以得出以下最佳實踐:

  1. 方案選擇的黃金法則: 順序敏感場景優先方案1,高吞吐場景優先方案2,超大規模場景采用"多進程+多線程"混合架構。

  2. 參數配置的核心原則: 圍繞max.poll.interval.msmax.poll.records構建平衡,確保單批處理時間 < max.poll.interval.ms

  3. 健壯性設計的關鍵

    • 位移提交需與業務處理結果強綁定,避免"空提交"或"漏提交"。

    • 線程池與Consumer實例需優雅關閉(如shutdown()+wakeup()),避免資源泄露。

    • 完善監控告警,重點關注消費延遲、Rebalance頻率與線程池指標。

  4. 未來演進方向: 隨著Kafka 3.x版本中彈性消費者(Flexible Consumer)的引入,多線程消費可能向更動態、自適應的方向發展,例如自動調整線程數與分區分配策略。

多線程消費不是銀彈,而是需要根據業務場景靈活調整的工具。開發者應在理解Kafka線程模型的基礎上,結合實際需求選擇最優方案,才能真正發揮多核CPU的性能潛力,構建高效、穩定的Kafka消費系統。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/90609.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/90609.shtml
英文地址,請注明出處:http://en.pswp.cn/web/90609.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

PDF 轉 HTML5 —— HTML5 填充圖形不支持 Even-Odd 奇偶規則?(第二部分)

這是關于該主題的第二部分。如果你還沒有閱讀第一部分&#xff0c;請先閱讀&#xff0c;以便理解“繞組規則”的問題。 快速回顧一下&#xff1a;HTML5 只支持 Non-Zero&#xff08;非零&#xff09;繞組規則&#xff0c;而 PDF 同時支持 Non-Zero 和 Even-Odd&#xff08;奇偶…

機器學習 KNN 算法,鳶尾花案例

目錄 一.機器學習概述 二.人工智能的兩大方向 三.KNN算法介紹 1.核心思想&#xff1a;“物以類聚&#xff0c;人以群分” 2.算法步驟 四.KNN算法實現 1.安裝scikit-learn庫 2.導入knn用于分類的類KNeighborsClassifier 3.設置KNeighborsClassifier的相關參數 4.訓練模…

強化學習(第三課第三周)

文章目錄強化學習&#xff08;第三課第三周&#xff09;一、以火星探測器為例說明強化學習的形式化表示二、強化學習中的回報三、強化學習算法的目標&#xff08;一&#xff09;馬爾可夫決策過程&#xff08;二&#xff09;狀態動作價值函數&#xff08;四&#xff09;使用Bell…

星痕共鳴數據分析2

今天實驗內容是攻擊力部分 1.思路 由于昨天數據分析出了一個函數 這個函數可以把奇怪的字節變成正常的數字 int parse_varint(unsigned const char* data, int count) {int value 0;int shift 0;for (int i 0; i < count; i) {unsigned char byte data[i];value | ((byt…

強化學習新發現:僅需更新5%參數的稀疏子網絡可達到全模型更新效果

摘要&#xff1a;強化學習&#xff08;RL&#xff09;已成為大語言模型&#xff08;LLM&#xff09;在完成預訓練后與復雜任務及人類偏好對齊的關鍵步驟。人們通常認為&#xff0c;要通過 RL 微調獲得新的行為&#xff0c;就必須更新模型的大部分參數。本研究對這一假設提出了挑…

electron 使用記錄

目錄 代理設置以打包成功 參考文檔 代理設置以打包成功 參考文檔 使用 JavaScript、HTML 和 CSS 構建跨平臺桌面應用 |電子 --- Build cross-platform desktop apps with JavaScript, HTML, and CSS | Electron

Spring boot Grafana優秀的監控模板

JVM (Micrometer) | Grafana Labs 1 SLS JVM監控大盤 | Grafana Labs Spring Boot 2.1 Statistics | Grafana Labs springboot granfana 監控接口指定接口響應的 在Spring Boot應用中&#xff0c;使用Grafana進行監控通常涉及以下幾個步驟&#xff1a; 設置Prometheus作…

LeetCode11~30題解

LeetCode11.盛水最多的容器&#xff1a; 題目描述&#xff1a; 給定一個長度為 n 的整數數組 height 。有 n 條垂線&#xff0c;第 i 條線的兩個端點是 (i, 0) 和 (i, height[i]) 。 找出其中的兩條線&#xff0c;使得它們與 x 軸共同構成的容器可以容納最多的水。 返回容器…

計算機結構-邏輯門、存儲器、內存、加法器、鎖存器、程序計數器

邏輯門 邏輯門簡單地理解即通過特定的條件實現與、或、非、異或等相關邏輯二極管 這些最基礎的邏輯門都是通過電路元器件進行搭建的&#xff0c;即半導體材料搭建的二極管二極管有個特點&#xff0c;一定條件下才可以導通&#xff0c;即得接對正負極&#xff0c;具體的原理可以…

連鎖店鋪巡查二維碼的應用

在連鎖店鋪的運營管理中&#xff0c;巡查工作是保障各門店規范運作、提升服務質量的關鍵環節。巡查二維碼的出現&#xff0c;為這一環節帶來了高效、便捷且規范的解決方案&#xff0c;其應用場景廣泛&#xff0c;優勢顯著。在如今的繁雜且效果參差不齊電子二維碼市場中&#xf…

各種前端框架界面

前端技術更新迭代很快&#xff0c;已經有不少新的前端框架問世&#xff0c;而且像geeker-admin風格的界面設計也挺不錯的。 今天去面試了前端開發崗位&#xff0c;感覺希望不大。畢竟中間空了一段時間沒接觸&#xff0c;得趕緊把新的知識點補上&#xff0c;這樣哪怕是居家辦公也…

DApp 開發者 學習路線和規劃

目錄 ?? 一、學習路線圖 階段 1:基礎知識(1~2 周) 階段 2:智能合約開發(3~4 周) 階段 3:前端與區塊鏈交互(2~3 周) 階段 4:進階與生態系統(持續學習) ?? 二、學習規劃建議(3~4 個月) ?? 三、工具推薦 ?? 四、附加建議 ?? 一、學習路線圖 階段 …

數據結構 二叉樹(3)---層序遍歷二叉樹

在上篇文章中我們主要講了關于實現二叉樹的內容&#xff0c;包括遍歷二叉樹&#xff0c;以及統計二叉樹等內容。而在這篇文章中我們將詳細講解一下利用隊列的知識實現層序遍歷二叉樹。那么層序遍歷是什么&#xff1f;以及利用隊列遍歷二叉樹又是怎么遍歷的&#xff1f;下面讓我…

【橘子分布式】gRPC(番外篇-攔截器)

一、簡介 我們之前其實已經完成了關于grpc的一些基礎用法&#xff0c;實際上還有一些比較相對進階的使用方式。比如&#xff1a; 攔截器&#xff1a;包括客戶端和服務端的攔截器&#xff0c;進而在每一端都可以劃分為流式的攔截器和非流式的攔截器。和以前我們在spring web中的…

深入探索嵌入式仿真教學:以酒精測試儀實驗為例的高效學習實踐

引言&#xff1a;嵌入式技術普及下的教學革新 嵌入式系統作為現代科技的核心驅動力&#xff0c;其教學重要性日益凸顯。然而&#xff0c;傳統硬件實驗面臨設備成本高、維護難、時空受限等挑戰。如何突破這些瓶頸&#xff0c;實現高效、靈活、專業的嵌入式教學&#xff1f;本文將…

三種深度學習模型(GRU、CNN-GRU、貝葉斯優化的CNN-GRU/BO-CNN-GRU)對北半球光伏數據進行時間序列預測

代碼功能 該代碼實現了一個光伏發電量預測系統&#xff0c;采用三種深度學習模型&#xff08;GRU、CNN-GRU、貝葉斯優化的CNN-GRU/BO-CNN-GRU&#xff09;對北半球光伏數據進行時間序列預測對北半球光伏數據進行時間序列預測&#xff0c;并通過多維度評估指標和可視化對比模型性…

PostgreSQL對象權限管理

本文記述在postgreSQL中對用戶/角色操作庫、模式、表、序列、函數、存儲過程的權限管理針對數據庫的授權 授權&#xff1a;grant 權限 on database 數據庫 to 用戶/角色; 撤權&#xff1a;revoke 權限 on database 數據庫 from 用戶/角色; 針對模式的授權 授權&#xff1a;gran…

Wordpress主題配置

一、下載主題 主題下載地址&#xff1a;https://www.iztwp.com/tag/blog-theme 二、主題安裝 三、上傳主題安裝即可 四、安裝完成啟動主題

lock 和 synchronized 區別

1. 引言 在多線程編程中&#xff0c;我們經常需要確保某些代碼在同一時刻只由一個線程執行。這種機制通常叫做“互斥鎖”或“同步”。Java 提供了兩種主要的同步機制&#xff1a;synchronized 關鍵字和 Lock 接口。盡管它們的作用相似&#xff0c;都用于實現線程的同步&#xf…

Tkinter - Python圖形界面開發指南

作者&#xff1a;唐叔在學習 專欄&#xff1a;唐叔學python 標簽&#xff1a;Python GUI編程 Tkinter教程 圖形界面開發 Python實戰 界面設計 事件監聽 Python入門 唐叔Python 編程學習 軟件開發 文章目錄一、Tkinter是什么&#xff1f;為什么選擇它&#xff1f;二、Tkinter基礎…