7 集中式日志和分布式跟踪

news2025/1/17 3:52:09

文章目录

  • 日志聚合模式
  • 日志集中化的简单解决方案
  • 使用日志并输出
  • 分布式跟踪
    • Spring Cloud Sleuth
    • 实现分布式跟踪
  • 小结

前面的文章:
1、 1 一个测试驱动的Spring Boot应用程序开发
2、 2 使用React构造前端应用
3、 3 试驱动的Spring Boot应用程序开发数据层示例
4、 4 向微服务架构转变
5、 5 转向事件驱动的架构
6、 6 网关和配置服务器

代码:下载

前面的系统中已经涉及几个组件生成的日志(Multiplication、Gamification、Gateway、Consul、RabbitMQ),其中某些组件可能有多个实例,很多日志输出是独立运行的,很难获得系统活动的整体视图。如果用户报告错误,很难找出哪个组件或实例出现故障。在一个屏幕上安排多个日志窗口会有用,但当微服务实例数量增加时,这就不是那么容易解决的了。
要很好地维护像微服务架构这样的分布式系统,需要一个中心位置,在那里可以访问所有聚合日志并进行搜索。

日志聚合模式

通常是将所有的日志输出从应用程序发送到另一个组件,该组件将它们聚合在一起,另外,希望日志能保留一段时间,因此,需要数据存储功能。理想情况下,应该能够浏览这些日志,搜索并过滤每个微服务、实例、类等信息,为此,许多工具提供了一个用户界面,用于连接到聚合日志存储。如图所示:

Gamification
Multiplication
Gateway
分析/查询/过滤
发送日志
发送日志
发送日志
集中式日志
日志聚合
过滤和搜索
日志记录代理
日志记录代理
日志记录代理
用户

实现集中式日志记录时,最好的做法是应用程序逻辑不逻辑此模式,服务应该只使用公共接口来输出消息,将这些日志传送到中央聚合器的日志记录代理独立工作,捕获应用程序产生的输出。
现在有这种模式的多种实现,包括免费和付费的解决方案,其中最受欢迎的是ELK堆栈:Elasticsearch、Logstash和Kibana。
随着时间的推移,建立ELK堆栈已经越来越容易,但仍然不是一项容易的任务,这里不使用ELK实现,就不做介绍了。

日志集中化的简单解决方案

要实现集中式日志处理,需要建立一个新的微服务,来汇总来自Spring Boot应用程序的日志,为了简单起见,不用数据层来保存日志,只接收来自其他服务的日志,并输出到标准输出中。这种方案有助于实现分布式跟踪。
要实现日志输出,需要使用已有的工具RabbitMQ,要捕获应用程序中的每个日志记录行并以RabbitMQ消息的形式发送,因为Spring Boot一直使用Logback,不需要修改应用程序中的代码,就可以由外部配置文件驱动。
在Logback中,将日志行写入特定目标的逻辑部分称为附加程序,此日志记录库包含一些内置的附加程序,用于将消息输出到控制台(ConsoleAppender)或文件(FileAppender和RollingFileAppender)。不需要配置,因为Spring Boot在其依赖项中包含了一些默认的Logback配置,还设置了输出的消息格式。
Spring AMQP提供了一个Logback AMQP日志记录附加程序,可以满足其需要,该附加程序接收每一行日志并为RabbitMQ中的给定交换生成一条消息,其中包含格式和其他一些自定义的选项。
首先准备要添加的Logback配置。Spring Boot可以在应用程序资源文件夹(src/main/resources)中创建一个logback-spring.xml文件来扩展默认值,该文件将在应用程序初始化时自动获取。AMQP附加程序文档列出了所有参数及其含义:

  • applicationId:Application ID — 应用ID - 如果 pattern 包括 %X{applicationId},则添加到 routing key 中。将其设置为应用程序名称,便于在汇总日志时区分源。
  • host:连接到的RabbitMQ主机。由于环境的不同,将该值连接到spring.rabbitmq.host,Spring可以使用springProperty标签来设置。应该给Logback的host属性起一个名字RabbitMQHost,并使用语法${rabbitMQHost:-localhost}来使用该属性值(如果以设置)或使用默认的localhost(默认使用:-分隔设置)。
  • routingKeyPattern:Logging 子系统的 pattern format,用于生成 routing key。如果要在消费者端进行过滤,需要将其设置为applicationId和level(用%p表示)的串联,以提供更大的灵活性。
  • exchangeName:要发布日志事件的 exchange 的名称。默认情况下,会是一个主题交换,可定义为logs.topic。
  • declareExchange:是否在这个 appender 启动时声明配置的 exchange。如果尚未创建交换,则设为true。
  • durable:当 declareExchange 为 true 时,durable 标志被设置为这个值。以便交换在服务器重新启动后继续存在。
  • deliveryMode:设为 PERSISTENT,以便存储日志消息,直到聚合器使用为止。PERSISTENT 或 NON_PERSISTENT,以确定 RabbitMQ 是否应该持久化消息。
  • generateId:用于确定 messageId 属性是否被设置为唯一值。设为true,每条消息有唯一标识。
  • charset:将 String 转换为 byte[] 时使用的字符集。默认:null(使用系统默认字符集)。如果当前平台不支持该字符集,将退回到使用系统字符集。最好设为UTF-8,以确保使用相同的编码。

Gamification项目的logback-spring.xml内容如下:

<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />
    <include resource="org/springframework/boot/logging/logback/console-appender.xml" />

    <springProperty scope="context" name="rabbitMQHost" source="spring.rabbitmq.host"/>

    <appender name="AMQP"
              class="org.springframework.amqp.rabbit.logback.AmqpAppender">
        <layout>
            <pattern>%d{HH:mm:ss.SSS} [%t] %logger{36} - %msg</pattern>
        </layout>

        <applicationId>gamification</applicationId>
        <host>${rabbitMQHost:-localhost}</host>
        <routingKeyPattern>%property{applicationId}.%p</routingKeyPattern>
        <exchangeName>logs.topic</exchangeName>
        <generateId>true</generateId>
        <charset>UTF-8</charset>
        <durable>true</durable>
        <declareExchange>true</declareExchange>
        <deliveryMode>PERSISTENT</deliveryMode>
    </appender>

    <root level="INFO">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="AMQP" />
    </root>
</configuration>

从中可以看到如何将自定义模式添加到附加程序中的,这就可以对消息进行编码,不仅包括消息(%msg),还包括一些额外的信息,如时间(%d{hh:mm:ss.SSS})、线程名([%t])和记录器类(%logger{36})等。文件最后配置根记录器(默认记录器),使用某个包含的文件中定义的CONSOLE附加程序和新定义的AMQP附加程序。
现在需要在其他项目中添加类似的文件,并修改相应的applicationId值。
除了设置日志生成器外,还可以将附加程序用于连接到RabbitMQ的类的日志级别调整为WARN,可避免当RabbitMQ服务器不可用时生成数百条日志,可添加到application配置中:

logging.level.org.springframework.amqp.rabbit.connection.CachingConnectionFactory=WARN

启动应用程序时,日志不仅输出到控制台,还在RabbitMQ服务器的logs.topic交换中生成消息,可在RabbitMQ的Web界面进行验证,如图所示:
logs.topic

使用日志并输出

已经将所有日志发布到交换了,现在就来构建一个新的微服务,来使用这些消息并将之输出。
创建一个新的项目:logs,使用Maven和Jdk21,支持依赖:RabbitMQ、Spring Web、Validation、Spring Boot Actuator、Lombok、Consul Configuration等,不需要服务发现,因此不添加Consul Discovery,如图所示:
new
打开pom.xml,结果如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cn.zhangjuli</groupId>
    <artifactId>logs</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>logs</name>
    <description>logs</description>
    <properties>
        <java.version>21</java.version>
        <spring-cloud.version>2023.0.0</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-config</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

在src/main/resources/application.properties文件中配置如下:

spring.application.name=logs
server.port=8580
spring.config.import=consul:
spring.cloud.consul.config.prefix=config
spring.cloud.consul.config.format=yaml
spring.cloud.consul.config.default-context=defaults
spring.cloud.consul.config.data-key=application.yml

现在需要一个Spring Boot配置类来声明交换、要使用的消息队列,以及将队列附加到主题交换的绑定对象,并使用绑定键模式来使用所有这些对象(#),AMQPConfiguration类如下:

package cn.zhangjuli.logs.configuration;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author Juli Zhang, <a href="mailto:zhjl@lut.edu.cn">Contact me</a> <br>
 */
@Configuration
public class AMQPConfiguration {
    @Bean
    public TopicExchange logsExchange() {
        return ExchangeBuilder.topicExchange("logs.topic")
                .durable(true)
                .build();
    }

    @Bean
    public Queue logsQueue() {
        return QueueBuilder.durable("logs.queue").build();
    }

    @Bean
    public Binding logsBinding(final Queue logsQueue,
                               final TopicExchange logsExchange) {
        return BindingBuilder.bind(logsQueue)
                .to(logsExchange).with("#");
    }
}

下面使用@RabbitListener注解创建一个简单服务,使用相应的log.info()、log.error()或log.warn()将作为RabbitMQ消息头传递的接收消息的日志记录级别映射到Logs微服务中的日志记录级别。注意,这里使用@Header注解将AMQP消息头提取为方法参数,还使用日志记录Marker将应用程序名称(appId)添加到日志行,而不必将其作为消息的一部分进行串联,这是SLF4J标准的灵活用法,可将上下文值添加到日志中。接收RabbitMQ日志消息的消费者类如下:

package cn.zhangjuli.logs.consumer;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Service;

/**
 * @author Juli Zhang, <a href="mailto:zhjl@lut.edu.cn">Contact me</a> <br>
 */
@Slf4j
@Service
public class LogsConsumer {
    @RabbitListener(queues = "logs.queue")
    public void log(final String msg,
                    @Header("level") String level,
                    @Header("amqp_appId") String appId) {
        Marker marker = MarkerFactory.getMarker(appId);
        switch (level) {
            case "INFO" -> log.info(marker, msg);
            case "ERROR" -> log.error(marker, msg);
            case "WARN" -> log.warn(marker, msg);
        }
    }
}

最后,定义日志输出,因为要聚合来自不同服务的多个日志,相关属性是应用程序名称,logback-spring.xml如下:

<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>
                [%-15marker] %highlight(%-5level) %msg%n
            </Pattern>
        </layout>
    </appender>

    <root level="INFO">
        <appender-ref ref="CONSOLE" />
    </root>
</configuration>

重新启动整个系统,就可以在Logs控制台看到所有日志信息,例如:

[multiplication ] INFO  17:30:57.018 [http-nio-10080-exec-8] c.z.m.challenge.ChallengeServiceImpl - attempt: ChallengeAttempt(id=352, user=User(id=202, alias=noise7), factorA=50, factorB=60, resultAttempt=3000, correct=true)
[gamification   ] INFO  17:30:57.046 [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] c.z.g.game.GameEventHandler - 已接收到成功挑战事件:352
[gamification   ] INFO  17:30:57.101 [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] c.z.g.game.GameServiceImpl - 用户 noise7 的尝试 352 得分 10
[gamification   ] INFO  17:31:01.856 [http-nio-8081-exec-10] c.z.g.game.LeaderBoardController - 查询排行榜
[gamification   ] INFO  17:31:01.856 [http-nio-8081-exec-6] c.z.g.game.LeaderBoardController - 查询排行榜

这个简单的日志聚合器没有花费多少时间,现在可以在同一源中搜索日志,并看到所有服务中近乎实时的输出流。其过程如图所示:

Gamification
网关
-发送尝试
-获取用户别名
-获取排行榜
查找服务实例
注册表自身(每个实例)
获取配置
获取配置
日志
日志
日志
-发送尝试
-获取用户别名
-获取排行榜
注册服务者
Multiplication
注册服务者
服务注册表和配置服务器
服务注册表
集中式配置
服务发现客户端
负载均衡器
路由器
浏览器
尝试(主题交换)
Gamification队列
日志(主题交换)
日志队列
集中式日志

分布式跟踪

将所有日志放在一个位置能提高可观察性,但不具备可追溯性,在微服务之间了解并发用户和事件链的情况就是一个难题,尤其是当事件链具有触发了相同操作的多个事件类型分支时,更是困难。
为解决此问题,需要关联同一进程链中的所有操作和事件。一种简单的方法是在用于处理不同操作的所有HTTP调用、RabbitMQ消息和Java线程中注入相同的标识符,然后,可在所有相关日志中输出此标识符。
在系统中使用用户标识符,如果将来所有功能都将围绕用户操作而构建,就可以在每个事件和调用中传播一个userId字段,然后,将其记录在不同的服务中,以便将日志与特定用户相关联,这会改善可追溯性。但是,也可能在短时间内收到来自同一用户的多个操作,例如,两次尝试在一秒内解决乘法问题,这些操作分散在实例中。这种情况下,很难区分微服务的各个流。理想情况下,每种操作都应该有一个唯一的标识符,该标识符是在链的起点生成的。此外,最好是透明地传播,而不必在所有服务中显式地对其可追溯性问题进行建模。
在Spring中,实现分布式跟踪的工具是Sleuth。

Spring Cloud Sleuth

Sleuth是Spring Cloud系列的一部分,使用Brave库来实现分布式跟踪,通过关联span的工作单元在不同组件之间构建跟踪。例如,一个span正在检查Multiplication微服务中的尝试,而另一个span正在基于RabbitMQ事件添加分数和徽章。每个span都有一个不同的唯一标识符,但都属于同一个跟踪,因此具有相同的跟踪标识符。此外,每个span都链接到父级,而根级除外,根级是原始操作。如图所示:

Gateway
Trace id:100
Parent Span id:-
Span id:200
Multiplication
Trace id:100
Parent Span id:200
Span id:201
Gamification
Trace id:100
Parent Span id:201
Span id:202

在更高级的系统中,可能会有复杂的跟踪结构,其中多个span具有相同的父级,如图所示:

Trace id:100
Parent Span id:201
Span id:202
Trace id:100
Parent Span id:201
Span id:900
Trace id:100
Parent Span id:202
Span id:800
Trace id:100
Parent Span id:202
Span id:600
Gateway
Trace id:100
Parent Span id:-
Span id:200
Multiplication
Trace id:100
Parent Span id:200
Span id:201
Gamification
Rewards & Bonus
Reports

为了透明地注入这些值,Sleuth使用SLF4J的映射诊断上下文(MDC)对象,该对象是一个日志记录上下文,其生命周期仅限于当前线程,还可以在上下文中注入自定义字段,可传播并在日志中使用这些值。
Spring Boot在Sleuth中自动配置了一些内置的拦截器,用于自动检查和修改HTTP调用和RabbitMQ消息,还集成了Kafka、gRPC和其他通信接口。拦截器的工作方式类似:对于传入的通信,检查是否在调用或消息中添加了跟踪标头,并将其放入MDC中;当作为客户端进行调用或发布数据时,拦截器会从MDC中获取这些字段并将标头添加到请求或消息中。
Sleuth有时与Zipkin结合使用,可以跟踪采样来测量每个span中以及整个链中所花费的时间。这些数据可以发送到Zipkin服务器,该服务器提供一个UI,可查看跟踪层次结构以及每个服务完成其工作所需的时间。这里不会使用Zipkin,因为其不适用具有trace和span标识符的集中式日志记录系统。

实现分布式跟踪

Spring Cloud Sleuth为REST API和RabbitMQ消息提供了拦截器,Spring Boot进行自动配置,因此,实现分布式跟踪并不困难。这里使用Zipkin来进行跟踪。
首先,在Multiplication、Gamification、Gateway、Logs中添加Spring Cloud Sleuth启动器,如下所示:

        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-tracing-bridge-brave</artifactId>
        </dependency>
        <dependency>
            <groupId>io.zipkin.reporter2</groupId>
            <artifactId>zipkin-reporter-brave</artifactId>
        </dependency>

只有添加了对应的依赖项,才会将trace和span标识符注入每个受支持的通信通道和MDC对象中,默认的Spring Boot日志记录模式也会自动调整,用于在日志中输出trace和span值。
为了使日志更详细并查看trace标识符,在ChallengeAttemptController中添加一条日志信息,以便每次用户发送尝试时输出一条消息,如下所示:

package cn.zhangjuli.multiplication.challenge;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * @author Juli Zhang, <a href="mailto:zhjl@lut.edu.cn">Contact me</a> <br>
 */
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/attempts")
public class ChallengeAttemptController {
    private final ChallengeService challengeService;

    @PostMapping
    ResponseEntity<ChallengeAttempt> postResult(@RequestBody @Valid ChallengeAttemptDTO challengeAttemptDTO) {
        log.info("从{}收到新的尝试。", challengeAttemptDTO.getUserAlias());
        return ResponseEntity.ok(challengeService.verifyAttempt(challengeAttemptDTO));
    }

    @GetMapping
    ResponseEntity<List<ChallengeAttempt>> getStatistics(@RequestParam String alias) {
        return ResponseEntity.ok(challengeService.getStatisticsForUser(alias));
    }
}

另外,还想在集中式日志中包含trace和parent标识符,要将来自MDC上下文(由Sleuth使用Brave注入)中的属性X-B3-TraceId和X-B3-SpanId手动添加到Logs项目的logback-spring.xml中。这些标头是OpenZipkin的B3 Propagation规范的一部分,由Sleuth的拦截器包含在MDC中,代码如下:

<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>
                [%70marker] %highlight(%-5level) %msg%n
            </Pattern>
        </layout>
    </appender>

    <root level="INFO">
        <appender-ref ref="CONSOLE" />
    </root>
</configuration>

另外,修改LogsConsumer 类,让日志信息能够显示traceId和spanId,以便追踪,如下所示:

package cn.zhangjuli.logs.consumer;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Service;

/**
 * @author Juli Zhang, <a href="mailto:zhjl@lut.edu.cn">Contact me</a> <br>
 */
@Slf4j
@Service
public class LogsConsumer {
    @RabbitListener(queues = "logs.queue")
    public void log(final String msg,
                    @Header("level") String level,
                    @Header("amqp_appId") String appId) {
        Marker marker = MarkerFactory.getMarker(appId);
        switch (level) {
            case "INFO" -> log.info(marker, msg);
            case "ERROR" -> log.error(marker, msg);
            case "WARN" -> log.warn(marker, msg);
        }
    }
}

重启服务后,就会发挥作用,当然,还需要安装Zipkin服务器,可使用docker容器,命令很简单:docker run -d -p 9411:9411 openzipkin/zipkin,可以看到Multiplication和Gamification的日志,带有trace标识符,如下所示:

2023-12-26T11:37:37.693+08:00  INFO 44448 --- [multiplication] [io-10080-exec-9] [658a4a8152b5ca6ff46e57bc2c704440-deb6e42026ec20e9] c.z.m.c.ChallengeAttemptController       : 从noise7收到新的尝试。
2023-12-26T11:37:37.700+08:00  INFO 44448 --- [multiplication] [io-10080-exec-9] [658a4a8152b5ca6ff46e57bc2c704440-deb6e42026ec20e9] c.z.m.challenge.ChallengeServiceImpl     : attempt: ChallengeAttempt(id=457, user=User(id=202, alias=noise7), factorA=50, factorB=60, resultAttempt=3000, correct=true)
2023-12-26T11:37:38.390+08:00  INFO 44448 --- [multiplication] [io-10080-exec-8] [658a4a823f424e031efe5eb3dec5988c-81180d75a911d673] c.z.multiplication.user.UserController   : 解析用户别名:[154, 202, 3, 1, 102, 153, 152]
2023-12-26T11:37:43.448+08:00  INFO 44448 --- [multiplication] [io-10080-exec-4] [658a4a87a383b6ed58ed38f84f4d56f3-d05b566e3550cfdb] c.z.multiplication.user.UserController   : 解析用户别名:[154, 202, 3, 1, 102, 153, 152]

将所有日志连同traceId和spanId输出到更复杂的集中式日志工具(如ELK)时,效果更好,可以使用这些标识符来执行过滤后的文本搜索。

小结

文章介绍了日志聚合模式,以解决微服务实施过程中面临的问题,每个微服务都有日志输出,不便于了解整个系统的状态,通过日志集中化解决方案,将所有日志引导到一个中央位置,还可以在其中看到单个进程运行的完整轨迹,可使用Sleuth、Zipkin实现分布式跟踪,以便发现存在的问题。

后续文章:
容器化微服务

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

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

相关文章

笔记本摄像头模拟监控推送RTSP流

使用笔记本摄像头模拟监控推送RTSP流 一、基础安装软件准备 本文使用软件下载链接:下载地址 FFmpeg软件: Download ffmpeg 选择Windows builds by BtbN 一个完整的跨平台解决方案&#xff0c;用于录制、转换和流式传输音频和视频。 EasyDarwin软件&#xff1a;Download Easy…

【链表】力扣206反转链表

题目 力扣206反转链表 思路图解 代码实现 双指针代码实现 public static ListNode reverseList(ListNode head) {// 初始化pre&#xff0c;curListNode pre null;ListNode cur head;// 当cur为null时&#xff0c;说明反转结束while(cur ! null) {// 临时保存cur.next节点…

目标检测-One Stage-YOLOx

文章目录 前言一、YOLOx的网络结构和流程1.YOLOx的不同版本2.Yolox-Darknet53YOLOv3 baselineYolox-Darknet53 3.Yolox-s/Yolox-m/Yolox-l/Yolox-x4.Yolox-Nano/Yolox-Tiny 二、YOLOx的创新点总结 前言 根据前文CenterNet、YOLOv4等可以看出学界和工业界都在积极探索使用各种t…

解决在eclipse2021中,用mysql-connector-java-8.0.18.jar不兼容,导致无法访问数据库问题

1.环境场景 组件版本mysql5.7.44mysql-connector-java80.18 2. 问题描述 报mysql-connector-java 驱动连不上mysql数据库。 3. 可能的原因分析 查看数据库连接句柄是否对 如果数据库连接句柄中没有 useSSLfalse 的话可能会导致这样的问题。 就像下面这样&#xff1a; jdb…

认识Linux指令之 “more less” 命令

01.more命令 语法&#xff1a;more [选项][文件] 功能&#xff1a;more命令&#xff0c;功能类似 cat 常用选项&#xff1a; -n 对输出的所有行编号 q 退出more cat适合打开查看一些小文件 当遇到大文本文件的时候&#xff0c;使用more命令&#xff0c;more可以打满一屏…

GPT大模型在生物、地球、农业、气象、生态、环境科学可以应用?

以ChatGPT、LLaMA、Gemini、DALLE、Midjourney、Stable Diffusion、星火大模型、文心一言、千问为代表AI大语言模型带来了新一波人工智能浪潮&#xff0c;可以面向科研选题、思维导图、数据清洗、统计分析、高级编程、代码调试、算法学习、论文检索、写作、翻译、润色、文献辅助…

银河麒麟服务器系统安装KVM和创建KVM虚拟机

银河麒麟服务器系统安装KVM和创建KVM虚拟机 一 KVM概念二 安装KVM组件2.1 安装KVM前提2.2 KVM管理工具2.3 KVM安装2.3.1 安装kvm组件2.3.2 启动服务并设置开机自启 三 创建&管理虚拟机3.1 创建虚拟机3.2 安装过程3.2.1 选择安装方式3.2.2 选择安装ISO文件3.2.3选择内存和CP…

微信扫码进入小程序特定页面

小程序配置 开发 - 开发管理 - 开发设置-普通链接二维码打开小程序 配置好的截图 如下&#xff1a;二维码规则建议是自己的域名 /mini/ 功能页面 pages/index/index 是为了方便跳转其他页面 记得把校验文件发给后端 web 端处理 二维码格式为&#xff1a;二维码规则/功能页…

单调栈练习(二)— 柱状图中最大的矩形

题目&#xff1a; 这是一道LeetCode上的原题&#xff1a;链接地址 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 思路 因为是力扣原题&#xff0c;所…

怎样在Anaconda下安装pytorch(conda安装和pip安装)

前言 文字说明 本文中标红的&#xff0c;代表的是我认为比较重要的。 版本说明 python环境配置&#xff1a;jupyter的base环境下的python是3.10版本。CUDA配置是&#xff1a;CUDA11.6。目前pytorch官网提示支持的版本是3.7-3.9 本文主要用来记录自己在安装pytorch中出现的问…

@DependsOn:解析 Spring 中的依赖关系之艺术

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 DependsOn&#xff1a;解析 Spring 中的依赖关系之艺术 前言简介基础用法高级用法在 XML 配置中使用 DependsOn通过 Java Config 配置实现依赖管理 生命周期与初始化顺序Bean 生命周期的关键阶段&…

FPGA 高端项目:基于 SGMII 接口的 UDP 协议栈,提供2套工程源码和技术支持

目录 1、前言免责声明 2、相关方案推荐我这里已有的以太网方案本协议栈的 1G-UDP版本本协议栈的 10G-UDP版本本协议栈的 25G-UDP版本1G 千兆网 TCP-->服务器 方案1G 千兆网 TCP-->客户端 方案10G 万兆网 TCP-->服务器客户端 方案 3、该UDP协议栈性能4、详细设计方案设…

HTML5 article标签,<time>...</time>标签和pubdate属性的运用

1、<article>...</article>标签的运用 article标签代表文档、页面或应用程序中独立的、完整的、可以独自被外部引用的内容。它可以是一篇博客或报竟杂志中的文章、一篇论坛帖子、一段用户评论或一个独立的插件&#xff0c;或者其他任何独立的内容。把文章正文放在h…

奇偶大冒险(判断奇偶,逆序输出)

题目&#xff1a; 代码&#xff1a; #include <bits/stdc.h> using namespace std; int main(){int n;cin>>n;int i0;int c[100]{0}; //数组初始化 while(n!1) {if(n%21){ //判断奇数 c[i]n;n3*n1;i;}else if(n%20){ //判断偶数 c[i]n;nn…

flutter 打包安卓apk 常用配置

打包之前需要先不配置不然会报错 Execution failed for task ‘:app:mergeReleaseResources’. APP目录下的build.gradleaaptOptions.cruncherEnabled falseaaptOptions.useNewCruncher false如图 配置targetSdkVersion 、minSdkVersion 在android/app/src目录下的build.…

PINN物理信息网络 | 泊松方程的物理信息神经网络PINN解法

基本介绍 泊松方程是一种常见的偏微分方程&#xff0c;它在物理学和工程学中具有广泛的应用。它描述了在某个区域内的标量场的分布与该场在该区域边界上的值之间的关系。 物理信息神经网络&#xff08;PINN&#xff09;是一种结合了物理定律和神经网络的方法&#xff0c;用于…

6 - 数据备份与恢复|innobackupex

数据备份与恢复&#xff5c;innobackupex 数据备份与恢复数据备份相关概念物理备份与恢复逻辑备份&#xff08;推荐&#xff09;使用binlog日志文件实现对数据的时时备份‘使用日志 恢复数据 innobackupex 对数据做备份和恢复增量备份与恢复 数据备份与恢复 数据备份相关概念 …

[uniapp] uni-ui+vue3.2小程序评论列表组件 回复评论 点赞和删除

先看效果 下载地址 uni-app官方插件市场: cc-comment组件 环境 基于vue3.2和uni-ui开发; 依赖版本参考如下: "dependencies": {"dcloudio/uni-mp-weixin": "3.0.0-3090820231124001","dcloudio/uni-ui": "^1.4.28","…

提升测试多样性,揭秘Pytest插件pytest-randomly

大家可能知道在Pytest测试生态中&#xff0c;插件扮演着不可或缺的角色&#xff0c;为开发者提供了丰富的功能和工具。其中&#xff0c;pytest-randomly 插件以其能够引入随机性的特性而备受欢迎。本文将深入探讨 pytest-randomly 插件的应用&#xff0c;以及如何通过引入随机性…