SpringCloud之Stream消息驱动RocketMQ讲解

news2024/11/19 22:53:03

文章目录

  • 1 Stream消息驱动
    • 1.1 简介
      • 1.1.1 定义
      • 1.1.2 抽象模型
      • 1.1.3 绑定器
    • 1.2 操作实操
      • 1.2.1 pom.xml
      • 1.2.2 操作实体
    • 1.3 Stream 3.x 之前操作
      • 1.3.1 自定义通道
      • 1.3.2 消费消息
      • 1.3.3 发送消息
      • 1.3.4 配置文件
    • 1.4 Stream 3.x 之后操作
      • 1.4.1 Stream 3.x 之后讲解
      • 1.4.2 消费消息
      • 1.4.3 发送消息
        • 1.4.3.1 自动发送
        • 1.4.3.2 手动触发
      • 1.4.4 配置文件
      • 1.4.5 中转函数Function
    • 1.5 配置文件讲解
      • 1.5.1 spring.cloud.function.definition
      • 1.5.2 spring.cloud.stream.binders和bindings区别
      • 1.5.3 消费分组
      • 1.5.4 spring.cloud.stream.rocketmq.binder.group和spring.cloud.stream.bindings.通道名字.group两个属性区别

1 Stream消息驱动

本文是以 RocketMQ 为例讲解,点击此处了解SpringBoot整合RocketMQ

1.1 简介

1.1.1 定义

Spring Cloud Stream 是一个用来为微服务应用构建消息驱动能力的框架。它可以基于 Spring Boot 来创建独立的、可用于生产的 Spring 应用程序。Spring Cloud Stream 为一些供应商的消息中间件产品提供了个性化的自动化配置实现,并引入了发布-订阅、消费组、分区这三个核心概念。简单的说,Spring Cloud Stream本质上就是整合了Spring BootSpring Integration,实现了一套轻量级的消息驱动的微服务框架。

1.1.2 抽象模型

我们都知道市面上有很多消息中间件,Sping Cloud Stream 为了可以集成各种各样的中间件,它抽象出了 Binder 的概念,每个消息中间件都需要有对应自己的 Binder。这样它就可以根据不同的 Binder 集成不同的中间件。下图的input和outputchannelBinder则是消息中间件和通道之间的桥梁
在这里插入图片描述

1.1.3 绑定器

通过使用 Spring Cloud Stream,可以有效简化开发人员对消息中间件的使用复杂度,让系统开发人员可以有更多的精力关注于核心业务逻辑的处理。但是目前 Spring Cloud Stream 只支持 RabbitMQKafka 的自动化配置。
Spring Cloud Stream 提供了 Binder (负责与消息中间件进行交互),我们则通过 inputs 或者 outputs 这样的消息通道与 Binder 进行交互。

Binder 绑定器是 Spring cloud Stream 中一个非常重要的概念,实现了应用程序和消息中间件之间的隔离,同时我们也可以通过应用程序实现,消息中间件之间的通信。在我们的项目的可以继承多种绑定器,我们可以根据不同特性的消息使用不同的消息中间件。Spring Cloud Stream 为我们实现了 RabbitMQ 和Kafka 的绑定器。如果你想使用其他的消息中间件需要自己去实现绑定器接口.

1.2 操作实操

1.2.1 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>RocketMQDemo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <encoding>UTF-8</encoding>
        <spring.boot.version>2.6.11</spring.boot.version>
        <spring.cloud.version>2021.0.4</spring.cloud.version>
        <spring.cloud.alibaba>2021.0.4.0</spring.cloud.alibaba>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-client</artifactId>
            <version>4.9.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-remoting</artifactId>
            <version>4.9.4</version>
        </dependency>

    </dependencies>

    <dependencyManagement>
    <dependencies>
        <!--springboot父依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring.boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!--springcloud父依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring.cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!--springcloudalibaba父依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>${spring.cloud.alibaba}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

    </dependencies>
    </dependencyManagement>

</project>

1.2.2 操作实体

@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class UserEntity {
    private String name;//账号
    private String pass;//密码
}

1.3 Stream 3.x 之前操作

虽然在 SpringCloudStream 3.x 版本后是可以看到 @StreamListener@EnableBinding 都打上了@Deprecated 注解,但是不妨碍我们测试学习

1.3.1 自定义通道

package cn.mq;

import org.springframework.cloud.stream.annotation.Input;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;

public interface MyChannel {

    String INPUT = "test-input";
    String OUTPUT = "test-output";

    /**
     * 这两个通道可能定义在两个不同的通道里面,这里为了方便放在同一个项目中演示
     */
    // 收(订阅频道/消息消费者)
    @Input(INPUT)
    SubscribableChannel input();
    // 发(消息生产者)
    @Output(OUTPUT)
    MessageChannel output();
}

1.3.2 消费消息

此处可以使用我们自定义的通道,也可以使用原装的 Sink.class

package cn.mq;

import cn.entity.UserEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;

@Slf4j
//@EnableBinding(Sink.class)
@EnableBinding(MyChannel.class)
public class ReceiveMQ {
    @StreamListener(MyChannel.INPUT)
    public void receive(UserEntity entity){
        log.info("收到消费消息:{}",entity.toString());
    }
}

默认情况下,如果消费者是一个集群,此时,一条消息会被多次消费。通过消息分组,我们可以解决这个问题。

添加如下配置分组,放入组 g1:

spring.cloud.stream.bindings.test-input.group=g1
spring.cloud.stream.bindings.test-output.group=g1

1.3.3 发送消息

package cn.controller;

import cn.entity.UserEntity;
import cn.mq.MyChannel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
public class MQController {
    @Autowired
    private MyChannel myChannel;
    @GetMapping("/test")
    public void test(){
        UserEntity userEntity = new UserEntity("hello", "pass");
        boolean send = myChannel.output().send(MessageBuilder.withPayload(userEntity).build());
        log.info("发送消息:{},结果:{}",userEntity.toString(),send);
    }
}

其中,MessageBuilderSpring Integration中用于创建消息的工具类。以下是createMessage, fromMessagewithPayload方法的区别:

  • createMessage:这是一个静态方法,用于创建一个新的消息。你需要提供消息的负载(payload)和消息头(header)。
    例如:Message<String> message = MessageBuilder.createMessage("Hello World", new MessageHeaders(headers));
  • fromMessage:这个方法用于从一个已存在的消息创建一个新的消息。新的消息将会有相同的负载和消息头。这个方法通常在你想修改一个已存在消息的部分属性但保持其他部分不变时使用。
    例如:Message<String> newMessage = MessageBuilder.fromMessage(oldMessage).setHeader("newHeader", "newValue").build();
  • withPayload:这个方法用于设置消息的负载。你可以链式地调用其他方法(如setHeader)来设置消息头。
    例如:Message<String> message = MessageBuilder.withPayload("Hello World").setHeader("headerKey", "headerValue").build();

总的来说,这三个方法提供了灵活的方式来创建和修改消息,你可以根据具体的需求来选择使用哪一个。

1.3.4 配置文件

spring:
  application:
    name: rokcet-mq-demo
  cloud:
    stream:
      bindings: # 配置消息通道的信息
        test-input: # 自定义消费 通道
          destination: test-optic
          group: test
          binder: rocketmq
        test-output: # 自定义发送 通道
          destination: test-optic
          group: test
          binder: rocketmq
      rocketmq:
        binder:
          name-server: ip:port
          group: test #此处定义整体消费者组名字

1.4 Stream 3.x 之后操作

1.4.1 Stream 3.x 之后讲解

由于 SpringCloudStream 3.x 版本后是 可以看到 @StreamListener@EnableBinding 都打上了@Deprecated 注解。后续的版本更新中会逐渐替换成函数式的方式实现。
既然通过四大函数式接口的方式替换了注解的方式 那么该如何进行绑定呢?
通过 spring.cloud.stream.function.definition:名称的方式进行绑定 公开 topic
不管是创建 Consumer 还是 Supplier 或者是 Function Stream都会将其的 方法名称 进行 一个 topic拆封绑定 假设 创建了一个 Consumer< String > myTopic 的方法,Stream 会将其 拆分成 Inout 两个通道:

  • 输入 - <functionName> + -in- + < index >
    myTopic-in-0
  • 输出 - <functionName> + -out- + < index >
    myTopic-out-0

注意:这里的 functionName需要和代码声明的函数名称还有spring.cloud.stream.function.definition下的名称保持一致

1.4.2 消费消息

package cn.mq;

import cn.entity.UserEntity;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;

import java.util.function.Consumer;

@Configuration
public class ReceiveMQ {
    @Bean
    public Consumer<Message<UserEntity>> myTopicC(){
        return (data)->{
            UserEntity user = data.getPayload();
            MessageHeaders headers = data.getHeaders();
            System.out.println("myTopicC 接收一条记录:" + user);
            System.out.println("getHeaders headerFor:" + headers.get("for"));
        };
    }
  }

1.4.3 发送消息

1.4.3.1 自动发送

package cn.mq;

import cn.entity.UserEntity;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;

import java.util.function.Supplier;

@Configuration
public class SendMQ {
    Integer i = 1;
    @Bean
    public Supplier<Message<UserEntity>> myTopicP() {
        return () -> {
            UserEntity entity = new UserEntity();
            entity.setPass(i++ + "");
            entity.setName(Thread.currentThread().getName());
            System.out.println("myTopicP 发送一条记录:" + entity);
            return MessageBuilder
                    .withPayload(entity)
                    .build();
        };
    }
}

这种方式定义 suppelier 会 默认1000ms 发送一次记录
可以修改:spring.cloud.stream.poller:fixedDelay: 延迟毫秒值

1.4.3.2 手动触发

通过 StreamBridge 触发

package cn.controller;

import cn.entity.UserEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
public class MQController {

    @Autowired
    private StreamBridge streamBridge;

    @GetMapping("/test")
    public void sendMsg() {
        UserEntity entity = new UserEntity("hello","world");
        System.out.println("sendMsg 发送一条记录:" + entity);
        streamBridge
                .send(
                        "myTopicP-out-0",
                        MessageBuilder.withPayload(entity)
                                .setHeader("for", "这是一个请求头~")
                                .build());
    }
}

1.4.4 配置文件

spring:
  cloud:
    stream:
      rocketmq:
        binder:
          name-server: localhost:9876
# -------------- 分割线 ---------------
      function:
      # 组装和绑定
      # 手动时把 myTopicP 去掉
        definition: myTopicC;myTopicP
      bindings:
        myTopicC-in-0:
          destination: my-topic
          group: test
       myTopicP-out-0:
          destination: my-topic

1.4.5 中转函数Function

Function< String,String > 范型中有两个参数 :一个入参,一个出参,所以在Stream中可以用来作于一个消息中转站来使用。相当于 top-1 接受到消息 但是我不想处理 我对其数据进行一次处理 发送到 top-2 通道,交给top-2 进行数据的最终处理。

采用手动触发示例,在上面改造测试:

@Bean
public Consumer<UserEntity> testFunctionQ(){
    return (data)->{
        System.out.println("testFunctionQ 消息中转后接收一条记录:" + data);
    };
}

@Bean
public Function<UserEntity, UserEntity> testFunction() {
    return value -> {
        System.out.println("中转 testFunction: " + value);
        value.setPass(value.getPass().toUpperCase());
        value.setName(value.getName().toUpperCase());
        return value;
    };
}

配置文件:

spring:
  application:
    name: rokcet-mq-demo
  cloud:
    stream:
      bindings:
        myTopicP-out-0:
          destination: test-topic
        testFunction-in-0:
          destination: test-topic
          group: my_input_group
        testFunction-out-0:
          destination: test-topic-Q
        testFunctionQ-in-0:
          destination: test-topic-Q
          group: my_input_group-Q

      rocketmq:
        binder:
          name-server: localhost:9876
          group: test
      function:
        definition: testFunction;testFunctionQ

1.5 配置文件讲解

1.5.1 spring.cloud.function.definition

spring.cloud.function.definition 是一个配置属性,用于指定 Spring Cloud Function 应用程序中的函数定义。
这个属性的值是一个以 逗号分隔(如果用逗号分隔有顺序问题,还是最好用分号分隔)的字符串,表示要使用的函数、消费者(Consumer)或生产者(Supplier)的名称。
Spring Cloud Stream 中,这个属性用于将函数、消费者或生产者与消息队列(如 RabbitMQKafka 等)进行绑定。当指定为 Supplier 时,它将作为消息队列的生产者,负责生成并发送消息;当指定为 Consumer 时,它将作为消息队列的消费者,负责接收并处理消息。
例如,假设有一个名为 process 的函数,你可以通过以下配置将其作为消费者与消息队列进行绑定:

spring.cloud.function.definition=process

这样,process 函数将作为消息队列的消费者,接收并处理来自队列的消息。同样,可以将 Supplier 与消息队列进行绑定,作为生产者生成并发送消息。

1.5.2 spring.cloud.stream.binders和bindings区别

spring.cloud.stream.bindersspring.cloud.stream.bindings都是Spring Cloud Stream的配置属性,但它们的用途是不同的。

  • spring.cloud.stream.binders用于配置消息中间件的连接信息。
    例如,如果使用的是 RabbitMQ,你需要在这里配置 RabbitMQ 的主机名、端口、用户名和密码等信息。可以配置多个binder,每个binder对应一个消息中间件。
  • spring.cloud.stream.bindings用于配置消息通道的信息。在Spring Cloud Stream中,消息通道是消息生产者和消费者之间的桥梁。可以在这里配置通道的名称、目标(对应消息中间件中的队列或主题名)、分区等信息。

简单来说,spring.cloud.stream.binders是用来配置消息中间件的,而spring.cloud.stream.bindings是用来配置消息通道的。

spring:
  cloud:
    stream:
      # 如果你项目里只对接一个中间件,那么不用定义binders
      # 当系统要定义多个不同消息中间件的时候,使用binders定义
      binders:
        my-rabbit:
          type: rabbit # 消息中间件类型
          environment: # 连接信息
            spring:
              rabbitmq:
                host: localhost
                port: 5672
                username: guest
                password: guest
      bindings:
        # 添加coupon - Producer
        addCoupon-out-0:
          destination: request-coupon-topic
          content-type: application/json
          binder: my-rabbit
        # 添加coupon - Consumer
        addCoupon-in-0:
          destination: request-coupon-topic
          content-type: application/json
          # 消费组,同一个组内只能被消费一次
          group: add-coupon-group
          binder: my-rabbit  

1.5.3 消费分组

Spring Cloud Stream中,发送者(Producer)不需要分组,只有消费者(Consumer)需要分组。
分组的主要目的是为了实现消息的广播或者分区。当多个消费者在同一个组中时,消息会被任何一个消费者消费,但不会被同一组的所有消费者消费,这就实现了消息的负载均衡。如果每个消费者有自己的组,那么每个消费者都会收到一份消息的拷贝,这就实现了消息的广播。

1.5.4 spring.cloud.stream.rocketmq.binder.group和spring.cloud.stream.bindings.通道名字.group两个属性区别

spring.cloud.stream.rocketmq.binder.group 是全局配置,用于设置默认的消费组名。如果没有在具体的通道中设置消费组名,那么就会使用这个全局配置。

spring.cloud.stream.bindings.通道名字.group 是针对具体通道的配置,用于设置该通道的消费组名。如果在这里设置了消费组名,那么就会覆盖全局配置。

总的来说,这两个属性都是用于设置消费组名的,但是作用范围不同,一个是全局的,一个是针对具体通道的。

报错Property 'group' is required - producerGroup
这时候就需要在 spring.cloud.stream.rocketmq.binder.group属性中设置值,就不会报错了

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

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

相关文章

linux屏幕旋转/weston去掉边框

目录 屏幕旋转 去掉边框 buildroot运行QT&#xff0c;你需要修改的都在这里了。 首先QT界面大小需要适应屏幕的分辨率&#xff0c;如果屏幕是1280*800&#xff0c;QT界面大小就不能设置为800*1280&#xff0c;一样需要设置为1280*800 屏幕旋转 方法一&#xff1a; xrandr…

GBASE观察 | 展望大模型与数据库相融合的未来

大模型和数据库的相互作用是数据处理领域的重要发展趋势之一&#xff0c;在目前的大数据时代中正发挥着越来越重要的作用。大模型需要依托于海量数据进行训练和学习&#xff0c;在实际应用中需要依托于数据库进行实时的数据存储、筛选、处理、甚至还可以实现数据的可视化和转化…

计算机网络-数据链路层上篇

目录 一、数据链路层概述 二、封装成帧 三、差错检测 &#xff08;一&#xff09;奇偶校验 &#xff08;二&#xff09;循环冗余校验CRC 四、可靠传输 &#xff08;一&#xff09;停止-等待协议SW &#xff08;二&#xff09;后退N帧协议GBN &#xff08;三&#xff…

代码随想录二刷day37 | 贪心 之 738.单调递增的数字 968.监控二叉树

day37 738.单调递增的数字968.监控二叉树确定遍历顺序如何隔两个节点放一个摄像头情况1&#xff1a;左右节点都有覆盖情况2&#xff1a;左右节点至少有一个无覆盖的情况情况3&#xff1a;左右节点至少有一个有摄像头情况4&#xff1a;头结点没有覆盖 738.单调递增的数字 题目链…

H5学习(四)-- 常用属性

文章目录 一、字体设置字体格式设置字体大小设置字体粗细设置字体样式设置字体颜色 二、 文本设置文本对齐方式设置文本装饰方式设置文本缩进设置文本行高 三、背景设置背景颜色设置背景图片设置背景平铺设置背景位置设置背景尺寸 四、圆角矩形五、元素的显示模式块级元素行内元…

解决win10开机卡顿、配置很高但是玩游戏卡顿掉帧等问题

解决win10开机卡顿、配置很高但是玩游戏卡顿掉帧等问题 最近组装了一台高配置的新电脑&#xff0c;装好了各种驱动、软件等。发现系统开机后卡顿一分钟左右&#xff08;加载应用配置等&#xff09;&#xff0c;但是我的系统启动项明明就没多少&#xff0c;不应该是这样的情况&…

最优化--梯度下降法--牛顿法(详解)

目录 梯度下降法 梯度下降法 步骤 牛顿法 牛顿法的基本思想 牛顿法的优缺点 作用 梯度下降法 梯度下降法&#xff08;Gradient Descent&#xff09;是一种常用的优化算法&#xff0c;用于求解函数的最小值或最大值。它通过迭代的方式&#xff0c;不断更新参数的取值&…

cmd获取apk签名hash、获取apk是否启用了V1\V2\V3\V4签名

1、从APK中获取签名hash keytool -printcert -jarfile apk路径 2、获取apk是否启用了V2\V3\V4签名 //在android sdk 目录&#xff0c;如 xxx\Sdk\build-tools\34.0.0 里&#xff0c;执行apksigner.bat verify -v apk路径

小白到运维工程师自学之路 第四十三集 (mariadb读写分离)

一、读写分离的优点 1. 负载均衡&#xff1a;将读操作分散到多个从库上&#xff0c;可以有效地分担主库的读负载&#xff0c;提高系统的 并发处理能力。 2. 提高读性能&#xff1a;从库可以通过增加硬件资源或者优化查询语句等方式来提高读操作的性 能&#xff0c;从而提升…

为什么用了代理ip访问网站还是被限制了

随着网络的发展和应用的普及&#xff0c;越来越多的用户开始使用代理ip来访问网站。代理ip可以隐藏真实IP地址&#xff0c;提供匿名性和访问自由&#xff0c;但有时候使用代理ip仍然会导致被禁止访问网站的情况发生。下面就让我们来分析一下吧。 代理ip被网站识别 一些网站通过…

从零详细地梳理一个完整的 LLM 训练流程

深度学习自然语言处理 分享知乎&#xff1a;何枝 在这篇文章中&#xff0c;我们将尽可能详细地梳理一个完整的 LLM 训练流程。包括模型预训练&#xff08;Pretrain&#xff09;、Tokenizer 训练、指令微调&#xff08;Instruction Tuning&#xff09;等环节。 文末进群&#xf…

两种特定网络环境下,如何实现外网SSH访问内网LINUX主机?

目前&#xff0c;大多数网友的网络环境分为两种&#xff0c;一种是没有公网IP&#xff0c;一种是动态公网IP环境。今天跟大家分享一下在这两种特定网络环境下&#xff0c;如何通过快解析内网穿透&#xff0c;实现外网SSH访问内网LINUX。 1.动态公网IP环境。路由器分配的是动态…

深入了解 KaiwuDB 负载行为数据采集

KAP 基于数据库系统内部反馈的各项数据指标&#xff0c;可帮助用户全面掌握 KaiwuDB 集群的整体运行情况&#xff0c;实时监测集群相关性能&#xff0c;可提供整体资源和集群状态角度的系统监控。 除此之外&#xff0c;KaiwuDB 数据库内部开发实现基于负载业务的行为数据采集功…

(九)栅格数据的空间分析——表面分析①

生成等值线 文章目录 生成等值线 1.等值线2.等值线质量3.控制等值线质量3.1对数据进行平滑处理3.2调整起始等值线 4.生成等值线4.1焦点统计4.2生成等值线 1.等值线 沿着特定等值线的折线 (polyline) 可识别出值相同的位置。等值线也是一种呈现表面的有效形式&#xff0c;因为它…

jmeter 报此错误 \report‘ as folder is not empty

jmeter 报此错误 \report’ as folder is not empty 解决方案 出现此错误的原因试因为同一个界面出现同样的文件&#xff0c;只要把文件删除&#xff0c;重新执行此命令即可。 删除文件框住得report和result.jtl 即可 执行成功

计算机图形学 3D渲染 笔记(一)

1 坐标系 大多数计算机屏幕采用的坐标系是以左上角为原点&#xff0c;水平&#xff08;右&#xff09;为x轴&#xff0c;纵向&#xff08;下&#xff09;为y轴3D图形学习中通常使用另一种坐标系&#xff0c; 即 正中心为原点&#xff0c;水平&#xff08;右&#xff09;为x轴&…

【算法题解】44. N 叉树的前序遍历(递归 + 迭代)

这是一道 简单 题 https://leetcode.cn/problems/n-ary-tree-preorder-traversal/ 题目 给定一个 n 叉树的根节点 root &#xff0c;返回 其节点值的 前序遍历 。 n 叉树 在输入中按层序遍历进行序列化表示&#xff0c;每组子节点由空值 null 分隔&#xff08;请参见示例&…

Golang源码探究——从Go程序的入口到GMP模型

在大多数的编程语言中&#xff0c;main函数都是用户程序的入口函数&#xff0c;go中也是如此。那么main.main是整个程序的入口吗, 肯定不是&#xff0c;因为go程序依赖于runtime&#xff0c;在程序的初始阶段需要初始化运行时&#xff0c;之后才会运行到用户的main函数&#xf…

【话题达人】做开发时遇到过无理的需求吗?面对这些无理需求你是怎么做的?

导读 工作过程中难免遇见一些“神奇的甲方”&#xff0c;他们总是会给你提出一些匪夷所思甚至无厘头的需求。你是否也有这样的经历&#xff0c;面对这样“无理的需求”你又是怎么做的呢&#xff1f; 面对这些无理需求时你是怎么做的&#xff1f; 首先深入了解需求&#xff0…

C++一键安装工具(vcpkg)

0. 简介 相较于python而言&#xff0c;C因为其复杂的环境安装一直受到很多人的诟病&#xff0c;比如说一个pcl的安装就需要有很多操作步骤。译过程仍然复杂和多样化。当了解了这些还不够&#xff0c;我们还需要考虑预先编译出哪种类型的开源库程序。比如&#xff1a;Debug还是…