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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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基础…