RaabitMQ(三) - RabbitMQ队列类型、死信消息与死信队列、懒队列、集群模式、MQ常见消息问题

news2024/12/26 11:00:31

RabbitMQ队列类型

Classic经典队列

  • 这是RabbitMQ最为经典的队列类型。在单机环境中,拥有比较高的消息可靠性。

  • 经典队列可以选择是否持久化(Durability)以及是否自动删除(Auto delete)两个属性。

  • Durability有两个选项,Durable和Transient。 Durable表示队列会将消息保存到硬盘,这样消息的安全性更高。但是同时,由于需要有更多的IO操作,所以生产和消费消息的性能,相比Transient会比较低。

  • Auto delete属性如果选择为是,那队列将在至少一个消费者已经连接,然后所有的消费者都断开连接后删除自己。

  • 经典队列不适合积累太多的消息。如果队列中积累的消息太多了,会严重影响客户端生产消息以及消费消息的性能。因此,经典队列主要用在数据量比较小,并且生产消息和消费消息的速度比较稳定的业务场景。比如内部系统之间的服务调用

Quorum仲裁队列

  • 仲裁队列,是3.8引入的一个新队列类型;仲裁队列相比Classic经典队列,在分布式环境下对消息的可靠性保障更高。

  • Quorum是基于Raft一致性协议实现的一种新型的分布式消息队列,他实现了持久化,多备份的FIFO队列,主要就是针对RabbitMQ的镜像模式设计的。简单理解就是quorum队列中的消息需要有集群中多半节点同意确认后,才会写入到队列中。

  • Classic与Quorum对比、少了一些高级特性:
    在这里插入图片描述

  • Quorum队列更适合于 队列长期存在,并且对容错、数据安全方面的要求比低延迟、不持久等高级队列更能要求更严格的场景。例如 电商系统的订单,引入MQ后,处理速度可以慢一点,但是订单不能丢失。

  • Quorum不适合的场景如下:

    • 队列的临时性:暂时性或独占队列、高队列变动率(声明和删除率)
    • 尽可能低的延迟:由于其数据安全功能,底层共识算法固有的延迟更高
    • 当数据安全不是优先事项时(例如,应用程序不使用手动确认,不使用发布者确认)
    • 很长的队列积压(流可能更适合)

创建Quorum队列

Spring创建仲裁队列需要设置参数“-x-queue-type”为“quorum”

@Configuration
public class QuorumConfig {
    public final static String QUEUE_TYPE = "x-queue-type";
    public final static String QUEUE_TYPE_VAL = "quorum";
    public final static String QUEUE_NAME = "quorumQueue";
    @Bean
    public Queue quorumQueue() {
        HashMap<String, Object> params = new HashMap<>();
        params.put(QUEUE_TYPE,QUEUE_TYPE_VAL);
        Queue queue = new Queue(QUEUE_NAME, true, false, false, params);
        return queue;
    }
}

在这里插入图片描述
Rabbit Client创建Quorum队列:

Map<String,Object> params = new HashMap<>();
params.put("x-queue-type","quorum");
//声明Quorum队列的方式就是添加一个x-queue-type参数,指定为quorum。默认是classic
channel.queueDeclare(QUEUE_NAME, true, false, false, params);

​ Quorum队列的消息是必须持久化的,所以durable参数必须设定为true,如果声明为false,就会报错。同样,exclusive参数必须设置为false。这些声明,在Producer和Consumer中是要保持一致的。

Stream队列

  • Stream队列是3.9.0版本引入新队列类型。
  • 持久化到磁盘并且具备分布式备份的,更适合于消费者多,读消息非常频繁的场景。
  • Stream队列的核心是以append-only只添加的日志来记录消息,整体来说,就是消息将以append-only的方式持久化到日志文件中,然后通过调整每个消费者的消费进度offset,来实现消息的多次分发。类似kafka;

创建Stream队列

Spring AMQP目前还不支持创建Stream队列;只能使用原生API创建

  Map<String,Object> params = new HashMap<>();
        params.put("x-queue-type","stream");
        params.put("x-max-length-bytes", 20_000_000_000L); // maximum stream size: 20 GB
        params.put("x-stream-max-segment-size-bytes", 100_000_000); // size of segment files: 100 MB
        channel.queueDeclare(QUEUE_NAME, true, false, false, params);

Stream队列的durable参数必须声明为true,exclusive参数必须声明为false。
x-max-length-bytes 表示日志文件的最大字节数, x-stream-max-segment-size-bytes 每一个日志文件的最大大小。这两个是可选参数,通常为了防止stream日志无限制累计,都会配合stream队列一起声明。

消费者:

 	Map<String,Object> consumeParam = new HashMap<>();
    consumeParam.put("x-stream-offset","last");
    channel.basicConsume(QUEUE_NAME, false,consumeParam, myconsumer);

x-stream-offset的类型:

  • first: 从日志队列中第一个可消费的消息开始消费
  • last: 消费消息日志中最后一个消息
  • next: 相当于不指定offset,消费不到消息。
  • Offset: 一个数字型的偏移量
  • Timestamp:一个代表时间的Data类型变量,表示从这个时间点开始消费。例如 一个小时前Date timestamp = new Date(System.currentTimeMillis() - 60 * 60 * 1_000)

Stream队列产品目前不够成熟,目前用的最多的还是Classic经典队列。RabbitMQ目前主推的是Quorum队列;

死信消息

有以下三种情况,RabbitMQ会将一个正常消息转成死信

  • 消息被消费者确认拒绝。消费者把requeue参数设置为true(false),并且在消费后,向 RabbitMQ返回拒绝。channel.basicReject或者channel.basicNack。

  • 消息达到预设的TTL时限还一直没有被消费。

  • 消息由于队列已经达到最长长度限制而被丢掉

    • TTL即最长存活时间 Time-To-Live 。消息在队列中保存时间超过这个TTL,即会被认为死亡。死亡的消息会被丢入死信队列,如果没有配置死信队列的话,RabbitMQ会保证死了的消息不会再次被投递,并且在未来版本中,会主动删除掉这些死掉的消息。

    • 声明队列时、设置"x-message-ttl"值;

		Map<String, Object> args = new HashMap<String, Object>();
		args.put("x-message-ttl", 60000);
		channel.queueDeclare("myqueue", false, false, false, args);

如何判断消息是否为死信

消息被作为死信转移到死信队列后,header中还会加上第一次成为死信的三个属性,并且这三个属性在以后的传递过程中都不会更改。具体可以调试去看看;

  1. x-first-death-reason :原因
  2. x-first-death-queue : 队列
  3. x-first-death-exchange : 交换机

死信队列

  • 存在死信消息的队列;
  • RabbitMQ中有两种方式可以声明死信队列,一种是针对某个单独队列指定对应的死信队列。另一种就是以策略的方式进行批量死信队列的配置。
    流程图如下:
    在这里插入图片描述

代码:
死信交换机、队列:

@Configuration
public class DeadConfig {
    public final static String DEAD_EXCHANGE = "deadExchange";

    public final static String DEAD_QUEUE_NAME = "deadQueue";
    @Bean
    public FanoutExchange deadExchange() {
        FanoutExchange directExchange = new FanoutExchange(DEAD_EXCHANGE);
        return directExchange;
    }

    @Bean
    public Queue deadQueue() {
        Queue queue = new Queue(DEAD_QUEUE_NAME);
        return queue;
    }

    @Bean
    public Binding deadBinding(FanoutExchange deadExchange, Queue deadQueue) {
        return BindingBuilder.bind(deadQueue).to(deadExchange);
    }

}

发送者:

@Controller
public class MessageTx {

    @Autowired
    private MessageService messageService;

    @GetMapping("/sendDeadMsg")
    @ResponseBody
    public String sendMoreMsgTx(){
        //发送10条消息
        for (int i = 0; i < 10; i++) {
            String msg = "msg"+i;
            System.out.println("发送消息  msg:"+msg);
            // xiangjiao.exchange  交换机
            // xiangjiao.routingKey  队列
            messageService.sendMessage(MessageConfig.EXCHANGE_NAME, "", msg);
            //每两秒发送一次
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return "send ok";
    }
}

@Slf4j
@Component
public class MessageService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void sendMessage(String exchange,String routingKey,Object msg) {

        // 暂时关闭 return 配置
        //rabbitTemplate.setReturnCallback(this);
        //发送消息
        rabbitTemplate.convertAndSend(exchange,routingKey,msg);
    }

}

消费者:

public class MessageConsumer {
    //    @RabbitHandler : 标记的方法只能有一个参数,类型为String ,若是传Map参数、则需要传入map参数
    // @RabbitListener:标记的方法可以传入Channel, Message参数
    @RabbitListener(queues = MessageConfig.MESSAGE_QUEUE_NAME)
    public void listenObjectQueue(Channel channel, Message message, String msg) throws IOException {
        System.out.println("接收到object.queue的消息" + msg);
        System.out.println("消息ID : " + message.getMessageProperties().getDeliveryTag());
        try {
            channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            System.out.println("拒绝消息 , tag = " + message.getMessageProperties().getDeliveryTag());
//            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        }catch (IOException exception) {
            //拒绝确认消息
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);

            //拒绝消息
//            channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
        }
    }


}

注入容器:

@Configuration
public class MessageConfig {
    public final static String EXCHANGE_NAME = "deadMessageTestExchange";
    public final static String MESSAGE_QUEUE_NAME = "deadMessageTestQueue";
    public final static String MESSAGE_ROUTE_KEY = "deadMessageTestRoutingKey";
    public final static String DEAD_EXCHANGE_KEY = "x-dead-letter-exchange";

    @Bean
    public FanoutExchange deadMessageTestExchange() {
        return new FanoutExchange(EXCHANGE_NAME);
    }

    @Bean
    public Queue deadMessageTestQueue() {
        HashMap<String, Object> params = new HashMap<>();
        params.put(DEAD_EXCHANGE_KEY, DeadConfig.DEAD_EXCHANGE);
        return new Queue(MESSAGE_QUEUE_NAME, true, false, false, params);
    }

    @Bean
    public MessageConsumer deadMessageTestConsumer() {
        return new MessageConsumer();
    }

    @Bean
    public Binding messageBinding(Queue deadMessageTestQueue, FanoutExchange deadMessageTestExchange) {
        return BindingBuilder.bind(deadMessageTestQueue).to(deadMessageTestExchange);
    }
}

延迟队列

RabbitMQ有提供插件使用延迟队列, 另外可借助 死信队列 实现延迟队列;
实现思路:

  1. 给普通队列设置消息过期时间(延迟时间), 不设置消费者;
  2. 当消息过期后,将消息放入死信队列, 给死信队列设置消费者;

懒队列

懒队列会尽可能早的将消息内容保存到硬盘当中,并且只有在用户请求到时,才临时从硬盘加载到RAM内存当中。 可解决部分消息积压问题、(海量消息积压,RabbitMQ存不下就得使用分布式存储消息)
适用的一些场景:

  • 消费者服务宕机了
  • 有一个突然的消息高峰,生产者生产消息超过消费者
  • 消费者消费太慢了

​ 默认情况下,RabbitMQ接收到消息时,会保存到内存以便使用,同时把消息写到硬盘。但是,
消息写入硬盘的过程是会阻塞队列的。RabbitMQ虽然做了优化,但是在长队列中表现不是很理想,所以有了懒队列、 以磁盘IO为代价解决消息积压问题;

SpringBoot懒队列声明方式:

@Configuration
public class LazyQueueConfig {
    @Bean
    public Queue lazyQueue() {
        HashMap<String, Object> params = new HashMap<>();
        params.put("x-queue-mode", "lazy");
        return new Queue("lazyQueue", true, false, false, params);
    }
}

原生API方式:

Map<String, Object> args = new HashMap<String, Object>();
args.put("x-queue-mode", "lazy");
channel.queueDeclare("myqueue", false, false, false, args);

懒队列适合消息量大且长期有堆积的队列,可以减少内存使用,加快消费速度。但是这是以大量消耗集群的网络及磁盘IO为代价的。

集群模式

分布式环境下,是不允许单点故障存在,需要保证高可用, 因此需要集群环境保证高可用,另外若存在海量消息,还需要保证存放得下、即分布式存储;

普通集群模式

  • 集群的各个节点之间只会有相同的元数据,即队列结构,而消息不会进行冗余,只存在一个节点中。
  • 消费时,如果消费的不是存有数据的节点, RabbitMQ会临时在节点之间进行数据传输,将消息从存有数据的节点传输到消费的节点。
  • 此模式解决分布式存储问题、但可靠性不高,相当于多个单机服务,每个都是独立的,一个都不可以宕机。某台机器宕机、则存储的消息无法消费、若未开启持久化、则丢失消息, 若消费者正在处理消息,则机器无法收到确认信息,该消息重新入队,则重复消费;
  • 普通集群模式不支持高可用,即当某一个节点服务挂了后,需要手动重启服务,才能保证这一部分消息能正常消费。

镜像集群模式

  • 在普通集群的基础上,每次保存消息后,机器主动同步到多台机器上, 而不是消费者获取消息时,再去其他节点上获取;
  • 集群会选举主节点master, 当主节点挂了,则会重新选举;
  • 此方式实现了集群高可用,但是集群之间同步消息频繁,海量数据时、同步频率更大,导致占满带宽;

消息常见问题

RabbitMQ如何保证消息不丢失

先看看哪些情况下,会存在丢失消息?
在这里插入图片描述
1,2,4步骤是可能丢消息的,因为三个步骤都是跨网络的;

生产者保证消息正确发送到RibbitMQ

  • 对于单个数据,可以使用生产者确认机制。通过多次确认的方式,保证生产者的消息能够正确的发送到RabbitMQ中。
  • ​RabbitMQ的生产者确认机制分为同步确认和异步确认。同步确认主要是通过在生产者端使用Channel.waitForConfirmsOrDie()指定一个等待确认的完成时间。异步确认机制则是通过channel.addConfirmListener(ConfirmCallback var1, ConfirmCallback var2)在生产者端注入两个回调确认函数。第一个函数是在生产者消息发送成功时调用,第二个函数则是生产者消息发送失败时调用。两个函数需要通过sequenceNumber自行完成消息的前后对应。sequenceNumber的生成方式需要通过channel的序列获取。int sequenceNumber = channel.getNextPublishSeqNo();
  • ​ 如果发送批量消息,在RabbitMQ中,另外还有一种手动事务的方式,可以保证消息正确发送
  • 手动事务机制主要有几个关键的方法: channel.txSelect() 开启事务; channel.txCommit() 提交事务; channel.txRollback() 回滚事务; 用这几个方法来进行事务管理。但是这种方式需要手动控制事务逻辑,并且手动事务会对channel产生阻塞,造成吞吐量下降

RabbitMQ消息存盘不丢消息

消息若是只存内存中,则宕机会丢失消息, 因此队列需要开启持久化,durable参数、默认创建队列,durable都会为true; 而Quorum和Stream队列默认都是开启持久化;

RabbitMQ 主从消息同步时不丢消息

普通集群模式,消息是分散存储的,不会主动进行消息同步了,是有可能丢失消息的。而镜像模式集群,数据会主动在集群各个节点当中同步,这时丢失消息的概率不会太高。

RabbitMQ消费者不丢失消息

消费者确认,分为自动确认,手动确认;若是自动确认,则消息处理完,会返回确认ack;若是处理出现异常, 则会重新入队,再次处理, 因此存在重复消费问题;

若是手动确认,消息处理过程中使用channel#basicAck, basicNack, basicReject返回确认或拒绝;SpringBoot配置文件中通过属性spring.rabbitmq.listener.simple.acknowledge-mode需要设置mutual手动确认;

SpringBoot配置文件中通过属性spring.rabbitmq.listener.simple.acknowledge-mode 进行指定。可以设定为 AUTO 自动应答; MANUAL 手动应答;NONE 不应答;

如何保证消息幂等?

当消费者消费消息处理业务逻辑时,如果抛出异常,或者不向RabbitMQ返回响应,默认情况下,RabbitMQ会无限次数的重复进行消息消费。

处理幂等问题,要设定RabbitMQ的重试次数。在SpringBoot集成RabbitMQ时,可以在配置文件
中指定spring.rabbitmq.listener.simple.retry开头的一系列属性,来制定重试策略。

需要在业务上处理幂等问题, 处理幂等问题的关键是要给每个消息一个唯一的标识;虽然RabbitMQ会给每条消息带上MessageId (处理幂等问题的关键是要给每个消息一个唯一的标识);
SpringBoot框架集成RabbitMQ后,可以给每个消息指定一个全局唯一的MessageID,在消费者端针对MessageID做幂等性判断。

//发送者
Message message2 = MessageBuilder.withBody(message.getBytes()).setMessageId(UUID.randomUUID().toString()).build();
rabbitTemplate.send(message2);

//消费者获取MessageID,自己做幂等性判断
@RabbitListener(queues = "fanout_email_queue")
public void process(Message message) throws Exception {
    // 获取消息Id
    String messageId = message.getMessageProperties().getMessageId();
    ...
}

可为了业务上的方便,再封装一层, 专门用来放入消息ID, 否则设置ID的代码随处可见;

如何保证消息的顺序?

RabbitMQ中保证顺序的方法是 单队列+单消息推送; 若是多队列的情况下,RabbitMQ没有很好的解决方案;

个人思考:如果RabbitMQ架构上很难处理,可以通过业务设置保证顺序, 即给每条消息设置序号, 消费时、查询数据库之前的消息是否处理完,若没有查到,则等待一会, 若查得到,则处理消息,处理完后,把消息id + 序号 放入数据库代表已经处理完;

RabbitMQ的数据堆积问题

bbitMQ一直以来都有一个缺点,就是对于消息堆积问题的处理不好。当RabbitMQ中有大量消息堆积时,整体性能会严重下降。而目前新推出的Quorum队列以及Stream队列,目的就在于解决这个核心问题。目前大部分企业还是围绕Classic经典队列构建应用。因此,在使用RabbitMQ时,还是要非常注意消息堆积的问题。尽量让消息的消费速度和生产速度保持一致。

  • 对于生产者:
    最明显的方式自然是降低消息生产的速度。但是,生产者端产生消息的速度通常是跟业务息息相关的,一般情况下不太好直接优化。但是可以选择尽量多采用批量消息的方式,降低IO频率。
  • 对于服务器端
    • 可使用懒队列方式存储 部分消息积压(单机的磁盘容量还是有限)
    • 可使用Sharding分片队列(分布式存储)
  • 对于消费者
    • 检查业务代码是不是太挫了, 优化代码
    • 代码性能没问题、则要增加消费者数量,提升消费速度;
    • 若是经常存在海量消息,则可以放入数据库、慢慢消费;

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

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

相关文章

mysql高级三:sql性能优化+索引优化+慢查询日志

内容介绍 单表索引失效案例 0、思考题&#xff1a;如果把100万数据插入MYSQL &#xff0c;如何提高插入效率 &#xff08;1&#xff09;关闭自动提交&#xff0c;只手动提交一次 &#xff08;2&#xff09;删除除主键索引外其他索引 &#xff08;3&#xff09;拼写mysql可以执…

matlab智能算法程序包89套最新高清录制!matlab专题系列!

关于我为什么要做代码分享这件事&#xff1f; 助力科研旅程&#xff01; 面对茫茫多的文献&#xff0c;想复现却不知从何做起&#xff0c;我们通过打包成品代码&#xff0c;将过程完善&#xff0c;让您可以拿到一手的复现过程以及资料&#xff0c;从而在此基础上&#xff0c;照…

CNN成长路:从AlexNet到EfficientNet(02)

一、说明 在~10年的深度学习中&#xff0c;进步是多么迅速&#xff01;早在 2012 年&#xff0c;Alexnet 在 ImageNet 上的准确率就达到了 63.3% 的 Top-1。现在&#xff0c;我们超过90%的EfficientNet架构和师生训练&#xff08;teacher-student&#xff09;。 二、第一阶段 …

白盒测试怎么做

一、什么是白盒测试 白盒测试又称结构测试、逻辑驱动测试或基于代码的测试。 白盒测试是一种测试用例设计方法&#xff0c;盒子指的是被测试的软件&#xff0c;白盒指的是盒子是可视的&#xff0c;即清楚盒子内部的东西以及里面是如何运作的。 "白盒"法需要测试者…

Spring学习笔记——2

Spring学习笔记——2 1、Bean的基本注解开发1.1、注解版本和Component简介1.2、Component使用1.3、Component的三个衍生注解 二、Bean依赖注入注解开发2.1、依赖注入相关注解2.2、Autowired扩展 三、非自定义Bean注解开发四、Bean配置类的注解开发五、Spring注解的解析原理六、…

解决Vue+Element UI使用表单rules国际化时From表单验证信息不能实时更新

说明&#xff1a;该篇博客是博主一字一码编写的&#xff0c;实属不易&#xff0c;请尊重原创&#xff0c;谢谢大家&#xff01; 博主在工作之余开始进行自动化测试平台的开发&#xff0c;虽然已经996一个月了但是还是在使劲挤时间做这件事情&#xff0c;目前平台使用前端框架vu…

mysql再docker中运行,直接在实体机上运行mysql命令初始化数据库数据

背景 项目上我们使用docker安装mysql&#xff0c;项目启动的时候需要利用sql语句初始化数据。 直接在实体上是识别不到mysql命令的。 实现方式 实现方式1&#xff1a;在docker容器内部执行sql语句 1. 将sql文件上传到容器内 docker cp /root/1.sql d5:/home/ 说明&#…

日期格式化的最佳实践:如何在Java中处理日期格式化

文章目录 前言一、使用format()方法二、使用注解JsonFormat三、使用消息转换器1.定义用户类2.重写DateSerializer 方法3.定义对象映射器&#xff1a;4.定义消息转换器5.调用测试 总结 前言 当涉及到日期格式化时&#xff0c;了解正确的方式和最佳实践是至关重要的。 日期格式化…

Stephen Wolfram:ChatGPT 的训练

The Training of ChatGPT ChatGPT 的训练 OK, so we’ve now given an outline of how ChatGPT works once it’s set up. But how did it get set up? How were all those 175 billion weights in its neural net determined? Basically they’re the result of very large…

目标检测与跟踪 (3)- TensorRTYOLO V8性能优化与部署测试

系列文章目录 目标检测与跟踪 &#xff08;1&#xff09;- 机器人视觉与YOLO V8_Techblog of HaoWANG的博客-CSDN博客 目标检测与跟踪 &#xff08;2&#xff09;- YOLO V8配置与测试_Techblog of HaoWANG的博客-CSDN博客 目录 系列文章目录 前言 YOLO v8 TensorRT 一、…

等保三级中“身份鉴别”要求与2FA双因子认证有何关联?

为了保护信息的安全&#xff0c;我国实行对信息及信息载体按照重要性等级分别进行保护&#xff0c;也就是信息安全等级保护制度。根据信息系统在国家安全、经济建设、社会生活中的重要程度&#xff0c;信息系统遭到破坏后对国家安全、社会秩序、公共利益以及公民、法人和其他组…

c++(类型转换+IO)[30]

类型转换 意义相近的类型------隐式类型转换 意义不想近的类型&#xff0c;值转换后有意义------显示的强制类型转换 static_cast 任何隐式类型的转换&#xff0c;非多态类型的转换&#xff08;静态转换&#xff09;&#xff0c;意义相近的转换。 用于常见的隐式类型转换&am…

Spring Boot数据访问基础知识与JDBC简单实现

目录 Spring Boot数据访问基础知识 Spring Data ORM JDBC JPA JDBC简单实现 步骤1&#xff1a;新建Maven项目&#xff0c;添加依赖 步骤2&#xff1a;配置数据源—让程序可以访问到 步骤3&#xff1a;配置数据源—让IDEA可以访问到 步骤4&#xff1a;添加数据库和表 …

今天面了一个来字节要求月薪24K,明显感觉他背了很多面试题...

最近有朋友去字节面试&#xff0c;面试前后进行了20天左右&#xff0c;包含4轮电话面试、1轮笔试、1轮主管视频面试、1轮hr视频面试。 据他所说&#xff0c;80%的人都会栽在第一轮面试&#xff0c;要不是他面试前做足准备&#xff0c;估计都坚持不完后面几轮面试。 其实&…

4.DNS和负载均衡

文章目录 coreDNS概念部署croeDNS测试 kubernetes多master集群结构master节点部署 负载均衡配置部署nginx做四层反向代理安装高可用 keepalivednginx监控脚本修改k8s中组件的配置文件 coreDNS 概念 coreDNS是kubernetes的默认DNS实现。可以为集群中的service资源创建一个资源名…

ThinkPHP6企业OA办公系统

有需要请加文章底部Q哦 可远程调试 ThinkPHP6企业OA办公系统 一 介绍 勾股OA基于ThinkPHP6开发&#xff0c;前端Layui&#xff0c;数据库mysql&#xff0c;是一款实用的企业办公系统。可多角色登录&#xff0c;集成了系统设置、人事管理、消息管理、审批管理、日常办公、客户…

构建器/建造者/构建者模式(C++)

定义 将一个复杂对象的构建与其表示相分离,使得同样的构建过程(稳定)可以创建不同的表示(变化)。 应用场景 在软件系统中&#xff0c;有时候面临着“一个复杂对象”的创建工作&#xff0c;其通常由各个部分的子对象用一定的算法构成;由于需求的变化&#xff0c;这个复杂对象…

Homer:一个简单的静态主页

什么是 Homer ? Homer 是一个完全静态的 html/js 仪表板&#xff0c;基于一个简单的 yaml 配置文件。它旨在由 HTTP 服务器提供服务&#xff0c;如果您直接通过 file:// 协议打开 index.html&#xff0c;它将无法工作。 安装 在群晖上以 Docker 方式安装。 在注册表中搜索 h…

RISC-V基础之函数调用(四)非叶函数调用(包含实例)

叶函数是指不调用其他函数&#xff0c;也不改变任何非易失性寄存器的函数2。叶函数通常是一些简单的操作&#xff0c;如数学运算或逻辑判断。叶函数的特点是可以通过模拟返回来展开&#xff0c;即不需要保存或恢复寄存器的状态。 非叶函数是指调用其他函数或改变非易失性寄存器…

百度智能云“千帆大模型平台”升级:大模型最多,Prompt模板最全

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…