kafka集成篇

news2024/10/6 4:05:34

kafka的Java客户端

生产者

1.引入依赖

        <dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
            <version>2.6.3</version>
        </dependency>

2.生产者发送消息的基本实现

/**
 * 消息的发送⽅
 */
public class MyProducer {
    private final static String TOPIC_NAME = "my-replicated-topic";

    public static void main(String[] args) {
        Properties props = new Properties();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,
                "124.222.253.33:9092,124.222.253.33:9093,124.222.253.33:9094");

        // 把发送的key从字符串序列化为字节数组
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
                StringSerializer.class.getName());
        // 把发送消息value从字符串序列化为字节数组
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
                StringSerializer.class.getName());
        RecordMetadata metadata = null;
        try (Producer<String, String> producer = new KafkaProducer<>(props)) {
            Order order = new Order(1L, 99.9D);

            // 未指定发送分区,具体发送的分区计算公式:hash(key)%partitionNum
            ProducerRecord<String, String> producerRecord = new
                    ProducerRecord<>(TOPIC_NAME
                    , order.getOrderId().toString(), JSON.toJSONString(order));

            // 等待消息发送成功的同步阻塞⽅法
            metadata = producer.send(producerRecord).get();
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
        } finally {
            if (metadata != null) {
                // =====阻塞=======
                System.out.println("同步⽅式发送消息结果:" + "topic-" +
                        metadata.topic() + "|partition-"
                        + metadata.partition() + "|offset-" +
                        metadata.offset());
            }
        }
    }
}

3.发送消息到指定分区

image-20230814170847906

4.发送消息未指定分区

发送消息未指定分区,会通过业务key的hash运算,算出消息往哪个分区上发

// 未指定发送分区,具体发送的分区计算公式:hash(key)%partitionNum
ProducerRecord<String, String> producerRecord = new
    ProducerRecord<>(TOPIC_NAME
                     , order.getOrderId().toString(), JSON.toJSONString(order));

5.同步发送消息

image-20230814172356139

如果生产者发送消息没有收到ack,生产者会阻塞,阻塞到3s的时间,如果还没有收到消息,会进行重试。重试的次数3次。

    RecordMetadata metadata = producer.send(producerRecord).get();
     System.out.println("同步⽅式发送消息结果:" + "topic-" +
    metadata.topic() + "|partition-"
     + metadata.partition() + "|offset-" + metadata.offset());

6.异步发送消息

image-20230814173250894

异步发送,生产者发送完消息后就可以执行之后的业务,broker在收到消息后异步调用生产者提供的callback回调方法。

            // 异步发送消息 Callback回调接口
            producer.send(producerRecord, new Callback() {
                // 异步回调方法
                @Override
                public void onCompletion(RecordMetadata metadata, Exception e) {
                    if (e != null) {
                        System.err.println("发送消息失败:" +
                                e.getMessage());
                    }
                    if (metadata != null) {
                        System.out.println("异步⽅式发送消息结果:" + "topic-" +
                                metadata.topic() + "|partition-"
                                + metadata.partition() + "|offset-" + metadata.offset());
                    }
                }
            });
			System.out.println("处理之后的逻辑~");

输出结果:

image-20230814173709486

7.生产者中的ack的配置

在同步发消息的场景下:生产者发送消息到broker上后,ack会有3种不同的选择

  • ack = 0 :kafka-cluster不需要任何的broker收到消息,就立即返回ack给生产者就可以继续发送下一条消息,效率是最高的但最容易丢消息
  • ack=1(默认):多副本之间的leader已经收到消息,并把消息写⼊到本地的log中,才会返回ack给生产者,性能和安全性是最均衡的(这种情况下,如果follower没有成功备份数据,而此时leader又挂掉,则消息会丢失)
  • ack=-1/all:需要等待 min.insync.replicas(默认为1,推荐配置大于等于2) 这个参数配置的副本个数都成功写入日志才会返回ack给生产者,这种策略会保证只要有⼀个备份存活就不会丢失数据。这种方式最安全但性能最差。(⼀般除非是金融级别,或跟钱打交道的场景才会使用这种配置)

image-20230814175916962

code:

props.put(ProducerConfig.ACKS_CONFIG, "1");

关于ack和重试(如果没有收到ack,就开启重试)的配置

  • 发送会默认会重试3次,每次间隔100ms
props.put(ProducerConfig.ACKS_CONFIG, "1");
 /*
 发送失败会重试,默认重试间隔100ms,【重试能保证消息发送的可靠性,但是也可能造成消息重复发送】,⽐如⽹络抖动,所以【需要在接收者那边做好消息接收的幂等性处理】
 */
 props.put(ProducerConfig.RETRIES_CONFIG, 3);
 // 重试间隔设置
 props.put(ProducerConfig.RETRY_BACKOFF_MS_CONFIG, 300);

8.关于消息发送的缓冲区

发送的消息会先进入到本地缓冲区(32mb),kakfa会跑⼀个线程,该线程去缓冲区中取16k的数据,发送到kafka,如果到10毫秒数据没取满16k,也会发送⼀次。

image-20230814180715793

  • kafka默认会创建一个消息缓冲区,用来存放要发送的消息,缓冲区是32m
props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);
  • kafka本地线程会去缓冲区中⼀次拉16k的数据,发送到broker
props.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
  • 如果线程拉不到16k的数据,间隔10ms也会将已拉到的数据发到broker
props.put(ProducerConfig.LINGER_MS_CONFIG, 10);

消费者

1.消费者消费消息的基本实现

public class MyConsumer {
    private final static String TOPIC_NAME = "my-replicated-topic";
    private final static String CONSUMER_GROUP_NAME = "testGroup";

    public static void main(String[] args) {
        Properties props = new Properties();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
                "124.222.253.33:9092,124.222.253.33:9093,124.222.253.33:9094");
        // 消费分组名
        props.put(ConsumerConfig.GROUP_ID_CONFIG, CONSUMER_GROUP_NAME);
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
                StringDeserializer.class.getName());
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
                StringDeserializer.class.getName());
        // 1.创建⼀个消费者的客户端
        try (KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props)) {
            // 2.消费者订阅主题列表
            consumer.subscribe(Collections.singletonList(TOPIC_NAME));

            while (true) {
                /*
                 * 3.poll()API 是拉取消息的⻓轮询
                 */
                ConsumerRecords<String, String> records =
                        consumer.poll(Duration.ofMillis(1000));
                for (ConsumerRecord<String, String> record : records) {
                    // 4.操作消息
                    System.out.printf("收到消息:partition = %d,offset = %d, key = %s, value = %s%n ", record.partition(), record.offset(), record.key(), record.value());
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

2.消费者自动提交和手动提交offset

1)提交的内容

消费者无论是自动提交还是手动提交,都需要把所属的消费组+消费的某个主题+消费的某个分区及消费的偏移量,这样的信息提交到集群的_consumer_offsets主题里面。

2)自动提交

消费者poll消息下来以后就会自动提交offset

// 是否自动提交offset,默认就是true
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true");
// 自动提交offset的间隔时间
props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000");

注意自动提交会丢消息。因为消费者在消费前提交offset,有可能提交完后还没消费时消费者挂了。于是下⼀个消费者会从已提交的offset的下一个位置开始消费消息。之前未被消费的消息就丢失掉了。

3)手动提交

需要把自动提交的配置改成false

props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");

手动提交又分成了两种

  • 手动同步提交

在消费完消息后调用同步提交的方法,当集群返回ack前⼀直阻塞,返回ack后表示提交成功,执行之后的逻辑

            while (true) {
                /*
                 * poll()API 是拉取消息的⻓轮询
                 */
                ConsumerRecords<String, String> records =
                        consumer.poll(Duration.ofMillis(1000));
                for (ConsumerRecord<String, String> record : records) {
                    // 操作消息
                    System.out.printf("收到消息:partition = %d,offset = %d, key = %s, value = %s%n ", record.partition(), record.offset(), record.key(), record.value());
                }

                // 所有的消息已消费完
                if (records.count() > 0) {// 有消息
                    // ⼿动同步提交offset,当前线程会阻塞直到offset提交成功
                    // 【⼀般使⽤同步提交】,因为提交之后⼀般也没有什么逻辑代码了
                    consumer.commitSync();// =======阻塞=== 提交成功
                }
            }
  • 手动异步提交

在消息消费完后提交,不需要等到集群ack,直接执行之后的逻辑,可以设置⼀个回调方法,供集群调用

            while (true) {
                /*
                 * poll()API 是拉取消息的⻓轮询
                 */
                ConsumerRecords<String, String> records =
                        consumer.poll(Duration.ofMillis(1000));
                for (ConsumerRecord<String, String> record : records) {
                    // 操作消息
                    System.out.printf("收到消息:partition = %d,offset = %d, key = %s, value = %s%n ", record.partition(), record.offset(), record.key(), record.value());
                }

                // 所有的消息已消费完
                if (records.count() > 0) {// 有消息
                    // ⼿动异步提交offset,当前线程提交offset不会阻塞,可以继续处理后⾯的程序逻辑
                    consumer.commitAsync(new OffsetCommitCallback() {
                        @Override
                        public void onComplete(Map<TopicPartition,
                                OffsetAndMetadata> offsets, Exception exception) {
                            if (exception != null) {
                                System.err.println("Commit failed for " + offsets);
                                System.err.println("Commit failed exception: " + exception.getMessage());
                            }
                        }
                    });
                }
            }

3.长轮询poll消息(消费者拉取消息)

  • 消费者建立了与broker之间的长连接,开始poll消息

  • 默认情况下,消费者一次会poll500条消息

// ⼀次poll最⼤拉取消息的条数,可以根据消费速度的快慢来设置
props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 500);
  • 代码中设置了长轮询的时间是1000毫秒
            while (true) {
                /*
                 * poll()API 是拉取消息的⻓轮询
                 */
                ConsumerRecords<String, String> records =
                        consumer.poll(Duration.ofMillis(1000));
                for (ConsumerRecord<String, String> record : records) {
                    System.out.printf("收到消息:partition = %d,offset = %d, key = %s, value = %s%n ", record.partition(), record.offset(), record.key(), record.value());
                }
            }
  • 意味着:
    • 如果⼀次poll到500条,就直接执行for循环
    • 如果这⼀次没有poll到500条。且时间在1秒内,那么长轮询继续poll,要么到500条,要么到1s,执行后续for循环
    • 如果多次poll都没达到500条,且1秒时间到了,那么直接执行for循环
    • 如果两次poll的间隔超过30s(poll时间短但是消费时间长,消费者消费可能会达到30s左右),集群会认为该消费者的消费能力过 弱,该消费者被踢出消费组,触发rebalance机制,rebalance机制会造成性能开销

可以通过设置参数, 让⼀次poll的消息条数少⼀点,避免触发rebalance损耗性能

 // ⼀次poll最⼤拉取消息的条数,可以根据消费速度的快慢来设置
 props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 500);
 // 如果两次poll的时间如果超出了30s的时间间隔,kafka会认为其消费能⼒过弱,将其踢出消费组。将分区分配给其他消费者。-rebalance
 props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, 30 * 1000);

4.消费者的健康状态检查

消费者每隔1s向kafka集群发送心跳,集群发现如果有超过10s没有续约的消费者,将被踢出消费组,触发该消费组的rebalance机制,将该分区交给消费组里的其他消费者进行消费。

// consumer给broker发送心跳的间隔时间  1s一次
props.put(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG, 1000);
// kafka如果超过10秒没有收到消费者的心跳,则会把消费者踢出消费组,进⾏rebalance,把分区分配给其他消费者。
props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, 10 * 1000);

5.指定分区和偏移量、时间消费

  • 指定分区消费
consumer.assign(Arrays.asList(new TopicPartition(TOPIC_NAME, 0)));
  • 从头消费
consumer.assign(Arrays.asList(new TopicPartition(TOPIC_NAME, 0)));
consumer.seekToBeginning(Arrays.asList(new TopicPartition(TOPIC_NAME, 0)));
  • 指定offset消费
consumer.assign(Arrays.asList(new TopicPartition(TOPIC_NAME, 0)));
consumer.seek(new TopicPartition(TOPIC_NAME, 0), 10);
  • 指定时间消费

根据时间,去所有的partition中确定该时间对应的offset,然后去所有的partition中找到该offset之后的消息开始消费。

// topic对应所有分区
List<PartitionInfo> topicPartitions = consumer.partitionsFor(TOPIC_NAME);
// 从1小时前开始消费
long fetchDataTime = new Date().getTime() - 1000 * 60 * 60;
Map<TopicPartition, Long> map = new HashMap<>();
for (PartitionInfo par : topicPartitions) {
    map.put(new TopicPartition(TOPIC_NAME, par.partition()), fetchDataTime);
}
Map<TopicPartition, OffsetAndTimestamp> parMap = consumer.offsetsForTimes(map);
for (Map.Entry<TopicPartition, OffsetAndTimestamp> entry : parMap.entrySet()) {
    TopicPartition key = entry.getKey();
    OffsetAndTimestamp value = entry.getValue();
    if (key == null || value == null) continue;
    long offset = value.offset();
    System.out.println("partition-" + key.partition() +
                       "|offset-" + offset);
    System.out.println();
    //根据消费⾥的timestamp确定offset
    consumer.assign(Arrays.asList(key));
    consumer.seek(key, offset);
}

6.新消费组的消费offset规则

新消费组中的消费者在启动以后,默认会从当前分区的最后⼀条消息的offset+1开始消费(消费新消息)。可以通过以下的设置,让新的消费者第⼀次从头开始消费。之后开始消费新消息(最后消费的位置的偏移量+1)

  • Latest:默认的,消费新消息

  • earliest:第⼀次从头开始消费。之后开始消费新消息(最后消费的位置的偏移量+1),这个需要区别于consumer.seekToBeginning(每次都从头开始消费)

props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");

SpringBoot集成kafka

1.引入依赖

        <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
        </dependency>

2.配置文件

server:
  port: 8080
spring:
  kafka:
    bootstrap-servers: 124.222.253.33:9092,124.222.253.33:9093,124.222.253.33:9094
    producer: # 生产者
      retries: 3 # 设置大于0的值,则客户端会将发送失败的记录重新发送
      batch-size: 16384 # 每次拉取多少数据发送broker 
      buffer-memory: 33554432 # 本地缓冲区大小
      acks: 1
      # 指定消息key和消息体的编解码⽅式
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
    consumer:
      group-id: default-group
      enable-auto-commit: false
      auto-offset-reset: earliest
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      max-poll-records: 500
    listener:
      # 当每⼀条记录被消费者监听器(ListenerConsumer)处理之后提交
      # RECORD
      # 当每⼀批poll()的数据被消费者监听器(ListenerConsumer)处理之后提交
      # BATCH
      # 当每⼀批poll()的数据被消费者监听器(ListenerConsumer)处理之后,距离上次提交时间大于TIME时提交
      # TIME
      # 当每⼀批poll()的数据被消费者监听器(ListenerConsumer)处理之后,被处理record数量大于等于COUNT时提交
      # COUNT
      # TIME | COUNT 有⼀个条件满足时提交
      # COUNT_TIME
      # 当每⼀批poll()的数据被消费者监听器(ListenerConsumer)处理之后, 手动调用Acknowledgment.acknowledge()后提交
      # MANUAL
      # 【手动调用Acknowledgment.acknowledge()后立即提交,⼀般使用这种】
      # MANUAL_IMMEDIATE
      ack-mode: MANUAL_IMMEDIATE

3.消息生产者

发送消息到指定topic

image-20230815110653415

4.消息消费者

设置消费组,消费指定topic

@Component
public class MyConsumer {
    @KafkaListener(topics = "my-replicated-topic", groupId = "MyGroup1")
    public void listenGroup(ConsumerRecord<String, String> record,
                            Acknowledgment ack) {
        String value = record.value();
        System.out.println(record);
        System.out.println(value);
        //⼿动提交offset
        ack.acknowledge();
    }
}

5.消费者中配置消费主题、分区和偏移量

设置消费组、多topic、指定分区、指定偏移量消费及设置消费者个数

    @KafkaListener(groupId = "testGroup", topicPartitions = {
            @TopicPartition(topic = "topic1", partitions = {"0", "1"}),
            @TopicPartition(topic = "topic2", partitions = "0",
                    partitionOffsets = @PartitionOffset(partition = "1", initialOffset = "100"))
    }, concurrency = "3")// concurrency:同消费组中消费者个数,就是并发消费数,建议小于等于分区总数
    public void listenGroupPro(ConsumerRecord<String, String> record,
                               Acknowledgment ack) {
        String value = record.value();
        System.out.println(value);
        System.out.println(record);
        //⼿动提交offset
        ack.acknowledge();
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/880533.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【 BERTopic应用 01/3】 分析卡塔尔世界杯推特数据

摄影&#xff1a; Rhett Lewis在 Unsplash上 一、说明 卡塔尔世界杯充满了惊喜&#xff01;从沙特阿拉伯通过击败阿根廷震惊世界到摩洛哥历史性地进入半决赛&#xff0c;你必须听到或见证那些足球热潮中的时刻。在这篇文章中&#xff0c;我将使用 BERTopic 来分析 2022 年世界杯…

ubuntu配置Android编译环境

本来以为把AndroidSDK下载到对应的位置上&#xff0c;直接用就行了&#xff0c;没想到编译的时候提示 说明自己下载的build-tools不行&#xff0c;没有同意过授权&#xff0c;还不能用。 正确的方式是怎样的呢&#xff0c;首先下载linux系统上运行的commandlinetools&#xff…

ROS2 学习(二)工作空间,节点

工作空间介绍 workspace 是存放整个项目的大目录。 其中包含&#xff1a; src&#xff1a;源码。 build&#xff1a;编译文件。 install&#xff1a;安装空间&#xff0c;存放编译成功后的目标文件。 log&#xff1a;日志。 我们新建一个工作空间目录&#xff0c;其中包…

Python自动化小技巧16——分类汇总写入excel不同sheet表

案例背景 上了两个月班的社畜博主最近终于有空来总结一下最近写的代码了。 因为上班都是文职工作&#xff0c;天天不是word就是excel就是PPT和pdf....这和什么机器学习还有数据科学不一样&#xff0c;任务更多的是处理实在的文字和表格等格式&#xff0c;按照领导要求来完成&…

【vue.js】手把手教你做一个会躲避鼠标指针的登录按钮

效果 背景 在登录的时候&#xff0c;我们都会做账号密码不可为空的验证&#xff0c;如何做出一个会躲避鼠标指针的登录按钮呢。废话不多说&#xff0c;3、2、1&#xff0c;上代码~ 代码 <!DOCTYPE html> <html lang"en" style"overflow: hidden;&qu…

ad+硬件每日学习十个知识点(34)23.8.14 (DCDC详细设计,续流二极管的选择,COMP引脚的环路设计)

文章目录 1.二极管的rrm电压和rms电压有什么不同2.DCDC续流二极管的选择3.充电电容4.COMP引脚的环路设计5.DCDC设计总结6.多路并联7.相位匹配8.工作模式9.低温输出偏离10.电源负载与效率11.降压升压模块 1.二极管的rrm电压和rms电压有什么不同 答&#xff1a; 二极管的 RRM &a…

JS_围绕圆形滑动

需求&#xff1a;滑动手势最大不能超过一个半径为50的圆形&#xff0c;超出围绕圆形边线滑动 这里只提供一个思路&#xff0c;下面代码可以运行&#xff0c;但是要使用需要改成自己的参数 <div style"width: 100%;height: 100vh;display: flex;justify-content: cente…

从雷军代码再上热搜给程序员们的启示

不久前&#xff0c;小米的一则关于雷军的宣传广告火了&#xff0c;这个宣传广告里隐藏了菜单&#xff0c;那些密密麻麻的字母就是雷军曾经写的代码&#xff0c;据小米公关部负责人王化表示&#xff0c;这张海报上的代码来自30年前&#xff0c;雷军在DOS环境下采用汇编语言亲自编…

Java鹰眼轨迹服务 轻骑小程序 运动健康与社交案例

Java地图专题课 基本API BMapGLLib 地图找房案例 MongoDB 百度地图鹰眼轨迹服务 鹰眼轨迹服务概述 鹰眼是一套轨迹管理服务&#xff0c;提供各端SDK和API供开发者便捷接入&#xff0c;追踪所管理的车辆/人员等运动物体。 基于鹰眼提供的接口和云端服务&#xff0c;开发者可以迅…

【 BERTopic应用 02/3】 分析卡塔尔世界杯推特数据

摄影&#xff1a;Fauzan Saari on Unsplash 一、说明 这是我们对世界杯推特数据分析的第3部分&#xff0c;我们放弃了。我们将对我们的数据进行情绪分析&#xff0c;以了解人们对卡塔尔世界杯的感受。我将在这里介绍的一个功能强大的工具包是Hugging Face&#xff0c;您可以在…

Mac鼠标增强工具Smooze Pro

Smooze Pro是一款Mac上的鼠标手势增强工具&#xff0c;可以让用户使用鼠标手势来控制应用程序和系统功能。 它支持多种手势操作&#xff0c;包括单指、双指、三指和四指手势&#xff0c;并且可以自定义每种手势的功能。例如&#xff0c;您可以使用单指向下滑动手势来启动Expos视…

el-table分页后序号连续的两种方法

实现效果&#xff1a; 第一页排序到10&#xff0c;第二页的排序应从11开始 实现方法一&#xff1a; 在el-table的序号列中使用template定义 <el-table><el-table-columnmin-width"10%"label"序号"><template slot-scope"scope"…

使用pymupdf实现PDF内容搜索并显示功能

简介&#xff1a; 在日常工作和学习中&#xff0c;我们可能需要查找和提取PDF文件中的特定内容。本文将介绍如何使用Python编程语言和wxPython图形用户界面库来实现一个简单的PDF内容搜索工具。我们将使用PyMuPDF模块来处理PDF文件&#xff0c;并结合wxPython构建一个用户友好的…

比例电磁铁控制放大器

GP63系列比例电磁铁应用于电液比例控制系统中&#xff0c;与比例控制放大器配套使用共同控制力士(REXROTH)型十通径螺纹比例阀。在额定行程及额定电流范围内&#xff0c;其输出力与输入电流成比例&#xff0c;通过内置反力弹簧&#xff0c;改变了输出力的特性&#xff0c;使系统…

能源存储蓄电池管理,0基础也能快速上手!

随着能源储存需求的不断增加&#xff0c;蓄电池作为关键的能量储存装置&#xff0c;其稳定性和性能的监测变得尤为重要。 蓄电池监控有助于提高能源系统的可靠性&#xff0c;确保连续供电&#xff0c;同时为维护人员提供及时的故障信息&#xff0c;以便他们能够迅速采取适当的措…

百望云联合华为发布票财税链一体化数智解决方案 赋能企业数字化升级

随着数据跃升为数字经济关键生产要素&#xff0c;数据安全成为整个数字化建设的重中之重。为更好地帮助企业发展&#xff0c;中央及全国和地方政府相继出台了多部与数据相关的政策法规&#xff0c;鼓励各领域服务商提供具有自主创新的软件产品与服务&#xff0c;帮助企业在合规…

【AutoLayout案例03-设置底部按钮之间相同间距 Objective-C语言】

一、好,咱们继续啊 1.咱们继续把autoLayout介绍一下 咱们的自动布局 给大家介绍一下 那么,自动布局呢 继续咱们给大家做的案例 做几个例子 把这几个例子做完以后 我们再给它 我们再给大家说一下,如何通过代码,来实现自动布局 虽然说,通过代码来实现自动布局,并不推荐 但…

AIF360入门教学

1、AIF360简介 AI Fairness 360 工具包(AIF360)是一个开源软件工具包&#xff0c;可以帮助检测和缓解整个AI应用程序生命周期中机器学习模型中的偏见。在整个机器学习的过程中&#xff0c;偏见可能存在于初始训练数据、创建分类器的算法或分类器所做的预测中。AI Fairness 360…

UI自动化测试(下拉框(select类),多窗口,属性,类的方法实战)

一、下拉框&#xff08;select类实现的&#xff09; 在UI的自动化测试实战中&#xff0c;如果遇到下拉框的选择&#xff0c;我们可以使用Select类里面的方法来具体进行定位和解决。下面我们使用HTML的代码来写一个下拉框的页面交互&#xff0c;让大家从直观上知道下拉框的交互…

Node工程的依赖包管理方式

在前端工程化中&#xff0c;JavaScript 依赖包管理是非常重要的一环。依赖包通常是项目所依赖的第三方库、工具和框架等资源&#xff0c;它们能够帮助我们减少重复开发、提高效率并且确保项目可以正确的运行。 目前比较常见的前端包管理器有 npm 和 Yarn&#xff0c;npm 是 No…