springboot kafka多数据源,通过配置动态加载发送者和消费者

前言

最近做项目,需要支持kafka多数据源,实际上我们也可以通过代码固定写死多套kafka集群逻辑,但是如果需要不修改代码扩展呢,因为kafka本身不处理额外逻辑,只是起到削峰,和数据的传递,那么就需要对架构做一定的设计了。

准备test

kafka本身非常容易上手,如果我们需要单元测试,引入jar依赖,JDK使用1.8,当然也可以使用JDK17

    <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.7.17</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><version>2.7.17</version><scope>test</scope></dependency><dependency><groupId>org.springframework.kafka</groupId><artifactId>spring-kafka</artifactId><version>2.9.13</version></dependency><dependency><groupId>org.springframework.kafka</groupId><artifactId>spring-kafka-test</artifactId><version>2.9.13</version><scope>test</scope></dependency><!-- https://mvnrepository.com/artifact/org.testcontainers/kafka --><dependency><groupId>org.testcontainers</groupId><artifactId>kafka</artifactId><version>1.20.1</version><scope>test</scope></dependency></dependencies>

修改发送者和接收者

@Component
public class KafkaProducer {private static final Logger LOGGER = LoggerFactory.getLogger(KafkaProducer.class);@Autowiredprivate KafkaTemplate<String, String> kafkaTemplate;public void send(String topic, String payload) {LOGGER.info("sending payload='{}' to topic='{}'", payload, topic);kafkaTemplate.send(topic, payload);}
}@Component
public class KafkaConsumer {private static final Logger LOGGER = LoggerFactory.getLogger(KafkaConsumer.class);private String payload;@KafkaListener(topics = "${test.topic}")public void receive(ConsumerRecord<?, ?> consumerRecord) {LOGGER.info("----------------received payload='{}'", consumerRecord.toString());payload = consumerRecord.toString();}public String getPayload() {return payload;}public void setPayload(String payload) {this.payload = payload;}
}

然后写main方法,随意写一个即可,配置入戏

spring:kafka:consumer:auto-offset-reset: earliestgroup-id: mytest
test:topic: embedded-test-topic

写一个单元测试

@SpringBootTest
@EmbeddedKafka(partitions = 1, brokerProperties = { "listeners=PLAINTEXT://localhost:9092", "port=9092" })
class DemoMainTest {@Autowiredprivate KafkaConsumer consumer;@Autowiredprivate KafkaProducer producer;@Value("${test.topic}")private String topic;@Testvoid embedKafka() throws InterruptedException {String data = "Sending with our own simple KafkaProducer";producer.send(topic, data);Thread.sleep(3000);assertThat(consumer.getPayload(), containsString(data));Thread.sleep(10000);}
}

通过

@EmbeddedKafka(partitions = 1, brokerProperties = { "listeners=PLAINTEXT://localhost:9092", "port=9092" })

直接模拟一个kafka,里面有一些注解参数,可以设置broker的 数量端口,zk的端口,topic和partition数量等

实际上是通过embed zk和kafka来mock了一个kafka server

单元测试运行成功

思路

有了kafka单元测试后,根据springboot map可以接收多套配置的方式不就实现了kafka的多数据源的能力,貌似非常简单;但是如果需要不用修改代码,消费端怎么办,发送者可以手动创建,消费端是注解方式,topic等信息在注解参数中,注解参数值却是常量,代码写死的,那么我们就需要:

  1. 不让Springboot自动扫描,根据配置手动扫描注册bean
  2. 字节码生成bean,就可以根据参数

这里没考虑把消费端和发送者的额外处理逻辑写在这里的做法,统一处理kafka,类似kafka网关,因为kafka一般不会仅一套,且不会仅有一个topic,需要分发处理,比如slb,feign等。

kafka消费者的原理 

其实kafka发送者和消费者也是类似逻辑,但是spring-kafka通过注解方式实现消费者,如果我们使用原生kafka的kafkaconsumer,那么只需要通过Map接收参数,然后自己实现消费逻辑就行,但是spring-kafka毕竟做了很多公共没必要的逻辑,拉取消费的一系列参数,线程池管理等处理措施。看看Spring-kafka的消费者初始化原理,

BeanPostProcessor的kafka实现

org.springframework.kafka.annotation.KafkaListenerAnnotationBeanPostProcessor

看前置处理

什么都没做,所以,所有逻辑都在后置处理

public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {if (!this.nonAnnotatedClasses.contains(bean.getClass())) {Class<?> targetClass = AopUtils.getTargetClass(bean);//找到注解,消费注解KafkaListener打在类上,一般不用这种方式Collection<KafkaListener> classLevelListeners = findListenerAnnotations(targetClass);//类上KafkaListener注解的标志final boolean hasClassLevelListeners = classLevelListeners.size() > 0;final List<Method> multiMethods = new ArrayList<>();//找到消费方法,去每个方法上找KafkaListener注解Map<Method, Set<KafkaListener>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,(MethodIntrospector.MetadataLookup<Set<KafkaListener>>) method -> {Set<KafkaListener> listenerMethods = findListenerAnnotations(method);return (!listenerMethods.isEmpty() ? listenerMethods : null);});if (hasClassLevelListeners) {//类上KafkaListener注解的时候,通过另外的注解KafkaHandler的方式,找到消费方法Set<Method> methodsWithHandler = MethodIntrospector.selectMethods(targetClass,(ReflectionUtils.MethodFilter) method ->AnnotationUtils.findAnnotation(method, KafkaHandler.class) != null);multiMethods.addAll(methodsWithHandler);}//实际上大部分类是没有kafka消费注解的,效率并不高,但是因为日志是trace,所以日志一般默认看不见//注解KafkaListener打在方法上的时候if (annotatedMethods.isEmpty() && !hasClassLevelListeners) {this.nonAnnotatedClasses.add(bean.getClass());this.logger.trace(() -> "No @KafkaListener annotations found on bean type: " + bean.getClass());}else {// Non-empty set of methodsfor (Map.Entry<Method, Set<KafkaListener>> entry : annotatedMethods.entrySet()) {Method method = entry.getKey();for (KafkaListener listener : entry.getValue()) {//核心逻辑processKafkaListener(listener, method, bean, beanName);}}this.logger.debug(() -> annotatedMethods.size() + " @KafkaListener methods processed on bean '"+ beanName + "': " + annotatedMethods);}//注解KafkaListener打在类上,实际上处理逻辑跟KafkaListener打在方法上差不多if (hasClassLevelListeners) {processMultiMethodListeners(classLevelListeners, multiMethods, bean, beanName);}}return bean;}

如果是注解打在类上,如下

 

本文中的示例的@KafkaListener打在方法上,所以分析

processKafkaListener 

其实原理都一样,spring-kafka不会写2份一样逻辑,只是读取处理的参数略有不同

protected synchronized void processKafkaListener(KafkaListener kafkaListener, Method method, Object bean,String beanName) {//检查代理Method methodToUse = checkProxy(method, bean);//终端设计思想,Spring很多地方都这样设计,尤其是swaggerMethodKafkaListenerEndpoint<K, V> endpoint = new MethodKafkaListenerEndpoint<>();endpoint.setMethod(methodToUse);//bean的名称,这里需要定制全局唯一,否则多个listener会冲突String beanRef = kafkaListener.beanRef();this.listenerScope.addListener(beanRef, bean);String[] topics = resolveTopics(kafkaListener);TopicPartitionOffset[] tps = resolveTopicPartitions(kafkaListener);if (!processMainAndRetryListeners(kafkaListener, bean, beanName, methodToUse, endpoint, topics, tps)) {//核心逻辑processListener(endpoint, kafkaListener, bean, beanName, topics, tps);}this.listenerScope.removeListener(beanRef);}

继续

processListener
protected void processListener(MethodKafkaListenerEndpoint<?, ?> endpoint, KafkaListener kafkaListener,Object bean, String beanName, String[] topics, TopicPartitionOffset[] tps) {//MethodKafkaListenerEndpoint赋值了,这个很关键processKafkaListenerAnnotation(endpoint, kafkaListener, bean, topics, tps);//容器工厂String containerFactory = resolve(kafkaListener.containerFactory());KafkaListenerContainerFactory<?> listenerContainerFactory = resolveContainerFactory(kafkaListener,containerFactory, beanName);//注册终端,最终生效this.registrar.registerEndpoint(endpoint, listenerContainerFactory);}

processKafkaListenerAnnotation

private void processKafkaListenerAnnotation(MethodKafkaListenerEndpoint<?, ?> endpoint,KafkaListener kafkaListener, Object bean, String[] topics, TopicPartitionOffset[] tps) {endpoint.setBean(bean);endpoint.setMessageHandlerMethodFactory(this.messageHandlerMethodFactory);endpoint.setId(getEndpointId(kafkaListener));endpoint.setGroupId(getEndpointGroupId(kafkaListener, endpoint.getId()));endpoint.setTopicPartitions(tps);endpoint.setTopics(topics);endpoint.setTopicPattern(resolvePattern(kafkaListener));endpoint.setClientIdPrefix(resolveExpressionAsString(kafkaListener.clientIdPrefix(), "clientIdPrefix"));endpoint.setListenerInfo(resolveExpressionAsBytes(kafkaListener.info(), "info"));String group = kafkaListener.containerGroup();if (StringUtils.hasText(group)) {Object resolvedGroup = resolveExpression(group);if (resolvedGroup instanceof String) {endpoint.setGroup((String) resolvedGroup);}}String concurrency = kafkaListener.concurrency();if (StringUtils.hasText(concurrency)) {endpoint.setConcurrency(resolveExpressionAsInteger(concurrency, "concurrency"));}String autoStartup = kafkaListener.autoStartup();if (StringUtils.hasText(autoStartup)) {endpoint.setAutoStartup(resolveExpressionAsBoolean(autoStartup, "autoStartup"));}resolveKafkaProperties(endpoint, kafkaListener.properties());endpoint.setSplitIterables(kafkaListener.splitIterables());if (StringUtils.hasText(kafkaListener.batch())) {endpoint.setBatchListener(Boolean.parseBoolean(kafkaListener.batch()));}endpoint.setBeanFactory(this.beanFactory);resolveErrorHandler(endpoint, kafkaListener);resolveContentTypeConverter(endpoint, kafkaListener);resolveFilter(endpoint, kafkaListener);}

各种参数注册,尤其是其中的ID和handler是必须的,不注册不行;笔者试着自己设置endpoint,发现其中的各种handler注册。 

解决方式

先写一个工具类,用于创建一些关键类的bean,定义了发送者创建,消费者工厂类,消费者的创建由注解扫描实现,引用工具类的消费者容器工厂bean。

public class KafkaConfigUtil {private DefaultKafkaProducerFactory<String, String> initProducerFactory(KafkaProperties kafkaProperties) {return new DefaultKafkaProducerFactory<>(kafkaProperties.buildProducerProperties());}public KafkaTemplate<String, String> initKafkaTemplate(KafkaProperties kafkaProperties) {return new KafkaTemplate<>(initProducerFactory(kafkaProperties));}private ConsumerFactory<? super Integer, ? super String> initConsumerFactory(KafkaProperties kafkaProperties) {return new DefaultKafkaConsumerFactory<>(kafkaProperties.buildConsumerProperties());}public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<Integer, String>>initKafkaListenerContainerFactory(KafkaProperties kafkaProperties) {ConcurrentKafkaListenerContainerFactory<Integer, String> factory =new ConcurrentKafkaListenerContainerFactory<>();factory.setConsumerFactory(initConsumerFactory(kafkaProperties));return factory;}
}

1、通过Map接收多数据源

定义一个配置接收器,仿造zuul的模式 


@ConfigurationProperties(prefix = "spring.kafka")
public class KafkaMultiProperties {private Map<String, KafkaProperties> routes;public Map<String, KafkaProperties> getRoutes() {return routes;}public void setRoutes(Map<String, KafkaProperties> routes) {this.routes = routes;}
}

每一个route其实就说一套kafka,再写一个Configuration,注入配置文件

@Configuration
@EnableConfigurationProperties(KafkaMultiProperties.class)
public class KafkaConfiguration {}

这样就可以注入配置了,从此可以根据配置的不同初始化不同的kafka集群逻辑。 这样就可以把自定义的Properties注入Springboot的placeholder中。

2、通过自定义扫描支持消费者

如果消费者或者发送者逻辑需要写在当前kafka网关应用,那么只能通过自定义扫描方式支持配置不同,所有配置的生成者和消费者必须代码实现逻辑,通过配置加载方式,自定义扫描注入bean即可。以消费者为例,生产者不涉及注解发送方式相对简单。

public class KafkaConfigInit {private KafkaMultiProperties kafkaMultiProperties;private ConfigurableApplicationContext applicationContext;public KafkaConfigInit(KafkaMultiProperties kafkaMultiProperties,ConfigurableApplicationContext applicationContext) {this.kafkaMultiProperties = kafkaMultiProperties;this.applicationContext = applicationContext;}@PostConstructpublic void initConfig() {if (kafkaMultiProperties == null || kafkaMultiProperties.getRoutes() == null) return;kafkaMultiProperties.getRoutes().forEach((k, v) -> {//register producer by configConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();beanFactory.registerSingleton(k + "_producer", KafkaConfigUtil.initKafkaTemplate(v));//register consumer container factoryKafkaListenerContainerFactory<ConcurrentMessageListenerContainer<Integer, String>> kafkaListenerContainerFactory = KafkaConfigUtil.initKafkaListenerContainerFactory(v);beanFactory.registerSingleton(k + "_consumerFactory", kafkaListenerContainerFactory);});}
}

写了一个初始化的bean,用于通过配置加载bean。但是有2个问题:

  1. 消费者是注解方式扫描,bean需要根据配置加载,不能写在代码里面
  2. 这里仅仅是注册bean,并不会被beanpostprocessor处理

关于第1点

因为需要按照配置加载,不能代码写bean的加载逻辑,只能自己扫描按照配置加载,那么需要自定义扫描注解和扫描包名(减少扫描范围,提高效率)

关于第2点

需要手动执行beanpostprocessor的逻辑即可

show me the code

完善刚刚写的部分代码:

写一个注解

@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface KafkaConfigConsumer {String beanId() default "";
}

通过beanId区分,配置文件的key+"_consumer"可以作为唯一标识,定义一种标准

可以使用Spring的

PathMatchingResourcePatternResolver

自己解析resources信息,来拿到写的自定义注解的类,然后生成对象,注入Spring

public class KafkaConfigInit {private KafkaMultiProperties kafkaMultiProperties;private ConfigurableApplicationContext applicationContext;private KafkaListenerAnnotationBeanPostProcessor<?,?> kafkaListenerAnnotationBeanPostProcessor;private static final Map<String, Object> consumerMap = new ConcurrentHashMap<>();public KafkaConfigInit(KafkaMultiProperties kafkaMultiProperties, ConfigurableApplicationContext applicationContext, KafkaListenerAnnotationBeanPostProcessor<?, ?> kafkaListenerAnnotationBeanPostProcessor) {this.kafkaMultiProperties = kafkaMultiProperties;this.applicationContext = applicationContext;this.kafkaListenerAnnotationBeanPostProcessor = kafkaListenerAnnotationBeanPostProcessor;}@PostConstructpublic void initConfig() throws IOException {scanConsumer();if (kafkaMultiProperties == null || kafkaMultiProperties.getRoutes() == null) return;kafkaMultiProperties.getRoutes().forEach((k, v) -> {//register producer by configConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();beanFactory.registerSingleton(k + "_producer", KafkaConfigUtil.initKafkaTemplate(v));//register consumer container factoryKafkaListenerContainerFactory<ConcurrentMessageListenerContainer<Integer, String>> kafkaListenerContainerFactory = KafkaConfigUtil.initKafkaListenerContainerFactory(v);beanFactory.registerSingleton(k + "_containerFactory", kafkaListenerContainerFactory);beanFactory.registerSingleton(k+"_consumer", consumerMap.get(k+"_consumer"));kafkaListenerAnnotationBeanPostProcessor.postProcessAfterInitialization(consumerMap.get(k+"_consumer"), k+"_consumer");});}private void scanConsumer() throws IOException {SimpleMetadataReaderFactory register = new SimpleMetadataReaderFactory();PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();Resource[] resources = resolver.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + "com/feng/kafka/demo/init/*");Arrays.stream(resources).forEach((resource)->{try {MetadataReader metadataReader = register.getMetadataReader(resource);if (metadataReader.getAnnotationMetadata().hasAnnotatedMethods("org.springframework.kafka.annotation.KafkaListener")){String className = metadataReader.getClassMetadata().getClassName();Class<?> clazz = Class.forName(className);KafkaConfigConsumer kafkaConfigConsumer = clazz.getDeclaredAnnotation(KafkaConfigConsumer.class);Object obj = clazz.newInstance();consumerMap.put(kafkaConfigConsumer.beanId(), obj);}} catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException e) {throw new RuntimeException(e);}});}}

同时,需要手动执行 

kafkaListenerAnnotationBeanPostProcessor

的逻辑,上面有源码分析,而且因为要支持多数据源,所以需要修改消费者的注解参数

//@KafkaListener(topics = "${test.topic}")
//@Component
@KafkaConfigConsumer(beanId = "xxx_consumer")
public class KafkaConsumer {private static final Logger LOGGER = LoggerFactory.getLogger(KafkaConsumer.class);private String payload;//    @KafkaHandler@KafkaListener(topics = "${test.topic}", beanRef = "xxx_listener", containerFactory = "xxx_containerFactory")public void receive(ConsumerRecord<?, ?> consumerRecord) {LOGGER.info("----------------received payload='{}'", consumerRecord.toString());payload = consumerRecord.toString();}// other getterspublic String getPayload() {return payload;}public void setPayload(String payload) {this.payload = payload;}
}

增加beanRef属性外加我们自己写的注解,然后通过@Configuration注入

@Configuration
@EnableConfigurationProperties(KafkaMultiProperties.class)
public class KafkaConfiguration {@Beanpublic KafkaConfigInit initKafka(KafkaMultiProperties kafkaMultiProperties,ConfigurableApplicationContext applicationContext,KafkaListenerAnnotationBeanPostProcessor<?, ?> kafkaListenerAnnotationBeanPostProcessor){return new KafkaConfigInit(kafkaMultiProperties, applicationContext, kafkaListenerAnnotationBeanPostProcessor);}
}

然后修改配置文件和单元测试类

spring:kafka:routes:xxx:producer:batchSize: 1consumer:auto-offset-reset: earliestgroup-id: xxx

然后修改单元测试代码

@SpringBootTest
@EmbeddedKafka(partitions = 1, brokerProperties = { "listeners=PLAINTEXT://localhost:9092", "port=9092" })
class DemoMainTest {@Lazy@Autowiredprivate KafkaConsumer consumer;@Autowiredprivate ApplicationContext applicationContext;@Value("${test.topic}")private String topic;@Testvoid embedKafka() throws InterruptedException {String data = "Sending with our own simple KafkaProducer";applicationContext.getBean("xxx_producer", KafkaTemplate.class).send(topic, data);Thread.sleep(3000);assertThat(consumer.getPayload(), containsString(data));Thread.sleep(10000);}
}

执行单元测试成功

 

数据正确发送消费,断言正常 

3、通过字节码生成支持消费者

上面的方式觉得还是不方便,一般而言处理消息和消费消息是异步的,即使是同步也不会在消费线程直接处理,一般是发送到其他地方接口处理,所以为啥还要写消费者代码呢,默认一个不就好了,但是注解参数确是常量,那么字节码生成一个唯一的类即可。

如果生成者和消费者处理逻辑不用网关应用处理,那么仅仅是无脑转发,类似zuul,可以通过字节码生成方式实现统一逻辑,主要是消费者,毕竟有注解,生产者不存在注解可以直接new出来注入bean。

以javassist为例,简单些,当然asm也可以

show me the code

其实就说把扫描的消费者类,变成固定某个类消费

//@KafkaListener(topics = "${test.topic}")
//@Component
//@KafkaConfigConsumer(beanId = "xxx_consumer")
public class KafkaConsumer {private static final Logger LOGGER = LoggerFactory.getLogger(KafkaConsumer.class);private String payload;//    @KafkaHandler
//    @KafkaListener(topics = "${test.topic}", beanRef = "xxx_listener", containerFactory = "xxx_containerFactory")public void receive(ConsumerRecord<?, ?> consumerRecord) {LOGGER.info("----------------received payload='{}'", consumerRecord.toString());payload = consumerRecord.toString();}

去掉注解,因为注解需要我们动态加上去,下一步修改bean创建流程

    @PostConstructpublic void initConfig() throws IOException {
//        scanConsumer();if (kafkaMultiProperties == null || kafkaMultiProperties.getRoutes() == null) return;kafkaMultiProperties.getRoutes().forEach((k, v) -> {//register producer by configConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();beanFactory.registerSingleton(k + "_producer", KafkaConfigUtil.initKafkaTemplate(v));//register consumer container factoryKafkaListenerContainerFactory<ConcurrentMessageListenerContainer<Integer, String>> kafkaListenerContainerFactory = KafkaConfigUtil.initKafkaListenerContainerFactory(v);beanFactory.registerSingleton(k + "_containerFactory", kafkaListenerContainerFactory);//            beanFactory.registerSingleton(k + "_consumer", consumerMap.get(k + "_consumer"));Object obj = initConsumerBean(k);beanFactory.registerSingleton(k + "_consumer", obj);kafkaListenerAnnotationBeanPostProcessor.postProcessAfterInitialization(obj, k + "_consumer");});}private Object initConsumerBean(String key) {try {ClassPool pool = ClassPool.getDefault();CtClass ct = pool.getCtClass("com.feng.kafka.demo.init.KafkaConsumer");//修改类名,避免重复ct.setName("com.feng.kafka.demo.init.KafkaConsumer"+key);//获取类中的方法CtMethod ctMethod = ct.getDeclaredMethod("receive");MethodInfo methodInfo = ctMethod.getMethodInfo();ConstPool cp = methodInfo.getConstPool();//获取注解属性AnnotationsAttribute attribute = new AnnotationsAttribute(cp, AnnotationsAttribute.visibleTag);Annotation annotation = new Annotation("org.springframework.kafka.annotation.KafkaListener", cp);ArrayMemberValue arrayMemberValue = new ArrayMemberValue(cp);arrayMemberValue.setValue(new MemberValue[]{new StringMemberValue("embedded-test-topic", cp)});annotation.addMemberValue("topics", arrayMemberValue);annotation.addMemberValue("beanRef", new StringMemberValue(key+"_listener", cp));annotation.addMemberValue("containerFactory", new StringMemberValue(key+"_containerFactory", cp));attribute.addAnnotation(annotation);methodInfo.addAttribute(attribute);byte[] bytes = ct.toBytecode();Class<?> clazz = ReflectUtils.defineClass("com.feng.kafka.demo.init.KafkaConsumer" + key, bytes, Thread.currentThread().getContextClassLoader());return clazz.newInstance();} catch (Exception e) {throw new RuntimeException(e);}}

通过字节码生成和动态加载class方式,生成唯一的对象,实现通过配置方式支持多数据源,不需要写一句消费代码。

单元测试

去掉了断言,因为类是动态变化的了。 

总结

实际上spring-kafka已经非常完善了,spring-kafka插件的支持也很完善,不需要关注kafka的消费过程,只需要配置即可,但是也为灵活性埋下了隐患,当然一般而言我们基本上用不到多kafka的情况,也不会做一个kafka网关应用,不过当业务需要的时候,可以设计一套kafka网关应用,分发kafka的消息,起到一个流量网关的能力,解耦业务的应用,实现架构的松耦合。

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

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

相关文章

Koa学习

Koa 安装与配置 1. 初始化项目 在终端中执行以下命令&#xff1a; # 创建项目文件夹 mkdir koa cd koa# 初始化并安装依赖 npm init -y npm install koa npm install nodemon --save-dev2. 修改 package.json 在 package.json 文件中进行如下修改&#xff1a; {"type…

llava论文阅读

论文名称是 Visual Instruction Tuning 视觉指令微调 摘要 我们首次尝试仅使用语言模型GPT-4来生成多模态的语言-图像指令跟随数据。 通过在生成的数据上进行指令微调&#xff0c;我们引入了LLaVA&#xff08;Large Language and Vision Assistant&#xff09;&#xff1a;一…

c++基础知识复习(1)

前期知识准备 1 构造函数 &#xff08;1&#xff09;默认构造函数&#xff1a;没有参数传入&#xff0c;也没有在类里面声明 &#xff08;2&#xff09;手动定义默认构造函数&#xff1a;没有参数传入&#xff0c;但是在类里面进行了声明 可以在类外实现或者类内实现 以下案…

计算机网络803-(4)网络层

目录 1.虚电路服务 虚电路是逻辑连接 2.数据报服务 3.虚电路服务与数据报服务的对比 二.虚拟互连网络-IP网 1.网络通信问题 2.中间设备 3.网络互连使用路由器 三.分类的 IP 地址 1. IP 地址及其表示方法 2.IP 地址的编址方法 3.分类 IP 地址 &#xff08;1&#x…

LabVIEW中的非阻塞定时器

在LabVIEW编程中&#xff0c;通常需要在某些任务执行过程中进行非阻塞的延时操作。例如&#xff0c;显示某条信息一段时间&#xff0c;同时继续执行其他任务&#xff0c;并在延时时间结束后停止显示该信息。这类需求通常用于处理优先级不同的信息显示&#xff0c;如错误信息需要…

【Arduino IDE安装】Arduino IDE的简介和安装详情

目录 &#x1f31e;1. Arduino IDE概述 &#x1f31e;2. Arduino IDE安装详情 &#x1f30d;2.1 获取安装包 &#x1f30d;2.2 安装详情 &#x1f30d;2.3 配置中文 &#x1f30d;2.4 其他配置 &#x1f31e;1. Arduino IDE概述 Arduino IDE&#xff08;Integrated Deve…

Jupyter的使用分享

文章目录 碎碎念安装方法1.安装Anaconda方法2.通过库的安装方式 启动使用教程1.指定目录打开2.启动后的简单使用 小结 碎碎念 前情提示 之前与许多小伙伴交流的时候&#xff0c;发现大家对于pycharm更容易上手&#xff08;可能是比较好设置中文的原因&#xff09;&#xff0c;在…

算法: 前缀和题目练习

文章目录 前缀和题目练习前缀和二维前缀和寻找数组的中心下标除自身以外数组的乘积和为 K 的子数组和可被 K 整除的子数组连续数组矩阵区域和 前缀和题目练习 前缀和 自己写出来了~ 坑: 数据太大,要用long. import java.util.Scanner;public class Main {public static voi…

【element-tiptap】如何引进系统中的字体?

源码地址&#xff1a; https://github.com/Leecason/element-tiptap 源码中给出的字体如下 可以看到&#xff0c;咱们日常需要的黑体、微软雅黑等都没有&#xff0c;所以这篇文章来探索一下怎么加字体。 另外呢&#xff0c;肯定有小伙伴发现&#xff0c;这个按钮点击的时候&am…

IDEA 配置 Git 详解

本文将介绍在IntelliJ IDEA 中如何配置Git 没有安装配置 Git 的可以参考我的这篇文章&#xff1a;安装配置 Git 一、操作环境及准备 1.win 10 2.已安装且配置了Git 3.有Gitee账户 4.安装了IntelliJ IDEA 2023.2.1 5.全程联网 二、配置步骤 2.1 配置git 1.采用全局设置&…

C++继承与菱形继承(一文了解全部继承相关基础知识和面试点!)

目的减少重复代码冗余 Class 子类(派生类) &#xff1a; 继承方式 父类&#xff08;基类&#xff09; 继承方式共有三种&#xff1a;公共、保护、私有 父类的私有成员private无论哪种继承方式都不可以被子类使用 保护protected权限的内容在类内是可以访问&#xff0c;但是在…

息肉检测数据集 yolov5 yolov8适用于目标检测训练已经调整为yolo格式可直接训练yolo网络

息肉检测数据集 yolov5 yolov8格式 息肉检测数据集介绍 数据集概述 名称&#xff1a;息肉检测数据集&#xff08;基于某公开的分割数据集调整&#xff09;用途&#xff1a;适用于目标检测任务&#xff0c;特别是内窥镜图像中的息肉检测格式&#xff1a;YOLO格式&#xff08;边…

【3dgs】总结3DGS与NeRF如何重塑SLAM24年4月最新进展

【3dgs】总结3DGS与NeRF如何重塑SLAM&#xff01; 1. 摘要2. 简洁3. 背景3.1 Existing SLAM Surveys3.2 progress in Radiance Field Theory3.3.1 NeRF3.3.2 3dgs3.4 数据集 4 数据集4.1 SLAM3.1 RGB-D SLAM方法3.1.1 基于NeRF风格的RGB-D SLAM3.1.2 基于3DGS风格的 RGB-D SLAM…

React(一) 认识React、熟悉类组件、JSX书写规范、嵌入变量表达式、绑定属性

文章目录 一、初始React1. React的基本认识2. Hello案例2.1 三个依赖2.2 渲染页面2.3 hello案例完整代码 二、类组件1. 封装类组件2. 组件里的数据3. 组件里的函数 (重点)4. 案例练习(1) 展示电影列表 三、JSX语法1. 认识JSX2. JSX书写规范及注释3. JSX嵌入变量作为子元素4. JS…

遍历有向图链路(DFS算法)- 优化版

在上一节基础上&#xff0c;去除了节点的pre集合&#xff0c;只保留节点next的结合&#xff0c;对数据模型进行了优化&#xff0c;实现思想做了优化。 有向图示例&#xff1a; 基本思路 构建有向图数据模型校验有向图不能出现回路&#xff0c;即当前节点不能出现在历史链路中首…

连续点击三次用户

有用户点击日志记录表 t2_click_log&#xff0c;包含user_id(用户ID),click_time(点击时间)&#xff0c;请查询出连续点击三次的用户数&#xff0c; 连续点击三次&#xff1a;指点击记录中同一用户连续点击&#xff0c;中间无其他用户点击&#xff1b; CREATE TABLE t2_click…

Unity实现自定义图集(三)

以下内容是根据Unity 2020.1.0f1版本进行编写的   1、实现编辑器模式下进游戏前Pack全部自定义图集 同Unity的图集一样,Unity的编辑器模式会在进游戏前把全部的SpriteAtlas都打一次图集,如图: 我们也实现这样的效果。 首先需要获取全部的图集路径。因为目前使用的是以.…

【数据结构】6道经典链表面试题

目录 1.返回倒数第K个节点【链接】 ​代码实现 2.链表的回文结构【链接】 代码实现 3.相交链表【链接】 代码实现 4.判断链表中是否有环【链接】 代码实现 常见问题解析 5.寻找环的入口点【链接】 代码实现1 ​代码实现2 6.随机链表的复制【链接】 代码实现 1.…

如何进行数据中心负载测试的自动化?

数据中心负载测试的自动化是一种通过使用软件工具和脚本来模拟大量用户访问数据中心的过程&#xff0c;以评估其性能、稳定性和可扩展性的方法。以下是进行数据中心负载测试自动化的一些建议&#xff1a; 市场上有许多负载测试工具可供选择&#xff0c;如LoadRunner、JMeter、…

字节跳动青训营开始报名了!

关于青训营&#xff1a; 青训营是字节跳动技术团队发起的技术系列培训 &人才选拔项目;面向高校在校生&#xff0c;旨在培养优秀且具有职业竞争力的开发工程师。 本次技术训练营由掘金联合豆包MarsCode 团队主办课程包含前端、后端和 A 方向&#xff0c;在这个飞速发…