消息队列 /RabbitMq/消息总线:Spring Cloud Bus

1.RabbitMq工作原理

在这里插入图片描述

2.RabbitMQ工作模式

RabbitMQ有以下几种工作模式 :

2.1 hello world 点对点

在这里插入图片描述

生产者发送的消息,到队列后,由一个消费者消费

2.2 Work queues 工作队列

在这里插入图片描述

work queues与入门程序相比,多了一个消费端,两个消费端共同消费同一个队列中的消息。
应用场景:对于 任务过重或任务较多情况使用工作队列可以提高任务处理的速度。

2.3 Publish/Subscribe 发布订阅模式

在这里插入图片描述

发布订阅模式
1、每个消费者监听自己的队列。
2、生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息

2.4 Routing 路由模式

在这里插入图片描述

路由模式:
1、每个消费者监听自己的队列,并且设置routingkey。
2、生产者将消息发给交换机,由交换机根据routingkey来转发消息到指定的队列。

2.5 Topics 统配符模式

在这里插入图片描述

统配符模式:
1、每个消费者监听自己的队列,并且设置带统配符的routingkey。
2、生产者将消息发给broker,由交换机根据routingkey来转发消息到指定的队列。

3.5 其它模式

2.6 Header Header模式

header模式与routing不同的地方在于,header模式取消routingkey,使用header中的
key/value(键值对)匹配队列。

案例:

根据用户的通知设置去通知用户,设置接收Email的用户只接收Email,设置接收sms的用户只接收sms,设置两种通知类型都接收的则两种通知都有效。

2.7 RPC

在这里插入图片描述
RPC即客户端远程调用服务端的方法 ,使用MQ可以实现RPC的异步调用,基于Direct交换机实现,流程如下:

1、客户端即是生产者就是消费者,向RPC请求队列发送RPC调用消息,同时监听RPC响应队列。
2、服务端监听RPC请求队列的消息,收到消息后执行服务端的方法,得到方法返回的结果 3、服务端将RPC方法 的结果发送到RPC响应队列
4、客户端(RPC调用方)监听RPC响应队列,接收到RPC调用结果。

3.RabbitMq和springboot结合

3.1 导入依赖
<!--RabbitMq 依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
3.2 配置yml文件
server:
  port: 8080
spring:
  rabbitmq:
    host: # 远程连接的ip地址(即:rabbitMq的管理端的ip)  也可以是本地ip localhost
    port: 5672 # 内部连接的端口号 ,而15672是管理端的端口号
    username: guest
    password: guest
3.3 生产者RabbitMqConfig类

RabbitMqConfig 类,本案例主要定义了,队列 交换机、队列与交换机的绑定、routingkey.

springboot 启动的时候,帮助我们把连接创建好,直接使用连接发送就可以了
一对多: 发布订阅FanoutExchange
一对一: routing模式 DirectExchange
一对一, 一对多: 通配符模式TopicExchange

/**
 *
 */
@Configuration
public class RabbitMqConfig {

   /* //定义队列
    @Bean
    public Queue queue(){
        return new Queue("hello");
    }*/


    //发布订阅模式 与Routing路由模式下的两个队列
    @Bean(name = "smsQueue")
    public Queue smsQueue(){
        return new Queue("sms");//短信队列
    }
    @Bean(name = "emailQueue")
    public Queue emailQueue(){
        return new Queue("email");//邮箱队列
    }


    /*//定义交换机 发布订阅的交换机
    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange("my-publish-exchange");
    }
    //将交换机与队列进行绑定
    @Bean
    Binding bindSmsQueueToFanoutExchange(@Qualifier("smsQueue")Queue queue,FanoutExchange fanoutExchange){
        //绑定sms队列 到 交换机上
        return BindingBuilder.bind(queue).to(fanoutExchange);
    }
    @Bean
    Binding bindeMailQueueToFanoutExchange(@Qualifier("emailQueue")Queue queue,FanoutExchange fanoutExchange){
        //绑定email队列 到 交换机上
        return BindingBuilder.bind(queue).to(fanoutExchange);
    }*/



    //定义routing模式的交换机
    /*@Bean
    public  DirectExchange directExchange(){
        return new DirectExchange("my-direct-exchange");
    }
    //将队列绑定到directExchange交换机上  smsQueue 放在ioc中的 队列的唯一标识
    @Bean
    public Binding bindSmsQueueToDirectExchange(@Qualifier("smsQueue")Queue queue,DirectExchange directExchange){
        //绑定sms队列 到 交换机上
        return BindingBuilder.bind(queue).to(directExchange).with("sms");
    }
    @Bean
    public Binding bindMailQueueToDirectExchange(@Qualifier("emailQueue")Queue queue,DirectExchange directExchange){
        //绑定email队列 到 交换机上
        return BindingBuilder.bind(queue).to(directExchange).with("email");
    }*/



    /**
     * 统配符模式 在routingKey中设置通配符,只要满足routingKey的一部分可以接收到 交换机转发到队列的消息
     * @return
     */
    @Bean
    public TopicExchange topicExchange(){
        return new TopicExchange("my-topic-exchange");
    }

    //将队列绑定到交换机上
    @Bean
    Binding bindSmsQueueTopicExchange(@Qualifier("smsQueue")Queue queue,TopicExchange topicExchange){
        /**
         * 统配符:
         *  1.* 代表一个字符
         *  2.# 代表0个或者多个
         */
        return BindingBuilder.bind(queue).to(topicExchange).with("#.send.sms.#");
    }
    @Bean
    Binding bindEmailQueueTopicExchange(@Qualifier("emailQueue")Queue queue,TopicExchange topicExchange){
        return BindingBuilder.bind(queue).to(topicExchange).with("#.send.#.email");
    }


}

3.4 生产者测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class SendMessage {

    //使用springboot提供的模板类
    @Autowired
    RabbitTemplate rabbitTemplate;

    /**
     * hello world
     * 点对点的消息的发送
     * 消息生产者--->消息队列--->消息的消费者
     * 没加交换机 routingKey:代表消息队列的名称
     */
    @Test
    public void testSendHello(){
        //使用hello 1.交换机的名称 如果给"" ,会发送给rabbitMq默认的交换机 2.消息队列名称 3.发送的信息的内容
        rabbitTemplate.convertAndSend("","hello","哥是这样一个被爱伤害的男人!");
    }

    //工作队列 在hello world 这种模式 多加启动一个消费端服务


    //发布订阅模式 在工作队列的基础上 在加一个交换机
    @Test
    public void sendPublish(){
        /**
         * 1.exchange:交换机名称
         * 2.规则为null 代表发布订阅模式 代表着所有和交换机绑定的队列,都能接收到消息
         * 3.消息内容主题
         */
        rabbitTemplate.convertAndSend("my-publish-exchange","","发送到邮件服务和短信服务!");
    }


    //routing 路由模式
    @Test
    public void testSendRouting(){
        /**
         *  1.exchange:交换机名称
         *  2.路由的规则,规则与配置类中设置的规则进行匹配,只有满足规则的队列,才能接收到交换机转发的消息
         *  3.消息内容主题
         */
        rabbitTemplate.convertAndSend("my-direct-exchange","sms","路由模式:发送到短信服务");
        rabbitTemplate.convertAndSend("my-direct-exchange","email","路由模式:发送到邮件服务");
    }

    //Topic 通配符模式
    @Test
    public void testSendTopic(){
        //两个队列都能接收到
        rabbitTemplate.convertAndSend("my-topic-exchange","send.sms.email","通配符模式:同时发送短信邮箱服务");


        //只有邮箱队列能收到
        //rabbitTemplate.convertAndSend("my-topic-exchange","send.email","通配符模式:发送邮箱服务");

        //只有短信队列能收到
        //rabbitTemplate.convertAndSend("my-topic-exchange","send.sms","通配符模式:发送电信短信服务");
    }

}

3.4消费者监听消息队列

消息生产者服务与消费者服务配置文件中的关于RabbitMq配置相同依赖相同

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class ConsumerClient {


    @RabbitListener(queues = "sms")
    public void smsListen(String message){
        System.out.println("短信服务监听到的是:"+message);
    }

    @RabbitListener(queues = "email")
    public void emailListen(String message){
        System.out.println("邮件服务监到的是:"+message);
    }

}

4.0消息Ack确认机制

当消费者端消费消息时,发生异常,我们使用try-catch处理异常后,我们发现,消费者端没接收到消息,而rabbitMQ队列中的消息丢失了,这就有问题了,原因出在那呢?

原因在于RabbitMq有一个消息的自动确认机制,当我们想解决上述问题,需要将自动确认机制,改为手动确认

RabbitMQ的消息确认有两种。

  • 消息发送确认:这种是用来确认生产者将消息发送给交换器交换器传递给队列的过程中,消息是否成功投递。发送确认分为两步,一是确认是否到达交换器,二是确认是否到达队列。
  • 消费接收确认:这种是确认消费者是否成功消费了队列中的消息

4.1 消息发送到交换机的确认

4.1.1 创建一个类ConfirmCallback
ConfirmCallBackHandler.java

在消费者端创建一个类ConfirmCallBackHandler,实现RabbitTemplate.ConfirmCallback
通过实现ConfirmCallBack接口,消息发送到交换器Exchange后触发回调。

import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;

@Component
public class ConfirmCallBackHandler implements RabbitTemplate.ConfirmCallback {
    /**
     * 回调的方法
     * @param correlationData 消息的唯一标识
     * @param b 确认结果
     * @param s 失败原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean b, String s) {
        System.out.println("消息的唯一标识:"+correlationData);
        System.out.println("消息是否被投递到交换机:"+b);
        System.out.println("消息失败的原因:"+s);
    }
}

4.1.2 在RabbitConfig中加入一下代码

或者将下面的代码放在ConfirmCallBackHandler 中

@Autowired
    RabbitTemplate rabbitTemplate;

    //在类加载时最先进行声明
    @PostConstruct
    public void rabbitTemplate(){
        //正确将消息投递到交换机中进行回调
        rabbitTemplate.setConfirmCallback(new ConfirmCallBackHandler());
    }
4.1.3 在配置文件中加入 消息发送到交换机的确认
#开启消息发送交换机确认
spring.rabbitmq.publisher-confirms = true
4.1.4 controller层
@RestController
public class ConfirmController {

    @Autowired
    RabbitTemplate rabbitTemplate;

    @RequestMapping(value = "/send/{message}")
    public String sendMessage(@PathVariable("message") String message){//message 需要发送到交换机上的信息

        rabbitTemplate.convertAndSend("my-topic-exchange","send.sms.email",message);

        return "success";
    }
}

4.1.5 使用postMan 测试

在这里插入图片描述

4.1.6 控制台打印

在这里插入图片描述

4.2 消息发送到队列的确认

4.2.1 配置文件中配置
# 开启消息由交换机发送队列回调
spring.rabbitmq.publisher-returns = true
4.2.2 类ConfirmCallback 实现RabbitTemplate.ReturnCallback

重写方法

 /**
     * 消息发送到队列的确认(回调方法)
     * @param message 消息的主体
     * @param i 消息的应答码
     * @param s 失败原因描述
     * @param s1 交换机的名称
     * @param s2 消息使用的RoutingKey(路由键)
     */
    @Override
    public void returnedMessage(Message message, int i, String s, String s1, String s2) {
        System.out.println("消息的主体====="+message);
        System.out.println("消息的应答码====="+i);
        System.out.println("失败原因描述====="+s);
        System.out.println("交换机的名称====="+s1);
        System.out.println("消息使用的RoutingKey====="+s2);

    }
4.2.3 设置消息由交换机发送队列的回调(包括消息发送到交换机的回调)
@PostConstruct//在类加载时最先进行声明
    public void rabbitTemplate(){
        //正确将消息投递到交换机中进行的回调
        rabbitTemplate.setConfirmCallback(new ConfirmCallBackHandler());

        //设置由交换机将消息转换到队列的回调
        //消息发送确认,如果消息从交换器发送到对应队列失败时触发
        // (比如根据发送消息时指定的routingKey找不到队列时会触发)
        rabbitTemplate.setReturnCallback(new ConfirmCallBackHandler());
    }

4.2.4 修改消息发送时的routingKey为**,然后使用postMan,进行测试

测试结果如图所示:
在这里插入图片描述

我们发现消息发送到了,交换机,但是没有发送到队列,原因在于没有找到这个路由

4.2.5 将确认回调的配置(除配置文件外)所有配置
@Component
public class ConfirmCallBackHandler implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {

    @Autowired
    RabbitTemplate rabbitTemplate;


    @PostConstruct//在类加载时最先进行声明
    public void rabbitTemplate(){
        //正确将消息投递到交换机中进行的回调
        rabbitTemplate.setConfirmCallback(new ConfirmCallBackHandler());

        //设置由交换机将消息转换到队列的回调
        //消息发送确认,如果消息从交换器发送到对应队列失败时触发
        // (比如根据发送消息时指定的routingKey找不到队列时会触发)
        rabbitTemplate.setReturnCallback(new ConfirmCallBackHandler());
    }

    /**
     * 消息发送到交换机的确认(回调的方法)
     * @param correlationData 消息的唯一标识
     * @param b 消息是否被投递到交换机
     * @param s 消息发送失败的原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean b, String s) {
        System.out.println("消息的唯一标识:"+correlationData);
        System.out.println("消息是否被投递到交换机:"+b);
        System.out.println("消息发送失败的原因:"+s);
    }

    /**
     * 消息发送到队列的确认(回调方法)
     * @param message 消息的主体
     * @param i 消息的应答码
     * @param s 失败原因描述
     * @param s1 交换机的名称
     * @param s2 消息使用的RoutingKey(路由键)
     */
    @Override
    public void returnedMessage(Message message, int i, String s, String s1, String s2) {
        System.out.println("消息的主体====="+message);
        System.out.println("消息的应答码====="+i);
        System.out.println("失败原因描述====="+s);
        System.out.println("交换机的名称====="+s1);
        System.out.println("消息使用的RoutingKey====="+s2);

    }
}

4.2.6 消息发送端的配置文件
server:
  port: 8080
spring:
  rabbitmq:
    # 远程连接的ip地址(即:rabbitMq的管理端的ip)  也可以是本地ip localhost
    host: 115.29.212.111
    # 内部连接的端口号 ,而15672是管理端的端口号
    port: 5672
    username: guest
    password: guest
    #消息发送交换机确认回调
    publisher-confirms: true
    #消息由交换机发送队列确认回调
    publisher-returns: true

4.3 消息接收确认(由自动确认改为手动确认)

消费者端发生异常后,会使队列中的消息丢失,所以我们在消费者端,一旦,发生异常,设置,让队列中的消息不会被删除,在消息成功消费后,使用ack确认消息,将队列中的消息删除

(1)确认模式

消息消费者如何通知 Rabbit 消息消费成功?
消息通过 ACK 确认是否被正确接收,每个 Message 都要被确认(acknowledged),可以手动去 ACK 或自动 ACK
自动确认会在消息发送给消费者后立即确认,但存在丢失消息的可能,如果消费端消费逻辑抛出异常,也就是消费端没有处理成功这条消息,那么就相当于丢失了消息
如果消息已经被处理,但后续代码抛出异常,使用 Spring 进行管理的话消费端业务逻辑会进行回滚,这也同样造成了实际意义的消息丢失
如果手动确认则当消费者调用 ack、nack、reject 几种方法进行确认,手动确认可以在业务失败后进行一些操作,如果消息未被 ACK 则会发送到下一个消费者
如果某个服务忘记 ACK 了,则 RabbitMQ 不会再发送数据给它,因为 RabbitMQ 认为该服务的处理能力有限
ACK 机制还可以起到限流作用,比如在接收到某条消息时休眠几秒钟
消息确认模式有:
AcknowledgeMode.NONE:自动确认
AcknowledgeMode.AUTO:根据情况确认
AcknowledgeMode.MANUAL:手动确认
确认消息(局部方法处理消息)
默认情况下消息消费者是自动 ack (确认)消息的,如果要手动 ack(确认)则需要修改确认模式为 manual

(2)配置文件中添加配置
# 开启手动Ack确认
spring.rabbitmq.listener.simple.acknowledge-mode = manual
(3)修改监听邮箱队列
@Component
public class ConsumerClient {


    @RabbitListener(queues = "sms")
    public void smsListen(String message){

        System.out.println("短信服务监听到的是:"+message);
    }
    /**
     *
     * @param mess 消息的内容
     * @param channel 通道信息
     * @param message 消息的唯一标识
     */
    @RabbitListener(queues = "email")
    public void emailListen(String mess, Channel channel, Message message){

        //获取消息的唯一标识
        long deliveryTag = message.getMessageProperties().getDeliveryTag();

        try{
            //int i = 1/0;
            System.out.println("邮件服务监到的是:"+mess);

            //虽然消息消费成功了,但是队列中的消息还没有被确认,我们需要确认队列中的消息被消费
            //2参数 是否开启消息批处理
            channel.basicAck(deliveryTag,true);

        }catch (Exception e){
            //没有正确执行完成,消息不会被删除
            //消息没有消费成功,还是在RabbitMQ中
            //System.out.println(e.getMessage());
            try {
                //参数2:是否进行批处理消息
                //参数3:为true 代表消息始终在队列中 不会被删除 false 代表队列中的消息删除掉了
                channel.basicNack(deliveryTag,true,true);
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            //System.out.println(e.getMessage());

        }

    }

}

(4)消费者端配置文件
server:
  port: 8081
spring:
  rabbitmq:
    host: 115.29.212.111
    # 内部连接的端口号 ,而15672是管理端的端口号
    port: 5672
    username: guest
    password: guest
    # 开启手动ack的确认
    listener:
      simple:
        acknowledge-mode: manual

需要注意的 basicAck 方法需要传递两个参数

  • deliveryTag(唯一标识 ID):当一个消费者向 RabbitMQ 注册后,会建立起一个 Channel ,RabbitMQ 会用 basic.deliver 方法向消费者推送消息,这个方法携带了一个 delivery tag, 它代表了
    RabbitMQ 向该 Channel 投递的这条消息的唯一标识 ID
    ,是一个单调递增的正整数,delivery tag 的范围仅限于
    Channel
  • multiple:为了减少网络流量,手动确认可以被批处理,当该参数为 true 时,则可以一次性确认 delivery_tag 小于等于传入值的所有消息

4.4 使用过期时间TTl实现延迟队列

4.4.1 配置
@Configuration
public class RabbitMqDeadConfig {

    @Bean(name = "normalQueue")
    public Queue queue(){
        return new Queue("normal-queue");
    }


    /**
     1、name: 队列的名称;
     2、durable: 是否持久化;
     3、exclusive: 是否独享、排外的;
     4、autoDelete: 是否自动删除;
     5.arguments:队列的其他属性参数,
     (1)x-message-ttl:消息的过期时间,单位:毫秒;
     (2)x-expires:队列过期时间,队列在多长时间未被访问将被删除,单位:毫秒;
     (3)x-max-length:队列最大长度,超过该最大值,则将从队列头部开始删除消息;
     (4)x-max-length-bytes:队列消息内容占用最大空间,受限于内存大小,超过该阈值则从队列头部开始删除消息;
     (5)x-overflow:设置队列溢出行为。这决定了当达到队列的最大长度时消息会发生什么。有效值是drop-head、reject-publish或reject-publish-dlx。仲裁队列类型仅支持drop-head;
     (6)x-dead-letter-exchange:死信交换器名称,过期或被删除(因队列长度超长或因空间超出阈值)的消息可指定发送到该交换器中;
     (7)x-dead-letter-routing-key:死信消息路由键,在消息发送到死信交换器时会使用该路由键,如果不设置,则使用消息的原来的路由键值
     (8)x-single-active-consumer:表示队列是否是单一活动消费者,true时,注册的消费组内只有一个消费者消费消息,其他被忽略,false时消息循环分发给所有消费者(默认false)
     (9)x-max-priority:队列要支持的最大优先级数;如果未设置,队列将不支持消息优先级;
     (10)x-queue-mode(Lazy mode):将队列设置为延迟模式,在磁盘上保留尽可能多的消息,以减少RAM的使用;如果未设置,队列将保留内存缓存以尽可能快地传递消息;
     (11)x-queue-master-locator:在集群模式下设置镜像队列的主节点信息。
     */
    @Bean(name = "deadQueue")
    public Queue deadQueue(){
        //设置私信队列
        Map map = new HashMap<>();
        //设在私信队列种,当我们得队列过期后,回将消息转发到下方得交换机中
        map.put("x-dead-letter-exchange","normal-exchange");
        //转发到正常队列时得routingkey
        map.put("x-dead-letter-routing-key","123");
        return new Queue("dead-queue",true,false,false,map);
    }
    //声明得死亡交换机
    @Bean(name = "dead-topic-exchange")
    public TopicExchange topicExchange(){
        return new TopicExchange("dead-topic-exchange");
    }

    //声明正常得交换机
    @Bean(name = "normal-topic-exchange")
    public TopicExchange normalTopicExchange(){
        return new TopicExchange("normal-exchange");
    }
    @Bean
    Binding bindDeadQueueToExchange(@Qualifier("deadQueue")Queue queue,@Qualifier("dead-topic-exchange") TopicExchange topicExchange){
        return BindingBuilder.bind(queue).to(topicExchange).with("321");
    }
    @Bean
    Binding bindNoramlQueueToExchange(@Qualifier("normalQueue")Queue queue,@Qualifier("normal-topic-exchange") TopicExchange topicExchange){
        return BindingBuilder.bind(queue).to(topicExchange).with("123");
    }
}
4.4.2 controller层
 @RequestMapping("/send/dead/{message}")
    public String sendDeadMessage(@PathVariable("message") String message){

        rabbitTemplate.convertAndSend("dead-topic-exchange","321",message, new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                //设置消息过期时间10秒 单位毫秒
                message.getMessageProperties().setExpiration("10000");

                return message;
            }

        });
        return "success";
    }

5. 消息总线:Spring Cloud Bus

消息总线的核心思想:

首先请求一个微服务,然后由这个微服务去通知Spring Cloud Bus
Spring Cloud Bus收到消息后,去通知配置了相关配置的微服务

实质上Spring Cloud Bus 应用的就是RabbiteMQ 的发布订阅模式,

消息的生产者–>交换机–>转发消息到绑定的队列–>消费者监听到队列,消费

最常用应用的就是,Config配置中心的改造,以达到动态获取配置文件,当然你如果使用的nacos,没必要看了,因为nacos一个注解搞定了…嘟嘟嘟

5.1.1 将依赖导入Config客户端
<!-- 消息总线 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>
5.1.2 配置文件添加
## 开启消息跟踪
management.endpoints.web.exposure.include= bus-refresh
spring.cloud.bus.trace.enabled=true

spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest

当然上述配置文件内容也可以放在git上,启动会自动拉取

5.2.1 将依赖导入Config-server端
<!-- 消息总线 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>
5.2.2 配置文件加入以下配置
server:
  port: 9900
spring:
  application:
    name: shop-config-server
    # config和eureka 配置,自己配
  rabbitmq:
        host: 127.0.0.1
        port: 5672
        username: guest
        password: guest
management:
  endpoints:
    web:
      exposure:
        include: bus-refresh
5.2.3 修改配置文件,请求config-server端

请求路径:
http://localhost:9900/actuator/bus-refresh
去看其他微服务控制台,动态的获取配置信息,正在打印中…
打印信息结束后,去测试…(自己测)

5.2.4 最后在所有controller层加上@RefreshScope

本来想加在启动类上,试了试不行,哎还是换Nacos吧…这不行!!!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值