当前位置: 首页 > news >正文

RabbitMQ之延迟队列

RabbitMQ之延迟队列

  • 1. 延迟队列概念
  • 2. 延迟队列使用场景
  • 3. RabbitMQ 中的 TTL
    • 3.1 消息设置 TTL
    • 3.2 队列设置 TTL
    • 3.3 两者的区别
  • 4. 整合 SpringBoot
    • 4.1 创建项目
    • 4.2 添加依赖
    • 4.3 修改配置文件
    • 4.4 添加 Swagger 配置类
  • 5. 队列 TTL
    • 5.1 代码架构图
    • 5.2 配置文件类代码
    • 5.3 消息生产者代码
    • 5.4 消息消费者代码
  • 6. 延时队列优化
    • 6.1 代码架构图
    • 6.2 配置文件类代码
    • 6.3 消息生产者代码
  • 7. RabbitMQ 插件实现延时队列
    • 7.1. 安装延时队列插件
    • 7.2 代码架构图
    • 7.3 配置文件类代码
    • 7.4 消息生产者代码
    • 7.5 消息消费者代码
  • 8. 总结:rocket::rocket:

1. 延迟队列概念


  延时队列,队列内部是有序的,最重要的特性就体现在它的延时属性上,延时队列中的元素是希望在指定时间到了以后或之前取出和处理,简单来说,延时队列就是用来存放需要在指定时间被处理的元素的队列

2. 延迟队列使用场景


1️⃣ 订单在十分钟之内未支付则自动取消
2️⃣ 新创建的店铺,如果在十天内都没有上传过商品,则自动发送消息提醒
3️⃣ 用户注册成功后,如果三天内没有登陆则进行短信提醒
4️⃣ 用户发起退款,如果三天内没有得到处理则通知相关运营人员
5️⃣ 预定会议后,需要在预定的时间点前十分钟通知各个与会人员参加会议

  这些场景都有一个特点,需要在某个事件发生之后或者之前的指定时间点完成某一项任务,如:发生订单生成事件,在十分钟之后检查该订单支付状态,然后将未支付的订单进行关闭;看起来似乎使用定时任务,一直轮询数据,每秒查一次,取出需要被处理的数据,然后处理不就完事了吗?如果数据量比较少,确实可以这样做,比如:对于“如果账单一周内未支付则进行自动计算”这样的需求,如果对于时间不是严格限制,而是宽松意义上的一周,那么每天晚上跑个定时任务检查一下所有未支付的账单,确实也是一个可行的方案。但对于数据量比较大,并且时效性较强的场景,如:“订单十分钟内未支付则关闭”,短期内未支付的订单数据可能会有很多,活动期间甚至会达到百万甚至千万级别,对这么庞大的数据量仍旧使用轮询的方式显然是不可取的,很可能在一秒内无法完成所有订单的检查,同时会给数据库带来很大压力,无法满足业务要求而且性能低下。

在这里插入图片描述

3. RabbitMQ 中的 TTL


  TTL 是什么呢?TTL 是 RabbitMQ 中一个消息或者队列的属性,表明一条消息或者该队列中的所有消息的最大存活时间,单位是毫秒,换句话说,如果一条消息设置了 TTL 属性或者进入了设置 TTL 属性的队列,那么这条消息如果在TTL设置的时间内没有被消费,则会成为“死信”。如果同时配置了队列的 TTL 和消息的TTL,那么较小的那个值将会被使用,有两种方式设置TTL

3.1 消息设置 TTL


针对每条消息设置 TTL

rabbitTemplate.convertAndSend("X","XC",message,correlationData->{correlationData.getMessageProperties().setExpiration(ttlTime);return correlationData;
});

3.2 队列设置 TTL


在创建队列的时候设置队列的“x-message-ttl" 属性

// 声明队列的TTL
args.put("x-message-ttl",5000);
return QueueBuilder.durable(Queue_A).withArguments(args).build();

3.3 两者的区别


  如果设置了队列的 TTL 属性,那么一旦消息过期,就会被队列丢弃(如果配置了死信队列被丢到死信队列中),而第二种方式,消息即使过期,也不一定会被马上丢弃,因为消息是否过期是在即将投递到消费者之前判定的,如果当前队列有严重的消息积压情况,则已过期的消息也许还能存活较长时间;另外,还需要注意的一点是,如果不设置 TTL,表示消息永远不会过期,如果将 TTL 设置为 0 ,则表示除非此时可以直接投递该消息到消费者,否则消息将会被丢弃。

4. 整合 SpringBoot

4.1 创建项目


在这里插入图片描述

4.2 添加依赖


<!--RabbitMQ 依赖-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
</dependency>
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.47</version>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency>
<!--swagger-->
<dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version>
</dependency>
<dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.9.2</version>
</dependency>
<!--RabbitMQ 测试依赖-->
<dependency><groupId>org.springframework.amqp</groupId><artifactId>spring-rabbit-test</artifactId><scope>test</scope>
</dependency>

4.3 修改配置文件


spring.rabbitmq.host=182.92.234.71
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=123

4.4 添加 Swagger 配置类


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;@Configuration
@EnableSwagger2
public class SwaggerConfig {@Beanpublic Docket webApiConfig() {return new Docket(DocumentationType.SWAGGER_2).groupName("webApi").apiInfo(webApiInfo()).select().build();}private ApiInfo webApiInfo() {return new ApiInfoBuilder().title("rabbitmq接口文档").description("本文档描述了 rabbitmq 微服务接口定义").version("1.0").contact(new Contact("enjoy6288", "http://atguigu.com", "1873172950@qq.com")).build();}
}

5. 队列 TTL

5.1 代码架构图


  创建两个队列 QA 和 QB,两者队列 TTL 分别设置为 10S 和 40S,然后在创建一个交换机 X 和死信交换机 Y,它们的类型都是 direct,创建一个死信队列 QD,它们的绑定关系如下:
在这里插入图片描述

5.2 配置文件类代码


@Configuration
public class TtlQueueConfig {public static final String X_EXCHANGE = "X";public static final String QUEUE_A = "QA";public static final String QUEUE_B = "QB";public static final String Y_DEAD_LETTER_EXCHANGE = "Y";public static final String DEAD_LETTER_QUEUE = "QD";// 声明 xExchange@Bean("xExchange")public DirectExchange xExchange() {return new DirectExchange(X_EXCHANGE);}// 声明 yExchange@Bean("yExchange")public DirectExchange yExchange() {return new DirectExchange(Y_DEAD_LETTER_EXCHANGE);}// 声明队列 A ttl为10s 并绑定到对应的死信交换机@Bean("queueA")public Queue queueA() {Map<String, Object> args = new HashMap<>(3);// 声明当前队列绑定的死信交换机args.put("x-dead-letter-exchange", Y_DEAD_LETTER_EXCHANGE);// 声明当前队列的死信路由 keyargs.put("x-dead-letter-routing-key", "YD");// 声明队列的 TTLargs.put("x-message-ttl", 10000);return QueueBuilder.durable(QUEUE_A).withArguments(args).build();}// 声明队列 A 绑定 X 交换机@Beanpublic Binding queueBindingX(@Qualifier("queueA") Queue queueA, @Qualifier("xExchange") DirectExchange xExchange) {return BindingBuilder.bind(queueA).to(xExchange).with("XA");}// 声明队列 B ttl 为 40s 并绑定到对应的死信交换机@Bean("queueB")public Queue queueB() {Map<String, Object> args = new HashMap<>(3);// 声明当前队列绑定的死信交换机args.put("x-dead-letter-exchange", Y_DEAD_LETTER_EXCHANGE);// 声明当前队列的死信路由 keyargs.put("x-dead-letter-routing-key", "YD");// 声明队列的 TTLargs.put("x-message-ttl", 40000);return QueueBuilder.durable(QUEUE_B).withArguments(args).build();}// 声明死信队列 QD@Bean("queueD")public Queue queueD() {return new Queue(DEAD_LETTER_QUEUE);}// 声明死信队列 QD 绑定关系@Beanpublic Binding deadLetterBindingQAD(@Qualifier("queueD") Queue queueD, @Qualifier("yExchange") DirectExchange yExchange) {return BindingBuilder.bind(queueD).to(yExchange).with("YD");}
}

5.3 消息生产者代码


@Slf4j
@RequestMapping("ttl")
@RestController
public class SendMsgController {@Autowiredprivate RabbitTemplate rabbitTemplate;@GetMapping("sendMsg/{message}")public void sendMsg(@PathVariable String message) {log.info("当前时间: {}, 发送一条信息给两个 TTL 队列: {}", new Date(), message);rabbitTemplate.convertAndSend("X", "XA", "消息来自 ttl 为 10s 的队列:" + message);rabbitTemplate.convertAndSend("X", "XB", "消息来自 ttl 为 40s 的队列:" + message);}
}

5.4 消息消费者代码


@Slf4j
@Component
public class DeadLetterQueueConsumer {@RabbitListener(queues = "QD")public void receiveD(Message message, Channel channel) {String msg = new String(message.getBody());log.info("当前时间:{},收到死信队列信息{}", new Date().toString(), msg);}
}

发起一个请求 http://localhost:8080/ttl/sendMsg/嘻嘻嘻
在这里插入图片描述

  第一条消息在 10s 后变成了死信消息,然后被消费者消费掉,第二条消息在 40s 之后变成了死信消息,然后被消费掉,这样一个延时队列就打造完成了。

  不过,这样使用的话,岂不是每增加一个新的时间需求,就要新增一个队列,这里只有 10s 和 40s 两个时间选项,如果需要一个小时候处理,那么就需要增加 TTL 为一个小时的队列,如果是预定会议室然后提前通知这样的场景,岂不是要增加无数个队列才能满足需求?

6. 延时队列优化

6.1 代码架构图


  在这里新增了一个队列QC,绑定关系如下,该队列不设置 TTL 时间
在这里插入图片描述

6.2 配置文件类代码


@Component
public class MsgTtlQueueConfig {public static final String Y_DEAD_LETTER_EXCHANGE = "Y";public static final String QUEUE_C = "QC";// 声明队列 C 死信交换机@Bean("queueC")public Queue queueB() {Map<String, Object> args = new HashMap<>(3);// 声明当前队列绑定的死信交换机args.put("x-dead-letter-exchange", Y_DEAD_LETTER_EXCHANGE);// 声明当前队列的死信路由 keyargs.put("x-dead-letter-routing-key", "YD");// 没有声明 TTL 属性return QueueBuilder.durable(QUEUE_C).withArguments(args).build();}// 声明队列 B 绑定 X 交换机@Beanpublic Binding queuecBindingX(@Qualifier("queueC") Queue queueC, @Qualifier("xExchange") DirectExchange xExchange) {return BindingBuilder.bind(queueC).to(xExchange).with("XC");}
}

6.3 消息生产者代码


@GetMapping("sendExpirationMsg/{message}/{ttlTime}")
public void sendMsg(@PathVariable String message, @PathVariable String ttlTime) {rabbitTemplate.convertAndSend("X", "XC", message, correlationData -> {correlationData.getMessageProperties().setExpiration(ttlTime);return correlationData;});log.info("当前时间:{},发送一条时长{}毫秒 TTL 信息给队列 C:{}", new Date(), ttlTime, message);
}

发起请求
http://localhost:8080/ttl/sendExpirationMsg/你好 1/20000
http://localhost:8080/ttl/sendExpirationMsg/你好 2/2000
在这里插入图片描述
  看起来似乎没什么问题,但是在最开始的时候,就介绍过如果使用在消息属性上设置 TTL 的方式,消息可能并不会按时“死亡”,因为 RabbitMQ 只会检查第一个消息是否过期,如果过期则丢到死信队列,如果第一个消息的延时时长很长,而第二个消息的延时时长很短,第二个消息并不会优先得到执行。

7. RabbitMQ 插件实现延时队列


  上文中提到的问题,确实是一个问题,如果不能实现在消息粒度上的 TTL,并使其在设置的TTL 时间
及时死亡,就无法设计成一个通用的延时队列。那如何解决呢,接下来我们就去解决该问题。

7.1. 安装延时队列插件


  在官网上下载 https://www.rabbitmq.com/community-plugins.html,下载rabbitmq_delayed_message_exchange 插件,然后解压放置到 RabbitMQ 的插件目录。
  进入 RabbitMQ 的安装目录下的 plgins 目录,执行下面命令让该插件生效,然后重启 RabbitMQ
/usr/lib/rabbitmq/lib/rabbitmq_server-3.8.8/plugins
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
在这里插入图片描述
在这里插入图片描述

7.2 代码架构图


  在这里新增了一个队列delayed.queue,一个自定义交换机 delayed.exchange,绑定关系如下:
在这里插入图片描述

7.3 配置文件类代码


  在我们自定义的交换机中,这是一种新的交换类型,该类型消息支持延迟投递机制 消息传递后并不会立即投递到目标队列中,而是存储在 mnesia(一个分布式数据系统)表中,当达到投递时间时,才投递到目标队列中。

@Configuration
public class DelayedQueueConfig {public static final String DELAYED_QUEUE_NAME = "delayed.queue";public static final String DELAYED_EXCHANGE_NAME = "delayed.exchange";public static final String DELAYED_ROUTING_KEY = "delayed.routingkey";@Beanpublic Queue delayedQueue() {return new Queue(DELAYED_QUEUE_NAME);}// 自定义交换机 我们在这里定义的是一个延迟交换机@Beanpublic CustomExchange delayedExchange() {Map<String, Object> args = new HashMap<>();// 自定义交换机的类型args.put("x-delayed-type", "direct");return new CustomExchange(DELAYED_EXCHANGE_NAME, "x-delayed-message", true, false,args);}@Beanpublic Binding bindingDelayedQueue(@Qualifier("delayedQueue") Queue queue,@Qualifier("delayedExchange") CustomExchangedelayedExchange) {return BindingBuilder.bind(queue).to(delayedExchange).with(DELAYED_ROUTING_KEY).noargs();}
}

7.4 消息生产者代码


public static final String DELAYED_EXCHANGE_NAME = "delayed.exchange";
public static final String DELAYED_ROUTING_KEY = "delayed.routingkey";@GetMapping("sendDelayMsg/{message}/{delayTime}")
public void sendMsg(@PathVariable String message, @PathVariable Integer delayTime) {rabbitTemplate.convertAndSend(DELAYED_EXCHANGE_NAME, DELAYED_ROUTING_KEY, message,correlationData -> {correlationData.getMessageProperties().setDelay(delayTime);return correlationData;});log.info(" 当前时间: {}, 发送一条延迟 {} 毫秒的信息给队列 delayed.queue:{}", new Date(), delayTime, message);
}

7.5 消息消费者代码


public static final String DELAYED_QUEUE_NAME = "delayed.queue";@RabbitListener(queues = DELAYED_QUEUE_NAME)
public void receiveDelayedQueue(Message message) {String msg = new String(message.getBody());log.info("当前时间:{},收到延时队列的消息:{}", new Date().toString(), msg);
}

发起请求:
http://localhost:8080/ttl/sendDelayMsg/come on baby1/20000
http://localhost:8080/ttl/sendDelayMsg/come on baby2/2000

在这里插入图片描述
第二个消息被先消费掉了,符合预期

8. 总结🚀🚀


  延时队列在需要延时处理的场景下非常有用,使用 RabbitMQ 来实现延时队列可以很好的利用RabbitMQ 的特性,如:消息可靠发送、消息可靠投递、死信队列来保障消息至少被消费一次以及未被正确处理的消息不会被丢弃。另外,通过 RabbitMQ 集群的特性,可以很好的解决单点故障问题,不会因为单个节点挂掉导致延时队列不可用或者消息丢失。
当然,延时队列还有很多其它选择,比如利用 Java 的 DelayQueue,利用 Redis 的 zset,利用 Quartz或者利用kafka 的时间轮,这些方式各有特点,看需要适用的场景

相关文章:

RabbitMQ之延迟队列

RabbitMQ之延迟队列 1. 延迟队列概念2. 延迟队列使用场景3. RabbitMQ 中的 TTL3.1 消息设置 TTL3.2 队列设置 TTL3.3 两者的区别 4. 整合 SpringBoot4.1 创建项目4.2 添加依赖4.3 修改配置文件4.4 添加 Swagger 配置类 5. 队列 TTL5.1 代码架构图5.2 配置文件类代码5.3 消息生产…...

k8s部署手册-v06

一、基础配置 1.修改主机名 hostnamectl set-hostname k8s-master01 hostnamectl set-hostname k8s-master02 hostnamectl set-hostname k8s-master03 hostnamectl set-hostname k8s-node01 hostnamectl set-hostname k8s-node022.添加 主机名与IP地址解析 cat > /etc/ho…...

Qt 5.15集成Crypto++ 8.7.0(MSVC 2019)笔记

一、背景 笔者已介绍过在Qt 5.15.x中使用MinGW&#xff08;8.10版本&#xff09;编译并集成Crypto 8.7.0。 但是该编译出来的库&#xff08;.a和.dll&#xff09;不适用MSVC&#xff08;2019版本&#xff09;构建环境&#xff0c;需要重新编译&#xff08;.lib或和.dll&#xf…...

LeetCode——贪心篇(一)

刷题顺序及思路来源于代码随想录&#xff0c;网站地址&#xff1a;https://programmercarl.com 目录 455. 分发饼干 376. 摆动序列 53. 最大子数组和 122. 买卖股票的最佳时机 II 55. 跳跃游戏 45. 跳跃游戏 II 1005. K 次取反后最大化的数组和 455. 分发饼干 假设你是…...

2023高教社杯 国赛数学建模C题思路 - 蔬菜类商品的自动定价与补货决策

1 赛题 在生鲜商超中&#xff0c;一般蔬菜类商品的保鲜期都比较短&#xff0c;且品相随销售时间的增加而变差&#xff0c; 大部分品种如当日未售出&#xff0c;隔日就无法再售。因此&#xff0c; 商超通常会根据各商品的历史销售和需 求情况每天进行补货。 由于商超销售的蔬菜…...

【理解线性代数】(四)线性运算的推广与矩阵基础

1. 数值加法和乘法 数值加法与乘法&#xff0c;是小学数学课程中的基本数学运算。例如&#xff1a; 加法&#xff1a;112 乘法&#xff1a;2*24 在这个知识层次下&#xff0c;运算的基本单位是数字。 2. 从数值到向量 数值加法&#xff0c;可以看作一维空间中的向量加法&…...

C# 什么是继承和派生

C# 什么是继承和派生 在 C# 中&#xff0c;继承&#xff08;Inheritance&#xff09;是一种机制&#xff0c;它允许一个类&#xff08;子类&#xff09;从另一个类&#xff08;父类&#xff09;中继承属性和方法。这种关系使得子类可以重用父类的代码&#xff0c;同时可以在子…...

无涯教程-JavaScript - HEX2BIN函数

描述 HEX2BIN函数将十六进制数转换为二进制数。 语法 HEX2BIN (number, [places])争论 Argument描述Required/Optionalnumber 您要转换的十六进制数。 数字不能超过10个字符(40位)。数字的最高有效位是符号位(从右数第40位)。其余的39位是幅度位。 负数使用二进制补码表示。…...

前端面试0906

// 请给出输出结果 function foo(){ console.log(a); } function bar(){ var a 3; console.log(this.a); foo(); } var a 2; bar(); 2 2 // 请从下面的问题中挑选3道进行回答 1. 防抖和节流分别是什么&#xff0c;一般用在什么场景&#xff1f; 防抖&#xff08;Debounc…...

OceanBase社区版4.x核心技术解密

数字化时代&#xff0c;各行各业的数据量呈现爆发式增长&#xff0c;对于海量数据价值的挖掘和应用&#xff0c;正成为推动创新的主要力量&#xff0c;与此同时&#xff0c;数据计算复杂度正在提升。在此背景下&#xff0c;对于数据处理的基石数据库而言&#xff0c;正面临市场…...

快速安装k8s

RKE安装方式 官方文章资源地址 https://rke.docs.rancher.com/installation rke工具下载地址&#xff08;arm,amd,windows都有&#xff09; https://github.com/rancher/rke/releases x86的用amd64下载rke工具 https://github.com/rancher/rke/releases/download/v1.4.8/rke_li…...

[FFmpeg] 常用ffmpeg命令

去水印 ffmpeg -i water.jpeg -strict -2 -vf delogox300:y250:w56:h18:show0 no_water.jpeg 打时间戳 ffmpeg -i perf_60Hz_Raw.mp4 -vf "drawtextfontsize160:fontcolorred:text%{pts\:hms}" -c:v libx264 -an -f mp4 perf_output.mp4 -y ffmpeg -i perf_8k.mp4 -v…...

代码随想录训练营第五十七天|647. 回文子串、516.最长回文子序列

647. 回文子串 题目链接/文章讲解/视频讲解&#xff1a;代码随想录 1.代码展示 //647.回文子串 int countSubstrings(string s) {//step1 构建dp数组&#xff0c;明确dp数组的含义&#xff0c;dp[i][j]的含义是在下标为i和j区间内的字串是否为回文串vector<vector<bool&…...

对线程池设置做压测

线程池代码 Configuration public class ThreadPoolConfig {// 核心线程池大小private int corePoolSize 24;// 最大可创建的线程数private int maxPoolSize 25;// 队列最大长度private int queueCapacity 100;// 线程池维护线程所允许的空闲时间private int keepAliveSeco…...

【网络通信 -- WebRTC】项目实战记录 -- mediasoup android 适配 webrtc m94

【网络通信 -- WebRTC】项目实战记录 -- mediasoup android 适配 webrtc m94 【1】下载并配置 depot_tools 下载 depot_tools git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git编辑 ~/.bashrc 将 depot_tools 添加到路径中 vim ~/.bashrc export…...

【力扣周赛】第 357 场周赛(⭐反悔贪心)

文章目录 竞赛链接Q1&#xff1a;6925. 故障键盘解法1——直接模拟解法2——双端队列 Q2&#xff1a;6953. 判断是否能拆分数组&#xff08;贪心&#xff09;Q3&#xff1a;2812. 找出最安全路径⭐解法1——多源BFS瓶颈路模型&#xff1f;解法2——多源BFS 倒序枚举答案 并查…...

css重置

css 重置 CSS 重置的主要目标是确保浏览器之间的一致性&#xff0c;并撤消所有默认样式&#xff0c;创建一个空白板。 如今&#xff0c;主流浏览器都实现了css规范&#xff0c;在布局或间距方面没有太大差异。但是通过自定义 CSS 重置&#xff0c;也可以改善用户体验和提高开…...

tcpdump相关

Linux内核角度分析tcpdump原理&#xff08;一&#xff09;Linux内核角度分析tcpdump原理&#xff08;二&#xff09;...

MFC新建内部消息

提示&#xff1a;记录一下MFC新建内部消息的成功过程 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 先说一下基本情况&#xff0c;因为要在mapview上增加一个显示加载时间的功能。然后发现是要等加载完再显示时间&#xff0c;显示在主…...

linux查找目录

要在Linux中查找目录&#xff0c;可以使用find命令。下面是查询目录的几个示例&#xff1a; 1,查找当前目录下所有子目录&#xff1a; find . -type d 2,在指定路径下查找目录&#xff1a; find /path/to/directory -type d 3,查找以特定名称开头的目录&#xff1a; find . -t…...

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地

借阿里云中企出海大会的东风&#xff0c;以**「云启出海&#xff0c;智联未来&#xff5c;打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办&#xff0c;现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战

“&#x1f916;手搓TuyaAI语音指令 &#x1f60d;秒变表情包大师&#xff0c;让萌系Otto机器人&#x1f525;玩出智能新花样&#xff01;开整&#xff01;” &#x1f916; Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制&#xff08;TuyaAI…...

【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分

一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计&#xff0c;提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合&#xff1a;各模块职责清晰&#xff0c;便于独立开发…...

OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 在 GPU 上对图像执行 均值漂移滤波&#xff08;Mean Shift Filtering&#xff09;&#xff0c;用于图像分割或平滑处理。 该函数将输入图像中的…...

JavaScript 数据类型详解

JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型&#xff08;Primitive&#xff09; 和 对象类型&#xff08;Object&#xff09; 两大类&#xff0c;共 8 种&#xff08;ES11&#xff09;&#xff1a; 一、原始类型&#xff08;7种&#xff09; 1. undefined 定…...

Spring Security 认证流程——补充

一、认证流程概述 Spring Security 的认证流程基于 过滤器链&#xff08;Filter Chain&#xff09;&#xff0c;核心组件包括 UsernamePasswordAuthenticationFilter、AuthenticationManager、UserDetailsService 等。整个流程可分为以下步骤&#xff1a; 用户提交登录请求拦…...

boost::filesystem::path文件路径使用详解和示例

boost::filesystem::path 是 Boost 库中用于跨平台操作文件路径的类&#xff0c;封装了路径的拼接、分割、提取、判断等常用功能。下面是对它的使用详解&#xff0c;包括常用接口与完整示例。 1. 引入头文件与命名空间 #include <boost/filesystem.hpp> namespace fs b…...

如何通过git命令查看项目连接的仓库地址?

要通过 Git 命令查看项目连接的仓库地址&#xff0c;您可以使用以下几种方法&#xff1a; 1. 查看所有远程仓库地址 使用 git remote -v 命令&#xff0c;它会显示项目中配置的所有远程仓库及其对应的 URL&#xff1a; git remote -v输出示例&#xff1a; origin https://…...

高效的后台管理系统——可进行二次开发

随着互联网技术的迅猛发展&#xff0c;企业的数字化管理变得愈加重要。后台管理系统作为数据存储与业务管理的核心&#xff0c;成为了现代企业不可或缺的一部分。今天我们要介绍的是一款名为 若依后台管理框架 的系统&#xff0c;它不仅支持跨平台应用&#xff0c;还能提供丰富…...