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

RabbitMQ死信队列详解

什么是死信队列

由于特定的**原因导致 Queue 中的某些消息无法被消费,**这类消费异常的数据将会保存在死信队列中防止消息丢失,例如用户在商城下单成功并点击支付后,在指定时间未支付时的订单自动失效
死信队列只不过是绑定在死信交换机上的队列。死信交换机只不过是用来接受死信的交换机,可以为任何类型【Direct、Fanout、Topic】。一般来说,会为每个业务队列分配一个独有的路由key,并对应的配置一个死信队列进行监听,也就是说,一般会为每个重要的业务队列配置一个死信队列。

来源

  1. 消息的存活时间到了,ttl过期
  2. 队列积压的消息达到最大长度(在队列中等待时间最久的消息会成为死信)
  3. 消息被拒(消费方返回nack进行否定应答)且不重新加入队列(requeue=false)

演示

模拟一条正常应该被C1消费者接收的消息,由于出现消费异常情况进入死信队列被C2消费者进行消费的案例

架构图

死信队列案例示意图
死信队列标记


TTL过期

  1. 在生产者方进行指定为当前发送消息的过期时间,缺点是消息即使过期也不一定会被马上丢弃,因为消息是否过期是在即将投递到消费者之前判定的(** 如果当前队列有严重的消息积压情况,已过期的消息依旧会被积压在队列中,如果队列配置了消息积压上限,**将导致后续应当正常消费的消息全部进入死信队列 )
  2. 在队列指定为所有到达该队列的消息的过期时间,时间从消息入队列开始计算,只要超过了队列的超时时间配置,消息会自动清除

该案例为在生产者方进行指定
配置类:

package com.example.rabbitmqqoslimiting.demos;import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;public class RabbitUtils {private static final ConnectionFactory connectionFactory;    //放到静态代码块中,在类加载时执行,只执行一次。达到工厂只创建一次,每次获取是新连接的效果static {//创建连接工厂connectionFactory = new ConnectionFactory();connectionFactory.setHost("101.133.141.74");                   //设置MQ的主机地址connectionFactory.setPort(5672);                                //设置MQ服务端口connectionFactory.setVirtualHost("study");                      //设置Virtual Hosts(虚拟主机)connectionFactory.setUsername("guest");                         //设置MQ管理人的用户名(要在Web版先配置,保证该用户可以管理设置的虚拟主机)connectionFactory.setPassword("guest");                           //设置MQ管理人的密码}//定义提供连接对象的方法,封装public static Connection getConnection(){try {//创建连接对象并返回return connectionFactory.newConnection();} catch (Exception e) {e.printStackTrace();}return null;}//关闭通道和关闭连接工具类的方法public static void closeConnectionAndChannel(Channel channel, Connection connection){try {if (channel!=null) {channel.close();}if (connection!=null){connection.close();}} catch (Exception e) {e.printStackTrace();}}
}

生产者:

package com.dmbjz.one;import com.dmbjz.utils.RabbitUtils;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;import java.nio.charset.StandardCharsets;/* 死信队列TTL案例 生产者 */
public class Provider {private static final String EXCHANGE_NAME = "normal_exchange";              //正常交换机名称private static final String KEY = "zhangsan";        //普通队列 RoutingKeypublic static void main(String[] args) throws Exception {Connection connection = RabbitUtils.getConnection();Channel channel = connection.createChannel();channel.exchangeDeclare(EXCHANGE_NAME,BuiltinExchangeType.DIRECT);  //声明交换机/*死信队列 设置TTL消息过期时间 单位毫秒*/AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration("20000").build();/*模拟消息循环发送*/for(int i = 1; i < 11; i++) {String message = "INFO " + i;channel.basicPublish(EXCHANGE_NAME,KEY,properties,message.getBytes(StandardCharsets.UTF_8));  }}
}

消费者C1:

package com.dmbjz.one;import com.dmbjz.utils.RabbitUtils;
import com.rabbitmq.client.*;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;/* 死信队列TTL案例 消费者C1 */
public class ConsumerC1 {private static final String EXCHANGE_NAME = "normal_exchange";              //正常交换机名称private static final String DEAD_EXCHANGE_NAME = "dead_exchange";           //死信队列交换机名称private static final String KEY = "zhangsan";        //普通队列 RoutingKeyprivate static final String DEAD_KEY = "lisi";       //死信队列 RoutingKeyprivate static final String QUEUE_NAME = "normal-queue";       //普通队列名称private static final String DEAD_QUEUE_NAME = "dead-queue";    //死信队列名称public static void main(String[] args) throws IOException {Connection connection = RabbitUtils.getConnection();Channel channel = connection.createChannel();/*声明死信和普通交换机,正常交换机已被生产者声明,实际可以省略第一行代码*/channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);channel.exchangeDeclare(DEAD_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);/*创建队列* 通过额外参数实现什么情况下转发到死信队列 ?,key都是固定的*   1、TTL过期时间设置(一般由生产者指定)*   2、死信交换机的名称*   3、死信交换机的RoutingKey* */Map<String,Object> arguments = new HashMap<>(8);arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE_NAME);     //死信交换机的名称arguments.put("x-dead-letter-routing-key",DEAD_KEY);            //死信交换机的RoutingKey// arguments.put("x-dead-letter-ttl",10000);   指定消息的有效时间为20秒(一般为生产者指定)channel.queueDeclare(QUEUE_NAME,false,false,false,arguments);channel.queueDeclare(DEAD_QUEUE_NAME,false,false,false,null);/*绑定队列*/channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,KEY);channel.queueBind(DEAD_QUEUE_NAME,DEAD_EXCHANGE_NAME,DEAD_KEY);DeliverCallback successBack = (consumerTag, message) -> {System.out.println("C1用户接收到的信息为:"+new String(message.getBody()));};CancelCallback cnaelBack = a->{System.out.println("C1用户进行取消消费操作!");};channel.basicConsume(QUEUE_NAME,true,successBack,cnaelBack);}
}

消费者C2:

package com.dmbjz.one;import com.dmbjz.utils.RabbitUtils;
import com.rabbitmq.client.*;import java.io.IOException;/* 死信队列TTL案例 消费者C2 */
public class ConsumerC2 {private static final String DEAD_EXCHANGE_NAME = "dead_exchange";           //死信队列交换机名称private static final String DEAD_KEY = "lisi";       //死信队列 RoutingKeyprivate static final String DEAD_QUEUE_NAME = "dead-queue";    //死信队列名称public static void main(String[] args) throws IOException {Connection connection = RabbitUtils.getConnection();Channel channel = connection.createChannel();/*声明队列和普通交换机并进行绑定,由于消费者C1已经声明过了,这里实际可以省略这三行代码*/channel.exchangeDeclare(DEAD_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);channel.queueDeclare(DEAD_QUEUE_NAME,false,false,false,null);channel.queueBind(DEAD_QUEUE_NAME,DEAD_EXCHANGE_NAME,DEAD_KEY);DeliverCallback successBack = (consumerTag, message) -> {System.out.println("C2用户接收到的信息为:"+new String(message.getBody()));};CancelCallback cnaelBack = a->{System.out.println("C2用户进行取消消费操作!");};channel.basicConsume(DEAD_QUEUE_NAME,true,successBack,cnaelBack);}}

效果演示:
  1. 执行消费者C1,创建出所有交换机和队列绑定后停止运行。
  2. 执行生产者,等待10秒钟后查看控制台,消息全部通过交换机进入死信队列
  3. 运行消费者C2,死信队列消息被成功消费

20秒后消息全部进入死信队列
运行消费者C2,死信队列内的消息被全部取出


队列消息积压达到最大长度

在绑定死信队列的消费者端添加队列的消息积压长度限制即可,核心代码为TTL案例的消费者C1基础上再添加Map参数

//指定队列能够积压消息的大小,超出该范围的消息将进入死信队列
arguments.put("x-max-length",6);            

生产者:

package com.dmbjz.maxlength;import com.dmbjz.utils.RabbitUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;import java.nio.charset.StandardCharsets;/* 死信队列 队列达到最大长度案例 生产者 */
public class Provider {private static final String EXCHANGE_NAME = "normal_exchange";              //正常交换机名称private static final String KEY = "zhangsan";        //普通队列 RoutingKeypublic static void main(String[] args) throws Exception {Connection connection = RabbitUtils.getConnection();Channel channel = connection.createChannel();channel.exchangeDeclare(EXCHANGE_NAME,BuiltinExchangeType.DIRECT);  //声明交换机/*循环消息发送*/for(int i = 1; i < 11; i++) {String message = "INFO " + i;channel.basicPublish(EXCHANGE_NAME,KEY,null,message.getBytes(StandardCharsets.UTF_8));  //发送超级VIP消息}}}

消费者C1:
消费者C2的代码与TTL案例中保持一致

package com.dmbjz.maxlength;import com.dmbjz.utils.RabbitUtils;
import com.rabbitmq.client.*;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;/* 死信队列 队列达到最大长度案例 消费者C1 */
public class ConsumerC1 {private static final String EXCHANGE_NAME = "normal_exchange";              //正常交换机名称private static final String DEAD_EXCHANGE_NAME = "dead_exchange";           //死信队列交换机名称private static final String KEY = "zhangsan";        //普通队列 RoutingKeyprivate static final String DEAD_KEY = "lisi";       //死信队列 RoutingKeyprivate static final String QUEUE_NAME = "normal-queue";       //普通队列名称private static final String DEAD_QUEUE_NAME = "dead-queue";    //死信队列名称public static void main(String[] args) throws IOException {Connection connection = RabbitUtils.getConnection();Channel channel = connection.createChannel();/*声明死信和普通交换机,正常交换机已被生产者声明,实际可以省略第一行代码*/channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);channel.exchangeDeclare(DEAD_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);/*创建队列* 通过额外参数实现什么情况下转发到死信队列 ?,key都是固定的*   1、TTL过期时间设置(一般由生产者指定)*   2、死信交换机的名称*   3、死信交换机的RoutingKey* */Map<String,Object> arguments = new HashMap<>(8);arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE_NAME);     //死信交换机的名称arguments.put("x-dead-letter-routing-key",DEAD_KEY);            //死信交换机的RoutingKeyarguments.put("x-max-length",6);            //指定正常队列的长度,超出该范围的消息将进入死信队列channel.queueDeclare(QUEUE_NAME,false,false,false,arguments);channel.queueDeclare(DEAD_QUEUE_NAME,false,false,false,null);/*绑定队列*/channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,KEY);channel.queueBind(DEAD_QUEUE_NAME,DEAD_EXCHANGE_NAME,DEAD_KEY);DeliverCallback successBack = (consumerTag, message) -> {System.out.println("C1用户接收到的信息为:"+new String(message.getBody()));};CancelCallback cnaelBack = a->{System.out.println("C1用户进行取消消费操作!");};channel.basicConsume(QUEUE_NAME,true,successBack,cnaelBack);}
}

效果演示:
  1. 执行消费者C1,创建出所有交换机和队列绑定后停止运行。
  2. 执行生产者,由于通道积压的六条消息从未被消费。剩余消息进入死信队列
  3. 运行消费者C2,死信队列消息被成功消费

消息积压六条,剩下四条消息进入死信队列,LIM为限制长度标记
启动消费者C2,死信队列消息被消费


消息拒绝应答

在绑定死信队列的消费者端添加需要拒绝应答的消息判断即可,核心代码为消息拒绝应答

/* requeue 设置为 false 代表拒绝重新入队 该队列如果配置了死信交换机将发送到死信队列中,未配置则进行丢弃操作*/
channel.basicReject(message.getEnvelope().getDeliveryTag(),false);

生产者:

package com.dmbjz.noack;import com.dmbjz.utils.RabbitUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;import java.nio.charset.StandardCharsets;/* 死信队列 队列达到最大长度案例 生产者 */
public class Provider {private static final String EXCHANGE_NAME = "normal_exchange";              //正常交换机名称private static final String KEY = "zhangsan";        //普通队列 RoutingKeypublic static void main(String[] args) throws Exception {Connection connection = RabbitUtils.getConnection();Channel channel = connection.createChannel();channel.exchangeDeclare(EXCHANGE_NAME,BuiltinExchangeType.DIRECT);  //声明交换机/*循环消息发送*/for(int i = 1; i < 11; i++) {String message = "INFO " + i;channel.basicPublish(EXCHANGE_NAME,KEY,null,message.getBytes(StandardCharsets.UTF_8));  //发送超级VIP消息}}
}

消费者C1:
消费者C2的代码和TTL案例保持一致

package com.dmbjz.noack;import com.dmbjz.utils.RabbitUtils;
import com.rabbitmq.client.*;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;/* 死信队列 队列达到最大长度案例 消费者C1 */
public class ConsumerC1 {private static final String EXCHANGE_NAME = "normal_exchange";              //正常交换机名称private static final String DEAD_EXCHANGE_NAME = "dead_exchange";           //死信队列交换机名称private static final String KEY = "zhangsan";        //普通队列 RoutingKeyprivate static final String DEAD_KEY = "lisi";       //死信队列 RoutingKeyprivate static final String QUEUE_NAME = "normal-queue";       //普通队列名称private static final String DEAD_QUEUE_NAME = "dead-queue";    //死信队列名称public static void main(String[] args) throws IOException {Connection connection = RabbitUtils.getConnection();Channel channel = connection.createChannel();/*声明死信和普通交换机,正常交换机已被生产者声明,实际可以省略第一行代码*/channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);channel.exchangeDeclare(DEAD_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);/*创建队列* 通过额外参数实现什么情况下转发到死信队列 ?,key都是固定的*   1、TTL过期时间设置(一般由生产者指定)*   2、死信交换机的名称*   3、死信交换机的RoutingKey* */Map<String,Object> arguments = new HashMap<>(8);arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE_NAME);     //死信交换机的名称arguments.put("x-dead-letter-routing-key",DEAD_KEY);            //死信交换机的RoutingKeychannel.queueDeclare(QUEUE_NAME,false,false,false,arguments);channel.queueDeclare(DEAD_QUEUE_NAME,false,false,false,null);/*绑定队列*/channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,KEY);channel.queueBind(DEAD_QUEUE_NAME,DEAD_EXCHANGE_NAME,DEAD_KEY);DeliverCallback successBack = (consumerTag, message) -> {String info = new String(message.getBody(),"UTF-8");if(info.equals("INFO 5")){System.out.println("C1用户拒绝的信息为:"+new String(message.getBody()));/* requeue 设置为 false 代表拒绝重新入队 该队列如果配置了死信交换机将发送到死信队列中,未配置则进行丢弃操作*/channel.basicReject(message.getEnvelope().getDeliveryTag(),false);}else{System.out.println("C1用户接收到的信息为:"+new String(message.getBody()));channel.basicAck(message.getEnvelope().getDeliveryTag(),false);}};CancelCallback cnaelBack = a->{System.out.println("C1用户进行取消消费操作!");};channel.basicConsume(QUEUE_NAME,false,successBack,cnaelBack);}
}

效果演示:
  1. 执行消费者C1
  2. 执行生产者,等待10秒钟后查看控制台,消息5被拒绝接收进入死信队列
  3. 运行消费者C2,死信队列消息被成功消费

消息5被消费者C1拒绝接收
死信队列中的消息5被消费者C2消费

相关文章:

RabbitMQ死信队列详解

什么是死信队列 由于特定的**原因导致 Queue 中的某些消息无法被消费&#xff0c;**这类消费异常的数据将会保存在死信队列中防止消息丢失&#xff0c;例如用户在商城下单成功并点击支付后&#xff0c;在指定时间未支付时的订单自动失效死信队列只不过是绑定在死信交换机上的队…...

计算机网络:物理层(编码与调制)

今天又学会了一个知识&#xff0c;加油&#xff01; 目录 一、基带信号与宽带信号 1、基带信号 2、宽带信号 3、选择 4、关系 二、数字数据编码为数字信号 1、非归零编码【NRZ】 2、曼彻斯特编码 3、差分曼彻斯特编码 4、归零编码【RZ】 5、反向不归零编码【NRZI】 …...

嵌入式开发板qt gdb调试

1&#xff09; 启动 gdbserver ssh 或者 telnet 登陆扬创平板 192.168.0.253&#xff0c; 进入命令行执行如下&#xff1a; chmod 777 /home/HelloWorld &#xff08;2&#xff09; 打 开 QTcreator->Debug->StartDebugging->Attach to Running Debug Server 进行…...

基于python实现原神那维莱特开转脚本

相信不少原友都抽取了枫丹大C那维莱特&#xff0c;其强力的输出让不少玩家爱不释手。由于其转的越快&#xff0c;越不容易丢伤害的特点&#xff0c;很多原友在开转时容易汗流浃背&#xff0c;所以特意用python写了一个自动转圈脚本&#xff0c;当按住鼠标侧键时&#xff0c;即可…...

C# 实现Lru缓存

C# 实现Lru缓存 LRU 算法全称是最近最少使用算法&#xff08;Least Recently Use&#xff09;&#xff0c;是一种简单的缓存策略。 通常用在对象池等需要频繁获取但是又需要释放不用的地方。 代码实现的基本原理就是使用链表&#xff0c;当某个元素被访问时&#xff08;Get或…...

牛客网BC107矩阵转置

答案&#xff1a; #include <stdio.h> int main() {int n0, m0,i0,j0,a0,b0;int arr1[10][10]{0},arr2[10][10]{0}; //第一个数组用来储存原矩阵&#xff0c;第二个数组用来储存转置矩阵scanf("%d%d",&n,&m); if((n>1&&n<10)&&am…...

协作办公原来如此简单?详解 ONLYOFFICE 协作空间 2.0 更新

协作办公原来如此简单&#xff1f;详解 ONLYOFFICE 协作空间 2.0 更新 上周&#xff0c;ONLYOFFICE 的协作空间推出升级版 2.0 版本了&#xff1a; ONLYOFFICE 协作空间 2.0 现已发布&#xff1a;新增公共房间、插件、重新分配数据、RTL 界面等功能 ONLYOFFICE 协作空间是去…...

2023年国赛高教杯数学建模A题定日镜场的优化设计解题全过程文档及程序

2023年国赛高教杯数学建模 A题 定日镜场的优化设计 原题再现 构建以新能源为主体的新型电力系统&#xff0c;是我国实现“碳达峰”“碳中和”目标的一项重要措施。塔式太阳能光热发电是一种低碳环保的新型清洁能源技术[1]。   定日镜是塔式太阳能光热发电站&#xff08;以下…...

c/c++ 结构体、联合体、枚举

结构体 结构体内存对齐规则&#xff1a; 1、结构体的第一个成员对齐到结构体变量起始位置偏移量为0的地址处 2、其他成员变量要对齐到某个数字&#xff08;对齐数&#xff09;的整数倍的地址处。 对齐数&#xff1a;编译器默认的一个对齐数与该成员变量大小的较小值。 vs 中…...

stl模板库成员函数重载类型混肴编译不通过解决方法

stl模板库成员函数重载类型混肴编译不通过解决方法 这种方式编译不通过IsArithmetic和HasMemberList编译器存在混肴 template <typename T, typename Enable std::enable_if<IsArithmetic<T>::value>::type >static void DumpWrapper(T* filed, std::strin…...

MySQL——表的约束

目录 一.表的约束 二.空属性 ​编辑三.默认值 四.列描述 五.主键 1.主键 2.符合主键 六.自增长 七.唯一键 八.外键 一.表的约束 真正约束字段的是数据类型&#xff0c;但是数据类型约束很单一&#xff0c;需要有一些额外的约束&#xff0c;更好的保证数据的合法性&…...

cordic 算法学习记录

参考&#xff1a;b站教学视频FPGA&#xff1a;Cordic算法介绍与实现_哔哩哔哩_bilibili FPGA硬件实现加减法、移位等操作比较简单&#xff0c;但是实现乘除以及函数计算复杂度高且占用资源多&#xff0c;常见的计算三角函数/平方根的求解方式有①查找表&#xff1a;先把函数对应…...

【STM32】电机驱动

一、电机分类 二、直流电机的分类 1.有刷电机 2.无刷电机 3.直流减速电机 三、H桥电路 正向旋转 驱动Q1和Q4 反向旋转 驱动Q2和Q3 四、MC3386电机驱动芯片 1.基本原理图 1&#xff09;前进/后退&#xff1a;IN1和IN2的电平顺序决定电机的正反转 2&#xff09;调节速度&#…...

csp 如此编码 C语言(回归唠嗑版)

熟悉的开篇废话&#xff0c;最近其实在研究那个web开发这一块&#xff0c;导致csp联系就减少了&#xff0c;好久没更csp的帖子了&#xff0c;尽管明天就要考了&#xff0c;但是嘞&#xff0c;能看一道是一道呗对吧。 等过段时间我把web开发这一块整明白了就发帖子&#xff0c;…...

或许是全网最全的延迟队列

什么是延迟队列 作用&#xff1a;用来存储延迟消息延迟消息&#xff1a;生产者发送一个消息给mq&#xff0c;然后mq会经过一段时间&#xff08;延迟时间&#xff09;&#xff0c;然后在把这个消息发送给消费者 应用场景 预定会议后&#xff0c;需要在预定的时间点前十分钟通…...

C语言结构体小项目之通讯录代码实现+代码分析

一、思路 1.文件 这里由于通讯录实现代码较长&#xff0c;因此分三个文件进行&#xff0c;contact.c用于实现通讯录主体代码&#xff0c;声明各项头文件用contact.h实现&#xff0c;测试用test.c 二.功能 增加联系人删除联系人修改联系人查找指定联系人排序显示通讯录的信息…...

tp5 rewrite nginx重写

tp框架,默认的访问路径是 www.xxxx.com/index.php/admin/shop/index格式的&#xff0c;为了方便和更规范&#xff0c;也看起来有逼格一些&#xff0c;需要将index.php去掉 无index.php就会报404 我这里是宝塔 #地址重写if (!-e $request_filename) {rewrite ^(.*)$ /index.…...

.NET 反射优化的经验分享

比如针对 GetCustomAttributes 通过反射获取属性的优化,以下例子 // dotnet run -c Release -f net7.0 --filter "*" --runtimes net7.0 net8.0public class Tests{public object[] GetCustomAttributes() => typeof(C).GetCustomAttributes(typeof(MyAttribute…...

使用opencv的Sobel算子实现图像边缘检测

1 边缘检测介绍 图像边缘检测技术是图像处理和计算机视觉等领域最基本的问题&#xff0c;也是经典的技术难题之一。如何快速、精确地提取图像边缘信息&#xff0c;一直是国内外的研究热点&#xff0c;同时边缘的检测也是图像处理中的一个难题。早期的经典算法包括边缘算子方法…...

亿欧网首届“元创·灵镜”科技艺术节精彩纷呈,实在智能AI Agent智能体展现硬核科技图景

12月4日-10日&#xff0c;持续一周的首届“元创灵镜”科技艺术节在海南陵水香水湾拉开帷幕&#xff0c;虚实交互创造出的“海岛之镜”开幕式呈现出既真实又虚幻的未来感&#xff0c;融入前沿科技元素的艺术装置作品在“虚实之镜&自然生长”科技艺术展诠释着浪漫想象&#x…...

MVC 数据库

MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...

屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!

5月28日&#xff0c;中天合创屋面分布式光伏发电项目顺利并网发电&#xff0c;该项目位于内蒙古自治区鄂尔多斯市乌审旗&#xff0c;项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站&#xff0c;总装机容量为9.96MWp。 项目投运后&#xff0c;每年可节约标煤3670…...

分布式增量爬虫实现方案

之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面&#xff0c;避免重复抓取&#xff0c;以节省资源和时间。 在分布式环境下&#xff0c;增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路&#xff1a;将增量判…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题

分区配置 (ptab.json) img 属性介绍&#xff1a; img 属性指定分区存放的 image 名称&#xff0c;指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件&#xff0c;则以 proj_name:binary_name 格式指定文件名&#xff0c; proj_name 为工程 名&…...

iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈

在日常iOS开发过程中&#xff0c;性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期&#xff0c;开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发&#xff0c;但背后往往隐藏着系统资源调度不当…...

Java毕业设计:WML信息查询与后端信息发布系统开发

JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发&#xff0c;实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构&#xff0c;服务器端使用Java Servlet处理请求&#xff0c;数据库采用MySQL存储信息&#xff0…...

如何更改默认 Crontab 编辑器 ?

在 Linux 领域中&#xff0c;crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用&#xff0c;用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益&#xff0c;允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...

云原生周刊:k0s 成为 CNCF 沙箱项目

开源项目推荐 HAMi HAMi&#xff08;原名 k8s‑vGPU‑scheduler&#xff09;是一款 CNCF Sandbox 级别的开源 K8s 中间件&#xff0c;通过虚拟化 GPU/NPU 等异构设备并支持内存、计算核心时间片隔离及共享调度&#xff0c;为容器提供统一接口&#xff0c;实现细粒度资源配额…...

Sklearn 机器学习 缺失值处理 获取填充失值的统计值

💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 使用 Scikit-learn 处理缺失值并提取填充统计信息的完整指南 在机器学习项目中,数据清…...

【无标题】湖北理元理律师事务所:债务优化中的生活保障与法律平衡之道

文/法律实务观察组 在债务重组领域&#xff0c;专业机构的核心价值不仅在于减轻债务数字&#xff0c;更在于帮助债务人在履行义务的同时维持基本生活尊严。湖北理元理律师事务所的服务实践表明&#xff0c;合法债务优化需同步实现三重平衡&#xff1a; 法律刚性&#xff08;债…...