【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 {
    @Autowired
    private 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<>();
        // 设置队列TTL
        arguments.put("x-message-ttl", 10000);
        // 设置该队列所关联的死信交换器(当队列消息TTL到期后依然没有消费,则加入死信队列)
        arguments.put("x-dead-letter-exchange", "exchange.dlx");
        // 设置该队列所关联的死信交换器的routingKey,如果没有特殊指定,使用原队列的 routingKey
        arguments.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 {

    @Bean
    public Queue queue() {
        Map<String, Object> props = new HashMap<>();
        // 消息的生存时间 10s
        props.put("x-message-ttl", 10000);
        // 设置该队列所关联的死信交换器(当队列消息TTL到期后依然没有消费,则加 入死信队列)
        props.put("x-dead-letter-exchange", "ex.go.dlx");
        // 设置该队列所关联的死信交换器的routingKey,如果没有特殊指定,使用原 队列的routingKey
        props.put("x-dead-letter-routing-key", "go.dlx");
        Queue queue = new Queue("q.go", true, false, false, props);
        return queue;
    }

    @Bean
    public Queue queueDlx() {
        Queue queue = new Queue("q.go.dlx", true, false, false);
        return queue;
    }

    @Bean
    public Exchange exchange() {
        DirectExchange exchange = new DirectExchange("ex.go", true, false, null);
        return exchange;
    }

    /**
     * 死信交换器
     *
     * @return
     */
    @Bean
    public Exchange exchangeDlx() {
        DirectExchange exchange = new DirectExchange("ex.go.dlx", true, false, null);
        return exchange;
    }

    @Bean
    public Binding binding() {
        return BindingBuilder.bind(queue()).to(exchange()).with("go").noargs();
    }

    /**
     * 死信交换器绑定死信队列
     * @return
     */
    @Bean
    public 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";

    @Bean
    public CustomExchange exchange() {
        Map<String, Object> args = new HashMap<>();
        args.put("x-delayed-type", "direct");
        return new CustomExchange(EXCHANGE, "x-delayed-message", true, false, args);
    }

    /**
     * 声明队列
     */
    @Bean
    public Queue queue() {
        return QueueBuilder.durable(QUEUE).build();
    }

    /**
     * 绑定关系
     */
    @Bean
    public 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 {

    @Autowired
    private 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 ,而是 setDelay
            message.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";

    /**
     * 声明交换机
     */
    @Bean
    public DirectExchange xExchange() {
        return new DirectExchange(EXCHANGE_X);
    }

    /**
     * 声明死信交换机
     */
    @Bean
    public DirectExchange yExchange() {
        return new DirectExchange(DEAD_EXCHANGE_Y);
    }

    /**
     * 声明队列
     */
    @Bean
    public Queue aQueue() {
        return QueueBuilder.durable(QUEUE_A)
                // 声明当前队列绑定的死信交换机
                .deadLetterExchange(DEAD_EXCHANGE_Y)
                // 声明当前队列绑定的死信队列
                .deadLetterRoutingKey(DEAD_ROUTING_KEY_YD)
                // 设置 TTL 时间
                .ttl(10 * 1000)
                .build();
    }

    /**
     * 声明队列
     */
    @Bean
    public Queue bQueue() {
        return QueueBuilder.durable(QUEUE_B)
                // 声明当前队列绑定的死信交换机
                .deadLetterExchange(DEAD_EXCHANGE_Y)
                // 声明当前队列绑定的死信队列
                .deadLetterRoutingKey(DEAD_ROUTING_KEY_YD)
                // 设置 TTL 时间
                .ttl(40 * 1000)
                .build();
    }

    /**
     * 声明死信队列
     */
    @Bean
    public Queue dQueue() {
        return QueueBuilder.durable(DEAD_QUEUE_D).build();
    }

    /**
     * 绑定关系
     */
    @Bean
    public Binding xaBinding() {
        return BindingBuilder.bind(aQueue()).to(xExchange()).with(ROUTING_KEY_XA);
    }

    /**
     * 绑定关系
     */
    @Bean
    public Binding xbBinding() {
        return BindingBuilder.bind(bQueue()).to(xExchange()).with(ROUTING_KEY_XB);
    }

    /**
     * 绑定关系
     */
    @Bean
    public 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 {

    @Autowired
    private 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.mfbz.cn/a/326051.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Netty开篇——NIO章下(五)

SelectionKey 表示 Selector 和网络通道的注册关系&#xff0c;共四种(全是常量): Int OP_ACCEPT:有新的网络连接可以接受&#xff0c;值为 16 &#xff08;1 << 4&#xff09;Int OP_CONNECT: 代表连接已经建立&#xff0c;值为 8 &#xff08;1 << 3&#xff09;…

#AIGC#text2video文生视频,开源DragNUWA:通过集成文本、图像和轨迹对视频生成进行细粒度控制

DragNUWA&#xff1a;通过集成文本、图像和轨迹对视频生成进行细粒度控制 论文地址&#xff1a;https://arxiv.org/abs/2308.08089 DragNUWA 使用户能够直接操纵图像中的背景或对象&#xff0c;模型将这些动作无缝地转换为相机运动或对象运动&#xff0c;生成相应的视频。 Drag…

linux基础学习(3):挂载

挂载可以理解为给磁盘空间一个可访问的入口&#xff0c;那个入口称为挂载点&#xff0c;相当于windows中的盘符。 1.挂载命令mount 1.1直接输入mount 查看系统已挂载的设备 1.2挂载与卸载命令 mount -t 文件系统名 设备文件名 挂载点 | umount 挂载点 或 umount 设…

机器学习——支持向量机SVM

1 摘要&#xff1a; 支持向量机&#xff08;SVM&#xff09;是一种二类分类模型&#xff0c;其基本模型是在特征空间上找到最佳的分离超平面使得训练集上正负样本间隔最大&#xff0c;间隔最大使它有别于感知机&#xff0c;支持向量机也可通过核技巧使它成为非线性分类器。支持…

防火墙部署安全区域

目录 为什么需要安全区域在防火墙上如何来区分不同的网络将接口划分到安全区域安全区域、受信任程度与安全级别安全域间、安全策略与报文流动的方向 安全区域配置案例 为什么需要安全区域 防火墙主要部署在网络边界起到隔离的作用 在防火墙上如何来区分不同的网络 防火墙通过安…

MobaXterm无法上传文件处理

ssh能成功通过mobaxterm连接虚拟机但sftp上传失败的解决办法 1、出现问题时&#xff0c;/etc/ssh/sshd_config的配置文件关于sftp的这行下图所示的情况 2、更改配置文件/etc/ssh/sshd_config的配置文件关于sftp为“internal-sftp”。 3、执行命令systemctl restart sshd&…

leetcode82. 删除排序链表中的重复元素 II

文章目录 题目思路1复杂度Code2 思路2复杂度2Code2 题目 给定一个已排序的链表的头 head &#xff0c; 删除原始链表中所有重复数字的节点&#xff0c;只留下不同的数字 。返回 已排序的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,3,4,4,5] 输出&#xff1a;…

10.云原生之在线开发调试

云原生专栏大纲 文章目录 vscode-server介绍VSCode Server 和云开发结合vscode-server安装code-server安装插件在线安装插件离线安装插件安装中文插件 配置开发环境在容器中安装开放环境Dockerfile制作镜像 git拉取项目 vscode-server介绍 VSCode Server&#xff08;Visual S…

C++ 编程需要什么样的开发环境?

C 编程需要什么样的开发环境&#xff1f; 在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「C的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#…

k8s之pod基础(下)

k8s之pod基础&#xff08;下&#xff09; 存活探针和就绪探针&#xff0c;会伴随整个pod的生命周期 就绪探针的特点&#xff1a;pod的状态是running&#xff0c;ready状态是notready&#xff0c;容器不可以提供正常的业务访问&#xff0c;就绪探针不会重启容器 就绪探针exec的…

闲鱼宝库亮相!闲鱼商品详情关键词搜索电商API接口助你畅享无尽好货!

随着互联网的快速发展&#xff0c;电商平台的崛起已经改变了人们的购物习惯。而在众多电商平台中&#xff0c;闲鱼作为一款社区二手交易平台&#xff0c;一直备受用户喜爱。如今&#xff0c;闲鱼宝库正式亮相&#xff0c;为用户带来了更加全面、详细的商品详情关键词搜索电商AP…

IP地址冲突警告!你的网络正在受到威胁

IP地址冲突是网络安全中的一个严重问题&#xff0c;可能导致网络不稳定、数据泄漏等严重后果。本文将深入探讨IP地址冲突的原因、影响以及如何应对&#xff0c;以提醒用户关注网络安全问题。 1. IP地址冲突的原因&#xff1a; 动态分配问题&#xff1a;在使用动态IP地址分配的…

开发需求总结9-el-tree获取选中节点,节点全选时返回被全选子级的父节点,未全选则返回被选中的节点

目录 需求描述 代码实现&#xff1a; 需求描述 需要获取树组件选中的节点&#xff0c;假如父节点被选中&#xff08;该节点全选&#xff09;&#xff0c;即只返回父节点的数据&#xff0c;如父节点未被全选&#xff0c;则正常返回被选中节点的数据。 示例一&#xff1a; 如上图…

大众点评评论采集软件使用教程

导出字段&#xff1a; 店铺ID 评论ID 发布时间 人均消费 评分 详情链接 点赞数 浏览数 评论数 最后更新时间 发布平台 推荐 评论详情 原始评论 图片数 图片链接 用户等级 用户名称 用户头像 VIP 私

农业无人机行业分析:单年内作业量突破13亿亩次

面对我国18亿亩的耕地植保市场需求&#xff0c;未来我国植保无人机将依然保持快速发展态势&#xff0c;预计2022年我国植保无人机销量将增长至8万架。 植保无人机市场呈现爆发式增长&#xff0c;同时也吸引了不少企业进入&#xff0c;我们从2022年植保无人机企业网络热度榜中可…

Linux学习记录——사십일 高级IO(2)--- Select型服务器

文章目录 1、思路2、select接口3、实现1、准备工作2、实现等待多个fd3、辨别连接和简单处理读事件4、简单处理写、读事件 4、特点 1、思路 select就是多路转接IO。select能以某种形式&#xff0c;等待多个文件描述符&#xff0c;只要有哪个fd有数据就可以读取并全部返回。就绪…

服务异步通讯——springcloud

服务异步通讯——springcloud 文章目录 服务异步通讯——springcloud初始MQRabbitMQ快速入门单机部署1.1.下载镜像安装MQ SpringAMQPwork Queue 工作队列Fanout Exchange广播模式DirectExchange路由模式TopicExchange话题模式 消息转换器 初始MQ RabbitMQ快速入门 官网https:/…

手把手教你SWOT分析!建议收藏

最近&#xff0c;我一直为一件事情感到困扰。那家位于市中心的西点店生意越来越好&#xff0c;甚至已经开了两家分店&#xff0c;但是挣来的钱还不足够买房子。于是最近&#xff0c;我被这如火如荼的奶茶市场所吸引&#xff0c;想要利用已有的资源开一家奶茶店。但是我不确定这…

计算机视觉开发工程师怎么考?报考难度大吗?证书含金量高吗?

为进一步贯彻落实中共中央印发《关于深化人才发展体制机制改革的意见》和国务院印发《关于“十四五”数字经济发展规划》等有关工作的部署要求&#xff0c;深入实施人才强国战略和创新驱动发展战略&#xff0c;加强全国数字化人才队伍建设&#xff0c;持续推进人工智能专业人员…

JNPF低代码引擎到底是什么?

最近听说一款可以免费部署本地进行试用的低代码引擎&#xff0c;源码上支持100%源码&#xff0c;提供的功能和技术支持比较完善。借助这篇篇幅我们了解下JNPF到底是什么&#xff1f; JNPF开发平台是一款PaaS服务为核心的零代码开发平台&#xff0c;平台提供了多租户账号管理、主…
最新文章