微服务—RabbitMQ高级(业务在各方面的可靠性)

  本博客为个人学习笔记,学习网站:2023黑马程序员RabbitMQ入门到实战教程 高级篇章节

目录

生产者可靠性

生产者重连机制

生产者确认机制

介绍

实现

总结与建议

MQ可靠性

数据持久化 

LazyQueue

消费者可靠性

消费者确认机制

失败重试机制

失败重试策略 

业务幂等性

何为幂等

唯一id

业务判断

兜底方案

总结


生产者可靠性

生产者重连机制

注意:生产者重连机制只有在连接MQ失败的时候进行重连,消息发送失败时并不会重连

配置代码如下:

spring:
  rabbitmq:
    connection-timeout: 1s # 设置MQ的连接超时时间
    template:
      retry:
        enabled: true # 开启超时重试机制
        initial-interval: 1000ms # 失败后的初始等待时间
        multiplier: 1 # 失败后下次的等待时长倍数,下次等待时长 = initial-interval * multiplier
        max-attempts: 3 # 最大重试次数

测试: 
利用docker命令停掉RabbitMQ服务,再进行消息发送测试。结果如下图所示,总共重试了3次,证明消息发送的超时重试机制配置成功。


生产者确认机制

介绍

其中ack和nack属于Publisher Confirm机制,ack是投递成功;nack是投递失败。而return(消息到达交换机,但是路由失败,如:匹配不到routingKey)则属于Publisher Return机制。

其中,Publisher Confirm又分为两种类型,一种是同步阻塞等待MQ的回执,另一种是MQ异步回调返回回执,即同步与异步两种。

默认两种机制都是关闭状态,需要通过配置文件来开启。 


实现

实现生产者确认机制:

步骤1:在publisher模块的application.yaml中添加配置

spring:
  rabbitmq:
    publisher-confirm-type: correlated # 开启publisher confirm机制,并设置confirm类型
    publisher-returns: true # 开启publisher return机制

这里publisher-confirm-type有三种模式可选:
none:关闭confirm机制
simple:同步阻塞等待MQ的回执
correlated:MQ异步回调返回回执
一般我们推荐使用correlated,回调机制。 

步骤2:编写回调函数,定义ReturnCallback
每个RabbitTemplate只能配置一个ReturnCallback,因此我们可以在配置类中统一设置。我们在publisher模块定义一个配置类:

package com.itheima.publisher.config;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

@Slf4j
@AllArgsConstructor
@Configuration
public class MqConfig {
    private final RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void init(){
        rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
            @Override
            public void returnedMessage(ReturnedMessage returned) {
                log.error("触发return callback,");
                log.debug("exchange: {}", returned.getExchange());
                log.debug("routingKey: {}", returned.getRoutingKey());
                log.debug("message: {}", returned.getMessage());
                log.debug("replyCode: {}", returned.getReplyCode());
                log.debug("replyText: {}", returned.getReplyText());
            }
        });
    }
}

步骤三:定义ConfirmCallback 
由于每个消息发送时的处理逻辑不一定相同,因此ConfirmCallback需要在每次发消息时定义。具体来说,是在调用RabbitTemplate中的convertAndSend方法时,多传递一个参数:

这里的CorrelationData中包含两个核心的东西:
        id:消息的唯一标示,MQ对不同的消息的回执以此做判断,避免混淆
        SettableListenableFuture:回执结果的Future对象

将来MQ的回执就会通过这个Future来返回,我们可以提前给CorrelationData中的Future添加回调函数来处理消息回执:

我们新建一个测试,向系统自带的交换机发送消息,并且添加ConfirmCallback:

@Test
void testPublisherConfirm() {
    // 1.创建CorrelationData
    CorrelationData cd = new CorrelationData();
    // 2.给Future添加ConfirmCallback
    cd.getFuture().addCallback(new ListenableFutureCallback<CorrelationData.Confirm>() {
        @Override
        public void onFailure(Throwable ex) {
            // 2.1.Future发生异常时的处理逻辑,基本不会触发
            log.error("send message fail", ex);
        }
        @Override
        public void onSuccess(CorrelationData.Confirm result) {
            // 2.2.Future接收到回执的处理逻辑,参数中的result就是回执内容
            if(result.isAck()){ // result.isAck(),boolean类型,true代表ack回执,false 代表 nack回执
                log.debug("发送消息成功,收到 ack!");
            }else{ // result.getReason(),String类型,返回nack时的异常描述
                log.error("发送消息失败,收到 nack, reason : {}", result.getReason());
            }
        }
    });
    // 3.发送消息
    rabbitTemplate.convertAndSend("hmall.direct", "q", "hello", cd);
}

成功接收消息结果:

把routingKey故意写错,路由失败结果: 

可以看到,由于传递的RoutingKey是错误的,路由失败后,触发了return callback,同时也收到了ack。 当我们修改为正确的RoutingKey以后,就不会触发return callback了,只收到ack。 而如果连交换机都是错误的,则只会收到nack。

交换机名填错结果:


总结与建议

总结
开启生产者确认比较消耗MQ性能,一般不建议开启

而且大家思考一下触发确认的几种情况:
1. 路由失败:一般是因为RoutingKey错误导致,往往是编程导致
2. 交换机名称错误:同样是编程错误导致
3. MQ内部故障:这种需要处理,但概率往往较低。因此只有对消息可靠性要求非常高的业务才需要开启,而且仅仅需要开启ConfirmCallback处理nack就可以了

建议
1. 生产者确认需要额外的网络和系统资源开销,尽量不要使用
2. 如果一定要使用,无需开启Publisher-Return机制,因为一般路由失败是自己业务问题
3. 对于nack消息可以有限次数重试,依然失败则记录异常消息


MQ可靠性

消息到达MQ以后,如果MQ不能及时保存,也会导致消息丢失,所以MQ的可靠性也非常重要。

数据持久化 

为了提升性能,默认情况下MQ的数据都是在内存存储的临时数据,重启后就会消失。为了保证数据的可靠性,必须配置数据持久化,包括:交换机持久化、队列持久化、消息持久化。

spring在声明创建交换机和队列的时候,会默认设置为持久化模式,在发送消息时,也会默认将消息持久化。

在MQ管理平台新增交换机或队列的时候可以选择持久化模式,如果在MQ选择发送一条持久化消息时,需要配置properties值为2。

注意
在开启持久化机制以后,如果同时还开启了生产者确认,那么MQ会在消息持久化以后才发送ACK回执,进一步确保消息的可靠性。
不过出于性能考虑,为了减少IO次数,发送到MQ的消息并不是逐条持久化到数据库的,而是每隔一段时间批量持久化。一般间隔在100毫秒左右,这就会导致ACK有一定的延迟,因此建议生产者确认全部采用异步方式。


LazyQueue

在默认情况下,RabbitMQ会将接收到的信息保存在内存中以降低消息收发的延迟。但在某些特殊情况下,这会导致消息积压,比如:
1. 消费者宕机或出现网络故障
2. 消息发送量激增,超过了消费者处理速度
3. 消费者处理业务发生阻塞

一旦出现消息堆积问题,RabbitMQ的内存占用就会越来越高,直到触发内存预警上限。此时RabbitMQ会将内存消息刷到磁盘上,这个行为成为PageOut. PageOut会耗费一段时间,并且会阻塞队列进程。因此在这个过程中RabbitMQ不会再处理新的消息,生产者的所有请求都会被阻塞。

为了解决这个问题,从RabbitMQ的3.6.0版本开始,就增加了Lazy Queues的模式,也就是惰性队列。惰性队列的特征如下:
1. 接收到消息后直接存入磁盘而非内存
2. 消费者要消费消息时才会从磁盘中读取并加载到内存(也就是懒加载)
3. 支持数百万条的消息存储

而在3.12版本之后,LazyQueue已经成为所有队列的默认格式。因此官方推荐升级MQ为3.12版本或者所有队列都设置为LazyQueue模式。

下面介绍一下LazyQueue模式的设置:
在MQ管理平台新增队列时,设置为LazQueue模式:

代码配置队列为LazQueue模式:
1. 基于Bean声明队列的设置方式

@Bean
public Queue lazyQueue(){
    return QueueBuilder
            .durable("lazy.queue")
            .lazy() // 开启Lazy模式
            .build();
}

2. 基于注解声明队列的设置方式

@RabbitListener(queuesToDeclare = @Queue(
        name = "lazy.queue",
        durable = "true",
        arguments = @Argument(name = "x-queue-mode", value = "lazy")
))
public void listenLazyQueue(String msg){
    log.info("接收到 lazy.queue的消息:{}", msg);
}

3. 对于已经存在的队列,也可以配置为lazy模式,但是要通过设置policy实现。 可以基于命令行设置policy

rabbitmqctl set_policy Lazy "^lazy-queue$" '{"queue-mode":"lazy"}' --apply-to queues  

消费者可靠性

消费者确认机制


一般reject方式用的较少,除非是消息格式有问题,那就是开发问题了。因此大多数情况下我们需要将消息处理的代码通过try catch机制捕获,消息处理成功时返回ack,处理失败时返回nack。

由于消息回执的处理代码比较统一,因此SpringAMQP帮我们实现了消息确认。并允许我们通过配置文件设置ACK处理方式,有三种模式:
none:不处理。即消息投递给消费者后立刻ack,消息会立刻从MQ删除。非常不安全,不建议使用
manual:手动模式。需要自己在业务代码中调用api,发送ack或reject,存在业务入侵,但更灵活
auto:自动模式。

SpringAMQP利用AOP对我们的消息处理逻辑做了环绕增强,当业务正常执行时则自动返回ack。当业务出现异常时,根据异常判断返回不同结果:
如果是业务异常,会自动返回nack;
如果是消息处理或校验异常,自动返回reject;

返回Reject的常见异常有:
Starting with version 1.3.2, the default ErrorHandler is now a ConditionalRejectingErrorHandler that rejects (and does not requeue) messages that fail with an irrecoverable error. Specifically, it rejects messages that fail with the following errors:

  • o.s.amqp…MessageConversionException: Can be thrown when converting the incoming message payload using a MessageConverter.

  • o.s.messaging…MessageConversionException: Can be thrown by the conversion service if additional conversion is required when mapping to a @RabbitListener method.

  • o.s.messaging…MethodArgumentNotValidException: Can be thrown if validation (for example, @Valid) is used in the listener and the validation fails.

  • o.s.messaging…MethodArgumentTypeMismatchException: Can be thrown if the inbound message was converted to a type that is not correct for the target method. For example, the parameter is declared as Message<Foo> but Message<Bar> is received.

  • java.lang.NoSuchMethodException: Added in version 1.6.3.

  • java.lang.ClassCastException: Added in version 1.6.3.

通过下面的配置可以修改SpringAMQP的ACK处理方式:

spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: none # 不做处理

测试
修改consumer服务的SpringRabbitListener类中的方法,模拟一个消息处理的异常:

@RabbitListener(queues = "simple.queue")
public void listenSimpleQueueMessage(String msg) throws InterruptedException {
    log.info("spring 消费者接收到消息:【" + msg + "】");
    if (true) {
        throw new MessageConversionException("故意的");
        //throw new RuntimeException("故意的");
    }
    log.info("消息处理完成");
}

测试可以发现:当消息处理发生异常时,消息依然被RabbitMQ删除了。
我们把确认机制修改为auto,放行以后,由于抛出的是消息转换异常,因此Spring会自动返回reject,所以消息依然会被删除。

我们重新将抛出异常改为RuntimeException类型,然后再次发送消息测试, 由于抛出的是业务异常,所以Spring返回ack,最终消息恢复至Ready状态,并且没有被RabbitMQ删除。

总结:
当我们把配置改为auto时,
消息处理成功:
        自动返回ack,并将消息删除。
消息处理失败:
        如果是业务异常,会自动返回nack,并将消息重新投递到消费者;
        如果是消息处理或校验异常,自动返回reject,并将消息删除。


失败重试机制

配置代码如下 

spring:
  rabbitmq:
    listener:
      simple:
        retry:
          enabled: true # 开启消费者失败重试
          initial-interval: 1000ms # 初识的失败等待时长为1秒
          multiplier: 1 # 失败的等待时长倍数,下次等待时长 = multiplier * last-interval
          max-attempts: 3 # 最大重试次数
          stateless: true # true无状态;false有状态。如果业务中包含事务,这里改为false

重启consumer服务,重复之前的测试。可以发现:
消费者在失败后消息没有重新回到MQ无限重新投递,而是在本地重试了3次,本地重试3次以后,抛出了AmqpRejectAndDontRequeueException异常。查看RabbitMQ控制台,发现消息被删除了,说明最后SpringAMQP返回的是reject。

结论:
开启本地重试时,消息处理过程中抛出异常,不会requeue到队列,而是在消费者本地重试。
重试达到最大次数后,Spring会返回reject,消息会被丢弃 。


失败重试策略 

在之前的测试中,本地测试达到最大重试次数后,消息会被丢弃。这在某些对于消息可靠性要求较高的业务场景下,显然不太合适了。 因此Spring允许我们自定义重试次数耗尽后的消息处理策略,这个策略是由MessageRecovery接口来定义的,它有3个不同实现:
1. RejectAndDontRequeueRecoverer:重试耗尽后,直接reject,丢弃消息。默认就是这种方式 
2. ImmediateRequeueMessageRecoverer:重试耗尽后,返回nack,消息重新入队 
3. RepublishMessageRecoverer:重试耗尽后,将失败消息投递到指定的交换机 
比较合适的一种处理方案是RepublishMessageRecoverer,失败后将消息投递到一个指定的,专门存放异常消息的队列,后续由人工集中处理。

步骤: 在consumer服务配置类中定义处理失败消息的交换机和队列,并定义一个RepublishMessageRecoverer,关联队列和交换机

package com.itheima.consumer.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.retry.MessageRecoverer;
import org.springframework.amqp.rabbit.retry.RepublishMessageRecoverer;
import org.springframework.context.annotation.Bean;

@Configuration
//设置当满足某个条件(失败重试策略被开启)时配置生效↓
@ConditionalOnProperty(name = "spring.rabbitmq.listener.simple.retry.enabled", havingValue = "true")
public class ErrorMessageConfig {
    @Bean
    public DirectExchange errorMessageExchange(){
        return new DirectExchange("error.direct");
    }
    @Bean
    public Queue errorQueue(){
        return new Queue("error.queue", true);
    }
    @Bean
    public Binding errorBinding(Queue errorQueue, DirectExchange errorMessageExchange){
        return BindingBuilder.bind(errorQueue).to(errorMessageExchange).with("error");
    }

    @Bean
    public MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate){
        return new RepublishMessageRecoverer(rabbitTemplate, "error.direct", "error");
    }
}


业务幂等性

何为幂等

幂等是一个数学概念,用函数表达式来描述是这样的:f(x) = f(f(x)),例如求绝对值函数。 在程序开发中,则是指同一个业务,执行一次或多次对业务状态的影响是一致的。

例如:根据id删除数据和查询数据操作,执行一次或多次对业务状态的影响是一致的。

但数据的更新往往不是幂等的,如果重复执行可能造成不一样的后果。
比如:取消订单,恢复库存的业务。如果多次恢复就会出现库存重复增加的情况。
退款业务,重复退款对商家而言会有经济损失。
所以,我们要尽可能避免业务被重复执行。然而在实际业务场景中,由于意外经常会出现业务被重复执行的情况,因此,我们必须想办法保证消息处理的幂等性。

这里给出两种方案:唯一消息ID、业务状态判断

唯一id

这个思路非常简单:
每一条消息都生成一个唯一的id,与消息一起投递给消费者。
消费者接收到消息后处理自己的业务,业务处理成功后将消息ID保存到数据库
如果下次又收到相同消息,去数据库查询判断是否存在,存在则为重复消息放弃处理。

我们该如何给消息添加唯一ID呢? 其实很简单,SpringAMQP的MessageConverter自带了MessageID的功能,我们只要开启这个功能即可。 以Jackson的消息转换器为例:

@Bean
public MessageConverter messageConverter(){
    // 1.定义消息转换器
    Jackson2JsonMessageConverter jjmc = new Jackson2JsonMessageConverter();
    // 2.配置自动创建消息id,用于识别不同消息,也可以在业务中基于ID判断是否是重复消息
    jjmc.setCreateMessageIds(true);
    return jjmc;
}

业务判断

业务判断就是基于业务本身的逻辑或状态来判断是否是重复的请求或消息,不同的业务场景判断的思路也不一样。 例如我们当前案例中,处理消息的业务逻辑是把订单状态从未支付修改为已支付。因此我们就可以在执行业务时判断订单状态是否是未支付,如果不是则证明订单已经被处理过,无需重复处理。

相比较而言,消息ID的方案需要改造原有的数据库,所以我们更推荐使用业务判断的方案。
以支付修改订单的业务为例,我们需要修改OrderServiceImpl中的markOrderPaySuccess方法:

    @Override
    public void markOrderPaySuccess(Long orderId) {
        // 1.查询订单
        Order old = getById(orderId);
        // 2.判断订单状态
        if (old == null || old.getStatus() != 1) {
            // 订单不存在或者订单状态不是1,放弃处理
            return;
        }
        // 3.尝试更新订单
        Order order = new Order();
        order.setId(orderId);
        order.setStatus(2);
        order.setPayTime(LocalDateTime.now());
        updateById(order);
    }

上述代码逻辑上符合了幂等判断的需求,但是由于判断和更新是两步动作,因此在极小概率下可能存在线程安全问题。

我们可以合并上述操作为这样:

@Override
public void markOrderPaySuccess(Long orderId) {
    // 代码等同于如下sql语句
    // UPDATE `order` SET status = ? , pay_time = ? WHERE id = ? AND status = 1
    lambdaUpdate()
            .set(Order::getStatus, 2)
            .set(Order::getPayTime, LocalDateTime.now())
            .eq(Order::getId, orderId)
            .eq(Order::getStatus, 1)
            .update();
}

我们在where条件中除了判断id以外,还加上了status必须为1的条件。如果条件不符(说明订单已支付),则SQL匹配不到数据,根本不会执行。


兜底方案

虽然我们利用各种机制尽可能增加了消息的可靠性,但也不好说能保证消息100%的可靠。万一真的MQ通知失败该怎么办呢? 有没有其它兜底方案,能够确保订单的支付状态一致呢?

其实思想很简单:既然MQ通知不一定发送到交易服务,那么交易服务就必须自己主动去查询支付状态。这样即便支付服务的MQ通知失败,我们依然能通过主动查询来保证订单状态的一致。 流程如下:

 

图中黄色线圈起来的部分就是MQ通知失败后的兜底处理方案,由交易服务自己主动去查询支付状态。

不过需要注意的是,交易服务并不知道用户会在什么时候支付,如果查询的时机不正确(比如查询的时候用户正在支付中),可能查询到的支付状态也不正确。 那么问题来了,我们到底该在什么时间主动查询支付状态呢?

这个时间是无法确定的,因此,通常我们采取的措施就是利用定时任务定期查询,例如每隔20秒就查询一次,并判断支付状态。如果发现订单已经支付,则立刻更新订单状态为已支付即可。


总结

综上,支付服务与交易服务之间的订单状态一致性是如何保证的?
首先,支付服务会正在用户支付成功以后利用MQ消息通知交易服务,完成订单状态同步。
其次,为了保证MQ消息的可靠性,我们采用了生产者确认机制、消费者确认、消费者失败重试等策略,确保消息投递的可靠性
最后,我们还在交易服务设置了定时任务,定期查询订单支付状态。这样即便MQ通知失败,还可以利用定时任务作为兜底方案,确保订单支付状态的最终一致性。 


 

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

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

相关文章

Nginx 403 forbidden

1、没有权限问题 Linux系统中如果Nginx没有web目录的操作权限&#xff0c;也会出现403错误。解决办法&#xff1a;修改web目录的读写权限&#xff0c;或者是把Nginx的启动用户改成目录的所属用户&#xff0c;重启Nginx即可解决。(windows 下则用管理员启动nginx即可)。 chmod -…

uniapp上传文件到腾讯云

官方API地址 javaScript_SDK 下载cos npm i cos-js-sdk-v5 --save 生成签名 获取secretId和secretKey let cos new COS({SecretId: *******************************,SecretKey: ******************************,}) 参考文章&#xff1a;腾讯云如何获取secretId和secret…

力扣精选算法100道——提莫攻击(模拟专题)

目录 &#x1f6a9;题目解析 &#x1f6a9;算法原理 &#x1f6a9;实现代码 &#x1f6a9;题目解析 输入&#xff1a;timeSeries [1,4], duration 2 输出&#xff1a;4 解释&#xff1a;提莫攻击对艾希的影响如下&#xff1a; - 第 1 秒&#xff0c;提莫攻击艾希并使其立即…

【Git】:分支管理

分支管理 一.概念二.分支管理基本操作三.分支管理策略1.noff模式2.分支策略 一.概念 在版本回退⾥&#xff0c;你已经知道&#xff0c;每次提交&#xff0c;Git都把它们串成⼀条时间线&#xff0c;这条时间线就可以理解为是⼀个分⽀。截⽌到⽬前&#xff0c;只有⼀条时间线&…

搜索中关于稀疏检索和稠密向量检索的召回效果比较

不同检索方式说明 最近在做搜索召回提升相关的研究工作。对比了稀疏检索和稠密向量检索的效果。其中使用的搜索引擎为elasticsearch8.x版本。稀疏检索包括BM25的检索方式&#xff0c;以及es官方在8.8之后版本提供的稀疏向量模型的方式。稠密向量检索&#xff0c;是指借助机器学…

tqdm,Python进度条库快速上手

前言 在编程中&#xff0c;尤其是处理长时间运行的任务时&#xff0c;了解程序的进度是非常重要的。Python中有一个非常实用的库&#xff0c;名为 tqdm &#xff0c;它能够以极简的方式帮助我们快速创建进度条。本文将向初学者介绍如何在Python中使用 tqdm 库来制作进度条。 …

2、电源管理入门之开机详解

目录 1. 硬件上电 2. ATF运行 3. Linux启动 3.1 内核启动start_kernel 3.2 平台启动setup_arch 3.4 DTS初始化psci_dt_init 3.5 系统rest创建kernel_init线程 3.6 SMP初始化smp_init 3.7 PSCI接口psci_cpu_on 3.8 SMC返回secondary_entry 系统开机牵扯到:“我是…

Springboot 中使用 Redisson+AOP+自定义注解 实现访问限流与黑名单拦截

&#x1f3f7;️个人主页&#xff1a;牵着猫散步的鼠鼠 &#x1f3f7;️系列专栏&#xff1a;Java全栈-专栏 &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&…

python3 flask 实现对config.yaml文件的内容的增删改查,并重启服务

config.yaml配置文件内容 功能就是userpass下的用户名和密码做增删改查&#xff0c;并重启hy2服务 auth:type: userpassuserpass:csdn: csdnlisten: :443 masquerade:proxy:rewriteHost: trueurl: https://www.bing.com/type: proxy tls:cert: /root/hyst*****马赛克******er…

JS文本加密方法探究

在前端开发中&#xff0c;有时候我们需要对敏感文本进行简单的加密&#xff0c;以提高安全性。本文将介绍一种基于 JavaScript 实现的文本加密方法&#xff0c;使用了 Base64、Unicode 和 ROT13 编码。 示例代码 function encodeText(text) {// Base64编码var base64Encoded …

苍穹外卖——第一天nginx

放到全是英文路径的打不开 到安装路径进入cmd&#xff0c;输入nginx -t nginx: the configuration file E:\Astudy\nginx-1.20.2/conf/nginx.conf syntax is ok nginx: [emerg] bind() to 0.0.0.0:80 failed (10013: An attempt was made to access a socket in a way forbid…

linux部署jenkins,支持jdk1.8

无废话&#xff0c;纯干活安装指令 本文前提条件需安装jdk8&#xff0c;安装参考&#xff1a;Linux配置jdk环境 下载资源 # 创建安装目录 mkdir -p /data/jenkins && cd /data/jenkins# 下载jenkins的war包&#xff0c;v2.346.x支持jdk1.8&#xff0c;高于这个版本的…

板块一 Servlet编程:第六节 HttpSession对象全解 来自【汤米尼克的JAVAEE全套教程专栏】

板块一 Servlet编程&#xff1a;第六节 HttpSession对象全解 一、什么是HttpSessionSession的本质 二、创建Seesion及常用方法三、Session域对象四、Session对象的销毁 在上一节中&#xff0c;我们学习了Servlet五大对象里的第三个Cookie对象&#xff0c;但Cookie是有大小限制和…

ajax 如何从服务器上获取数据?

在Web开发中&#xff0c;AJAX&#xff08;Asynchronous JavaScript and XML&#xff09;是一种常用的技术&#xff0c;用于在不重新加载整个页面的情况下&#xff0c;从服务器获取数据并更新网页的某一部分。使用AJAX&#xff0c;你可以创建异步请求&#xff0c;从而提供更快的…

AI:133-基于深度学习的工业质检自动化

🚀点击这里跳转到本专栏,可查阅专栏顶置最新的指南宝典~ 🎉🎊🎉 你的技术旅程将在这里启航! 从基础到实践,深入学习。无论你是初学者还是经验丰富的老手,对于本专栏案例和项目实践都有参考学习意义。 ✨✨✨ 每一个案例都附带有在本地跑过的关键代码,详细讲解供…

Git 关于SSH密钥的生成

一&#xff1a;配置ssh 桌面右键鼠标打开 “Git Bash Here” 键入命令&#xff1a;ssh-keygen -t ed25519 -C "自己邮箱 " 接着就一路回车 打开 C:\Users\Administrator.ssh 目录&#xff0c; 复制 id_xxxxx.pub 内容 文件里面则是一些信息&#xff0c;如下 …

Reliable Visualization for Deep Speaker Recognition - 语音可解释性

MOTIVATION OF READING: 语音任务可解释性 Link: http://arxiv.org/abs/2204.03852 Code&#xff1a;http://project.cslt.org/ 1. Overview Motivation of the work: If any of the visualization tools are reliable when applied to speaker recognition, which makes th…

二百二十四、Kettle——曲线实现从Hive插入更新到ClickHouse(分区字段是month或year)

一、目的 对于以month、year为分区字段的数据&#xff0c;不是像day字段分区那样每天增量插入更新即可&#xff0c;而是要以部分字段查询、部分字段更新&#xff0c;但是ClickHouse数据库并不适合更新操作&#xff0c;直接使用Kettle的插入更新控件会导致问题&#xff0c;必须…

数据仓库选型建议

1 数仓分层 1.1 数仓分层的意义 **数据复用&#xff0c;减少重复开发&#xff1a;**规范数据分层&#xff0c;开发一些通用的中间层数据&#xff0c;能够减少极大的重复计算。数据的逐层加工原则&#xff0c;下层包含了上层数据加工所需要的全量数据&#xff0c;这样的加工方…

JVM对象的创建流程与内存分配

对象的创建流程与内存分配 创建流程对象内存分配方式内存分配安全问题对象内存分配流程【重要】:对象怎样才会进入老年代?重点 案例演示:对象分配过程大对象直接进入老年代02-对象内存分配的过程: 创建流程 加载 验证 解析 准备 初始化 使用 写在 对象内存分配方式 内存分配…