RabbitMQ队列

RabbitMQ队列

1、死信的概念

​ 先从概念解释上搞清楚这个定义,死信,顾名思义就是无法被消费的消息,字面意思可以这样理解,一般来说,producer将消息投递到broker或者直接到queue里了,consumer 从 queue取出消息进行消费,但某些时候由于特定的原因导致queue中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信,有死信自然就有了死信队列。
​ 应用场景:为了保证订单业务的消息数据不丢失,需要使用到RabbitMQ的死信队列机制,当消息消费发生异常时,将消息投入死信队列中.还有比如说:用户在商城下单成功并点击去支付后在指定时间未支付时自动失效

1.1、死信的来源

  • 消息TTL过期;
  • 队列达到最大长度(队列满了,无法再添加数据到mq.中);
  • 消息被拒绝(basic.reject 或 basic.nack)并且requeue=false;

1.2、死信实战

架构图

image-20240301203605503

1.3、消费者01

//死信队列
public class DeadMessageConsumer01 {//普通交换机public static final String NORMAL_EXCHANGE = "normal_exchange";//死信交换机public static final String DEAD_EXCHANGE = "dead_exchange";//普通队列public static final String NORMAL_QUEUE = "normal_queue";//死信队列public static final String DEAD_QUEUE = "dead_queue";public static void main(String[] args) throws IOException, TimeoutException {Channel channel = RabbitMQUtils.getChannel();//声明普通和死信交换机channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);//声明普通队列,这一次需要使用参数了Map<String, Object> arguments = new HashMap<>();//这里就要设置死信到死信交换机上了arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);//设置死信routingKeyarguments.put("x-dead-letter-routing-key","deadKey");channel.queueDeclare(NORMAL_QUEUE,false,false,false,arguments);//声明死信队列channel.queueDeclare(DEAD_QUEUE,false,false,false,null);//开始分别将死信和普通交换机与队列绑定channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,"normalKey");channel.queueBind(DEAD_QUEUE,DEAD_EXCHANGE,"deadKey");DeliverCallback deliverCallback = (consumerTag,message) ->{System.out.println("死信测试普通队列消费者01收到消息:"+new String(message.getBody()));};channel.basicConsume(NORMAL_QUEUE,true,deliverCallback,consumerTag -> {});}}

1.4、生产者

//死信队列的生产者
public class DeadMessageProducers {//普通交换机public static final String NORMAL_EXCHANGE = "normal_exchange";public static void main(String[] args) throws IOException, TimeoutException {Channel channel = RabbitMQUtils.getChannel();//死信消息设置TTL过期时间,设置10秒AMQP.BasicProperties basicProperties = new AMQP.BasicProperties().builder().expiration("10000").build();//发10条消息for (int i = 1; i < 11; i++) {String message = "dead"+i;channel.basicPublish(NORMAL_EXCHANGE,"normalKey",basicProperties,message.getBytes());System.out.println("发送消息成功:"+ message);}}
}

​ 先把消费者启动,将交换机和队列先声明出来,然后关掉消费者再启动生产者,生产者一直发这10个消息,但是消费者已经停止了,没人消费,所以TTL一到期就会被放进死信队列里,我们在web管理界面就能看到,普通队列的10条消息很快就跑到死信队列中了。

image-20240302110147069

image-20240302111240369

​ 然后现在死信队列里有那10条消息,再来一个专门消费死信消息的消费者2,非常简单,只需要消费死信队列即可。

//死信队列
public class DeadMessageConsumer02 {//死信队列public static final String DEAD_QUEUE = "dead_queue";public static void main(String[] args) throws IOException, TimeoutException {Channel channel = RabbitMQUtils.getChannel();DeliverCallback deliverCallback = (consumerTag,message) ->{System.out.println("死信测试普通队列消费者02收到消息:"+new String(message.getBody()));};channel.basicConsume(DEAD_QUEUE,true,deliverCallback,consumerTag -> {});}
}

启动它消费死信队列

image-20240302111857175

消费完成,看看管理界面,死信消息归0

image-20240302111933926

1.5、队列达到最大长度的死信

​ 在原来的消费者基础上增加一条

//设置队列最大长度
arguments.put("x-max-length",6);

​ 原来的生产者也不需要给消息设置过期时间了直接发送消息即可。

​ 测试之前先将原来的普通队列删除再启动消费者

image-20240302134737969

​ 仍然把消费者停掉,生产者启动,发送10条消息6条正常4条超过了限制会被放到死信队列

image-20240302134915165

1.6、消息被拒死信队列

我们先把原来的消息全部消费掉或者直接删除原来的队列,再演示新的。

消费者做一些修改

DeliverCallback deliverCallback = (consumerTag,message) ->{String msg = new String(message.getBody());if (msg.equals("dead5")){System.out.println("拒绝第5个消息:"+ msg);//basicReject第二个参数是是否还放回队列channel.basicReject(message.getEnvelope().getDeliveryTag(),false);} else {System.out.println("死信测试普通队列消费者01收到消息:" + msg);channel.basicAck(message.getEnvelope().getDeliveryTag(),false);}};
channel.basicConsume(NORMAL_QUEUE,false,deliverCallback,consumerTag -> {});

生产者发送10条消息,看到消费者01拒绝了第5个消息,其他的正常收到

image-20240302143440745

通过管理界面看到第5条消息放到了死信队列中

image-20240302143507611

启动专门消费死信队列的消费者02将第5个消息消费。

image-20240302143537357

2、延迟队列

2.1、延迟队列概念

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

其实,TTL过期导致的死信队列就是延迟队列

应用场景:

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

​ 这些场景都有一个特点,需要在某个事件发生之后或者之前的指定时间点完成某一项任务,如:发生订单生成事件,在十分钟之后检查该订单支付状态,然后将未支付的订单进行关闭;看起来似乎使用定时任务,一直轮询数据,每秒查一次,取出需要被处理的数据,然后处理不就完事了吗?

​ 如果数据量比较少,确实可以这样做,比如:对于“如果账单一周内未支付则进行自动结算”这样的需求,如果对于时间不是严格限制,而是宽松意义上的一周,那么每天晚上跑个定时任务检查一下所有未支付的账单,确实也是一个可行的方案。

​ 但对于数据量比较大,并且时效性较强的场景,如:“订单十分钟内未支付则关闭“,短期内未支付的订单数据可能会有很多,活动期间甚至会达到百万甚至千万级别,对这么庞大的数据量仍旧使用轮询的方式显然是不可取的,很可能在一秒内无法完成所有订单的检查,同时会给数据库带来很大压力,无法满足业务要求而且性能低下。

2.1.1、RabbitMQ 中的 TTL

​ TTL 是什么呢?TTL 是 RabbitMQ 中一个消息或者队列的属性,表明一条消息或者该队列中的所有 消息的最大存活时间, 单位是毫秒。

​ 换句话说,如果一条消息设置了 TTL 属性或者进入了设置 TTL 属性的队列,那么这 条消息如果在 TTL 设置的时间内没有被消费,则会成为"死信"。如果同时配置了队列的 TTL 和消息的 TTL,那么较小的那个值将会被使用,有两种方式设置 TTL。

  • 消息设置 TTL

    例如:

     rabbitTemplate.convertAndSend("X","XC",message,msg -> {//发送消息设置消息的TTLmsg.getMessageProperties().setExpiration(ttl+"000");return msg;});
    
  • **队列设置 TTL **

    例如:在创建队列的时候设置队列的“x-message-ttl”属性

     //声明普通队列 TTL 为40秒@Bean("queueB")public Queue queueB(){HashMap<String, Object> map = new HashMap<>(3);//设置死信交换机map.put("x-dead-letter-exchange",Y_DEAD_LETTER_EXCHANGE);//设置死信RoutingKeymap.put("x-dead-letter-routing-key","YD");//设置过期时间,10秒map.put("x-message-ttl",40000);return QueueBuilder.durable(QUEUE_B).withArguments(map).build();}
    

2.2、整合SpringBoot

添加依赖项

<properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><!-- 添加 Spring Boot 版本 --><spring.boot.version>2.3.4.RELEASE</spring.boot.version><!-- 添加 Lombok 版本 --><lombok.version>1.18.20</lombok.version><!-- 添加 Spring AMQP 版本 --><spring.amqp.version>2.1.4.RELEASE</spring.amqp.version><!-- 添加 Springfox Swagger 版本 --><springfox.version>2.9.2</springfox.version></properties><dependencies><!-- RabbitMQ 依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId><version>${spring.boot.version}</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>${spring.boot.version}</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><version>${spring.boot.version}</version><scope>test</scope></dependency><dependency><groupId>com.taobao.arthas</groupId><artifactId>fastjson</artifactId><version>1.2.80-fix</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version></dependency><!-- Swagger --><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>${springfox.version}</version></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>${springfox.version}</version></dependency><!-- RabbitMQ 测试依赖 --><dependency><groupId>org.springframework.amqp</groupId><artifactId>spring-rabbit-test</artifactId><version>${spring.amqp.version}</version><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>

创建application.properties文件

spring.rabbitmq.host=192.168.111.28
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.redis.password=123

swagger2的配置类SwaggerConfig

@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("zm", "http://zmblog.vip","3339332352@qq.com")).build();}
}

2.3、队列TTL

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

image-20240302163555580

2.4、配置文件类代码

TtlQueueConfig

//TTL队列,配置类代码
@Configuration
public class TtlQueueConfig {//普通交换机名称public static final String X_EXCHANGE = "X";//死信交换机名称public static final String Y_DEAD_LETTER_EXCHANGE = "Y";//普通队列名称,两个public static final String QUEUE_A = "QA";public static final String QUEUE_B = "QB";//死信队列名称public static final String DEAD_LETTER_QUEUE = "QD";//声明普通交换机X_EXCHANGE,起一个别明xExchange注入bean@Bean("xExchange")public DirectExchange xExchange(){return new DirectExchange(X_EXCHANGE);}//声明死信交换机Y_EXCHANGE,起一个别明yExchange注入bean@Bean("yExchange")public DirectExchange yExchange(){return new DirectExchange(Y_DEAD_LETTER_EXCHANGE);}//声明普通队列 TTL 为10秒@Bean("queueA")public Queue queueA(){HashMap<String, Object> map = new HashMap<>(3);//设置死信交换机map.put("x-dead-letter-exchange",Y_DEAD_LETTER_EXCHANGE);//设置死信RoutingKeymap.put("x-dead-letter-routing-key","YD");//设置过期时间,10秒map.put("x-message-ttl",10000);return QueueBuilder.durable(QUEUE_A).withArguments(map).build();}//声明普通队列 TTL 为40秒@Bean("queueB")public Queue queueB(){HashMap<String, Object> map = new HashMap<>(3);//设置死信交换机map.put("x-dead-letter-exchange",Y_DEAD_LETTER_EXCHANGE);//设置死信RoutingKeymap.put("x-dead-letter-routing-key","YD");//设置过期时间,10秒map.put("x-message-ttl",40000);return QueueBuilder.durable(QUEUE_B).withArguments(map).build();}//死信队列@Bean("queueD")public Queue queueD(){return QueueBuilder.durable(DEAD_LETTER_QUEUE).build();}//绑定@Beanpublic Binding QABindingX(@Qualifier("queueA") Queue queueA,@Qualifier("xExchange") DirectExchange xExchange){return BindingBuilder.bind(queueA).to(xExchange).with("XA");}@Beanpublic Binding QBBindingX(@Qualifier("queueB") Queue queueB,@Qualifier("xExchange") DirectExchange xExchange){return BindingBuilder.bind(queueB).to(xExchange).with("XB");}@Beanpublic Binding QDBindingY(@Qualifier("queueD") Queue queueD,@Qualifier("yExchange") DirectExchange yExchange){return BindingBuilder.bind(queueD).to(yExchange).with("YD");}
}

生产者controller,DeadLetterProducers

//生产者发送延迟消息
@Slf4j
@RestController
public class DeadLetterProducers {//自动注入@Autowiredprivate RabbitTemplate rabbitTemplate;//发消息@GetMapping("/sendMsg/{message}")public void sendMsg(@PathVariable String message){log.info("当前时间:{},发送一条消息给两个TTL队列:{}",new Date().toString(),message);rabbitTemplate.convertAndSend("X","XA","消息来自于TTL为10秒的队列:"+message);rabbitTemplate.convertAndSend("X","XB","消息来自于TTL为40秒的队列:"+message);}
}

消费者:

//消费者
@Slf4j
@Component
public class DeadLetterQueueConsumer {//接收消息,定义一个监听器来监听死信队列中的消息@RabbitListener(queues = "QD")public void receiveD(Message message, Channel channel){String s = new String(message.getBody());log.info("当前时间:{},收到死信队列的消息:{}",new Date().toString(),s);}}

成功启动

image-20240302203312306

​ 在浏览器上输入localhost:8080/sendMsg/哈哈哈哈哈

​ 发现后台开始输出信息,先是发送的提示信息,然后过了10秒消费者收到了一个信息,然后再过30秒收到了第二条信息。

image-20240302203706023

​ 结果是没有问题的,第一条消息在10S后变成了死信消息,然后被消费者消费掉,第二条消息在40S之后变成了死信消息,然后被消费掉,这样一个延时队列就打造完成了。

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

2.5、延迟队列优化

​ 在这里新增了一个队列 QC,它是通用的延迟队列,生产者掌握时间(这是设定消息的过期时间,之前是设定队列的过期时间),绑定关系如下,该队列不设置 TTL 时间

image-20240303104749887

我们让生产者来决定延迟队列的时间,需要多少就指定多少这样更符合需求。

在TtlQueueConfig类中添加内容

public static final String QUEUE_C = "QC";
//新加一个通用TTL的普通队列@Bean("queueC")public Queue queueC(){HashMap<String, Object> map = new HashMap<>(2);//设置死信交换机map.put("x-dead-letter-exchange",Y_DEAD_LETTER_EXCHANGE);//设置死信RoutingKeymap.put("x-dead-letter-routing-key","YD");//TTL就交给生产者指定了这里就不需要写了return QueueBuilder.durable(QUEUE_C).withArguments(map).build();}
//QC绑定普通交换机@Beanpublic Binding QCBindingX(@Qualifier("queueC") Queue queueC,@Qualifier("xExchange") DirectExchange xExchange){return BindingBuilder.bind(queueC).to(xExchange).with("XC");}

生产者的controller添加一个请求

//发指定TTL的消息
@GetMapping("/sendExpMsg/{message}/{ttl}")
public void sendExpMsg(@PathVariable String message,@PathVariable String ttl){log.info("当前时间:{},发送一条时长为:{}毫秒的队列消息给队列QC:{}",new Date(),ttl,message);rabbitTemplate.convertAndSend("X","XC",message,msg -> {//发送消息设置消息的TTLmsg.getMessageProperties().setExpiration(ttl+"000");return msg;});
}

​ 我们启动服务,浏览器上分别输入localhost:8080/sendExpMsg/这是第一条信息/20,localhost:8080/sendExpMsg/这是第二条信息/2

image-20240303121301599

​ 我们发现,结果并不是我们所设想的那样,延时20秒的和2秒的两条消息都是在20秒时接收到,同时接收了,而这个顺序也是按照先进先出的队列特性来的,显然这不是实际需求。

​ 在最开始的时候,我们就了解到如果在消息属性上设置TTL的方式,消息可能并不会按时“死亡”,因为RabbitMQ只会检查第一个消息是否过期,如果过期则丢到死新信队列,如果第一个消息的延时时间非常长,而第二个消息的延时时长很短,第二个消息并不会优先得到执行。

​ 那么如何解决这个问题,就需要使用RabbitMQ的插件实现延迟队列

2.6、RabbitMQ的插件实现延迟队列

​ 如果不能实现在消息粒度上的TTL,并使其在设置的TL时间及时死亡,就无法设计成一个通用的延时队列。

2.6.1、安装RabbitMQ的延时队列插件

​ 在官网上下载https://www.rabbitmq.com/community-plugins.html,rabbitmq_delayed_message__exchange插件,然后解压放置到RabbitMQ的插件目录。

​ 下载3.8.0的连接rabbitmq_delayed_message_exchange-3.8.0.ez (github.com)

image-20240303124317709

​ 进入 RabbitMQ 的安装目录下的 plgins 目录,执行下面命令让该插件生效,然后重启 RabbitMQ

/usr/lib/rabbitmq/lib/rabbitmq_server-3.8.8/plugins
rabbitmq-plugins enable rabbitmq_delayed_message_exchange

​ 把插件放到RabbitMQ的插件文件夹

image-20240303124759301

​ 然后进行安装

image-20240303124912826

​ 重启Rabbitmq之后就可以了,可以看到交换机的类型新加一个x-delayed-message类型的

image-20240303125247971

​ 这样延迟的任务就交给交换机来做了,生产者把消息发给交换机,然后消息就在交换机延迟了,然后延迟时间过了之后就可以发给普通的队列了。

2.6.2、基于插件的延迟消息实现

​ 我们新加一个队列delayed.queue,一个自定义交换机 delayed.exchange,绑定关系如下:

在这里插入图片描述

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

配置文件类

//使用插件实现延迟
public class DelayedQueueConfig {//声明延迟类型的交换机名称public static final String DELAYED_EXCHANGE_NAME = "delayed.exchange";//队列public static final String DELAYED_QUEUE_NAME = "delayed.queue";//routingKeypublic static final String DELAYED_ROUTING_KEY = "delayed.routingKey";//声明队列@Beanpublic Queue delayedQueue(){return QueueBuilder.durable(DELAYED_QUEUE_NAME).build();}//声明交换机CustomExchange是自定义交换机,由于使用插件提供的类型在原来的四个类型中没有所以只能自定义/* public CustomExchange(String name, String type, boolean durable, boolean autoDelete, Map<String, Object> arguments) {super(name, durable, autoDelete, arguments);this.type = type;}第一个参数就是交换机的名称第二个参数就是交换机的自定义类型第三个参数就是是否持久化第四个参数就是是否自动删除第五个是其他参数* */@Beanpublic CustomExchange delayedExchange(){HashMap<String, Object> arguments = new HashMap<>();//设置自定义交换机的类型//消息确实是延迟了,但是怎么传播到队列呢,是要多播呢还是直连呢,所以还得设置类型//这里设置直连,怎么发送是直连的发送,啥时候发送就是延迟。arguments.put("x-delayed-type","direct");return new CustomExchange(DELAYED_EXCHANGE_NAME,"x-delayed-message",true,false,arguments);}//绑定//返回中最后的构建,build有参数,noargs没有参数@Beanpublic Binding delayedBinding(@Qualifier("delayedQueue") Queue delayedQueue,@Qualifier("delayedExchange") CustomExchange delayedExchange){return BindingBuilder.bind(delayedQueue).to(delayedExchange).with(DELAYED_ROUTING_KEY).noargs();}
}

​ 生产者controller新加请求方法

//延迟插件发送消息
@GetMapping("/sendDelayMsg/{message}/{delayTime}")
public void sendDelayMsg(@PathVariable String message,@PathVariable int delayTime){log.info("当前时间:{},发送一条时长为:{}秒的队列消息延迟队列delay.queue:{}",new Date(),delayTime,message);rabbitTemplate.convertAndSend(DelayedQueueConfig.DELAYED_EXCHANGE_NAME,DelayedQueueConfig.DELAYED_ROUTING_KEY,message, msg -> {//发送消息设置延时时长msg.getMessageProperties().setDelay(delayTime*1000);return msg;});
}

​ 消费者

//专门消费由延迟交换机发出的消息
@Slf4j
@Component
public class DelayedLetterConsumer {@RabbitListener(queues = DelayedQueueConfig.DELAYED_QUEUE_NAME)public void receiveDelayed(Message message){String s = new String(message.getBody());log.info("当前时间:{},收到delayed.queue队列的消息:{}",new Date(),s);}
}

我们启动服务浏览器输入localhost:8080/sendDelayMsg/这是第一条信息/20,localhost:8080/sendDelayMsg/这是第二条信息/2

image-20240303142214558

​ 可以看到结果是很正确的,第二条消息在发送2秒后就收到了,第一条消息在它发送20秒后收到的

2.6.3、总结

​ 延时队列在需要延时处理的场景下非常有用,使用RabbitMQ来实现延时队列可以很好的利用RabbitMQ的特性,如:消息可靠发送、消息可靠投递、死信队列来保障消息至少被消费一次以及未被正确处理的消息不会被丢弃。另外,通过RabbitMQ集群的特性,可以很好的解决单点故障问题,不会因为单个节点挂掉导致延时队列不可用或者消息丢失。

​ 当然,延时队列还有很多其它选择,比如利用Java的DelayQueue,利用Redis的zset,利用Quartz或者利用kafka的时间轮,这些方式各有特点,看需要适用的场景。

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

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

相关文章

微服务基础

目录 一、单体架构 二、分布式架构 三、微服务 四、微服务结构 五、SpringCloud 六、服务拆分 七、远程调用 一、单体架构 单体架构就是将业务的所有功能都集中在一个项目中进行开发&#xff0c;并打成一个包进行部署。 他的优点很明显&#xff0c;就是架构简单&#xff…

JVM-类加载机制

名词解释 *.class文件的结构 查看指令&#xff1a; javap -verbose hello.class 包含信息&#xff1a; 结构信息&#xff08;版本号&#xff0c;大小信息&#xff09;&#xff1b; 元数据&#xff08;类&#xff0c;继承&#xff0c;接口&#xff0c;字段声明&#xff0c;方法声…

应用监控 eBPF 版:实现 Golang 微服务的无侵入应用监控

作者&#xff1a;古琦 在现代软件架构中&#xff0c;微服务已成为构建可扩展和灵活应用程序的流行方式。每个微服务负责应用程序的一部分功能&#xff0c;它们共同工作以提供完整的服务。由于微服务架构的分散特性&#xff0c;监控变得至关重要&#xff0c;有效的微服务监控是…

视频推拉流EasyDSS平台直播通道重连无法转推的原因排查与解决

视频推拉流EasyDSS视频直播点播平台&#xff0c;集视频直播、点播、转码、管理、录像、检索、时移回看等功能于一体&#xff0c;可提供音视频采集、视频推拉流、播放H.265编码视频、存储、分发等视频能力服务。 用户使用EasyDSS平台对直播通道进行转推&#xff0c;发现只要关闭…

Object.keys()的用法

1、语法 Object.keys(obj) 参数&#xff1a;要返回其枚举自身属性的对象 返回值&#xff1a;一个表示给定对象的所有可枚举属性的字符串数组 2、处理对象&#xff0c;返回可枚举的属性数组 let person {name:“张三”,age:25,address:“深圳”,getName:function(){}} Obj…

Wireshark——获取指定协议的数据包

1、问题 使用Wireshark捕获了大量的数据包&#xff0c;但是只想要指定协议的数据包。 2、方法 例如&#xff0c;只想要Modbus/TCP协议的数据包。 在应用显示过滤器中输入协议的名称&#xff08;小写&#xff09;&#xff0c;回车。 选择文件&#xff0c;导出特定分组。 将所…

基于redis实现用户登陆

因为session有数据共享问题&#xff0c;不同tomcat服务器中的session不能共享&#xff0c;之后负载均衡就无法实现。所以我们用redis代替session。redis可以被多个tomcat服务器共享&#xff0c;redis基于内存。 之前的session可以看做登陆凭证&#xff0c;本次登陆凭证由sessi…

算法 - 【受限条件下可到达节点的数目】

受限条件下可到达节点的数目 题目示例1示例2 分析代码 题目 现有一棵由 n 个节点组成的无向树&#xff0c;节点编号从 0 到 n - 1 &#xff0c;共有 n - 1 条边。给你一个二维整数数组 edges &#xff0c;长度为 n - 1 &#xff0c;其中 edges[i] [ai, bi] 表示树中节点 ai 和…

python基础第二天

世界杯小组赛成绩 注意&#xff1a; 1.循环 1.1while 1.2for 1.3 range 1.4 while else while 循环正常执行完才能执行else语句

数据库(mysql)-新手笔记-基本知识点(1)

基本概念 数据库 Database :存储数据的容器 表 Table : 在数据库中存储的基本结构,它由行和列组成 行 Row : 表中的一条记录 列 Column : 表中的字段,定义了数据的类型和约束 数据类型 数据值 如 INT(整型),FLAOT(浮点型) ,DECIMAL (精确小数点) 字符串 如 VARCHAR(可变长度字…

探索AI视频创新:Sora的奇迹

探索AI视频创新&#xff1a;Sora的奇迹 随着科技的不断演进&#xff0c;AI视频模型已经成为人工智能领域的一颗新星。在这场技术的风暴中&#xff0c;OpenAI的Sora模型以其杰出的性能和前瞻性的技术脱颖而出&#xff0c;正引领着AI视频领域的全新创新浪潮。 Sora的技术之光 …

【JS】关于this的使用

this 前言一、this是什么&#xff1f;二、做什么&#xff1f;1.全局环境2.函数环境3.new实例对象4.apply、bind、call绑定4.1 apply()4.2 call()4.3 bind() 三、为什么用this&#xff1f;四、如何改变this&#xff1f;五、应用场景&#xff1f;总结 前言 痛点 经常写Vue项目&a…