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的队列就是死信队列。
一个消息变成死信一般上是由于以下几种情况;
消息被拒绝
消息过期
队列达到了最大长度。
所以,通过 TTL 和 DLX 的特性可以模拟实现延时队列的功能。当队列中的消息超时成为死信后,会把消息死信重新发送到配置好的交换机中,然后分发到真实的消费队列。故简单来说,我们可以创建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-exchange 、x-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;}
这里,只是简单进行演示说明,在真实场景下,可以使用定时器,判断当前是否为高峰期,进而进行动态设置监听器的状态。
参考资料
- https://www.rabbitmq.com/admin-guide.html
- https://www.rabbitmq.com/ttl.html
相关文章:
SpringBoot基于RabbitMQ实现消息延迟队列方案
知识小科普 在此之前,简单说明下基于RabbitMQ实现延时队列的相关知识及说明下延时队列的使用场景。 延时队列使用场景 在很多的业务场景中,延时队列可以实现很多功能,此类业务中,一般上是非实时的,需要延迟处理的&a…...
Go语言使用标准库时常见错误
Go的标准库是一组增加和拓展语言的核心包。然而,很容易误用标准库,或者我们对其行为理解有限,导致产生了bug或不应该在生产级应用程序中某些功能。 1. 提供错误的持续时间 标准库提供了获取 time.Duration 的常用函数和方法,但由于 time.Duration 是 int64 的自定义类型,…...
UE5不打包启用像素流 ubuntu22.04
首先查找引擎中像素流的位置: zkzk-ubuntu2023:/media/zk/Data/Linux_Unreal_Engine_5.3.2$ sudo find ./ -name get_ps_servers.sh [sudo] zk 的密码: ./Engine/Plugins/Media/PixelStreaming/Resources/WebServers/get_ps_servers.sh然后在指定路径中…...
Redis 常用数据类型常用命令和应用场景
首先先混个眼熟 Redis 中的 8 种常用数据类型: 5 种基础数据类型:String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合࿰…...
ins视频批量下载,instagram批量爬取视频信息
简介 Instagram 是目前最热门的社交媒体平台之一,拥有大量优质的视频内容。但是要逐一下载这些视频往往非常耗时。在这篇文章中,我们将介绍如何使用 Python 编写一个脚本,来实现 Instagram 视频的批量下载和信息爬取。 我们使用selenium获取目标用户的 HTML 源代码,并将其保存…...
Canvas图形编辑器-数据结构与History(undo/redo)
Canvas图形编辑器-数据结构与History(undo/redo) 这是作为 社区老给我推Canvas,于是我也学习Canvas做了个简历编辑器 的后续内容,主要是介绍了对数据结构的设计以及History能力的实现。 在线编辑: https://windrunnermax.github.io/CanvasEditor开源地…...
阿里云Centos7下编译glibc
编译glibc 原来glibc版本 编译前需要的环境: CentOS7 gcc 8.3.0 gdb 8.3.0 make 4.0 binutils 2.39 (ld -v) python 3.6.8 其他看INSTALL, 但有些版本也不易太高 wget https://mirrors.aliyun.com/gnu/glibc/glibc-2.37.tar.gz tar -zxf glibc-2.37.tar.gz cd glibc-2.37/ …...
UE5数字孪生系列笔记(四)
场景的切换 创建一个按钮的用户界面UMG 创建一个Actor,然后将此按钮UMG添加到组件Actor中 调节几个全屏的背景 运行结果 目标点切换功能制作 设置角色到这个按钮的位置效果 按钮被点击就进行跳转 多个地点的切换与旋转 将之前的目标点切换逻辑替换成旋转的逻…...
品牌故事化:Kompas.ai如何塑造深刻的品牌形象
在这个信息爆炸的时代,品牌故事化已经成为企业塑造独特形象、与消费者建立情感联系的重要手段。一个引人入胜的品牌故事不仅能够吸引消费者的注意力,还能够在消费者心中留下持久的印象,建立起强烈的情感连接。本文将深入探讨品牌故事化对于构…...
5g和2.4g频段有什么区别
运行的频段不同 2.4G和5G频段的主要区别在于它们运行的频段不同,2.4G频段运行在2.4GHz的频段上,而5G频段(这里指的是5GHz频段)运行在5GHz的频段上。12 这导致了两者在传输速度、覆盖范围、抗干扰能力等方面的明显差异。以下是详…...
交通管理在线服务系统|基于Springboot的交通管理系统设计与实现(源码+数据库+文档)
交通管理在线服务系统目录 目录 基于Springboot的交通管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、用户信息管理 2、驾驶证业务管理 3、机动车业务管理 4、机动车业务类型管理 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计…...
konva.js 工具类
konva.js 工具类 class KonvaCanvas {/*** 初始化画布* param {String} domId 容器dom id*/constructor(domId) {this.layer null;this.stage null;this.scale 1;this.init(domId);}/*** 聚焦到指定元素* param {String} elementId 元素dom id*/focusOn(elementId) {if (!t…...
php未能在vscode识别?
在设置里搜php,找到settings.json,设置你的安装路径即可。 成功...
解读MongoDB官方文档获取mongo7.0版本的安装步骤与基本使用
mongo式一款NOSQL数据库,用于存储非结构化数据,mongo是一种用于存储json的数据数据,可以通过mongo提供的命令解析json获取想要的值。 数据模型 了解关系数据库会很熟悉database,table,row,column的概念,分别是数据库,…...
【数据结构|C语言版】顺序表
前言1. 初步认识数据结构2. 线性表3. 顺序表3.1 顺序表的概念3.1 顺序表的分类3.2 动态顺序表的实现 结语 前言 各位小伙伴大家好!小编来给大家讲解一下数据结构中顺序表的相关知识。 1. 初步认识数据结构 【概念】数据结构是计算机存储、组织数据的⽅式。 数据…...
Unity类银河恶魔城学习记录12-17 p139 In game UI源代码
Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释,可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili UI.cs using UnityEngine;public class UI : MonoBehaviour {[SerializeFie…...
MongoDB学习【一】MongoDB简介和部署
MongoDB简介 MongoDB是一种开源的、面向文档的、分布式的NoSQL数据库系统,由C语言编写而成。它的设计目标是为了适应现代Web应用和大数据处理场景的需求,提供高可用性、横向扩展能力和灵活的数据模型。 主要特点: 文档模型: Mon…...
html 引入vue Element ui 的方式
第一种:使用CDN的方式引入 <!--引入 element-ui 的样式,--> <link rel"stylesheet" href"https://unpkg.com/element-ui/lib/theme-chalk/index.css"> <!-- 必须先引入vue, 后使用element-ui --> <…...
曾经备受追捧的海景房,为何如今却没人要了?
独家首发 ------------ 全国多地的海景房如威海乳山、惠州大亚湾、北海银滩等多地的海景房如今大跌也难以卖出,与当初各地对海景房的追捧形成了鲜明对比,为何这些海景房变成如此样子,在于现实与宣传存在着很大的区别。 曾几何时面朝大海鸟语花…...
[docker] 镜像部分补充
[docker] 镜像部分补充 这里补充一下比较少用的,关于镜像的内容 检查镜像 ❯ docker images REPOSITORY TAG IMAGE ID CREATED SIZE <none> <none> ca61c1748170 2 hours ago 1.11GB node latest 5212d…...
shell脚本--常见案例
1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件: 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...
多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...
Java毕业设计:WML信息查询与后端信息发布系统开发
JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息࿰…...
Web中间件--tomcat学习
Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机,它可以执行Java字节码。Java虚拟机是Java平台的一部分,Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...
Caliper 负载(Workload)详细解析
Caliper 负载(Workload)详细解析 负载(Workload)是 Caliper 性能测试的核心部分,它定义了测试期间要执行的具体合约调用行为和交易模式。下面我将全面深入地讲解负载的各个方面。 一、负载模块基本结构 一个典型的负载模块(如 workload.js)包含以下基本结构: use strict;/…...
关于uniapp展示PDF的解决方案
在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项: 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库: npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...
【Linux】自动化构建-Make/Makefile
前言 上文我们讲到了Linux中的编译器gcc/g 【Linux】编译器gcc/g及其库的详细介绍-CSDN博客 本来我们将一个对于编译来说很重要的工具:make/makfile 1.背景 在一个工程中源文件不计其数,其按类型、功能、模块分别放在若干个目录中,mak…...
嵌入式常见 CPU 架构
架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集,单周期执行;低功耗、CIP 独立外设;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel(原始…...
