Kafka(四) Consumer消费者

news2024/11/27 12:28:45

一,基础知识


1,消费者与消费组

  • 每个消费者都有对应的消费组,不同消费组之间互不影响。
  • Partition的消息只能被一个消费组中的一个消费者所消费, 但Partition也可能被再平衡分配给新的消费者
  • 一个Topic的不同Partition会根据分配策略(消费者客户端参数partition.assignment strategy)分给不同消费者。

2,Kafka的消息模式

消息中间件一般有两种消息投递模式:点对点模式和发布/订阅模式,Kafka 同时支持两种。
  1. 如果所有的消费者都属于同一消费组,那么所有的消息都会被均衡地投递给每个消费者,即每条消息只会被一个消费者处理,这就相当于点对点模式;
  2. 如果所有的消费者都隶属于不同的消费组,那么所有的消息都会被广播给所有的消费者,即每条消息会被所有的消费者处理,这就相当于发布/订阅模式;

二,Client开发


1,消费逻辑需要具备以下几个步骤

  1. 配置消费者参数及创建消费者实例
  2. 订阅主题
  3. 拉取消息并消费
  4. 提交消费位移
  5. 关闭消费者实例
public class Consumer {
    private static final String BROKER_LIST = "localhost:9092";
    private static final String TOPIC = "TOPIC-A";
    private static final String GROUP_ID = "GROUP-A";
    private static final AtomicBoolean IS_RUNNING = new AtomicBoolean(true);

    public static Properties initConfig() {
        Properties properties = new Properties();
        // 以下3个必须
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, BROKER_LIST);
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        // 客户端ID
        properties.put(ConsumerConfig.CLIENT_ID_CONFIG, "eris-kafka-consumer");
        // 消费组ID
        properties.put(ConsumerConfig.GROUP_ID_CONFIG, GROUP_ID);
        // 自动提交,默认为true
        properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
        return properties;
    }


    public static void main(String[] args) {
        Properties properties = initConfig();
        KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<String, String>(properties);
        kafkaConsumer.subscribe(Arrays.asList(TOPIC));

        try {
            while (IS_RUNNING.get()) {
              // poll内部封装了消费位移提交、消费者协调器、组协调器、消费者的选举、分区分配与再均衡、心跳等
                // Duration用来控制在消费者的缓冲区里没有可用数据时阻塞等待的时间,0表示不等待直接返回
                ConsumerRecords<String, String> records = kafkaConsumer.poll(Duration.ofMillis(1000));
                for (ConsumerRecord<String, String> r : records) {
                    print("topic:" + r.topic() + ", patition:" + r.partition() + ", offset:" + r.offset());
                    print("key:" + r.key() + ", value:" + r.value());
                }
            }
        } catch (WakeupException e) {
            // wakeup方法是KafkaConsumer中唯一可以从其他线程里安全调用的方法,调用wakeup后可以退出poll的逻辑,并抛出WakeupException。我们也不需处理WakeupException,它只是一种跳出循环的方式。
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // maybe commit offset.
            kafkaConsumer.close();
        }
    }
}
意:KafkaConsumer是非线程安全的,wakeup()方法是 KafkaConsume 中唯一可以从其他线程里安全调用的方法。

2,subscribe有4个重载方法

public void subscribe(Collection<String> topics, ConsumerRebalanceListener listener)
public void subscribe(Collection<String> topics)

// 在之后如果又创建了新主题,并且与正表达式相匹配,那么这个消费者也可以消费到新添加的Topic
public void subscribe (Pattern pattern, ConsumerRebalanceListener listener)
public void subscribe (Pattern pattern)

3,assign订阅指定的分区

消费者不仅可以通过 subscribe方法订阅,还可以直接订阅指定分区 ,如下:
consumer.assign(Arrays.asList(new TopicPartition ("topic-demo", 0))) ;
通过subscribe订阅主题具有消费者自动再均衡功能 ,可以根据分区分配策略来自动分配各个消费者与分区的关系;
通过assign来订阅分区时,是不具备消费者自动均衡的功能的。

4,取消订阅

以下三行代码等效。
consumer.unsubscribe();
consumer.subscribe(new ArrayList<String>()) ;
consumer.assign(new ArrayList<TopicPartition>());

5,消息消费

消费者消费到的每条消息的类型为ConsumerRecord, 这个和生产者发送的ProducerRecord相对应。
注意与ConsumerRecords区别,ConsumerRecords实现了Iterable,是poll返回的对象。
public class ConsumerRecord<K, V> {
    private final String topic;
    private final int partition;
    pr vate final long offset;
    private final long timestamp;
    private final TimestampType timestampType;
    private final int serializedKeySize;
    private final int serializedValueSize;
    private final Headers headers;
    private final K key;
    private final V value;
    private volatile Long checksum;
    //省略若干方法
}
根据Partition或Topic来消费消息
① 根据分区对当前批次消息分类:public List<ConsumerRecord<K, V> records(TopicPart tion partition)

for (TopicPartition tp : records.partitions()) {
    for (ConsumerRecord<String, String> record : records.records(tp)) {
        System.out.println(record.partition() + ":" + record.value());
    }
}

② 根据主题对当前批次消息分类:public Iterable<ConsumerRecord<K, V> records(String topic)

// ConsumerRecords类中并没提供与partitions()类似的topics()方法来查看拉取的消息集中所含的主题列表。
for (String topic : Arrays.asList(TOPIC)) {
    for (ConsumerRecord<String, String> record : records.records(topic)) {
        System.out.println(record.topic() + ":" + record.value());
    }
}

6,反序列化

ConsumerRecord里的value,就是经过反序列化后的业务对象。
Kafka所提供的反序列器有ByteBufferDeserializer、ByteArrayDeserializer、BytesDeserializer、DoubleDeserializer、FloatDeserializer、IntegerDeserializer、LongDeserializer、ShortDeserializer、StringDeserializer。
在实际应用中,在Kafka提供的序列化器和反序列化器满足不了应用需求的前提下,推荐使用 Avro、JSON、Thrift、 ProtoBuf、Protostuff等通用的序列化工具来包装,不建议使用自定义的序列化器或反序列化器。

三,位移提交


1,消费位移提交

在每次调用poll方法时,返回的是还没有被消费过的消息集。
消费位移必须做持久化保存,否则消费者重启之后就无法知晓之前的消费位移。再者,当有新的消费者加入时,那么必然会再均衡,某个分区可能在再均衡之后分配给新的消费者,如果不持久化保存消费位移,那么这个新消费者也无法知晓之前的消费位移。
在旧消费者客户端中,消费位移存储在ZooKeeper中,而在新消费者客户端中则存储在Kafka内部的主题 __consumer_offsets中。
这里把将消费位移持久化的动作称为“提交” ,消费者在消费完消息之后需要执行消费位移的提交。

2,三个位移的关系

           
  • lastConsumedOffset:当前消费到的位置,即poll拉到的该分区最后一条消息的offset
  • committed offset:提交的消费位移
  • position:下次拉取的位置
position = committed offset = lastConsumedOffset + 1(当然position和committed offset 并不会一直相同)
TopicPartition tp = new TopicPartition("topic", 0);
kafkaConsumer.assign(Arrays.asList(tp));
long lastConsumedOffset = 0;

while (true) {
    ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofMillis(1000));
    List<ConsumerRecord<String, String>> partitionRecords = consumerRecords.records(tp);
    lastConsumedOffset = partitionRecords.get(partitionRecords.size() - 1).offset();
    // 同步提交消费位移
    kafkaConsumer.commitSync();
   
    System.out.println("consumed off set is " + lastConsumedOffset);
    OffsetAndMetadata offsetAndMetadata = kafkaConsumer.committed(tp);
    System.out.println("commited offset is " + offsetAndMetadata.offset());
    long posititon = kafkaConsumer.position(tp);
    System.out.println("he offset of t he next record is " + posititon);
}

输出结果:
consumed offset is 377
commited offset is 378
the offset of the next record is 378

3,消息丢失与重复消费

  • 如果poll后立马提交位移,之后业务异常,再次拉取就从新位移开始,就丢失了数据。
  • 如果poll后先处理数据,处理到一半异常了,或者最后提交位移异常,重新拉取会从之前的位移拉,就重复消费了。

4,自动提交位移原理

Kafka中默认的消费位移的提交方式是自动提交,这个由消费者客户端参数enable.auto.commit配置,默认值为true。
不是每消费一条消息就提交一次,而是定期提交,这个定期的周期时间由客户端参数auto.commit.interval.ms配置,默认值为5秒,此参数生效的前提是enable.auto.commit参数为true。
在默认的方式下,消费者每隔5秒会将拉取到的每个分区中最大的消息位移进行提交。动位移提交的动作是在poll()方法的逻辑里完成的,在每次真正向服务端发起拉取请求之前会检查是否可以进行位移提交,如果可以,那么就会提交上一次轮询的位移。
自动提交让编码更简洁,但随之而来的是重复消费和消息丢失的问题。

5,手动提交位移

①  同步提交
ConsumerRecords<String, String> records = kafkaConsumer.poll(Duration.ofMillis(1000));
for (ConsumerRecord<String, String> record : records) {
    //do some logical processing 
    kafkaConsumer.commitSync();
}

1)commitSync会根据poll拉取的最新位移来进行提交(注意提交的值对应于图3-6 position的位置〉。

2)可以使用带参方法,提交指定位移:commitSync(final Map<TopicPartition OffsetAndMetadata> offsets)

3)没必要每条消息提交一次,可以改为批量提交。
② 异步提交
public void commitAsync()
public void commitAsync(OffsetCommitCallback callback)
public void commitAsync(final Map<TopicPartition, OffsetAndMetadata> offsets, OffsetCommitCallback callback)

1)异步提交在执行的时候消费者线程不会被阻塞,可能在提交消费位移的结果还未返回之前就开始了新一次的poll操作。

2)提交也可能会异常,可引入重试机制。但重试可能出现问题,若第一次commitAsync失败在重试,第二次成功了,然后第一次重试也成功了,就会覆盖位移为之前。解决方案:可以提交时维护一个序号,如果发现过期的序号就不再重试。
总结:不管是自动提交还是手动提交(同步、异步),都可能出现漏消费和重复消费,一般情况提交位移这一步操作很少失败,至于业务异常如何影响提交,需要结合具体情况分析。
可以引入重试机制,重试提交或者业务处理。但重试会增加代码逻辑复杂度。
还有对于消费者异常退出,重复消费的问题就很难避免,因为这种情况下无法及时提交消费位移,需要消费者的幂等处理。
如果消费者正常退出或发生再均衡况,那么可以 在退出或再均衡执行之前使用同步提交的方式做最后的把关
try {
    while (IS_RUNNING.get()) {
        // poll records and do some logical processing .
        kafkaConsumer.commitAsync();
    }
} finally {
    try {
        kafkaConsumer.commitSync();
    } finally {
        kafkaConsumer.close();
    }
}

四,暂停或恢复消费


暂停某些分区在poll时返回数据给客户端和恢复某些分区返回数据给客户端。
public void pause(Collection<TopicPartition> partitions)
public roid resume(Collection<TopicPartition> partitions)

五,指定位移消费


有了消费位移的持久化,才使消费者在关闭、崩溃或者在遇到再均衡的时候,可以让接替的消费者能够根据存储的消费位移继续进行消费。
当新的消费组建立的时候,它根本没有可以查找的消费位移。或者消费组内的新消费者订阅了一个新的主题,它也没有可以查找的消费位移。
auto.offset.reset 配置
① 作用:
  • 决定从何处消费;
② 何时生效:
  1. 找不到消费位移记录时;
  2. 位移越界时(seek);
③ 配置值:
  1. (默认值)auto.offset.reset=latest,从分区末尾开始消费;
  2. auto.offset.reset=earliest,从分区起始开始消费;
seek方法
① 定义:指定消费的位置,可以向前跳过若干消息或回溯消息。
// partition:分区,offset:从哪个位置消费 
public void seek(TopicPartition partition, long offset)
② seek方法只能重置消费者分配到的分区的消费位置,而分区的分配是在 poll方法过程中实现的,也就是说在执行seek之前需要先执行一次poll,等到分配到分区之后才可以重置消费位置。
Set<TopicPartition> assignment = new HashSet<>();
while (assignment.size() == 0) {
    // 如采不为空,则说明已经成功分配到了分区
    kafkaConsumer.poll(Duration.ofMillis(1000));
    assignment = kafkaConsumer.assignment();
}


for (TopicPartition tp : assignment) {
    kafkaConsumer.seek(tp, 10);
}
while (true) {
    ConsumerRecords<String, String> records = kafkaConsumer.poll(Duration.ofMillis(1000));
    //consume the record
}
③ 如果对当前消费者未分配到的分区执行 seek方法,那么会报IllegalStateException。
④ 如果消费者启动能找到位移记录,又想从头或者尾消费,可以通过seek结合endOffsets、beginningOffsets或者直接seekToBeginning、seekToEnd实现。
注意:分区的起始位置是0,但并不代表每时每刻都为0,因为日志清理会清理旧的数据 ,所以分区的起始位置自然会增加。
⑤ 按时间戳指定消费位置
public Map<TopicPartition, OffsetAndTimestamp> offsetsForTimes(Map<TopicPartition, Long> timestampsToSearch)
public Map<TopicPartition, OffsetAndTimestamp> offsetsForTimes(Map<TopicPartition, Long> timestampsToSearch, Duration timeout)

给定待查分区和时间戳,返回大于等于该时间戳的第一条消息对应的offset和timestamp,对应于OffsetAndTimestamp中的offset、timestamp字段。
⑥ 将分区消费位移存储在数据库、文件等外部介质,再通过seek指定消费,可以配合再均衡监听器来实现新消费者的继续消费。

六,消费再均衡


再均衡是指分区的所属权从一个消费者转移到另一消费者的行为。它为消费组具备高可用性和伸缩性提供保障,使我们可以既方便又安全地删除消费组内的消费者或往消费组内添加消费者。
不过, 在再均衡发生期间的这一小段时间,消费组会变得不可用。而且再均衡容易导致重复消费等问题,一般情况应尽量避免不必要的再均衡。
再均衡监听器  ConsumerRebalanceListener
注册:
subscribe(Collection<String> topics, ConsumerRebalanceListener listener) 和 subscribe(Patten pattern, ConsumerRebalanceListener listener)


ConsumerRebalanceListener是一个接口,有2个方法。

(1) void onPartitionsRevoked(Collection<TopicPartition> partitions)
再均衡开始之前和消费者停止读取消息之后被调用。

(2) void onPartitionsAssigned(Collection<TopicPartition> partitions)
新分配分区之后和消费者开始拉取消费之前被调用 。
示例①:消费时把位移暂存在Map,再均衡之前同步提交,避免发送再均衡的同时,异步提交还没提交上去(重复消费)。
Map<TopicPartition, OffsetAndMetadata> currentOffsets = new HashMap<>();
kafkaConsumer.subscribe(Arrays.asList("topic"), new ConsumerRebalanceListener() {
    @Override
    public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
        kafkaConsumer.commitSync(currentOffsets);
        currentOffsets.clear();
    }
    @Override
    public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
        //do nothing .
    }
});
ConsumerRecords<String, String> records = kafkaConsumer.poll(Duration.ofMillis(1000));
for (ConsumerRecord<String, String> record : records) {
    //process the record
    currentOffsets.put(
            new TopicPartition(record.topic(), record.partition()),
            new OffsetAndMetadata(record.offset() + 1));
}
kafkaConsumer.commitAsync(currentOffsets, null);
示例②:把位移存在db,再均衡后通过seek定位继续消费
kafkaConsumer.subscribe(Arrays.asList(topic), new ConsumerRebalanceListener() {
    @Override
    public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
        // store offset in DB
    }

    @Override
    public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
        for (TopicPartition tp : partitions) {
            // 从DB中读取消费位移
            kafkaConsumer.seek(tp, getOffsetFromDB(tp));
        }
    }
}

七,消费者拦截器


与生产者拦截器对应,消费者拦截器需要自定义实现org.apache.kafka.clients.consumer.Consumerlnterceptor接口。
Consumerlnterceptor有3个方法:
// poll方法返回之前调用,可以修改返回的消息内容、按照某种规则过滤消息等
public ConsumerRecords<K, V> onConsume(ConsumerRecords<K , V> records);

// 提交完消费位移之后调用,可以用来记录跟踪所提交的位移信息,比如当使用commitSync的无参方法时,我们不知道提交的消费位移,而onCommit方法却可以做到这一点
public void onCommit(Map<TopicPartition, OffsetAndMetadata> offsets);

public void close();
在消费者中也有拦截链的概念,和生产者的拦截链一样, 也是按照interceptor.classes参数配置的拦截器的顺序来一一执行的。如果在拦截链中某个拦截器执行失败,那么下一个拦截器会接着从上一个执行成功的拦截器继续执行。

八,消费者多线程模型


① 每个消费者单独线程,只消费一个分区
模型简单,自动、手动提交位移都很简单。
优点:每个分区可以按顺序消费
缺点:多个TCP连接消耗
② 一个消费者拉取,提交线程池处理
各分区的消费是散乱在各线程,提交位移(顺序)复杂。
优点:拉取快,TCP连接少
缺点:分区消费顺序不好保障。
上图是自动提交位移。如果要手动提交,可考虑共享offsets方式,同时为了避免对同一个分区,后序批次提交了更大的位移,前序批次处理失败造成的消息丢失,可以考虑滑动窗口机制。(参考《深入理解Kafka核心设计与实践原理》)

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

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

相关文章

【C#】部分国家/语言,string字符串转decimal、float时,小数点解析异常、小数点丢失、小数点被忽略

现象&#xff1a; 部分国家地区&#xff0c;字符串转小数后&#xff0c;小数点丢失&#xff0c;比如&#xff1a;输入"12.34"&#xff0c;输出1234&#xff0c;而非12.34。 部分相关函数decimal.Parse、decimal.TryParse、float.Parse、float.TryParse 原因&…

【Linux】常用命令总结(updating)

1.date2.du&#xff08;disk use&#xff09;3.df&#xff08;disk free&#xff09;4.find5.crontab6.netstat shell命令可以使用man查看命令文档说明&#xff0c;说明界面中可通过b(backward)向上翻页&#xff0c;f(forward)向下翻页&#xff0c;g(go to)跳到说明首页&#x…

【问题记录】Docker配置mongodb副本集实现数据流实时获取

配置mongodb副本集实现数据流实时获取 前言操作步骤1. docker拉取mongodb镜像2. 连接mongo1镜像的mongosh3. 在mongosh中初始化副本集 注意点 前言 由于想用nodejs实现实时获取Mongodb数据流&#xff0c;但是报错显示需要有副本集的mongodb才能实现实时获取信息流&#xff0c;…

springboot老年慢性病药物管理系统-计算机毕业设计源码70568

目录 摘要 Abstract 第一章 绪论 1.1 选题背景及意义 1.2 国内外研究现状 1.3 研究方法 第二章 相关技术介绍 2.1 MySQL简介 2.2 Java编程语言 2.3 B/S模式 2.4 springboot框架 第三章 老年慢性病药物管理系统 系统分析 3.1 系统目标 3.2 系统可行性分析 3.2.1 技…

【linux】服务器ubuntu安装cuda11.0、cuDNN教程,简单易懂,包教包会

【linux】服务器ubuntu安装cuda11.0、cuDNN教程&#xff0c;简单易懂&#xff0c;包教包会 【创作不易&#xff0c;求点赞关注收藏】 文章目录 【linux】服务器ubuntu安装cuda11.0、cuDNN教程&#xff0c;简单易懂&#xff0c;包教包会一、版本情况介绍二、安装cuda1、到官网…

Java面试八股之Redis哨兵机制

Redis哨兵机制 Redis Sentinel&#xff08;哨兵&#xff09;模式是一种高可用解决方案&#xff0c;用于监控和自动故障转移Redis主从集群。以下是对哨兵模式详细过程的描述&#xff1a; 1. 初始化与配置 部署哨兵节点&#xff1a;在不同的服务器上部署一个或多个Redis Sentin…

链表题目专题

19. 删除链表的倒数第 N 个结点 给定一个链表&#xff0c;删除链表的倒数第 n 个节点&#xff0c;并且返回链表的头结点。 非递归解决 这题让删除链表的倒数第n个节点&#xff0c;首先最容易想到的就是先求出链表的长度length&#xff0c;然后就可以找到要删除链表的前一个结…

Hyper-v创建二代虚拟机无法进入bios问题解决

首先要确定从dvd驱动在上面&#xff0c;如果不在则把它向上移动然后保存。 启动虚拟机会进入下面界面 然后点下最左边的按钮然后疯狂点击f2(有的电脑是fnf2) 就可以顺利进入bios引导界面。

手机拯救计划:掌握3个技巧,轻松找回通讯录联系人号码

手机通讯录是我们的“社交地图”&#xff0c;一旦失去联系&#xff0c;就仿佛置身于茫茫人海中&#xff0c;不知所措。而安卓手机用户们&#xff0c;更是对通讯录的依赖达到了前所未有的高度&#xff0c;当发现它们丢失了&#xff0c;很容易产生焦虑情绪。别急&#xff0c;通过…

ARM架构(一)—— ARMV8V9基础概念

目录 1.ARMCore的时间线2.ARM术语小结2.1 A64和arrch642.2ARM架构现在的5个系列2.3 微架构2.4 PE2.5 Banked2.6 ARM文档术语2.7 IMPLEMENTATION DEFINFD 和 DEPRECATED2.8 EL1t和EL1h 3 ARMv7的软件架构4 安全状态切换模型4.1 Secure state和Non-secure state介绍 5 Interproce…

数据类型与结构设计:Rust 语言的深度探索

数据类型与结构设计&#xff1a;Rust 语言的深度探索 引言&#xff1a;数据与结构的精妙交响Rust 数据类型概览&#xff1a;坚实的基础数据类型详解基本数据类型&#xff1a;构建程序的原子单元复合数据类型&#xff1a;构建复杂数据结构的积木与结构体和枚举的结合 结构体与枚…

layui table template、或toolbar实现超出隐藏、更多展示全部效果

使用Layui table时&#xff0c;经常会使用template、或toolbar自定义模版属性。当使用该属性自定义HTML时&#xff0c;layui table 单元格原有的文本超出省略号隐藏功能&#xff0c;在该单元格讲不会生效。 前言&#xff1a;首先我们先搞懂layui超出隐藏原理&#xff0c;table单…

PHP微信小程序视频图文流量主变现小程序系统源码

&#x1f4b0;微信小程序新机遇&#xff01;视频图文流量主变现秘籍&#x1f511; &#x1f680;【流量变现新风口】&#x1f680; 还在为微信小程序的庞大流量如何转化为真金白银而苦恼吗&#xff1f;今天&#xff0c;就带你揭秘“微信小程序视频图文流量主变现小程序”的神…

mysql group_concat()函数、行转列函数

文章目录 一、group_concat函数1.1、语法1.2、示例1.2.1、查询所有姓名&#xff0c;并显示在一行1.2.2、单列合并&#xff0c;指定冒号分隔符1.2.3、单列合并&#xff0c;去重1.2.4、多列拼接合并1.2.5、多列拼接合并&#xff0c;列和列之间指定分隔符 在mysql的关联查询或子查…

十、Java集合 ★ ✔【泛型、通配符、List、Set、TreeSet、自然排序和比较器排序、Collections、可变参数、Map】

day05 泛型,数据结构,List,Set 今日目标 泛型使用 数据结构 List Set 1 泛型 1.1 泛型的介绍 ★ 泛型是一种类型参数&#xff0c;专门用来保存类型用的 最早接触泛型是在ArrayList&#xff0c;这个E就是所谓的泛型了。使用ArrayList时&#xff0c;只要给E指定某一个类型…

【Linux系统】信号的产生

信号 关于信号举一些生活中的例子 --- 比如交通指示灯... - 信号在生活中&#xff0c;随时可以产生 --- 信号的产生和我们是异步的&#xff01;&#xff08;异步的意思就是信号的产生和我没有直接关系&#xff09; - 你能认识这个信号 --- 我们知道这是信号&#xff0c;我们才…

C语言基础and数据结构

C语言程序和程序设计概述 程序:可以连续执行的一条条指令的集合 开发过程:C源程序(.c文件) --> 目标程序(.obj二进制文件,目标文件) --> 可执行文件(.exe文件) -->结果 在任何机器上可以运行C源程序生成的 .exe 文件 没有安装C语言集成开发环境,不能编译C语言程…

AES Android IOS H5 加密方案

前景&#xff1a; 1、本项目原有功能RSA客户端对敏感信息进行加密 2、本次漏洞说是服务端返回值有敏感信息&#xff0c;需要密文返回 方案&#xff1a; 本次方案不算完美&#xff0c;还是有被劫持篡改的风险&#xff0c;但基本https证书认证加持&#xff0c;风险相对较小 …

中介者模式(行为型)

目录 一、前言 二、中介者模式 三、总结 一、前言 中介者模式&#xff08;Mediator Pattern&#xff09;是一种行为型设计模式&#xff0c;又成为调停者模式&#xff0c;用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地互相引用&#xff0c;从而使其耦合…

颈动脉血管壁分割通过领域对齐、拓扑学习和稀疏标注中的Segment Anything模型在磁共振图像中的应用。

Title 题目 Carotid Vessel Wall Segmentation ThroughDomain Aligner, Topological Learning, andSegment Anything Model for Sparse Annotationin MR Images 颈动脉血管壁分割通过领域对齐、拓扑学习和稀疏标注中的Segment Anything模型在磁共振图像中的应用 01 文献速递…