SpringBoot基于RabbitMQ实现消息延迟队列方案

知识小科普

在此之前,简单说明下基于RabbitMQ实现延时队列的相关知识及说明下延时队列的使用场景。

延时队列使用场景

在很多的业务场景中,延时队列可以实现很多功能,此类业务中,一般上是非实时的,需要延迟处理的,需要进行重试补偿的。

  • 订单超时关闭:在支付场景中,一般上订单在创建后30分钟或1小时内未支付的,会自动取消订单。
  • 短信或者邮件通知:在一些注册或者下单业务时,需要在1分钟或者特定时间后进行短信或者邮件发送相关资料的。本身此类业务于主业务是无关联性的,一般上的做法是进行异步发送。
  • 重试场景:比如消息通知,在第一次通知出现异常时,会在隔几分钟之后进行再次重试发送。

RabbitMQ实现延时队列

本身在RabbitMQ中是未直接提供延时队列功能的,但可以使用 TTL(Time-To-Live,存活时间)DLX(Dead-Letter-Exchange ,死信队列交换机)的特性实现延时队列的功能。

存活时间(Time-To-Live 简称 TTL)

RabbitMQ中可以对队列和消息分别设置TTL,TTL表明了一条消息可在队列中存活的最大时间。当某条消息被设置了TTL或者当某条消息进入了设置了TTL的队列时,这条消息会在TTL时间后死亡成为Dead Letter。如果既配置了消息的TTL,又配置了队列的TTL,那么较小的那个值会被取用。

死信交换(Dead Letter Exchanges 简称 DLX)

上个知识点也提到了,设置了 TTL 的消息或队列最终会成为 Dead Letter ,当消息在一个队列中变成死信之后,它能被重新发送到另一个交换机中,这个交换机就是DLX,绑定此DLX的队列就是死信队列。

一个消息变成死信一般上是由于以下几种情况;

消息被拒绝
消息过期
队列达到了最大长度。

所以,通过 TTLDLX 的特性可以模拟实现延时队列的功能。当队列中的消息超时成为死信后,会把消息死信重新发送到配置好的交换机中,然后分发到真实的消费队列。故简单来说,我们可以创建2个队列,一个队列用于发送消息,一个队列用于消息过期后的转发的目标队列。

SpringBoot集成RabbitMQ实现延时队列实战

以下使用 SpringBoot 集成 RabbitMQ 进行实战说明,在进行 http 消息通知时,若通知失败(地址不可用或者连接超时)时,将此消息转入延时队列中,待特定时间后进行重新发送。

0.引入pom依赖

    <!-- rabbit --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency><!-- 简化http操作 --><dependency><groupId>cn.hutool</groupId><artifactId>hutool-http</artifactId><version>4.5.16</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-json</artifactId><version>4.5.16</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>

1.编写rabbitmq配置文件(关键配置)RabbitConfig.java

/** 
*
* @ClassName   类名:RabbitConfig 
* @Description 功能说明:
* <p>
* TODO
*</p>
************************************************************************
* @date        创建日期:2019年7月17日
* @author      创建人:oKong
* @version     版本号:V1.0
*<p>
***************************修订记录*************************************
* 
*   2019年7月17日   oKong   创建该类功能。
*
***********************************************************************
*</p>
*/
@Configuration
public class RabbitConfig {@AutowiredConnectionFactory connectionFactory;/*** 消费者线程数 设置大点 大概率是能通知到的*/@Value("${http.notify.concurrency:50}")int concurrency;/*** 延迟队列的消费者线程数 可设置小点*/@Value("${http.notify.delay.concurrency:20}")int delayConcurrency;@Beanpublic RabbitAdmin rabbitAdmin() {return new RabbitAdmin(connectionFactory);}@Beanpublic DirectExchange httpMessageNotifyDirectExchange(RabbitAdmin rabbitAdmin) {//durable 是否持久化//autoDelete 是否自动删除,即服务端或者客服端下线后 交换机自动删除DirectExchange directExchange = new DirectExchange(ApplicationConstant.HTTP_MESSAGE_EXCHANGE,true,false);directExchange.setAdminsThatShouldDeclare(rabbitAdmin);return directExchange;}//设置消息队列@Beanpublic Queue httpMessageStartQueue(RabbitAdmin rabbitAdmin) {/*创建接收队列,4个参数name - 队列名称durable - false,不进行持有化exclusive - true,独占性autoDelete - true,自动删除*/Queue queue = new Queue(ApplicationConstant.HTTP_MESSAGE_START_QUEUE_NAME, true, false, false);queue.setAdminsThatShouldDeclare(rabbitAdmin);return queue;}//队列绑定交换机@Beanpublic Binding bindingStartQuene(RabbitAdmin rabbitAdmin,DirectExchange httpMessageNotifyDirectExchange, Queue httpMessageStartQueue) {Binding binding = BindingBuilder.bind(httpMessageStartQueue).to(httpMessageNotifyDirectExchange).with(ApplicationConstant.HTTP_MESSAGE_START_RK);binding.setAdminsThatShouldDeclare(rabbitAdmin);return binding;}@Beanpublic Queue httpMessageOneQueue(RabbitAdmin rabbitAdmin) {Queue queue = new Queue(ApplicationConstant.HTTP_MESSAGE_ONE_QUEUE_NAME, true, false, false);queue.setAdminsThatShouldDeclare(rabbitAdmin);return queue;}@Beanpublic Binding bindingOneQuene(RabbitAdmin rabbitAdmin,DirectExchange httpMessageNotifyDirectExchange, Queue httpMessageOneQueue) {Binding binding = BindingBuilder.bind(httpMessageOneQueue).to(httpMessageNotifyDirectExchange).with(ApplicationConstant.HTTP_MESSAGE_ONE_RK);binding.setAdminsThatShouldDeclare(rabbitAdmin);return binding;}//-------------设置延迟队列--开始--------------------@Beanpublic Queue httpDelayOneQueue() {//name - 队列名称//durable - true//exclusive - false//autoDelete - falsereturn QueueBuilder.durable("http.message.dlx.one")//以下是重点:当变成死信队列时,会转发至 路由为x-dead-letter-exchange及x-dead-letter-routing-key的队列中.withArgument("x-dead-letter-exchange", ApplicationConstant.HTTP_MESSAGE_EXCHANGE).withArgument("x-dead-letter-routing-key", ApplicationConstant.HTTP_MESSAGE_ONE_RK).withArgument("x-message-ttl", 1*60*1000)//1分钟 过期时间(单位:毫秒),当过期后 会变成死信队列,之后进行转发.build();}//绑定到交换机上@Beanpublic Binding bindingDelayOneQuene(RabbitAdmin rabbitAdmin, DirectExchange httpMessageNotifyDirectExchange, Queue httpDelayOneQueue) {Binding binding = BindingBuilder.bind(httpDelayOneQueue).to(httpMessageNotifyDirectExchange).with("delay.one");binding.setAdminsThatShouldDeclare(rabbitAdmin);return binding;}//-------------设置延迟队列--结束--------------------//建议将正常的队列和延迟处理的队列分开//设置监听容器@Bean("notifyListenerContainer")public SimpleRabbitListenerContainerFactory httpNotifyListenerContainer() {SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);// 手动ackfactory.setConnectionFactory(connectionFactory);factory.setPrefetchCount(1);factory.setConcurrentConsumers(concurrency);return factory;}// 设置监听容器@Bean("delayNotifyListenerContainer")public SimpleRabbitListenerContainerFactory httpDelayNotifyListenerContainer() {SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);// 手动ackfactory.setConnectionFactory(connectionFactory);factory.setPrefetchCount(1);factory.setConcurrentConsumers(delayConcurrency);return factory;}
}

ApplicationConstant.java

public class ApplicationConstant {/*** 发送http通知的 exchange 队列*/public static final String HTTP_MESSAGE_EXCHANGE = "http.message.exchange";/*** 配置消息队列和路由key值*/public static final String HTTP_MESSAGE_START_QUEUE_NAME = "http.message.start";public static final String HTTP_MESSAGE_START_RK = "rk.start";public static final String HTTP_MESSAGE_ONE_QUEUE_NAME = "http.message.one";public static final String HTTP_MESSAGE_ONE_RK = "rk.one";/*** 通知队列对应的延迟队列关系,即过期队列之后发送到下一个的队列信息,可以根据实际情况添加,当然也可以根据一定规则自动生成*/public static final Map<String,String> delayRefMap = new HashMap<String, String>() {/*** */private static final long serialVersionUID = -779823216035682493L;{put(HTTP_MESSAGE_START_QUEUE_NAME, "delay.one");}};    
}

简单来说,就是创建一个正常消息发送队列,用于接收http消息请求的参数,同时进行http请求。同时,创建一个延时队列,设置其 x-dead-letter-exchangex-dead-letter-routing-key
x-message-ttl 值,将其转发到正常的队列中。使用一个map对象维护一个关系,当正常消息异常时,需要发送的延时队列的队列名称,当然时间场景汇总,根据需要可以进行动态配置或者根据一定规则进行动态映射。

2.创建监听类,用于消息的消费操作,此处使用@RabbitListener来消费消息(当然也可以使用SimpleMessageListenerContainer进行消息配置的),创建了一个正常消息监听和延时队列监听,由于一般上异常通知是低概率事件,可根据不同的监听容器进行差异化配置。

/** 
*
* @ClassName   类名:HttpMessagerLister 
* @Description 功能说明:http通知消费监听接口
* <p>
* TODO
*</p>
************************************************************************
* @date        创建日期:2019年7月17日
* @author      创建人:oKong
* @version     版本号:V1.0
*<p>
***************************修订记录*************************************
* 
*   2019年7月17日   oKong   创建该类功能。
*
***********************************************************************
*</p>
*/
@Component
@Slf4j
public class HttpMessagerLister {@AutowiredHttpMessagerService messagerService;@RabbitListener(id = "httpMessageNotifyConsumer", queues = {ApplicationConstant.HTTP_MESSAGE_START_QUEUE_NAME}, containerFactory = "notifyListenerContainer")public void httpMessageNotifyConsumer(Message message, Channel channel) throws Exception {doHandler(message, channel);}@RabbitListener(id= "httpDelayMessageNotifyConsumer", queues = {ApplicationConstant.HTTP_MESSAGE_ONE_QUEUE_NAME,}, containerFactory = "delayNotifyListenerContainer")public void httpDelayMessageNotifyConsumer(Message message, Channel channel) throws Exception {doHandler(message, channel);}private void doHandler(Message message, Channel channel) throws Exception {String body = new String(message.getBody(),"utf-8");String queue = message.getMessageProperties().getConsumerQueue();log.info("接收到通知请求:{},队列名:{}",body, queue);//消息对象转换try {HttpEntity httpNotifyDto = JSONUtil.toBean(body, HttpEntity.class);channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);//发送通知messagerService.notify(queue, httpNotifyDto);} catch(Exception e) {log.error(e.getMessage());//ackchannel.basicAck(message.getMessageProperties().getDeliveryTag(), false);}}
}

HttpMessagerService.java :消息真正处理的类,此类是关键,这里未进行日志记录,真实场景中,强烈建议进行消息通知的日志存储,防止日后信息的查看,同时也能通过发送状态,在重试次数都失败后,进行定时再次发送功能,同时也有据可查。

@Component
@Slf4j
public class HttpMessagerService {@AutowiredAmqpTemplate mqTemplate;    public void notify(String queue,HttpEntity httpEntity) {//发起请求log.info("开始发起http请求:{}", httpEntity);try {switch(httpEntity.getMethod().toLowerCase()) {case "POST":HttpUtil.post(httpEntity.getUrl(), httpEntity.getParams());break;case "GET":default:HttpUtil.get(httpEntity.getUrl(), httpEntity.getParams());}} catch (Exception e) {//发生异常,放入延迟队列中String nextRk = ApplicationConstant.delayRefMap.get(queue);if(ApplicationConstant.HTTP_MESSAGE_ONE_QUEUE_NAME.equals(queue)) {//若已经是最后一个延迟队列的消息队列了,则后续可直接放入数据库中 待后续定时策略进行再次发送log.warn("http通知已经通知N次失败,进入定时进行发起通知,url={}", httpEntity.getUrl());} else {log.warn("http重新发送通知:{}, 通知队列rk为:{}, 原队列:{}", httpEntity.getUrl(), nextRk, queue);mqTemplate.convertAndSend(ApplicationConstant.HTTP_MESSAGE_EXCHANGE, nextRk, cn.hutool.json.JSONUtil.toJsonStr(httpEntity));}}}
}

3.创建控制层服务(真实场景中,如SpringCloud微服务中,一般上是创建个api接口,供其他服务进行调用)

@Slf4j
@RestController
@Api(tags = "http测试接口")
public class HttpDemoController {@AutowiredAmqpTemplate mqTemplate;@PostMapping("/send")@ApiOperation(value="send",notes = "发送http测试")public String sendHttp(@RequestBody HttpEntity httpEntity) {//发送http请求log.info("开始发起http请求,发布异步消息:{}", httpEntity);mqTemplate.convertAndSend(ApplicationConstant.HTTP_MESSAGE_EXCHANGE, ApplicationConstant.HTTP_MESSAGE_START_RK, cn.hutool.json.JSONUtil.toJsonStr(httpEntity));return "发送成功:url=" + httpEntity.getUrl();        }
}

4.配置文件添加RabbitMQ相关配置信息

spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/# 通知-消费者线程数 设置大点 大概率是能通知到的
http.notify.concurrency=150
# 延迟队列的消费者线程数 可设置小点 
http.notify.delay.concurrency=10

5.编写启动类。

@SpringBootApplication
@Slf4j
public class DelayQueueApplication {public static void main(String[] args) throws Exception {SpringApplication.run(DelayQueueApplication.class, args);log.info("spring-boot-rabbitmq-delay-queue-chapter38服务启动!");}
}

6.启动服务。使用swagger进行简单调用测试。

  • 正常通知:

在这里插入图片描述

2019-07-20 23:52:23.792  INFO 65216 --- [nio-8080-exec-1] c.l.l.s.c.controller.HttpDemoController  : 开始发起http请求,发布异步消息:HttpEntity(url=www.baidu.com, params={a=1}, method=get)
2019-07-20 23:52:23.794  INFO 65216 --- [TaskExecutor-97] c.l.l.s.chapter38.mq.HttpMessagerLister  : 接收到通知请求:{"method":"get","params":{"a":1},"url":"www.baidu.com"},队列名:http.message.start
2019-07-20 23:52:23.794  INFO 65216 --- [TaskExecutor-97] c.l.l.s.c.service.HttpMessagerService    : 开始发起http请求:HttpEntity(url=www.baidu.com, params={a=1}, method=get)
  • 异常通知:访问一个不存在的地址
    在这里插入图片描述
2019-07-20 23:53:14.699  INFO 65216 --- [nio-8080-exec-4] c.l.l.s.c.controller.HttpDemoController  : 开始发起http请求,发布异步消息:HttpEntity(url=www.baidu.com1, params={a=1}, method=get)
2019-07-20 23:53:14.705  INFO 65216 --- [TaskExecutor-84] c.l.l.s.chapter38.mq.HttpMessagerLister  : 接收到通知请求:{"method":"get","params":{"a":1},"url":"www.baidu.com1"},队列名:http.message.start
2019-07-20 23:53:14.705  INFO 65216 --- [TaskExecutor-84] c.l.l.s.c.service.HttpMessagerService    : 开始发起http请求:HttpEntity(url=www.baidu.com1, params={a=1}, method=get)
2019-07-20 23:53:14.706  WARN 65216 --- [TaskExecutor-84] c.l.l.s.c.service.HttpMessagerService    : http重新发送通知:www.baidu.com1, 通知队列rk为:delay.one, 原队列:http.message.start

RabbitMQ 后台中,可以看见 http.message.dlx.one 队列中存在这需要延时处理的消息,在一分钟后会转发至 http.message.one 队列中。

在这里插入图片描述
在一分钟后,可以看见消息本再次消费了。

2019-07-20 23:54:14.722  INFO 65216 --- [TaskExecutor-16] c.l.l.s.chapter38.mq.HttpMessagerLister  : 接收到通知请求:{"method":"get","params":{"a":1},"url":"www.baidu.com1"},队列名:http.message.one
2019-07-20 23:54:14.723  INFO 65216 --- [TaskExecutor-16] c.l.l.s.c.service.HttpMessagerService    : 开始发起http请求:HttpEntity(url=www.baidu.com1, params={a=1}, method=get)
2019-07-20 23:54:14.723  WARN 65216 --- [TaskExecutor-16] c.l.l.s.c.service.HttpMessagerService    : http通知已经通知N次失败,进入定时进行发起通知,url=www.baidu.com1

一些最佳实践

在正式场景中,一般上补偿或者重试机制大概率是不会发送的,倘若发生时,一般上是第三方业务系统出现了问题,故一般上在进行补充时,应该在非高峰期进行操作,故应该对延时监听器,应该在高峰期时停止消费,在非高峰期时进行消费。同时,还可以根据不同的通知类型,放入不一样的延时队列中,保障业务的正常。这里简单说明下,动态停止或者启动演示监听器的方式。一般上是使用RabbitListenerEndpointRegistry 对象获取延时监听器,之后进行动态停止或者启用。可设置 @RabbitListener 的id属性,直接进行获取,当然也可以直接获取所有的监听器,进行自定义判断了。

    @AutowiredRabbitListenerEndpointRegistry registry;@GetMapping("/set")@ApiOperation(value = "set", notes = "设置消息监听器的状态")public String setSimpleMessageListenerContainer(String status) {if("1".equals(status)) {registry.getListenerContainer("httpDelayMessageNotifyConsumer").start();} else {registry.getListenerContainer("httpDelayMessageNotifyConsumer").stop();}return status;}

这里,只是简单进行演示说明,在真实场景下,可以使用定时器,判断当前是否为高峰期,进而进行动态设置监听器的状态。

参考资料

  1. https://www.rabbitmq.com/admin-guide.html
  2. https://www.rabbitmq.com/ttl.html

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

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

相关文章

【深入探讨】JavaScript 中的 forEach 和 map 区别

&#x1f431; 个人主页&#xff1a;不叫猫先生&#xff0c;公众号&#xff1a;前端Clodplay &#x1f64b;‍♂️ 作者简介&#xff1a;前端领域优质作者、阿里云专家博主&#xff0c;共同学习共同进步&#xff0c;一起加油呀&#xff01; ✨优质专栏&#xff1a;VS Code插件开…

房贷还款(C语言)

一、运行结果&#xff1b; 二、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h> # include <math.h>int main() {//初始化变量值&#xff1b;double m, r 0.01;float d 300000;float p 6000;//运算还款所需月份&#xff1b;m log10…

元象4.2B参数 MoE大模型实战

01 简介 近期&#xff0c;元象公司推出了其首个Moe大模型XVERSE-MoE-A4.2B。该模型采用了混合专家模型架构&#xff08;Mixture of Experts&#xff09;&#xff0c;并拥有4.2B的激活参数&#xff0c;其性能可与13B模型相媲美。值得一提的是&#xff0c;这个模型是完全开源的&…

Python 实战人工智能数学基础:图像处理应用

1.背景介绍 在许多计算机视觉任务中&#xff0c;图像处理占据了很重要的角色&#xff0c;尤其是在目标检测、特征提取、分类、跟踪等计算机视觉任务中。图像处理是一个复杂的过程&#xff0c;涉及到图像的采集、分析、存储、显示等环节。本文将讨论基于Python实现的图像处理的…

spring快速搭建聊天AI

官网url: https://spring.io/projects/spring-ai 本文演示的是open AI 1创建java项目 2.拿到AI的key&#xff08;没有的话可以去淘宝花几块钱买一个&#xff09; //YOUR_API_KEY写你自己的open AI的key spring.ai.openai.api-keyYOUR_API_KEY spring.ai.openai.chat.options.…

在家如何查找下载外文文献

查找下载外文文献的数据库大部分都需要使用权限的&#xff0c;那么我们如何在家进入这些数据库查找下载文献资源呢&#xff1f;请看本文的经验分享&#xff1a; 举例1、 一位同学的文献求助&#xff1a;Performance of financial hedging and earnings management under dive…

WordPress 图片压缩插件:Compress JPEG PNG images 使用方法

插件介绍 Compress JPEG & PNG images是一款非常好用的图片压缩插件:&#xff0c;非常值得大家安装使用&#xff1b;特别是图片类型网站。其实我们很多服务器磁盘空间是不在乎多那么几十 MB 大小的&#xff0c;但是压缩了图片能提升网站速度&#xff0c;节省宽带&#xff…

【论文阅读——SplitFed: When Federated Learning Meets Split Learning】

级别CCFA 1.摘要 联邦学习&#xff08;FL&#xff09;和分割学习&#xff08;SL&#xff09;是两种流行的分布式机器学习方法。两者都采用了模型对数据的场景&#xff1b;客户端在不共享原始数据的情况下训练和测试机器学习模型。由于机器学习模型的架构在客户端和服务器之间…

BackTrader 中文文档(七)

原文&#xff1a;www.backtrader.com/ TA-Lib 原文&#xff1a;www.backtrader.com/docu/talib/talib/ 即使backtrader提供了大量内置指标&#xff0c;而且开发指标主要是定义输入、输出并以自然方式编写公式&#xff0c;一些人还是想要使用TA-LIB。一些原因包括&#xff1a; 指…

基于SignalR视频聊天 一

环境 VS2022 WIN10 .NET8 VSCode VUE SignalR 1.安装SignalR客户端库 需要在Vue.js项目中安装SignalR客户端库。可以使用npm或者yarn来安装 npm install microsoft/signalr2.创建SignalR服务 创建SignalR服务&#xff0c;以便客户端&#xff08;Vue.js应用&#xff09;能…

抄袭可耻 - 2023面试高手抄袭对比图

原创博客(伏城之外)抄袭博客(2023面试高手)对比图华为OD机试 - 跳马(Java & JS & Python & C & C++)_华为od岗c卷机试马走日-CSDN博客2024年华为OD机试真题-跳马-Python-OD统一考试(C卷)-CSDN博客

集合体系java

Collection:单列集合&#xff1a;每个元素只包含一个值 Collection集合存储的是地址 Collection的三种遍历方法如下 //迭代器是用来遍历集合的专用方式&#xff08;数组没有迭代器&#xff09;&#xff0c;在java中迭代器的代表是Iterator //boolean hasNext():询问当前位置…

Java中队列

队列是一种常见的数据结构&#xff0c;它按照先进先出&#xff08;FIFO&#xff09;的原则管理元素。在 Java 中&#xff0c;队列通常是通过链表或数组实现的&#xff0c;不同的实现类在内部数据结构和操作上可能有所不同。 1.原理 1.数据结构&#xff1a;队列的基本数据结构…

【python图形界面问题解决】wxPython创建图形界面程序,在代码编译器中正常运行,但是打包后却不能运行解决办法

一、问题 使用wxPython创建一个图形界面&#xff0c;在VSCODE中正常运行&#xff0c;但是打包后&#xff0c;却不能运行&#xff0c;只出现一个一闪而过的窗口&#xff0c;这时最需要看看这窗口到底显示了什么内容。这里可以使用录屏软件录制屏幕&#xff0c;这里使用LICEcap小…

美国卖家需知!儿童玩具CPC认证ASTMF-23标准更新

2023年10月13日&#xff0c;美国材料与试验协会&#xff08;ASTM&#xff09;发布了最新版本的玩具安全标准ASTM F963-23。这一标准的修订涵盖了声学、电池可及性、充气材料、弹射玩具等技术方面的要求。同时&#xff0c;它还为邻苯二甲酸盐和重金属在玩具基材中的使用提供了豁…

Towards IP Geolocation Using Delay and TopologyMeasurements(TBG)(2006年)

下载地址:Towards IP geolocation using delay and topology measurements | Proceedings of the 6th ACM SIGCOMM conference on Internet measurement 被引次数:492 Katz-Bassett E, John J P, Krishnamurthy A, et al. Towards IP geolocation using delay and topology …

通讯录的实现(单链表版本)

我们首先要知道通讯录的实现是基于单链表的基础上的&#xff0c;所以我们首先要搞懂单链表。&#xff08;注意&#xff1a;今天的代码量较多&#xff09;&#xff0c;但这不是阻挡我们前进的脚步&#xff0c;冲冲冲&#xff01;&#xff01;&#xff01; 单链表的简要概述 我们…

2024.4.19 Python爬虫复习day07 可视化3

综合案例 需求: 已知2020年疫情数据,都是json数据,需要从文件中读出,进行处理和分析,最终实现数据可视化折线图 相关知识点: json json简介: 本质是一个特定格式的字符串 举例: [{},{},{}] 或者 {}python中json包: import jsonpython数据转为json数据: 变量接收json…

微服务架构使用和docker部署方法(若依)

这里以若依官方网站开源的微服务框架为例子记录使用方法过程。 开源地址&#xff1a;RuoYi-Cloud: &#x1f389; 基于Spring Boot、Spring Cloud & Alibaba的分布式微服务架构权限管理系统&#xff0c;同时提供了 Vue3 的版本 下载后&#xff0c;用IDEA社区版开发工具打…

GNU Radio Radar Toolbox编译及安装

文章目录 前言一、GNU Radio Radar Toolbox 介绍二、gr-radar 安装三、具体使用四、OFDM 雷达仿真 前言 GNU Radio Radar Toolbox&#xff08;gr-radar&#xff09;是一个开放源码的工具箱&#xff0c;用于 GNU Radio 生态系统&#xff0c;主要目的是为雷达信号处理提供必要的…