RabbitMQ发布确认和消息回退(6)

概念

发布确认原理

生产者将信道设置成 confirm 模式,一旦信道进入 confirm 模式,所有在该信道上面发布的消息都将会被指派一个唯一的 ID(从 1 开始),一旦消息被投递到所有匹配的队列之后,broker就会发送一个确认给生产者(包含消息的唯一 ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会在将消息写入磁盘之后发出,broker 回传给生产者的确认消息中 delivery-tag 域包含了确认消息的序列号,此外 broker 也可以设置basic.ack 的 multiple 域,表示到这个序列号之前的所有消息都已经得到了处理。

confirm 模式最大的好处在于他是异步的,一旦发布一条消息,生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果 RabbitMQ 因为自身内部错误导致消息丢失,就会发送一条 nack 消息,生产者应用程序同样可以在回调方法中处理该 nack 消息。

两种模式

1.简单确认模式(Simple Publisher Confirm)

在简单确认模式下,每次生产者发送一条消息到 RabbitMQ,都会立即等待 RabbitMQ 返回一个确认消息。如果消息成功发送到 RabbitMQ 服务器上的交换机,并且至少一个队列接收到了消息,RabbitMQ 就会返回一个确认消息给生产者。否则,如果消息发送失败,则会返回一个 Nack 消息。在这种模式下,生产者可以针对每一条消息都进行确认处理,确保消息是否被正确地发送到了 RabbitMQ。

3.批量确认模式(Publisher Confirm with Batch)

在批量确认模式下,生产者可以将一批消息发送到 RabbitMQ,然后等待一段时间后再收到确认消息。这样可以降低每条消息发送的确认成本,并提高性能。批量确认模式通过指定一个大小来定义批量确认的数量,当达到指定的数量后,RabbitMQ 会一次性发送确认消息给生产者。这种模式适用于需要发送大量消息的场景,可以减少确认消息的数量,提高消息发送的效率。

这两种发布确认模式在实现上有一些不同,可以根据实际的业务需求和性能要求来选择合适的模式。简单确认模式更适用于需要对每一条消息进行实时确认的场景,而批量确认模式适用于需要发送大量消息并且希望降低确认消息成本的场景。

开启方法

-- 确认模式配置

spring.rabbitmq.publisher-confirm-type: correlated

  • NONE

禁用发布确认模式,是默认值。

  • CORRELATED

发布消息成功到交换器后会触发回调方法。

  • SIMPLE

测试有两种效果,其一效果和 CORRELATED 值一样会触发回调方法,其二在发布消息成功后使用 rabbitTemplate 调用 waitForConfirms 或 waitForConfirmsOrDie 方法等待 broker 节点返回发送结果,根据返回结果来判定下一步的逻辑,要注意的点是 waitForConfirmsOrDie 方法如果返回 false 则会关闭 channel,则接下来无法发送消息到 broker。(相当于单一发布)

spring:# 配置RabbitMQrabbitmq:host: 192.168.0.70port: 5674username: guestpassword: guest# 虚拟主机virtual-host: my_vhost# 开启确认模式publisher-confirm-type: correlated

 测试

为了方便测试,此机制单独编写类去测试,使用的模式为路由模式(其他模式也可以)

1.创建回调函数

package com.model.callback;import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;/*** @Author: Haiven* @Time: 2024/4/22 17:26* @Description: TODO*/
@Component
@Slf4j
public class MyConfirmCallBack implements RabbitTemplate.ConfirmCallback {/*** 被调用的回调方法* @param correlationData 相关配置信息* @param ack 交换机是否成功收到消息 可以根据 ack 做相关的业务逻辑处理* @param cause 失败原因*/@Overridepublic void confirm(CorrelationData correlationData, boolean ack, String cause) {System.out.println("消息推送回调:配置信息="+correlationData+";结果="+ack+"失败原因="+cause);if(ack){//消息推送成功System.out.println("消息推送成功");}else{System.out.println("消息推送失败:" + cause);}}
}

回调函数会在消息推送到队列后调用

 2.配置回调函数

将上述回调函数设置到mq的配置中,这里再RabbitmqConfig文件中配置

package com.model.config;import com.model.callback.MyConfirmCallBack;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import javax.annotation.Resource;/*** @Author: Haiven* @Time: 2024/4/18 17:28* @Description: TODO*/
@Configuration
public class RabbitmqConfig {@Value("${rabbitmq.work.queue}")private String workQueue;@Resourceprivate MyConfirmCallBack myConfirmCallBack;/*** 工作模式的队列* @return 队列*/@Bean(name = "workQueue")public Queue getWorkQueue(){return QueueBuilder.durable(workQueue).build();}@Beanpublic RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory) {RabbitTemplate rabbitTemplate = new RabbitTemplate();rabbitTemplate.setConnectionFactory(connectionFactory);// 设置开启 Mandatory 强制执行调用回调函数rabbitTemplate.setMandatory(true);//设置回调rabbitTemplate.setConfirmCallback(myConfirmCallBack);//设置回退回调return rabbitTemplate;}}

 3.创建交换机和队列

这里创建ConfirmConfig配置文件与其他队列进行区分

package com.model.config;import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** @Author: Haiven* @Time: 2024/4/22 17:22* @Description: TODO*/
@Configuration
public class ConfirmConfig {/*** 测试发布确认的交换机* @return exchange*/@Bean(name = "confirmExchange")public Exchange getConfirmExchange(){return ExchangeBuilder.directExchange("exchange_confirm").build();}/*** 队列* @return queue*/@Bean(name = "confirmQueue")public Queue getConfirmQueue(){return QueueBuilder.durable("queue_confirm").build();}/*** 绑定队列* @return binding*/@Beanpublic Binding getConfirmBinding(){return BindingBuilder.bind(getConfirmQueue()).to(getConfirmExchange())//路由键 队列1接收debug级别的消息.with("confirm").noargs();}}

 4.创建消费者

package com.model.listener;import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;/*** @Author: Haiven* @Time: 2024/4/22 17:31* @Description: TODO*/
@Component
public class ConfirmConsumer {@RabbitListener(queues = {"queue_confirm"})public void routingConfirm(String msg){System.out.println("消费者 -confirm- 接收消息:" + msg);}
}

 5.发送并接收消息

package com.model.controller;import com.code.domain.Response;
import com.model.service.RabbitService;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;/*** @Author: Haiven* @Time: 2024/4/19 9:46* @Description: TODO*/
@RestController
@RequestMapping("/producer")
public class ProducerController {@Resourceprivate RabbitService rabbitService;@GetMapping("/simple")public Response<Void> simple(String msg){boolean res = rabbitService.simple(msg);return res ? Response.success() : Response.fail();}@GetMapping("/work")public Response<Void> work(String msg){boolean res = rabbitService.work(msg);return res ? Response.success() : Response.fail();}@GetMapping("/sub")public Response<Void> sub(String msg){boolean res = rabbitService.sub(msg);return res ? Response.success() : Response.fail();}@GetMapping("/routing")public Response<Void> routing(String msg, String type){boolean res = rabbitService.routing(msg, type);return res ? Response.success() : Response.fail();}@GetMapping("/topic")public Response<Void> topic(String msg, String type){boolean res = rabbitService.topic(msg, type);return res ? Response.success() : Response.fail();}@GetMapping("/confirm")public Response<Void> confirm(String msg, String type){boolean res = rabbitService.confirm(msg, type);return res ? Response.success() : Response.fail();}
}
package com.model.service.impl;import com.model.service.RabbitService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;import javax.annotation.Resource;/*** @Author: Haiven* @Time: 2024/4/19 10:51* @Description: TODO*/
@Service
@Slf4j
public class RabbitServiceImpl implements RabbitService {@Resourceprivate RabbitTemplate rabbitTemplate;@Value("${rabbitmq.simple.queue}")private String simpleQueue;@Value("${rabbitmq.work.queue}")private String workQueue;@Overridepublic boolean simple(String msg) {try {rabbitTemplate.convertAndSend(simpleQueue, msg);return true;}catch (Exception e){e.printStackTrace();return false;}}@Overridepublic boolean work(String msg) {try {rabbitTemplate.convertAndSend(workQueue, msg);return true;}catch (Exception e){e.printStackTrace();return false;}}@Overridepublic boolean sub(String msg) {try {//路由模式就不能直接发送消息到队列了, 而是发送到交换机,由交换机进行广播, routingKey为路由Key 订阅模式给""rabbitTemplate.convertAndSend("exchange_sub","", msg);return true;}catch (Exception e){e.printStackTrace();return false;}}@Overridepublic boolean routing(String msg, String type) {System.out.println("理由模式发送消息:msg="+msg+",type="+type+"");try {//路由模式就不能直接发送消息到队列了, 而是发送到交换机,由交换机进行广播, routingKey为路由Key 订阅模式给""rabbitTemplate.convertAndSend("exchange_routing",type, msg);return true;}catch (Exception e){e.printStackTrace();return false;}}@Overridepublic boolean topic(String msg, String type) {System.out.println("主题模式发送消息:msg="+msg+",type="+type+"");try {//主题模式会根据 type的通配符进行分发rabbitTemplate.convertAndSend("exchange_topic",type, msg);return true;}catch (Exception e){e.printStackTrace();return false;}}@Overridepublic boolean confirm(String msg, String type) {System.out.println("发布确认模式发送消息:msg="+msg+",type="+type+"");try {rabbitTemplate.convertAndSend("exchange_confirm",type, msg);return true;}catch (Exception e){e.printStackTrace();return false;}}
}

 发送消息

 接收消息

 发送成功后回调函数会执行,即使消费者没有消费该消息,回调函数仍然会执行

 消息回退

当第二条消息推送后消费者是没有消费消息的,虽然推送成功,但是却被丢弃了,而此时生产者需要知道此消息是否被消费成功,所有就使用到了消息回退机制

spring.rabbitmq.publisher-returns=true

1.设置回调函数

MyReturnCallBack

package com.model.callback;import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;/*** @Author: Haiven* @Time: 2024/4/23 10:16* @Description: TODO*/
@Component
@Slf4j
public class MyReturnCallBack implements RabbitTemplate.ReturnCallback {/**** @param msg 消息对象* @param errCode 错误码* @param errMsg 错误信息* @param exchange 交换机* @param rout 路由键*/@Overridepublic void returnedMessage(Message msg, int errCode, String errMsg, String exchange, String rout) {log.debug("消息对象={},错误码={},错误消息={},交换机={},路由键={}", msg, errCode, errMsg, exchange, rout);}
}

 此函数会在消息被丢弃或者消费失败后回调

2.设置到配置中

在RabbitmqConfig配置文件设置

package com.model.config;import com.model.callback.MyConfirmCallBack;
import com.model.callback.MyReturnCallBack;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import javax.annotation.Resource;/*** @Author: Haiven* @Time: 2024/4/18 17:28* @Description: TODO*/
@Configuration
public class RabbitmqConfig {@Value("${rabbitmq.work.queue}")private String workQueue;@Resourceprivate MyConfirmCallBack myConfirmCallBack;@Resourceprivate MyReturnCallBack myReturnCallBack;/*** 工作模式的队列* @return 队列*/@Bean(name = "workQueue")public Queue getWorkQueue(){return QueueBuilder.durable(workQueue).build();}@Beanpublic RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory) {RabbitTemplate rabbitTemplate = new RabbitTemplate();rabbitTemplate.setConnectionFactory(connectionFactory);// 设置开启 Mandatory 强制执行调用回调函数rabbitTemplate.setMandatory(true);//设置发布确认rabbitTemplate.setConfirmCallback(myConfirmCallBack);//设置消息回退rabbitTemplate.setReturnCallback(myReturnCallBack);//设置回退回调return rabbitTemplate;}}

先注入,在设置到rabbitTemplate对象中,这样发送消息时就可以回调

3.发送并接收消息

此处使用confirm交换机测试

发送一条没有路由的消息

 

 此时消息推送成功,但是没有被消费者消费,而是被丢弃,所有消息回退的回调函数执行:

消息对象=(

        Body:'消息',

        MessageProperties : [

                headers={},

                contentType=text/plain,

                contentEncoding=UTF-8,                                           

                contentLength=0,

                receivedDeliveryMode=PERSISTENT,

                priority=0, deliveryTag=0])

错误码=312

错误消息=NO_ROUTE

交换机=exchange_confirm

路由键=unknown

 发送一条路由的消息

 消息推送到交换机成功,并被成功消费,回退消息的回调函数未执行

备份交换机

有了发布确认和回退消息,我们获得了对无法投递消息的感知能力,在生产者的消息无法被投递时发现并处理。但有时候,我们并不知道该如何处理这些无法路由的消息,最多打个日志,然后触发报警,再来手动处理。而通过日志来处理这些无法路由的消息是很不优雅的做法,特别是当生产者所在的服务有多台机器的时候,手动复制日志会更加麻烦而且容易出错。而且设置参数会增加生产者的复杂性,需要添加处理这些被退回的消息的逻辑。如果既不想丢失消息,又不想增加生产者的复杂性,就可以使用备份交换机

 备份交换机可以理解为 RabbitMQ 中交换机的“备份”,当我们为某一个交换机声明一个对应的备份交换机时,就是为它创建一个 备份,当交换机接收到一条不可路由消息时,将会把这条消息转发到备份交换机中,由备份交换机来进行转发和处理,通常备份交换机的类型为 Fanout ,这样就能把所有消息都投递到与其绑定的队列中,然后我们在备份交换机下绑定一个队列,这样所有那些原交换机无法被路由的消息,就会都进 入这个队列了。当然,我们还可以建立一个报警队列,用独立的消费者来进行监测和报警。

1.创建备份交换机

为了方便区分,这里新建一个配置类ConfirmBackupConfig,用于创建虚拟机和队列,备份交换机的类型一定要为fanout

package com.model.config;import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.annotation.Order;/*** @Author: Haiven* @Time: 2024/4/23 15:07* @Description: TODO*/
@Configuration
public class ConfirmBackupConfig {/*** exchange_confirm 交换机的备份交换机* @return exchange*/@Bean(name = "confirmBackupExchange")public Exchange getConfirmBackupExchange(){return ExchangeBuilder.fanoutExchange("exchange_confirm_backup").build();}@Bean("confirmBackupQueue")public Queue getConfirmBackupQueue(){return QueueBuilder.durable("queue_confirm_backup").build();}@Bean("confirmBackupBinding")public Binding getConfirmBackupBinding(){return BindingBuilder.bind(getConfirmBackupQueue()).to(getConfirmBackupExchange()).with("").noargs();}
}

 2.绑定备份交换机

这里我们用上面的消息回退测试用的交换机进行测试,就不额外再创建了,在ConfirmConfig配置文件中直接绑定:

.withArgument("alternate-exchange", "exchange_confirm_backup")

在创建交换机的时候直接指定 alternate-exchange,exchange_confirm_backup为备份交换的名称

package com.model.config;import org.springframework.amqp.core.*;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;/*** @Author: Haiven* @Time: 2024/4/22 17:22* @Description: TODO*/
@Configuration
public class ConfirmConfig {/*** 测试发布确认的交换机* @return exchange*/@Bean(name = "confirmExchange")public Exchange getConfirmExchange(){return ExchangeBuilder.directExchange("exchange_confirm").withArgument("alternate-exchange", "exchange_confirm_backup").durable(true).build();}/*** 队列* @return queue*/@Bean(name = "confirmQueue")public Queue getConfirmQueue(){return QueueBuilder.durable("queue_confirm").build();}/*** 绑定队列* @return binding*/@Beanpublic Binding getConfirmBinding(){return BindingBuilder.bind(getConfirmQueue()).to(getConfirmExchange())//路由键 队列1接收debug级别的消息.with("confirm").noargs();}}

 由于之前创建exchange_confirm交换机的时候没有指定备份交换机,所以这里要先将该交换机删掉,然后重新创建,备份交换机一定要在创建的时候指定

 进入控制台删除,不想删可创建新的交换机用于测试

 3.备份交换机消费者

直接在ConfirmConsumer配置文件中声明confirmBackupConsumer

package com.model.listener;import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;/*** @Author: Haiven* @Time: 2024/4/22 17:31* @Description: TODO*/
@Component
@Slf4j
public class ConfirmConsumer {@RabbitListener(queues = {"queue_confirm"})public void confirmConsumer(String msg){System.out.println("消费者 -confirm- 接收消息:" + msg);}@RabbitListener(queues = {"queue_confirm_backup"})public void confirmBackupConsumer(String msg){log.debug("消费者 -- 备份队列 -- 接收消息:" + msg);}
}

4.发送消息测试

发送一条没有路由的消息

1.可以看到发布确认的消息回调执行,说明已经推送到交换机

2.但是消息回退的回调没有执行,但该消息没有被confirm的消费者消费

3.该消息被备份交换机的消费者消费

有了备份交换机,消息如果消费失败,消息不会回退,而会被备份队列的消费者接收

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

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

相关文章

GDPU 竞赛技能实践 天码行空9

1. 埃式筛法 求区间[2, n]内所有的素数对 &#x1f496; Main.java import java.util.Scanner;public class Main {static int N (int) 1e8, cnt 0;static int[] p new int[N];static boolean[] st new boolean[N];public static void main(String[] args){Scanner sc …

(mac)Promethues监控之mysqld_exporter(MySQL监控)

搭建Mysqld_exporterPrometheusGrafana监控系统 普罗米修斯是后端数据监控平台&#xff0c;通过Mysqld_exporter收集mysql数据&#xff0c;Grafana将数据用图形的方式展示出来 前提&#xff1a;已安装grafana和promethues 1.下载安装Mysql &#xff08;1&#xff09;启动MySQL…

一个联合均值与方差模型的R包——dglm

目录 一、引言二、包的安装与载入三、模拟例子3.1 数据生成3.2 数据查看3.3 模型估计参数 一、引言 在 R 语言中&#xff0c;dglm 包是用于拟合双参数广义线性模型&#xff08;Double Generalized Linear Models&#xff0c;简称 DGLMs&#xff09;的一个工具。这类模型允许同…

工厂高温如何降温?

工厂高温降温的方法有多种&#xff0c;以下是一些常见且有效的策略&#xff1a; 使用风扇或工业大风扇&#xff1a;风扇能够加速空气流动&#xff0c;使人体表面的汗液蒸发速度加快&#xff0c;从而带走更多的热量&#xff0c;实现降温效果。工业大风扇是小型风扇的升级产物&a…

go语言实现简单登陆返回token样例

目录 1、代码实现样例&#xff1a; 2、postman调用&#xff0c;获取登陆后的token&#xff1a; 1、代码实现样例&#xff1a; package mainimport ("net/http""time""github.com/dgrijalva/jwt-go""github.com/gin-gonic/gin" )var …

【网络编程】网络编程中的基本概念及Java实现UDP、TCP客户端服务器程序(万字博文)

系列文章目录 【网络通信基础】网络中的常见基本概念 【网络编程】网络编程中的基本概念及Java实现UDP、TCP客户端服务器程序&#xff08;万字博文&#xff09; 【网络原理】UDP协议的报文结构 及 校验和字段的错误检测机制&#xff08;CRC算法、MD5算法&#xff09; 文章目…

密码学 | 承诺:绑定性 + 隐藏性

&#x1f951;原文&#xff1a;承诺方案&#xff08;Commitment&#xff09;学习笔记 &#x1f951;写在前面&#xff1a; 本文属搬运博客&#xff0c;自己留存学习。本文只会讲承诺的两个安全属性&#xff0c;不会再讲解承诺的定义。 正文 承诺方案需要满足两个安全属性&…

C++笔记:C++中的重载

重载的概念 一.函数重载 代码演示例子&#xff1a; #include<iostream> using namespace std;//函数名相同&#xff0c;在是每个函数的参数不相同 void output(int x) {printf("output int : %d\n", x);return ; }void output(long long x) {printf("outp…

手撕netty源码(一)- NioEventLoopGroup

文章目录 前言一、NIO 与 netty二、NioEventLoopGroup 对象的创建过程2.1 创建流程图2.2 EventExecutorChooser 的创建 前言 processOn文档跳转 本文是手撕netty源码系列的开篇文章&#xff0c;会先介绍一下netty对NIO关键代码的封装位置&#xff0c;主要介绍 NioEventLoopGro…

浅谈叉车车载电脑的市场现状

叉车的起源 叉车源于美国&#xff0c;兴于日本&#xff0c;虽然中国起步较晚&#xff0c;但是近些年来发展迅速。叉车又称叉式装载车&#xff0c;是对于成件托盘类货物进行装卸、堆垛和短距离运输&#xff0c;实现重物搬运作业的轮式工业车辆。 叉车的分类 叉车分为以上六大类…

OpenWrt上的docker容器无法访问外网解决

容器里能ping通OpenWrt的管理地址和wan口地址&#xff0c;但ping外网别的ip或域名就无法访问 简单修改设置就可以&#xff1a; Luci>网络>防火墙>转发&#xff1a;接受 ->保存应用

【Web】DASCTF X GFCTF 2024|四月开启第一局 题解(全)

目录 EasySignin cool_index SuiteCRM web1234 法一、条件竞争(没成功) 法二、session反序列化 EasySignin 先随便注册个账号登录&#xff0c;然后拿bp抓包改密码(username改成admin) 然后admin / 1234567登录 康好康的图片功能可以打SSRF&#xff0c;不能直接读本地文…

Hive安装部署

Apache Hive是一个基于Hadoop分布式文件系统、使用MapReduce算法执行大规模离线数据分析的数据仓库&#xff0c;本文主要描述Hive的安装部署。 如上所示&#xff0c;Hive总体应用架构图&#xff0c;其中&#xff0c;Hive基于HBase或者使用Hadoop分布式文件系统执行MapReduce的分…

Zephyr sensor子系统学习

一、背景 2023年7月份nRF Connect SDK 2.4.0最新版本&#xff0c;使用的Zephyr V3.3版本。从Zephyr 3.5版本在子系统中加入了sensing子系统。 现在最新的nRF Connect SDK 2.6.0 release支持v3.5.99-ncs1&#xff0c;已经支持sensing子系统 nRF52840现在官方支持两个传感器de…

yudao-cloud微服务系统系统模块+后台管理系统成功运行

&#x1f339;作者主页&#xff1a;青花锁 &#x1f339;简介&#xff1a;Java领域优质创作者&#x1f3c6;、Java微服务架构公号作者&#x1f604; &#x1f339;简历模板、学习资料、面试题库、技术互助 &#x1f339;文末获取联系方式 &#x1f4dd; 系列文章目录 第一章 芋…

python基础知识—while和for循环(三)

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 一&#xff1a;while循环1.1程序的三种执行流程1.2while循环1.3循环变量和死循环 二&#xff1a;for循环2.1for循环2.2range 一&…

OSI七层模型、TCP/IP五层模型理解(个人解读,如何理解网络模型)

OSI七层模型 七层模型&#xff0c;亦称OSI&#xff08;Open System Interconnection&#xff09;。参考模型是国际标准化组织&#xff08;ISO&#xff09;制定的一个用于计算机或通信系统间互联的标准体系&#xff0c;一般称为OSI参考模型或七层模型。它是一个七层的、抽象的模…

UVa12313 A Tiny Raytracer

UVa12313 A Tiny Raytracer 题目链接题意分析AC 代码 题目链接 UVA - 12313 A Tiny Raytracer 题意 给出 《训练指南》题意翻译 本题的任务是实现一个小型光线追踪渲染器。场景由若干三角形网格&#xff08;triangle mesh&#xff09;组成&#xff0c;有且仅有一个点光源&…

ESP32开发

目录 1、简介 1.1 种类 1.2 特点 1.3 管脚功能 1.4 接线方式 1.5 工作模式 2、基础AT指令介绍 2.1 AT指令类型 2.2 基础指令及其描述 2.3 使用AT指令需要注意的事 3、AT指令分类和提示信息 3.1 选择是否保存到Flash的区别 3.2 提示信息 3.3 其他会保存到Flash的A…

界面组件DevExpress Blazor UI v23.2 - 支持.NET 8、全新的项目模版

DevExpress Blazor UI组件使用了C#为Blazor Server和Blazor WebAssembly创建高影响力的用户体验&#xff0c;这个UI自建库提供了一套全面的原生Blazor UI组件&#xff08;包括Pivot Grid、调度程序、图表、数据编辑器和报表等&#xff09;。 DevExpress Blazor控件目前已经升级…