【RabbitMQ】RabbitMQ高级:死信队列和延迟队列

目录

  • 设置TTL(过期时间)
    • 概述
    • RabbitMQ使用TTL
      • 原生API案例
      • springboot案例
  • 死信队列
    • 概述
    • 原生API案例
    • springboot案例
  • 延迟队列
    • 概述
    • 插件实现延迟队列
      • 安装插件
      • 代码
    • TTL实现延迟队列
      • 实现
      • 延迟队列优化

设置TTL(过期时间)

概述

在电商平台下单,订单创建成功,等待支付,一般会给30分钟的时间,开始倒计时。如果在这段时间内用户没有支付,则默认订单取消。

该如何实现?

  1. 定期轮询(数据库等)

用户下单成功,将订单信息放入数据库,同时将支付状态放入数据库,用户付款更改数据库状态。定期轮询数据库支付状态,如果超过30分钟就将该订单取消。

优点:设计实现简单

缺点:需要对数据库进行大量的IO操作,效率低下。

  1. Timer

Timer可以用来设置指定时间后执行的任务。

SimpleDateFormat simpleDateFormat=new SimpleDateFormat("HH:mm:ss");
Timer timer=new Timer();
TimerTask timerTask=new TimerTask(){@Override public void run(){System.out.println("用户没有付款,交易取消:"+simpleDateFormat.format(new Date(System.currentTimeMillis())));timer.cancel();}
};
System.out.println("等待用户付款:"+simpleDateFormat.format(new Date(System.currentTimeMillis())));
// 10秒后执行timerTask 
timer.schedule(timerTask, 10 * 1000);

缺点:

Timers没有持久化机制;不灵活 (只可以设置开始时间和重复间隔,对等待支付貌似够用);不能利用线程池,一个timer一个线程;没有真正的管理计划。

  1. ScheduledExecutorService
SimpleDateFormat format=new SimpleDateFormat("HH:mm:ss"); 
// 线程工厂
ThreadFactory factory = Executors.defaultThreadFactory(); 
// 使用线程池 
ScheduledExecutorService service = new ScheduledThreadPoolExecutor(10, factory);
System.out.println("开始等待用户付款10秒:" + format.format(new Date()));
service.schedule(new Runnable() { @Override public void run() { System.out.println("用户未付款,交易取消:" + format.format(new Date())); }// 等待10s 单位秒 
}, 10, TimeUnit.SECONDS);

优点:可以多线程执行,一定程度上避免任务间互相影响,单个任务异常不影响其它任务。

在高并发的情况下,不建议使用定时任务去做,因为太浪费服务器性能,不建议。

  1. RabbitMQ的TTL

  2. Quartz

  3. JCronTab

等等。

下面就重点介绍下,如何使用RabbitMQ的TTL

RabbitMQ使用TTL

TTL,Time to Live 的简称,即过期时间。

RabbitMQ 可以对消息和队列两个维度来设置TTL。

任何消息中间件的容量和堆积能力都是有限的,如果有一些消息总是不被消费掉,那么需要有一种过期的机制来做兜底。

目前有两种方法可以设置消息的TTL:

  1. 通过Queue属性设置,队列中所有消息都有相同的过期时间。
  2. 对消息自身进行单独设置,每条消息的TTL 可以不同。

如果两种方法一起使用,则消息的TTL 以两者之间较小数值为准。通常来讲,消息在队列中的生存时间一旦超过设置的TTL 值时,就会变成“死信”(Dead Message),消费者默认就无法再收到该消息。当然,“死信”也是可以被取出来消费的,下一小节我们会讲解。

原生API案例

try{Connection connection=factory.newConnection();Channel channel=connection.createChannel())// 创建队列(实际上使用的是AMQP default这个direct类型的交换器) // 设置队列属性 Map<String, Object> arguments=new HashMap<>();// 设置队列的TTL arguments.put("x-message-ttl",30000);// 设置队列的空闲存活时间(如该队列根本没有消费者,一直没有使用,队列可以存活多久) arguments.put("x-expires",10000);channel.queueDeclare(QUEUE_NAME,false,false,false,arguments);for(int i=0;i< 1000000;i++){String message="Hello World!"+i;channel.basicPublish("",QUEUE_NAME,new AMQP.BasicProperties().builder().expiration("30000").build(),message.getBytes());System.out.println(" [X] Sent '"+message+"'");}
}catch(TimeoutException e){e.printStackTrace();
}catch(IOException e){e.printStackTrace();
}

此外,还可以通过命令行方式设置全局TTL,执行如下命令:

rabbitmqctl set_policy TTL ".*" '{"message-ttl":30000}' --apply-to queues

还可以通过restful api方式设置,这里不做过多介绍。

默认规则:

  1. 如果不设置TTL,则表示此消息不会过期;
  2. 如果TTL设置为0,则表示除非此时可以直接将消息投递到消费者,否则该消息会被立即丢弃;

注意理解 message-ttl 、 x-expires 这两个参数的区别,有不同的含义。但是这两个参数属性都遵循上面的默认规则。一般TTL相关的参数单位都是毫秒(ms)。

springboot案例

在配置类里声明队列的时候设置TTL:

@Bean 
public Queue queueTTLWaiting() { Map<String, Object> props = new HashMap<>(); // 对于该队列中的消息,设置都等待10s props.put("x-message-ttl", 10000); Queue queue = new Queue("q.pay.ttl-waiting", false, false, false, props); return queue; 
}

在生产者发消息时,可以指定消息的TTL:

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.io.UnsupportedEncodingException;@RestController
public class PayController {@Autowiredprivate AmqpTemplate rabbitTemplate;@RequestMapping("/pay/queuettl")public String sendMessage() {rabbitTemplate.convertAndSend("ex.pay.ttl-waiting", "pay.ttl-waiting", "发送了TTL-WAITING-MESSAGE");return "queue-ttl-ok";}@RequestMapping("/pay/msgttl")public String sendTTLMessage() throws UnsupportedEncodingException {MessageProperties properties = new MessageProperties();properties.setExpiration("5000");  // 设置消息的过期时间Message message = new Message("发送了WAITING- MESSAGE".getBytes("utf-8"), properties);rabbitTemplate.convertAndSend("ex.pay.waiting", "pay.waiting", message);return "msg-ttl-ok";}
}

死信队列

概述

死信队列,英文缩写是:DLX(Dead Letter Exchange),其实应该称为死信交换机更为合适。

当消息成为死信后,可以被重新发送到另一个交换机,这个交换机就是死信交换机。

在这里插入图片描述

实际上,死信队列就是普通的交换机,只不过我们人为的给其赋予了特殊的含义:当消息成为死信后,会重新发送到 DLX(死信交换机)。

默认情况下,当消息成为死信(过期、队列满了、消息 TTL 过期)的时候,RabbitMQ 会将这些消息进行清理,但是当配置了死信队列之后,RabbitMQ 会将死信发送到 DLX (死信交换机)中,这样就可以避免消息丢失。

死信队列的应用场景:

    • 为了保证订单业务的消息数据不丢失,需要使用到 RabbitMQ 的死信队列机制,当消息消费发生异常时,将消息投入到死信队列中。
    • 用户在商城下单成功并进行支付活动,如果在指定的时候没有支付,将会将订单自动失效。

以下几种情况导致消息变为死信:

  1. 消息被拒绝(Basic.Reject/Basic.Nack),并且设置requeue参数为false;
  2. 消息过期;
  3. 队列达到最大长度。

对于RabbitMQ 来说,DLX 是一个非常有用的特性。它可以处理异常情况下,消息不能够被消费者正确消费(消费者调用了Basic.Nack 或者Basic.Reject)而被置入死信队列中的情况,后续分析程序可以通过消费这个死信队列中的内容来分析当时所遇到的异常情况,进而可以改善和优化系统。

原生API案例

try{Connection connection=factory.newConnection();Channel channel=connection.createChannel();// 定义一个死信交换器(也是一个普通的交换器)channel.exchangeDeclare("exchange.dlx","direct",true);// 定义一个正常业务的交换器channel.exchangeDeclare("exchange.biz", "fanout", true);Map<String, Object> arguments = new HashMap<>();// 设置队列TTLarguments.put("x-message-ttl", 10000);// 设置该队列所关联的死信交换器(当队列消息TTL到期后依然没有消费,则加入死信队列)arguments.put("x-dead-letter-exchange", "exchange.dlx");// 设置该队列所关联的死信交换器的routingKey,如果没有特殊指定,使用原队列的 routingKeyarguments.put("x-dead-letter-routing-key", "routing.key.dlx.test");channel.queueDeclare("queue.biz", true, false, false, arguments);channel.queueBind("queue.biz", "exchange.biz", "");channel.queueDeclare("queue.dlx", true, false, false, null);// 死信队列和死信交换器channel.queueBind("queue.dlx", "exchange.dlx", "routing.key.dlx.test");channel.basicPublish("exchange.biz", "", MessageProperties.PERSISTENT_TEXT_PLAIN, "dlx.test".getBytes());
} catch (Exception e) {e.printStackTrace();
}

springboot案例

下面通过设置TTL模拟在SpringBoot中如何使用死信队列。

修改RabbitConfig配置类,设置普通队列的属性(声明其死信队列和交换器),声明死信交换器,代码如下:

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.HashMap;
import java.util.Map;@Configuration
public class RabbitConfig {@Beanpublic Queue queue() {Map<String, Object> props = new HashMap<>();// 消息的生存时间 10sprops.put("x-message-ttl", 10000);// 设置该队列所关联的死信交换器(当队列消息TTL到期后依然没有消费,则加 入死信队列)props.put("x-dead-letter-exchange", "ex.go.dlx");// 设置该队列所关联的死信交换器的routingKey,如果没有特殊指定,使用原 队列的routingKeyprops.put("x-dead-letter-routing-key", "go.dlx");Queue queue = new Queue("q.go", true, false, false, props);return queue;}@Beanpublic Queue queueDlx() {Queue queue = new Queue("q.go.dlx", true, false, false);return queue;}@Beanpublic Exchange exchange() {DirectExchange exchange = new DirectExchange("ex.go", true, false, null);return exchange;}/*** 死信交换器** @return*/@Beanpublic Exchange exchangeDlx() {DirectExchange exchange = new DirectExchange("ex.go.dlx", true, false, null);return exchange;}@Beanpublic Binding binding() {return BindingBuilder.bind(queue()).to(exchange()).with("go").noargs();}/*** 死信交换器绑定死信队列* @return*/@Beanpublic Binding bindingDlx() {return BindingBuilder.bind(queueDlx()).to(exchangeDlx()).with("go.dlx").noargs();}
}

在生产者端代码不用变。

如果想演示超过最大最列长度,可以设置普通对列长度:

Map<String, Object> props = MapUtil.newHashMap();
// 设置队列的最大长度
props.put("x-max-length", 10);

延迟队列

概述

延迟消息是指的消息发送出去后并不想立即就被消费,而是需要等(指定的)一段时间后才触发消费。

例如下面的业务场景:在支付宝上面买电影票,锁定了一个座位后系统默认会帮你保留15分钟时间,如果15分钟后还没付款那么不好意思系统会自动把座位释放掉。怎么实现类似的功能呢?

  1. 可以用定时任务每分钟扫一次,发现有占座超过15分钟还没付款的就释放掉。但是这样做很低效,很多时候做的都是些无用功;

  2. 可以用分布式锁、分布式缓存的被动过期时间,15分钟过期后锁也释放了,缓存key也不存在了;

  3. 还可以用延迟队列,锁座成功后会发送1条延迟消息,这条消息15分钟后才会被消费,消费的过程就是检查这个座位是否已经是“已付款”状态;

延迟队列的应用场景:

    • ① 订单在 10 分钟之内没有付款就自动取消。
    • ② 新创建的店铺,如果在 10 天之内都没有上传过商品,则自动发送消息提醒。
    • ③ 用户注册成功后,如果三天没有登录,则发送短信进行提醒。
    • ④ 用户发起退款,如果三天之内没有得到处理,则通知相关运营人员。
    • ⑤ 预定会议后,需要在预定的时间点前 10 分钟通知各个与会人员参加会议。

遗憾的是,在AMQP协议和RabbitMQ中都没有相关的规定和实现。

不过可以使用rabbitmq_delayed_message_exchange插件实现。

还可以我们可以借助上一小节介绍的“死信队列”来变相的实现。

插件和TTL方式有个很大的不同就是TTL存放消息在死信队列(delayqueue)里,二基于插件存放消息在延时交换机里(x-delayed-message exchange)。

插件实现延迟队列

安装插件

官网,下载 rabbitmq_delayed_message_exchange 插件,并解压到 RabbitMQ 的插件目录。

进入 RabbitMQ 的插件目录:

cd /usr/lib/rabbitmq/lib/rabbitmq_server-3.8.8/plugins

启用插件:

rabbitmq-plugins list 
rabbitmq-plugins enable rabbitmq_delayed_message_exchange

重启rabbitmq-server:

systemctl restart rabbitmq-server

添加延迟队列插件之后:

在这里插入图片描述

代码

实现流程如下:

在这里插入图片描述

配置类RabbitmqConfig.java:

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.HashMap;
import java.util.Map;/*** 配置类,用来声明交换机和队列,并配置之间的关系*/
@Configuration
public class RabbitmqConfig {/*** 普通交换机*/public static final String EXCHANGE = "delayed.exchange";/*** routingkey*/public static final String ROUTING_KEY = "delayed.routingkey";/*** 普通队列*/public static final String QUEUE = "delayed.queue";@Beanpublic CustomExchange exchange() {Map<String, Object> args = new HashMap<>();args.put("x-delayed-type", "direct");return new CustomExchange(EXCHANGE, "x-delayed-message", true, false, args);}/*** 声明队列*/@Beanpublic Queue queue() {return QueueBuilder.durable(QUEUE).build();}/*** 绑定关系*/@Beanpublic Binding binding() {return BindingBuilder.bind(queue()).to(exchange()).with(ROUTING_KEY).noargs();}
}

生产者ProducerController.java:

import com.github.config.RabbitmqConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;import java.time.LocalDateTime;/*** 生产者*/
@Slf4j
@RestController
public class ProducerController {@Autowiredprivate RabbitTemplate rabbitTemplate;@GetMapping("/send/{msg}/{ttl}")public String msg(@PathVariable("msg") String msg, @PathVariable("ttl") Integer ttl) {log.info("当前时间:{},发送一条时长{}毫秒 TTL 信息给队列:{}", LocalDateTime.now(), ttl, msg);MessagePostProcessor messagePostProcessor = (message) -> {// 注意,这里不再是 setExpiration ,而是 setDelaymessage.getMessageProperties().setDelay(ttl);return message;};rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE, RabbitmqConfig.ROUTING_KEY, msg, messagePostProcessor);return "发送消息成功";}
}

消费者RabbitmqListener.java:

import com.github.config.RabbitmqConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;/*** 消费者*/
@Slf4j
@Component
public class RabbitmqListener {@RabbitListener(queues = RabbitmqConfig.QUEUE)public void receive(Message message) {log.info("当前时间:{},收到死信队列信息:{}", LocalDateTime.now(), new String(message.getBody(), StandardCharsets.UTF_8));}}

演示:

curl 'http://127.0.0.1:8080/send/消息1/20000' -X GET
curl 'http://127.0.0.1:8080/send/消息2/2000' -X GET

IDEA 控制台结果显示:

在这里插入图片描述

TTL实现延迟队列

实现

实现过程如下:

在这里插入图片描述

配置类RabbitmqConfig.java:

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** 配置类,用来声明交换机和队列,并配置之间的关系*/
@Configuration
public class RabbitmqConfig {/*** 普通交换机 X*/public static final String EXCHANGE_X = "X";/*** 普通队列 QA*/public static final String QUEUE_A = "QA";/*** 普通 routing key*/public static final String ROUTING_KEY_XA = "XA";/*** 普通队列 QB*/public static final String QUEUE_B = "QB";/*** 普通 routing key*/public static final String ROUTING_KEY_XB = "XB";/*** 死信交换机 Y*/public static final String DEAD_EXCHANGE_Y = "Y";/*** 死信队列 QD*/public static final String DEAD_QUEUE_D = "QD";/*** 死信 routing key*/public static final String DEAD_ROUTING_KEY_YD = "YD";/*** 声明交换机*/@Beanpublic DirectExchange xExchange() {return new DirectExchange(EXCHANGE_X);}/*** 声明死信交换机*/@Beanpublic DirectExchange yExchange() {return new DirectExchange(DEAD_EXCHANGE_Y);}/*** 声明队列*/@Beanpublic Queue aQueue() {return QueueBuilder.durable(QUEUE_A)// 声明当前队列绑定的死信交换机.deadLetterExchange(DEAD_EXCHANGE_Y)// 声明当前队列绑定的死信队列.deadLetterRoutingKey(DEAD_ROUTING_KEY_YD)// 设置 TTL 时间.ttl(10 * 1000).build();}/*** 声明队列*/@Beanpublic Queue bQueue() {return QueueBuilder.durable(QUEUE_B)// 声明当前队列绑定的死信交换机.deadLetterExchange(DEAD_EXCHANGE_Y)// 声明当前队列绑定的死信队列.deadLetterRoutingKey(DEAD_ROUTING_KEY_YD)// 设置 TTL 时间.ttl(40 * 1000).build();}/*** 声明死信队列*/@Beanpublic Queue dQueue() {return QueueBuilder.durable(DEAD_QUEUE_D).build();}/*** 绑定关系*/@Beanpublic Binding xaBinding() {return BindingBuilder.bind(aQueue()).to(xExchange()).with(ROUTING_KEY_XA);}/*** 绑定关系*/@Beanpublic Binding xbBinding() {return BindingBuilder.bind(bQueue()).to(xExchange()).with(ROUTING_KEY_XB);}/*** 绑定关系*/@Beanpublic Binding ydBinding() {return BindingBuilder.bind(dQueue()).to(yExchange()).with(DEAD_ROUTING_KEY_YD);}}

生产者ProducerController.java:

import com.github.config.RabbitmqConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;import java.time.LocalDateTime;/*** 生产者*/
@Slf4j
@RestController
public class ProducerController {@Autowiredprivate RabbitTemplate rabbitTemplate;@GetMapping("/send/{msg}")public String msg(@PathVariable("msg") String msg) {log.info("当前时间:{},发送一条信息给两个 TTL 队列:{}", LocalDateTime.now(), msg);rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE_X, RabbitmqConfig.ROUTING_KEY_XA, "消息来自 ttl 为 10S 的队列: " + msg);rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE_X, RabbitmqConfig.ROUTING_KEY_XB, "消息来自 ttl 为 40s 的队列: " + msg);return "发送消息成功";}}

消费者RabbitmqListener.java:

import com.github.config.RabbitmqConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;/*** 消费者*/
@Slf4j
@Component
public class RabbitmqListener {@RabbitListener(queues = RabbitmqConfig.DEAD_QUEUE_D)public void receive(Message message) {log.info("当前时间:{},收到死信队列信息:{}", LocalDateTime.now(), new String(message.getBody(), StandardCharsets.UTF_8));}}

延迟队列优化

上面使用TTL实现了延迟队列,但是此时有些问题,如果现在我需要 5 min、10 min……,那么我岂不是每增加一个时间需求,就需要增加一个队列,如果是预定会议提前通知的场景,难道要增加无数个队列来满足要求?

解决:在消费者那边设置消息的 TTL 时间。

但是注意: RabbitMQ只会检查队列头部的消息是否过期,如果过期就放到死信队列,假如第一个过期时间很长,10s,第二个消息3s,则系统先看第一个消息,等到第一个消息过期,放到DLX。此时才会检查第二个消息,但实际上此时第二个消息早已经过期了,但是并没有先于第一个消息放到DLX。 使用插件不会出现这个问题,所以推荐使用插件实现延迟队列。

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

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

相关文章

Komodor:Kubernetes 监控工具全面指南

为了方便起见&#xff0c;Komodor 提供了一个简单的 Web 界面&#xff0c;以帮助您监控 Kubernetes 集群的状态。它拥有付费和免费增值计划&#xff0c;除了在出现问题时通知用户外&#xff0c;还拥有一系列方便的工具&#xff0c;用于跟踪和管理集群中部署的资源的状态。让我们…

计算机网络 应用层

文章目录 应用层域名系统 DNS域名系统概述互联网的域名结构域名服务器 文件传送协议FTP 概述FTP 的基本工作原理简单文件传送协议 TFTP 远程终端协议 TELNET万维网 WWW统一资源定位符 URL超文本传送协议 HTTP万维网的信息检索系统 电子邮件电子邮件概述简单邮件传送协议 SMTP邮…

2024年机器人和人工智能将通过4种方式改变行业

文 | BFT机器人 前言&#xff1a; 2023年是人工智能界充满创造性和突破性的一年&#xff0c;包括生成式人工智能在内的人工智能 (AI) 技术的出现引起了全球的关注并占据了头条新闻。然而&#xff0c;生成式人工智能在企业中的应用仍处于早期阶段&#xff0c;如何最好地利用这项…

大物②练习题解

1.【单选题】关于磁场中磁通量&#xff0c;下面说法正确的是&#xff08; D&#xff09; A、穿过闭合曲面的总磁通量不一定为零 B、磁感线从闭合曲面内穿出&#xff0c;磁通量为负 C、磁感线从闭合曲面内穿入&#xff0c;磁通量为正D、穿过闭合曲面的总磁通量一定为零 磁感线从…

“轻松粘贴,高效办公:自动粘贴文本技术让您事半功倍

"在快节奏的现代工作中&#xff0c;时间就是金钱。使用自动粘贴文本技术&#xff0c;让您告别繁琐的手动操作&#xff0c;提高工作效率。一键粘贴&#xff0c;释放您的双手&#xff0c;让您专注于创作和思考。让工作更高效&#xff0c;生活更精彩&#xff01;" 首先…

广告投放场景中ABtest分析的评价、优化和决策建议

目录 写在开头1. AB测试基础知识1.1 AB测试概述1.2 原理和流程1.3 广告领域中的AB测试应用 2. 评价广告投放中的AB测试2.1 关键指标选择与解释2.2 统计学方法应用 3. AB测试分析中的常见问题与解决方案3.1 样本偏差3.2 季节性影响3.3 测试时长选择3.4 结果误解与分析失误 4. 优…

《TrollStore巨魔商店》TrollStore2安装使用教程支持IOS14.0-16.6.1

TrollStore(巨魔商店) 简单的说就相当于一个永久的免费证书&#xff0c;它可以给你的iPhone和iPad安装任何你想要安装的App软件&#xff0c;而且不需要越狱,不用担心证书签名过期的问题&#xff0c;不需要个人签名和企业签名。 支持的版本&#xff1a; TrollStore安装和使用教…

Markdown 流程图绘制详解

✍️作者简介&#xff1a;小北编程&#xff08;专注于HarmonyOS、Android、Java、Web、TCP/IP等技术方向&#xff09; &#x1f433;博客主页&#xff1a; 开源中国、稀土掘金、51cto博客、博客园、知乎、简书、慕课网、CSDN &#x1f514;如果文章对您有一定的帮助请&#x1f…

Python图像处理实战:使用PIL库批量添加水印的完整指南【第27篇—python:Seaborn】

文章目录 1. 简介2. PIL库概述3. PIL库中涉及的类4. 实现原理5. 实现过程5.1 原始图片5.2 导入相关模块5.3 初始化数据5.4 水印字体设置5.5 打开原始图片并创建存储对象5.6 计算图片和水印的大小5.7 选择性设置水印文字5.8 绘制文字并设置透明度5.9 遍历获取图片文件并调用绘制…

超简单的node爬虫小案例

同前端爬取参数一样&#xff0c;输入三个参数进行爬取 注意点也一样&#xff1a; 注意分页的字段需要在代码里面定制化修改&#xff0c;根据你爬取的接口&#xff0c;他的业务规则改代码中的字段。比如我这里总条数叫total&#xff0c;人家的不一定。返回的数据我这里是data.r…

内存泄漏检测方式

一 、 日志记录 通过宏定义重载了 malloc 和 free 函数&#xff0c;以在分配和释放内存的时候记录一些信息&#xff0c;包括文件名和行号&#xff0c;并将这些信息写入到相应的文件中。然后在 main 函数中演示了使用这些宏进行内存分配和释放。 _malloc 函数&#xff1a; 在分配…

基于java web的机票管理系统设计与实现设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

中国康复辅助器具协会脊柱侧弯康复技术委员会成立大会圆满召开

2024年1月13日,由中国康复辅助器具协会主办,中国康复辅助器具协会脊柱侧弯康复辅助器具技术专业委员会承办,北京蓝田医疗设备有限公司协办,中国康复辅助器具协会脊柱侧弯康复辅助器具技术专业委员会成立大会暨脊柱侧弯康复辅助器具技术交流会在北京市山西大厦隆重召开。本次会议…

Linux -- firewalld的富语言规则

1. Firewalld支持两种类型的NAT&#xff1a;IP地址伪装和端口转发。 &#xff08;1&#xff09;IP地址伪装 地址伪装&#xff08;masquerade)&#xff1a;通过地址伪装&#xff0c;NAT 设备将经过设备的包转发到指定接收方&#xff0c;同时将通过的数据包的源地址更改为其自己的…

基于SSM的流浪动物救助网站的设计与实现-计算机毕业设计源码82131

摘 要 随着生活水平的持续提高和家庭规模的缩小&#xff0c;宠物已经成为越来越多都市人生活的一部分&#xff0c;随着宠物的增多&#xff0c;流浪的动物的日益增多&#xff0c;中国的流浪动物领养和救助也随之形成规模&#xff0c;同时展现巨大潜力。本次系统的是基于SSM框架的…

C语言:底层剖析——函数栈帧的创建和销毁

一、究竟什么是函数栈帧 C语言的使用是面向过程的&#xff0c; 面向过程就是分析出解决问题所需要的步骤&#xff0c;然后用函数把这些步骤一步一步实现&#xff0c;使用的时候一个一个依次调用就可以了。所以C语言的程序都是以函数作为基本单位的&#xff0c;如果能够深入理解…

全光谱护眼灯有哪些?寒假护眼台灯推荐

全光谱指的是包含了整个可见光谱范围以及部分红外和紫外光的光线。通常的白炽灯或荧光灯只能发出有限范围内的光波&#xff0c;而全光谱台灯通过使用多种类型的LED灯或荧光灯管来产生更广泛的光谱。这样的光谱更接近自然光&#xff0c;能够提供更真实的颜色还原和更好的照明效果…

【MFC】学生成绩管理系统(期末项目)

如果需要代码请评论区留言或私信 课程设计具体实现 数据库设计 E-R图 关系模式 教师(工号&#xff0c;姓名&#xff0c;学院) 主键(工号)学生(学号&#xff0c;姓名&#xff0c;性别&#xff0c;年龄&#xff0c;班级&#xff0c;专业&#xff0c;学分) 主键(学号)课程(课程…

element-ui el-table表格勾选框条件禁用,及全勾选按钮禁用, 记录

项目场景&#xff1a; 表格的部分内容是可以被勾选的&#xff0c;部分内容是不可以被勾选的 使用的是 “element-plus”: “^2.2.22”, 以上应该都是兼容的 问题描述 要求el-table表格中&#xff0c;部分内容不可以被勾选&#xff0c;全选框在没有可选内容时&#xff0c;是禁…

【漏洞复现】Sentinel Dashboard SSRF漏洞(CVE-2021-44139)

Nx01 产品简介 Sentinel Dashboard是一个轻量级的开源控制台&#xff0c;提供机器发现以及健康情况管理、监控、规则管理和推送的功能。它还提供了详细的被保护资源的实际访问统计情况&#xff0c;以及为不同服务配置的限流规则。 Nx02 漏洞描述 CVE-2021-44139漏洞主要存在于…