kafka再均衡监听器测试

【README】

  • 本文使用的kafka是最新的 kafka3.0.0;本文kafka集群有3个节点分别是 centos201, centos202, centos203 ; brokerid 分别为 1,2,3;
  • 本文主要用于测试 再均衡监听器;当有新消费者加入时,会发生分区再均衡;再均衡前就会调用再均衡监听器的 onPartitionsRevoked()方法;
  • 本文的测试主题 hello12,有3个分区,每个分区2个副本;


【1】再均衡监听器

1)应用场景 

在消费者退出或进行分区再均衡前,会做一些清理工作,如提交偏移量,或关闭数据库连接;这些工作可以通过监听器来实现;

2)再均衡监听器 ConsumerRebalanceListener

实现 该监听器即可,有3个方法

  1. onPartitionsRevoked:在分区均衡开始【前】和消费者停止读取消息【后】被调用;
  2. onPartitionsAssigned:分区再均衡【后】和消费者开始读取消息【前】被调用 ;
  3. onPartitionsLost:分区宕机时调用(本文不涉及);
/*** @Description 消费者分区再均衡监听器实现类* @author xiao tang* @version 1.0.0* @createTime 2021年12月11日*/
public class ConsumerBalanceListenerImpl implements ConsumerRebalanceListener {/** 消费者 */private Consumer consumer;/** 主题分区偏移量数组  */private MyTopicPartitionOffset[] topicPartitionOffsetArr;/*** @description 构造器* @param consumer 消费者* @param curOffsets 当前偏移量* @author xiao tang* @date 2021/12/11*/public ConsumerBalanceListenerImpl(Consumer consumer, MyTopicPartitionOffset[] topicPartitionOffsetArr) {this.consumer = consumer;this.topicPartitionOffsetArr = topicPartitionOffsetArr;}/** * @description 在分区均衡开始【前】和消费者停止读取消息【后】被调用* @param partitions 分区列表(分区号从0开始计数)* @author xiao tang* @date 2021/12/12 */@Overridepublic void onPartitionsRevoked(Collection<TopicPartition> partitions) {System.out.println("=== 分区再均衡触发onPartitionsRevoked()方法");// 提交偏移量回调(或记录错误日志)OffsetCommitCallback offsetCommitCallback = new OffsetCommitCallbackImpl();// 打印日志Arrays.stream(topicPartitionOffsetArr).filter(x->x.partition()>-1).forEach(x->System.out.printf("提交偏移量信息,partition【%d】offset【%s】\n", x.partition(), x.offset()));// 把数组转为主题分区与偏移量映射,并提交最后一次处理的偏移量 (可以异步,也可以同步)// 同步提交一直重试或报超时异常// 异步提交传入提交回调,失败自行处理consumer.commitAsync(MyConsumerUtils.getTopicPartitionOffsetMetadataMap(topicPartitionOffsetArr), offsetCommitCallback);// 停止程序的原因在于做实验,下次从本次提交的偏移量开始消费throw new RuntimeException("发生分区再均衡,程序结束");}/*** @description 分区再均衡【后】和消费者开始读取消息【前】被调用* @param partitions 主题分区列表* @author xiao tang* @date 2021/12/12*/@Overridepublic void onPartitionsAssigned(Collection<TopicPartition> partitions) {// do sth}@Overridepublic void onPartitionsLost(Collection<TopicPartition> partitions) {ConsumerRebalanceListener.super.onPartitionsLost(partitions);}
}

为了测试,分区再均衡监听器中,onPartitionsRevoked() 方法提交最后已消费消息的偏移量后,就抛出运行时异常结束运行,让其他消费者消费以便查看监听器是否成功提交偏移量;

3)消费者工具  

/*** @Description 消费者工具* @author xiao tang* @version 1.0.0* @createTime 2021年12月12日*/
public enum MyConsumerUtils {/** 单例 */INSTANCE;/*** @description 获取主题分区与偏移量映射* @param topicPartitionOffsetArr 主题分区与偏移量数组* @return 主题分区与偏移量映射* @author xiao tang* @date 2021/12/12*/public static Map<TopicPartition, OffsetAndMetadata> getTopicPartitionOffsetMetadataMap(MyTopicPartitionOffset[] topicPartitionOffsetArr) {// 主题分区与偏移量映射Map<TopicPartition, OffsetAndMetadata> topicPartitionOffsetMetadataMap = new HashMap<>(topicPartitionOffsetArr.length);// 分区号大于-1,才是消费者接收的分区Arrays.stream(topicPartitionOffsetArr).filter(x->x.partition()>-1).forEach(x -> {topicPartitionOffsetMetadataMap.put(new TopicPartition(x.topic(), x.partition()), new OffsetAndMetadata(x.offset(), "no metadata"));});return topicPartitionOffsetMetadataMap;}
}


【2】生产者 

/*** @Description 生产者* @author xiao tang* @version 1.0.0* @createTime 2021年12月03日*/
public class MyProducer {/** 主题 */public final static String TOPIC_NAME = "hello12";public static void main(String[] args) {/* 1.创建kafka生产者的配置信息 */Properties props = new Properties();/*2.指定连接的kafka集群, broker-list */props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.163.201:9092,192.168.163.202:9092,192.168.163.203:9092");/*3.ack应答级别*/props.put(ProducerConfig.ACKS_CONFIG, "all");/*4.重试次数*/props.put(ProducerConfig.RETRIES_CONFIG, 0);/*5.批次大小,一次发送多少数据,当数据大于16k,生产者会发送数据到 kafka集群 */props.put(ProducerConfig.BATCH_SIZE_CONFIG, 16 * KfkNumConst._1K);/*6.等待时间, 等待时间超过1毫秒,即便数据没有大于16k, 也会写数据到kafka集群 */props.put(ProducerConfig.LINGER_MS_CONFIG, 1);// 超时时间props.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG, 3000);props.put(ProducerConfig.MAX_BLOCK_MS_CONFIG, 3000);/*7. RecordAccumulator 缓冲区大小*/props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 32 * KfkNumConst._1M);/*8. key, value 的序列化类 */props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());/** 设置压缩算法 */props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy");/** 设置拦截器 */
//        props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, Arrays.asList(TimeInterceptor.class.getName()));/** 设置阻塞超时时间 */props.put(ProducerConfig.MAX_BLOCK_MS_CONFIG, 3600 * 1000);/* 9.创建生产者对象 */KafkaProducer<String, String> producer = new KafkaProducer<>(props);/* 10.发送数据 */int order = 1;for (int i = 0; i < 10000; i++) {for (int j = 0; j < 3; j++) {Future<RecordMetadata> future = producer.send(new ProducerRecord<String, String>(TOPIC_NAME, j, "", String.format("[%s] ", order++) + " > " + DataFactory.INSTANCE.genChar(5)));try {System.out.println("[生产者] 分区【" + future.get().partition() + "】-offset【" + future.get().offset() + "】");} catch (Exception e) {}}}/* 11.关闭资源 */producer.close();System.out.println("kafka生产者写入数据完成");}
}

 【3】消费者

【3.1】带有均衡监听器的消费者1

/*** @Description 带有均衡监听器的消费者* @author xiao tang* @version 1.0.0* @createTime 2021年12月11日*/
public class MyConsumerWithRebalanceListener {public static void main(String[] args) {// 创建消费者配置信息Properties props = new Properties();// 属性配置props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.163.201:9092,192.168.163.202:9092,192.168.163.203:9092");props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());props.put(ConsumerConfig.GROUP_ID_CONFIG, MyProducer.TOPIC_NAME + "G1");//   关闭自动提交props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);// 设置消费消息的位置,消费最新消息props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");// 设置分区策略 (默认值-RangeAssignor)props.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG, RangeAssignor.class.getName());props.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG, RoundRobinAssignor.class.getName());// 创建消费者KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);int partitionSize = consumer.partitionsFor(MyProducer.TOPIC_NAME).size();// 创建分区偏移量数组并初始化 (仅考虑一个topic的情况)MyTopicPartitionOffset[] topicPartitionOffsetArr = new MyTopicPartitionOffset[partitionSize];IntStream.range(0, partitionSize).forEach(x -> topicPartitionOffsetArr[x] = new MyTopicPartitionOffset());// 订阅主题, 【传入分区再均衡监听器】consumer.subscribe(Arrays.asList(MyProducer.TOPIC_NAME), new ConsumerBalanceListenerImpl(consumer, topicPartitionOffsetArr));// 循环拉取try {while (!Thread.interrupted()) {System.out.println(DateUtils.getNowTimestamp() + " > 带均衡监听器消费者等待消费消息");TimeUtils.sleep(1000);// 消费消息ConsumerRecords<String, String> consumerRds = consumer.poll(100);System.out.println("poll begin {");for (ConsumerRecord<String, String> rd : consumerRds) {System.out.println("消费者-WithRebalanceListener-分区【" + rd.partition() + "】offset【" + rd.offset() + "】" + "值=" + rd.value());// 提交的偏移量,是 当前消息偏移量加1topicPartitionOffsetArr[rd.partition()].setAll(rd.topic(), rd.partition(), rd.offset() + 1);}System.out.println("poll end } ");// 【异步提交每个分区的偏移量】consumer.commitAsync(MyConsumerUtils.getTopicPartitionOffsetMetadataMap(topicPartitionOffsetArr), new OffsetCommitCallbackImpl());}} finally {try {// 【同步提交】 因为错误时,同步提交会一直重试,直到提交成功或发生无法恢复的错误consumer.commitSync(MyConsumerUtils.getTopicPartitionOffsetMetadataMap(topicPartitionOffsetArr));} finally {// 记得关闭消费者consumer.close();}}}
}

【3.2】 不带均衡监听器的消费者2 (测试用)

即一个普通消费者;

/*** @Description 不带均衡监听器的消费者* @author xiao tang* @version 1.0.0* @createTime 2021年12月11日*/
public class MyConsumerNoRebalanceListener {public static void main(String[] args) {// 创建消费者配置信息Properties props = new Properties();// 属性配置props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.163.201:9092,192.168.163.202:9092,192.168.163.203:9092");props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());props.put(ConsumerConfig.GROUP_ID_CONFIG, MyProducer.TOPIC_NAME + "G1");//   关闭自动提交props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);// 设置消费消息的位置,消费最新消息props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");// 设置分区策略 (默认值-RangeAssignor)props.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG, RangeAssignor.class.getName());props.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG, RoundRobinAssignor.class.getName());// 创建消费者KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);// 订阅主题, 【没有分区再均衡监听器】consumer.subscribe(Arrays.asList(MyProducer.TOPIC_NAME));// 循环拉取try {while(!Thread.interrupted()) {System.out.println(DateUtils.getNowTimestamp() + " > 没有均衡监听器的消费者等待消费消息");TimeUtils.sleep(1000);// 消费消息ConsumerRecords<String, String> consumerRds  = consumer.poll(100);for(ConsumerRecord<String, String> rd : consumerRds) {System.out.println("消费者-NoRebalanceListener-分区【" + rd.partition() + "】offset【" + rd.offset() + "】" + "值=" + rd.value());}// 【异步提交】consumer.commitAsync(new OffsetCommitCallbackImpl());if (!consumerRds.isEmpty()) break;}} finally {try {// 【同步提交】 因为错误时,同步提交会一直重试,直到提交成功或发生无法恢复的错误consumer.commitSync();} finally {// 记得关闭消费者consumer.close();System.out.println("消费者关闭");}}}
}

 我们可以发现,一旦消费者2消费了消息(消息不为空),就停止消费;

以便我们查看消费者2接收消息的偏移量是不是 消费者1的监听器在发生分区再均衡前提交的偏移量+1;


【4】测试

【4.1】测试步骤

step1) 运行生产者,发送消息到kafka;

step2) 运行 带有均衡监听器的消费者1 MyConsumerWithRebalanceListener, 消费消息;

在消费者订阅主题时,传入再均衡监听器;

// 订阅主题, 【传入分区再均衡监听器】
consumer.subscribe(Arrays.asList(MyProducer.TOPIC_NAME)
, new ConsumerBalanceListenerImpl(consumer, topicPartitionOffsetArr));

step3)运行 不带均衡监听器的消费者2 MyConsumerNoRebalanceListener,消费消息;

一旦消费者2加入消费者组,就会发生分区再均衡,消费者1的某些分区所有权会转给消费者2,触发消费者1 的 监听器 ConsumerBalanceListenerImpl 的 onPartitionsRevoked() 方法;

  • 然后 onPartitionsRevoked()方法提交 消费者1处理的消息的偏移量后,就原地抛出异常停止运行;

【4.2】测试结果分析

1)消费者1(带分区再均衡监听器)的监听器最后提交的偏移量日志如下:

2021-12-12 10:23:30 > 带均衡监听器消费者等待消费消息
=== 分区再均衡触发onPartitionsRevoked()方法
提交偏移量信息,partition【0】offset【1296】
提交偏移量信息,partition【1】offset【1269】
提交偏移量信息,partition【2】offset【1269】

2)消费者2接收到的起始消息的偏移量日志如下(全部):

2021-12-12 10:23:27 > 没有均衡监听器的消费者等待消费消息
2021-12-12 10:23:32 > 没有均衡监听器的消费者等待消费消息
消费者-NoRebalanceListener-分区【0】offset【1296】值=[589]  > ABCDE
消费者-NoRebalanceListener-分区【0】offset【1297】值=[592]  > ABCDE
消费者-NoRebalanceListener-分区【0】offset【1298】值=[595]  > ABCDE
消费者-NoRebalanceListener-分区【0】offset【1299】值=[598]  > ABCDE
消费者-NoRebalanceListener-分区【0】offset【1300】值=[601]  > ABCDE
消费者-NoRebalanceListener-分区【0】offset【1301】值=[604]  > ABCDE
消费者-NoRebalanceListener-分区【0】offset【1302】值=[607]  > ABCDE
消费者-NoRebalanceListener-分区【0】offset【1303】值=[610]  > ABCDE
消费者-NoRebalanceListener-分区【0】offset【1304】值=[613]  > ABCDE
消费者-NoRebalanceListener-分区【2】offset【1269】值=[510]  > ABCDE
消费者-NoRebalanceListener-分区【2】offset【1270】值=[513]  > ABCDE
消费者-NoRebalanceListener-分区【2】offset【1271】值=[516]  > ABCDE
消费者-NoRebalanceListener-分区【2】offset【1272】值=[519]  > ABCDE
消费者-NoRebalanceListener-分区【2】offset【1273】值=[522]  > ABCDE
消费者-NoRebalanceListener-分区【2】offset【1274】值=[525]  > ABCDE
消费者-NoRebalanceListener-分区【2】offset【1275】值=[528]  > ABCDE
消费者-NoRebalanceListener-分区【2】offset【1276】值=[531]  > ABCDE
消费者-NoRebalanceListener-分区【2】offset【1277】值=[534]  > ABCDE
消费者-NoRebalanceListener-分区【1】offset【1269】值=[509]  > ABCDE
消费者-NoRebalanceListener-分区【1】offset【1270】值=[512]  > ABCDE
消费者-NoRebalanceListener-分区【1】offset【1271】值=[515]  > ABCDE
消费者-NoRebalanceListener-分区【1】offset【1272】值=[518]  > ABCDE
消费者-NoRebalanceListener-分区【1】offset【1273】值=[521]  > ABCDE
消费者-NoRebalanceListener-分区【1】offset【1274】值=[524]  > ABCDE
消费者-NoRebalanceListener-分区【1】offset【1275】值=[527]  > ABCDE
消费者-NoRebalanceListener-分区【1】offset【1276】值=[530]  > ABCDE
消费者-NoRebalanceListener-分区【1】offset【1277】值=[533]  > ABCDE
消费者关闭

即 监听器提交的偏移量为:

partition【0】offset【1296】
partition【1】offset【1269】
partition【2】offset【1269】

而普通消费者接收消息的起始偏移量为

消费者-NoRebalanceListener-分区【0】offset【1296】值=[589]  > ABCDE
消费者-NoRebalanceListener-分区【2】offset【1269】值=[510]  > ABCDE
消费者-NoRebalanceListener-分区【1】offset【1269】值=[509]  > ABCDE

所以,偏移量是可以对上的;即再均衡监听器在发生分区再均衡前提交的消息偏移量后, 其他消费者可以接收该偏移量指定的消息;

所以,本次再均衡监听器测试是成功的


【注意】

注意1)监听器提交的偏移量是接收消息的当前偏移量+1;(注意要加1,非常重要),即加1后的偏移量作为其他消费者轮序消费的起始位置;

  • 代码:偏移量+1参见  MyConsumerWithRebalanceListener 的 接收消息的循环中的代码,如下:
 // 提交的偏移量,是 当前消息偏移量加1
topicPartitionOffsetArr[rd.partition()].setAll(
rd.topic(), rd.partition(), rd.offset() + 1); 

注意2)本文代码参考了 《kafka权威指南》 page63,但书中代码有问题

  • 在每次for循环中创建 TopicPartition对象和 OffsetAndMetadata对象,我觉得这是没有必要的,因为只有每个分区的最后一次消息的 topic,partition,offset,是有用的,但它每次循环都创建了对象,而且把 currentOffsets 放在了while循环外面,这样肯定会造成 oom;(本文仅记录了 topic,partition,offset,而没有创建对象,这是本文的优化点
  • 当然了,原作者写的是参考代码,可以理解;但业务生产代码绝对不能这样写;

【References】

  • kakfa权威指南;

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

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

相关文章

java面试常考系列四

转载自 java面试常考系列四 题目一 大O符号(big-O notation)的作用是什么&#xff1f;有哪些使用方法&#xff1f; 大O符号描述了当数据结构里面的元素增加的时候&#xff0c;算法的规模或者是性能在最坏的场景下有多么好。大O符号也可用来描述其他的行为&#xff0c;比如&…

用python进行自然语言处理_Python自然语言处理示例:SVM和贝叶斯分类

❝关于自然语言处理(NLP)方面的文章、书籍非常之多&#xff0c;对于自然语言处理的上手&#xff0c;很多人是不知所措的。通过对本文的浏览&#xff0c;您应该能够对自然语言处理会有一个能够完整的有趣的基于Python的对自然语言处理的了解。❞什么是文本分类文本分类是将文本按…

Build 2016,你可能忽视的几个细节

微软公司主办的Build 2016大会尚在进程中&#xff0c;但是两场重量级的主题演讲已经结束。下面列举了我个人非常关注的几个细节&#xff0c;介绍一些背景知识以饲读者。 Bash on Windows背后的历史和未来 微软和IBM二十多年前联合开发NT内核的时候就已经为接驳多种操作系统留下…

JAVA面试常考系列五

转载自 JAVA面试常考系列五 题目一 串行(serial)收集器和吞吐量(throughput)收集器的区别是什么&#xff1f;吞吐量收集器使用并行版本的新生代垃圾收集器&#xff0c;它用于中等规模和大规模数据的应用程序。串行收集器对大多数的小应用(在现代处理器上需要大概100M左右的内存…

kafka消费者开发方式小结

【README】 1&#xff0c; 本文总结了 kafka消费者开发方式&#xff1b;2&#xff0c; 本文使用的是最新的kafka版本 3.0.0&#xff1b;【1】 kafka消费则 【1.1】消费者与消费者组 1&#xff09;消费者&#xff1a; 应用程序需要创建消费者对象&#xff0c;订阅主题并开始接…

微软发布Azure Functions、Service Fabric和IoT Starter Kits新服务

微软此次 Build 2016 大会的重点主题一直都围绕开发和 Microsoft Azure 云服务&#xff0c;今天更是对外发布了 Azure Functions、Service Fabric 和 IoT Starter Kit 等一系列新服务。就目前与其它友商的竞争而言&#xff0c;微软近期不断的修炼内功&#xff0c;使 Microsoft …

python发送邮件 退回_python 发送邮件(收到的邮件要有发送方才能回复)

Python使用SMTP(简单邮件传输协议)发送邮件普通文本邮件普通文本邮件发送的实现&#xff0c;关键是要将MIMEText中_subtype设置为plain## -*- coding: UTF-8 -*-import smtplibfrom email.mime.text import MIMEText#导入MIMEText类from email import encodersfrom email.heade…

JAVA面试常考系列六

转载自 JAVA面试常考系列六 题目一一个Applet有哪些生命周期&#xff1f; 一个Applet的生命周期分为以下四个阶段&#xff1a; Init 每次加载时都会初始化一个小程序。此方法通知Applet&#xff0c;方法已经被装入系统&#xff0c;在第一次调用start方法之前总是先调用它。Init…

.NET的未来包含一个开源的Mono

在微软Build 2016大会的第二天&#xff0c;微软项目经理Scott Hunter和Scott Hanselman就.NET平台的现状和未来计划做了一场演讲。演讲的题目是“.NET概述”&#xff0c;他们的精彩演讲耗时一个小时&#xff0c;描绘了公司对于.NET的目标以及开发人员可以期待什么。就像开幕式主…

kafka消费者接收分区测试

【README】 本文演示了当有新消费者加入组后&#xff0c;其他消费者接收分区情况&#xff1b;本文还模拟了 broker 宕机的情况&#xff1b;本文使用的是最新的 kafka3.0.0 &#xff1b;本文测试案例&#xff0c;来源于 消费者接收分区的5种模型&#xff0c;建议先看模型&#…

python数据分析架构_Python数据分析

引言&#xff1a;本文重点是用十分钟的时间帮读者建立Python数据分析的逻辑框架。其次&#xff0c;讲解“如何通过Python 函数或代码和统计学知识来实现数据分析”。本次介绍的建模框架图分为六大版块&#xff0c;依次为导入数据&#xff0c;数据探索&#xff0c;数据处理&…

JAVA面试常考系列七

转载自 JAVA面试常考系列七 题目一 Swing的方法中&#xff0c;有哪些是线程安全的&#xff1f; Swing的规则是&#xff1a;当Swing组件被具现化时&#xff0c;所有可能影响或依赖于组件状态的代码都应该在事件派发线程中执行。 因此有3个线程安全的方法&#xff1a; repaint()…

图片中的Build 2016

微软主办的Build 2016大会刚刚落幕&#xff0c;让我们通过下面的图片集锦来回顾大会的一些容易被人忽视的细节。 Xamarin加入微软大家庭 微软公司于二月底花大价钱买下了Xamarin这家移动开发平台提供商&#xff0c;终于补全了它Mobile First Cloud First战略的短板。 图片一&am…

diy实现spring依赖注入

【README】 本文diy代码实现了 spring 依赖注入&#xff0c;一定程度上揭示了依赖注入原理&#xff1b; 【1】控制反转-Inversion of Control 是一种编码思想&#xff0c;简而言之就是 应用程序A可以使用组件B&#xff0c;但A无法控制B的生命周期&#xff08;如创建&#xff…

html 中一个格子拆分成两个_一个效果惊人的数字游戏

安爸曾多次讲过数学推理能力对孩子成长的重要性&#xff0c;听到有位家长说自己用扔骰子的方法教孩子数学等式。步骤大致是扔骰子时&#xff0c;如果骰子是3&#xff0c;就在棋盘上从0出发走3步&#xff0c;并且写出033的加法等式。扔到负数就后退&#xff0c;写出减法等式。科…

JAVA面试常考系列八

转载自 JAVA面试常考系列八 题目一 JDBC是什么&#xff1f; JDBC&#xff08;Java DataBase Connectivity,java数据库连接&#xff09;是一种用于执行SQL语句的Java API&#xff0c;可以为多种关系数据库提供统一访问&#xff0c;由一组用Java语言编写的类和接口组成。JDBC提供…

【广州/深圳 活动】 MVP社区巡讲

紧跟当今的技术发展趋势还远远不够&#xff0c;我们要引领变革&#xff01;加入本地技术专家社区&#xff0c;获取真实案例、实况培训演示以及探讨新一代解决方案。在此活动中&#xff0c;您将&#xff1a; 了解如何运用开源&#xff08;OSS&#xff09;技术、Microsoft 技术及…

java socket实现简单即时通讯

【1】socket服务器 /*** Description 即时消息服务器* author xiao tang* version 1.0.0* createTime 2022年01月23日*/ public class IMSocketServer {private static int PORT 13;public static void main(String[] args) {ServerSocket server null;try {// 开启端口serv…

蝌蚪网课助手mac_疫情期间如何录网课?(干货教程)手把手教你录出高质量网课。...

鉴于国外疫情的持续爆发&#xff0c;中小学开学日期进一步延期&#xff0c;我们的网课教学也同样面临持续后延。我们的很多教师朋友&#xff0c;可能此时他们正需要这么一个教程来熟悉网课的录制方法。于是这篇文章就应运而生了&#xff0c;希望它能给各位老师带来些许帮助。​…

JAVA面试常考系列九

转载自 JAVA面试常考系列九 题目一 RMI架构层的结构是如何组成的&#xff1f; RMI体系结构由三层组成&#xff0c;分别是&#xff1a; 存根和骨架层&#xff08;Stub and Skeleton Layer&#xff09; 远程引用层&#xff08;Remote Reference Layer&#xff09; 传输层&#xf…