04- 基于SpringAMQP封装RabbitMQ,消息队列的Work模型和发布订阅模型

news2024/12/25 2:40:00

SpringAMQP

概述

使用RabbitMQ原生API在代码中设置连接MQ的参数比较繁琐,我们更希望把连接参数写在yml文件中来简化开发

SpringAMQP是基于AMQP协议定义的一套API规范,将RabbitMQ封装成一套模板用来发送和接收消息

  • AMQP(Advanced Message Queuing Portocol)是用于在应用程序之间传递业务消息的开放标准, 该协议与语言和平台无关更符合微服务中独立性的要求
  • SpringAMQP由两部分组成: spring-AMQP是基础抽象,spring-Rabbit是底层的默认实现,并且利用SpringBoot对其实现了自动装配

SpringAMQP提供的三个功能

  • 自动声明队列、交换机及其绑定关系
  • 基于注解的监听模式(监听器容器),用于异步异步处理入站消息
  • 封装了RabbitTemplate工具,用于发送消息(之前在Redis中我们也接触过RedisTemplate)
方法名功能
convertAndSend(队列名称, Object类型的消息对象)发送消息到队列
convertAndSend(交换机名称, 路由key, Object类型的消息对象)发送消息到交换机,交换机再根据路由规则发送到对应的队列
注解名功能(声明交换机和队列时也可以基于配置类的方式注入到容器中)
@RabbitListener注解声明要监听/绑定的队列
@Exchange声明交换机
@Queue声明队列
@QueueBinding声明交换机和队列的绑定关系

Work模型

Wrok模型的使用: 多个消费者绑定到一个队列,同一条消息只会被一个消费者处理, 可以设置prefetch来控制消费者预取的消息数量

Basic Queue

第一步: 在父工程mq-demo中引入AMQP依赖,这样子模块也继承了该依赖就可以基于RabbitTemplate模板实现消息的发送和接收

<!--AMQP依赖,包含RabbitMQ-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

第二步:在publisher服务的application.yml中配置MQ地址,Spring已经帮我们跟MQ建立了连接,剩下只需要指定发送什么消息到哪个队列

spring:
  rabbitmq:
    host: 192.168.88.128 # 主机名
    port: 5672 #端口
    username: root # 用户名
    password: 123456 # 密码
    virtual-host: / # 虚拟主机

第三步: 在publisher服务中编写测试类SpringAmqpTest利用RabbitTemplate发送消息到指定队列(如果没有创建simple.queue可以在RabbitMQ管理平台创建)

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSimpleQueue() {
        String queueName = "simple.queue";
        String message = "Hello, SpringAMQP! ";
        rabbitTemplate.convertAndSend(queueName, message);
    }
}

第四步:在consumer服务的application.yml中同样配置MQ地址,这样Spring已经帮我们跟MQ建立了连接,剩下只需要关心要监听哪个队列以及监听到要干什么事儿

spring:
  rabbitmq:
    host: 192.168.88.128 # 主机名
    port: 5672 #端口
    username: root # 用户名
    password: 123456 # 密码
    virtual-host: / # 虚拟主机

第五步: 在consumer服务新建一个类用于编写消费逻辑,使用@Component注解将该类声明为一个Bean,使用@RabbitListener注解声明要监听/绑定的队列

@Component
public class SpringRabbitListener {
    @RabbitListener(queues = "simple.queue")
    public void listenSimpleQueueMessage(String msg) {
        // msg参数就是接收到的消息,Spring已经帮我们二进制数据转成了字符串
        System.out.println("Spring 消费者接收到消息:【" + msg + "】");
    }
}

第六步: 启动Consumer服务查看控制台接收到的消息,如果多次使用publisher服务发送消息consumer服务也会接收多次消息

  • 消息一旦被消费就会从队列删除,RabbitMQ没有消息回溯功能
Spring 消费者接收到消息:【Hello, SpringAMQP! 】

Wrok Queue(Task queues)

Work Queue也被称为任务模型,简单来说就是让多个消费者绑定到一个队列让它们共同处理队列中的信息提高消息的处理速度(同一条消息只会被一个消费者处理)

  • 如果消息处理比较耗时那么生产消息的速度会远远大于消息的消费速度,就会导致消息堆积的越来越多无法及时处理

在这里插入图片描述

第一步: 在publisher服务中的SpringAmqpTest类测试方法中循环发送消息模拟大量消息堆积的场景

@Test
public void testWorkQueue() throws InterruptedException {
    String queueName = "simple.queue";
    String message = "Hello, SpringAMQP! __ ";
    for (int i = 1; i <= 50; i++) {
        // 循环发送50条消息,带上消息编号
        rabbitTemplate.convertAndSend(queueName, message + i);
        // 休眠20ms,模拟在1s内发送完
        Thread.sleep(20);
    }
}

第二步: 在consumer服务的SpringRabbitListener类中添加两个方法模拟多个消费者绑定同一个队列(一个方法对应一个消费者)

@RabbitListener(queues = "simple.queue")
public void listenWorkQueueMessage1(String msg) throws InterruptedException {
    System.out.println("消费者1 接收到消息:【" + msg + "】" + LocalDateTime.now());
    // 休眠20ms,1s大致能处理50条消息
    Thread.sleep(20);
}

@RabbitListener(queues = "simple.queue")
public void listenWorkQueueMessage2(String msg) throws InterruptedException {
    System.err.println("消费者2........接收到消息:【" + msg + "】" + LocalDateTime.now());
    // 休眠200ms,1s大概能处理5条消息
    Thread.sleep(200);
}

第三步: 执行publisher服务中刚编写的测试方法发送50条消息,启动consumer服务查看控制台输出

  • 消费者1很快就完成了自己的25条消息,消费者2却在缓慢的处理自己的25条消息,即当前的处理方式是平均分配给每个消费者
消费者2........接收到消息:【Hello, SpringAMQP! __ 1】2022-12-23T13:16:41.407
消费者1 接收到消息:【Hello, SpringAMQP! __ 2】2022-12-23T13:16:41.414
消费者1 接收到消息:【Hello, SpringAMQP! __ 4】2022-12-23T13:16:41.461
消费者1 接收到消息:【Hello, SpringAMQP! __ 6】2022-12-23T13:16:41.502
消费者1 接收到消息:【Hello, SpringAMQP! __ 8】2022-12-23T13:16:41.549
消费者1 接收到消息:【Hello, SpringAMQP! __ 10】2022-12-23T13:16:41.592
消费者2........接收到消息:【Hello, SpringAMQP! __ 3】2022-12-23T13:16:41.609
消费者1 接收到消息:【Hello, SpringAMQP! __ 12】2022-12-23T13:16:41.635
消费者1 接收到消息:【Hello, SpringAMQP! __ 14】2022-12-23T13:16:41.680
消费者1 接收到消息:【Hello, SpringAMQP! __ 16】2022-12-23T13:16:41.722
消费者1 接收到消息:【Hello, SpringAMQP! __ 18】2022-12-23T13:16:41.767
....
消费者2........接收到消息:【Hello, SpringAMQP! __ 35】2022-12-23T13:16:44.837
消费者2........接收到消息:【Hello, SpringAMQP! __ 37】2022-12-23T13:16:45.040
消费者2........接收到消息:【Hello, SpringAMQP! __ 39】2022-12-23T13:16:45.240
消费者2........接收到消息:【Hello, SpringAMQP! __ 41】2022-12-23T13:16:45.444
消费者2........接收到消息:【Hello, SpringAMQP! __ 43】2022-12-23T13:16:45.646
消费者2........接收到消息:【Hello, SpringAMQP! __ 45】2022-12-23T13:16:45.846
消费者2........接收到消息:【Hello, SpringAMQP! __ 47】2022-12-23T13:16:46.048
消费者2........接收到消息:【Hello, SpringAMQP! __ 49】2022-12-23T13:16:46.250

我们希望按照服务器的处理能力来处理消息,避免出现消息积压的风险,在consumer服务中的application.yml文件添加prefetch配置控制预取消息的上限

spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息

发布订阅模型

在订阅模型中多了一个exchange角色,发送消息的流程也发生了变化

  • Publisher(生产者): 消息不再发送到队列中而是发送给exchange(交换机)
  • Exchange(交换机): 一方面负责接收生产者发送的消息,另一方面负责将消息按照规则路由到与之绑定的队列(如递交给某个特定队列、递交给所有队列)
  • Consumer(消费者): 订阅队列的消息
  • Queue(消息队列): 接收消息并缓存消息

交换机如何处理接收到的消息取决于Exchange的类型

  • Fanout(广播): 将消息交给所有绑定到交换机的队列
  • Direct(定向): 把消息交给符合指定routing key的队列
  • Topic(通配符): 把消息交给符合routing pattern(路由模式)的队列
  • 交换机只负责转发消息不具备存储消息的能力,因此如果没有任何队列与Exchange绑定或者没有符合路由规则的队列,那么消息会丢失

Spring提供了一个接口Exchange来表示所有不同类型的交换机(路由规则不同),Queue和Binding也是Springframework提供的类
在这里插入图片描述

Fanout(广播)

在广播模式下消息发送的流程

  • 广播模式下可以声明多个队列,但每个队列都要绑定一个Exchange(交换机)
  • 生产者发送的消息只能发送到交换机,由交换机决定要发给哪个队列
  • FanoutExchange类型的交换机会把消息发送给每一个跟其绑定的队列,然后订阅队列的消费者都能拿到同一条消息

在这里插入图片描述

第一步: 在consumer服务创建一个配置类config/FanoutConfig用来声明队列(Queue)和交换机(FanoutExchange)以及队列和交换机的绑定关系(Binding)

@Configuration
public class FanoutConfig {
    /**
     * 声明交换机
     * @return Fanout类型交换机
     */
    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange("itcast.fanout");
    }

    /**
     * 第1个队列,方法的名称就是bean的名称
     */
    @Bean
    public Queue fanoutQueue1() {
        return new Queue("fanout.queue1");
    }

    /**
     * 绑定第1个队列和交换机,根据方法形参的名称自动注入容器中对应的bean(队列和交换机)
     */
    @Bean
    public Binding bindingQueue1(Queue fanoutQueue1, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
    }

    /**
     * 第2个队列
     */
    @Bean
    Queue fanoutQueue2() {
        return new Queue("fanout.queue2");
    }

    /**
     * 绑定第2个队列和交换机,根据方法形参的名称中自动注入容器中对应的bean(队列和交换机)
     */
    @Bean
    public Binding bindingQueue2(Queue fanoutQueue2, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
    }
}

第二步: 在publisher服务的SpringAmqpTest类中添加测试方法向交换机发送一次消息

@Test
public void testFanoutExchange() {
    String exchangeName = "itcast.fanout";
    String message = "Hello Everyone~";
    // 交换机名称,路由key,消息
    rabbitTemplate.convertAndSend(exchangeName, "", message);
}

第三步: 在consumer服务的SpringRabbitListener中添加两个方法作为两个消费者分别绑定fanout.queue1fanout.queue2接收消息

@RabbitListener(queues = "fanout.queue1")
public void listenFanoutQueue1(String msg) {
    System.out.println("消费者1收到广播消息:【" + msg + "】");
}

@RabbitListener(queues = "fanout.queue2")
public void listenFanoutQueue2(String msg) {
    System.out.println("消费者2收到广播消息:【" + msg + "】");
}

第四步: 重启consumer服务然后运行publisher中新编写的测试方法查看控制台输出

消费者1收到广播消息:【Hello Everyone~】
消费者2收到广播消息:【Hello Everyone~】

Direct(基于注解方式声明)

在Fanout广播模式中一条消息会发给所有与交换机绑定队列,但是在某些场景下我们更希望不同的消息发送给不同的队列,这时就要用到Direct类型的Exchange

Direct类型的Exchange不再把接收的消息交给每一个与其绑定的队列,只有当队列的BindingKey消息的RoutingKey完全一致时该队列才会收到消息

  • 每一个队列与虚拟机绑定时需要指定一个BindingKey(路由key)
  • 发布者向Exchange发送消息时也必须指定消息的RoutingKey(路由规则)
  • Exchange将消息路由到BindingKey和消息的RoutingKey一致的队列

在这里插入图片描述

Direct交换机与Fanout交换机的差异

  • Fanout交换机会将消息路由给每一个与之绑定的队列
  • Direct交换机根据RoutingKey判断路由给哪个队列,如果多个队列具有相同的RoutingKey,则与Fanout功能类似,也是把消息路由给每一个匹配的队列

第一步: 在consumer服务SpringRabbitListener类中添加两个消费者监听direct.queue1和direct.queue2同时基于RabbitListener注解来声明队列和交换机

  • 由于基于Bean的方式声明队列与交换机比较麻烦所以Spring还提供了基于注解的方式来声明Exchange(@Exchange),Queue(@Queue),Binding(@QueueBinding)
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(name = "direct.queue1"),
        exchange = @Exchange(name = "directExchange", type = ExchangeTypes.DIRECT),// 默认type = "direct"
        key = {"red", "blue"}// key就是队列和交换机的BindingKey
))
public void listenDirectQueue1(String msg) {
    System.out.println("消费者收到direct.queue1的消息:【" + msg + "】");
}

@RabbitListener(bindings = @QueueBinding(
        value = @Queue(name = "direct.queue2"),
        exchange = @Exchange(name = "directExchange", type = ExchangeTypes.DIRECT),
        key = {"red", "yellow"}
))
public void listenDirectQueue2(String msg) {
    System.out.println("消费者收到direct.queue2的消息:【" + msg + "】");
}

第二步: 在publisher中编写三个测试方法向directExchange发送三条消息并指定消息的RoutingKey

@Test
public void testDirectExchange() {
    String exchangeName = "directExchange";
    String message = "hello,blue";
    rabbitTemplate.convertAndSend(exchangeName, "blue", message);
}

@Test
public void testDirectExchange() {
    String exchangeName = "directExchange";
    String message = "hello,yellow";
    rabbitTemplate.convertAndSend(exchangeName, "yellow", message);
}

@Test
public void testDirectExchange() {
    String exchangeName = "directExchange";
    String message = "hello,red";
    rabbitTemplate.convertAndSend(exchangeName, "red", message);
}

第三步: 重启consumer服务运行以上三个测试方法查看控制台输出

消费者收到direct.queue1的消息:hello,blue
消费者收到direct.queue2的消息:hello,yellow
消费者收到direct.queue1的消息:hello,red
消费者收到direct.queue2的消息:hello,red

Topic(使用通配符)

Topic类型和Direct类型的Exchange都可以根据RoutingKey把消息路由到不同的队列,只不过Topic类型的Exchange与队列绑定时指定的BindingKey可以使用通配符

  • #: 匹配一个或多个词,如item.#能够匹配item.kyle.violet或者item.kyle
  • *: 仅匹配一个词,如item.*只能匹配item.kyle或者item.violet

Direct交换机与Topic交换机的差异

  • Topic交换机接收的消息Routing Key必须是多个单词,多个单词间以.分割
  • Topic交换机与队列绑定时的Binding key可以指定通配符,其中#表示0个或多个单词,*仅表示1个单词

在这里插入图片描述

第一步: 在publisher服务的SpringAmqpTest类中添加测试方法向交换机发送不同RoutingKey的消息

@Test
public void testTopicExchange() {
    String exchangeName = "topic";
    String message = "如何看待马化腾称「短视频会侵蚀游戏时间」,「腾讯游戏要聚焦精品」?";
    rabbitTemplate.convertAndSend(exchangeName, "china.news", message);
}

@Test
public void testTopicExchange() {
    String exchangeName = "topic";
    String message = "今 天 也 是 个 emo 的 好 天 气";
    rabbitTemplate.convertAndSend(exchangeName, "china.weather", message);
}

@Test
public void testTopicExchange() {
    String exchangeName = "topic";
    String message = "自由美利坚,枪击每一天";
    rabbitTemplate.convertAndSend(exchangeName, "us.news", message);
}

第二步: 在consumer服务的SpringRabbitListener类中消费方法监听topic.queue1和topic.queue2, 利用@RabbitListener相关的注解声明Exchange,Queue,BindingKey

@RabbitListener(bindings = @QueueBinding(
        value = @Queue(name = "topic.queue1"),
        exchange = @Exchange(name = "topic", type = ExchangeTypes.TOPIC),
        key = "china.#"
))
public void listenTopicQueue1(String msg) {
    System.out.println("消费者接收到topic.queue1的消息:【" + msg + "】");
}

@RabbitListener(bindings = @QueueBinding(
        value = @Queue(name = "topic.queue2"),
        exchange = @Exchange(name = "topic", type = ExchangeTypes.TOPIC),
        key = "#.news"
))
public void listenTopicQueue2(String msg) {
    System.out.println("消费者接收到topic.queue2的消息:【" + msg + "】");
}
消费者接收到topic.queue2的消息:【如何看待马化腾称「短视频会侵蚀游戏时间」,「腾讯游戏要聚焦精品」?】
消费者接收到topic.queue1的消息:【如何看待马化腾称「短视频会侵蚀游戏时间」,「腾讯游戏要聚焦精品」?】
消费者接收到topic.queue1的消息:【今 天 也 是 个 emo 的 好 天 气】
消费者接收到topic.queue2的消息:【自由美利坚,枪击每一天】

消息转换器

Spring的convertAndSend方法接收消息的类型是Object也就是说它可以把任意对象类型的消息序列化为字节发送给MQ(默认采用JDK的序列化方式)

第一步: 在config.FanoutConfig中声明一个队列

@Bean
public Queue objectQueue(){
    return new Queue("object.queue");
}

第二步: 在测试方法中发送一个Map类型的对象消息到MQ

@Test
public void testSimpleQueue() {
    String queueName = "simple.queue";
    HashMap<String, Object> hashMap = new HashMap<>();
    hashMap.put("名称", "艾尔登法环");
    hashMap.put("价格", 299);
    rabbitTemplate.convertAndSend(queueName, hashMap);
}

JDK序列化的方式存在数据体积过大,有安全漏洞,可读性差的问题,我们更希望消息的体积更小可读性更高,因此可以使用JSON方式来做序列化和反序列化

在这里插入图片描述

第一步: 在publisher和consumer两个服务中引入依赖(或者直接在父工程mq-demo中引入依赖)

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.9.10</version>
</dependency>

第二步: 在publisher和consumer的启动类中都添加jsonMessageConverter并注册到容器中

@Bean
publis MessageConverter jsonMessageConverter() {
    return new Jackson2JsonMessageConverter();
}

第三步: 再次执行测试方法发送一个Map类型的对象消息到MQ,修改consumer服务的SpringRabbitListener添加方法并重启服务

  • consumerpublisher的序列化器需保持一致,同时consumer中接收数据的类型也需要和发送数据的类型保持一致,如HashMap<String, Object>
@RabbitListener(queues = "simple.queue")
public void listenSimpleQueueMessage(HashMap<String, Object> msg) throws InterruptedException {
    System.out.println("消费者接收到消息:【" + msg + "】");
}

在这里插入图片描述

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

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

相关文章

go语言基础笔记

1.基本类型 1.1. 基本类型 bool int: int8, int16, int32(rune), int64 uint: uint8(byte), uint16, uint32, uint64 float32, float64 string 复数&#xff1a;complex64, complex128 复数有实部和虚部&#xff0c;complex64的实部和虚部为32位&#xff0c;complex128的实部…

某赛通电子文档安全管理系统 DecryptApplication 任意文件读取漏洞(2024年3月发布)

漏洞简介 某赛通电子文档安全管理系统 DecryptApplication 接口处任意文件读取漏洞&#xff0c;未经身份验证的攻击者利用此漏洞获取系统内部敏感文件信息&#xff0c;导致系统处于极不安全的状态。 漏洞等级高危影响版本*漏洞类型任意文件读取影响范围>1W 产品简介 …

案例分析篇06:数据库设计相关28个考点(17~22)(2024年软考高级系统架构设计师冲刺知识点总结系列文章)

专栏系列文章推荐: 2024高级系统架构设计师备考资料(高频考点&真题&经验)https://blog.csdn.net/seeker1994/category_12593400.html 【历年案例分析真题考点汇总】与【专栏文章案例分析高频考点目录】(2024年软考高级系统架构设计师冲刺知识点总结-案例分析篇-…

Jmeter入参问题小记

表单入参的时候&#xff0c;这个地方需要勾选&#xff0c;如果不☑️选的话&#xff0c;会提示errorMsg":"Required String parameter code is not present",

Leet code 三步问题

解题思路&#xff1a;动态规划 先观察 1级台阶 1种方法 2级台阶 2种方法 3级台阶 4种方法 4级台阶 7种方法 5级台阶 13种方法 可以看出规律 从3级台阶后 每级台阶需要从前三层台阶和相加 注意&#xff1a;后面值会过大 需要在相加之后就模运算1000000007 代码如下 clas…

.NET高级面试指南专题十八【 外观模式模式介绍,提供了简化的接口,隐藏系统的复杂性】

介绍&#xff1a; 外观模式是一种结构设计模式&#xff0c;它提供了一个统一的接口&#xff0c;用于访问子系统中的一组接口。外观模式定义了一个高层接口&#xff0c;使得子系统更容易使用。 原理&#xff1a; 外观类&#xff08;Facade Class&#xff09;&#xff1a;提供了一…

C++面试问题收集

0 持续更新中 目录 0 持续更新中 1 C语言相关 1.1 malloc/free和new/delete区别 1.2 内存泄漏 1.3 堆区和栈区的区别 1.4 宏定义和const的区别 1.5 多态 1.6 类中的静态成员变量 2 操作系统相关 2.1 进程和&#xff08;用户&#xff09;线程的区别 2.2 系统调用 2.3…

【论文阅读】MoCoGAN: Decomposing Motion and Content for Video Generation

MoCoGAN: Decomposing Motion and Content for Video Generation 引用&#xff1a; Tulyakov S, Liu M Y, Yang X, et al. Mocogan: Decomposing motion and content for video generation[C]//Proceedings of the IEEE conference on computer vision and pattern recognitio…

【Java基础】IO流(二)字符集知识

目录 字符集知识 1、GBK字符集 2、Unicode字符集&#xff08;万国码&#xff09; 3、乱码 4、Java中编码和解码的方法 字符集知识 字符&#xff08;Character&#xff09;&#xff1a;在计算机和电信技术中&#xff0c;一个字符是一个单位的字形、类字形单位或符号的基本信…

最详细数据仓库项目实现:从0到1的电商数仓建设(采集部分)

1、数据库和数据仓库的区别&#xff1a; 数据仓库就是data warehouse&#xff0c;数据小卖店&#xff0c;相当于是对数据加工&#xff0c;计算然后对外提供服务&#xff0c;而不是单纯的存储 2、数据流转过程中数据仓库中的数据源部分 数据源部分的数据**不是只同步数据库当…

YOLOv8改进 | 图像去雾 | 特征融合注意网络FFA-Net增强YOLOv8对于模糊图片检测能力(北大和北航联合提出)

一、本文介绍 本文给大家带来的改进机制是由北大和北航联合提出的FFA-net: Feature Fusion Attention Network for Single Image Dehazing图像增强去雾网络,该网络的主要思想是利用特征融合注意力网络(Feature Fusion Attention Network)直接恢复无雾图像,FFA-Net通过特征…

海格里斯HEGERLS托盘搬运机器人四向车引领三维空间集群设备柔性运维

随着市场的不断迅猛发展变化&#xff0c;在物流仓储中&#xff0c;无论是国内还是海外&#xff0c;都对托盘式解决方案需求量很大。顾名思义&#xff0c;托盘式解决方案简单理解就是将产品放置在托盘上进行存储、搬运和拣选。 面对托盘式方案需求&#xff0c;行业中常见的方案是…

Wechaty 企业微信机器人报:return ‘port‘ in address

1.使用的依赖&#xff1a; "file-box": "^1.5.5", "qrcode": "^1.5.3", "install": "^0.13.0", "grpc/grpc-js": "^1.10.1","juzi/wechaty": "^1.0.65", "juzi/wec…

学习使用js获取当前ip地址的方法,使用第三方API获取ip地址

学习使用js获取当前ip地址的方法,使用第三方API获取ip地址 使用 DNS 查询使用第三方 API 使用 DNS 查询 DNS 是一种用于解析主机名为 IP 地址的系统。可以使用 JavaScript DNS 查询来获取本机IP地址。下面是如何使用 JavaScript 进行DNS查询的示例代码。 <p class"loc…

csp模拟题(201604-2,俄罗斯方块模拟下坠)

题目 问题描述 俄罗斯方块是俄罗斯人阿列克谢帕基特诺夫发明的一款休闲游戏。   游戏在一个15行10列的方格图上进行&#xff0c;方格图上的每一个格子可能已经放置了方块&#xff0c;或者没有放置方块。每一轮&#xff0c;都会有一个新的由4个小方块组成的板块从方格图的上方…

智慧城市物联网建设:提升城市管理效率与居民生活品质

目录 一、智慧城市物联网建设的意义 1、提升城市管理效率 2、改善居民生活品质 3、促进城市可持续发展 二、智慧城市物联网建设面临的挑战 1、技术标准与互操作性问题 2、数据安全与隐私保护问题 3、投资与回报平衡问题 三、智慧城市物联网建设的实施策略 1、制定统一…

让数据在业务间高效流转,镜舟科技与NineData完成产品兼容互认

近日&#xff0c;镜舟科技与NineData完成产品兼容测试。在经过联合测试后&#xff0c;镜舟科技旗下产品与NineData云原生智能数据管理平台完全兼容&#xff0c;整体运行高效稳定。 镜舟科技致力于帮助中国企业构建卓越的数据分析系统&#xff0c;打造独具竞争力的“数据护城河”…

【SysBench】Linux 安装 sysbench-1.20

安装目的是为了对 MySQL 8.0.x 、PostgreSQL 进行基准测试。 0、sysbench 简介 sysbench 是一个可编写脚本的多线程基准测试工具&#xff0c;基于 LuaJIT 。 它最常用于数据库基准测试&#xff0c;但也可以 用于创建任意不涉及数据库服务器的复杂工作负载。 sysbench 附带以…

服务注册与发现:Nacos

为什么需要服务注册与发现 假设 mafeng-user 用户微服务部署了多个实例&#xff08;组成集群模式&#xff09;&#xff0c;如下图所示&#xff1a; 会出现以下几个问题&#xff1a; mafeng-order订单微服务发出Http远程调用时&#xff0c;该如何得知mafeng-user实例的IP和端口…

【机器学习智能硬件开发全解】(五)—— 政安晨:嵌入式系统基本素养【总线、地址、指令集、微架构】

在智能硬件领域中&#xff0c;一个核心概念是嵌入式系统&#xff0c;整体结构可以分为以下几个主要组成部分&#xff1a; 控制器&#xff1a;控制器是嵌入式系统的核心&#xff0c;负责处理和执行系统中的各种任务和功能。它通常由中央处理器&#xff08;CPU&#xff09;和相关…