【RabbitMQ高级特性】消息可靠性原理

news2024/11/13 15:01:08

1. 消息确认机制

1.1 介绍

我们可以看到RabbitMQ的消息流转图:

当消息从Broker投递给消费者的时候会存在以下两种情况:

  1. consumer消费消息成功
  2. consumer消费消息异常

如果说RabbitMQ在每次将消息投递给消费者的时候就将消息从Broker中删除,此时如果消息处理异常,就会造成消息丢失的情况!因此RabbitMQ提供了消息确认机制(Message Acknowledge),消费者可以设置autoAck参数来进行确认:

  • 自动确认:当设置autoAck参数为true时,RabbitMQ就会将自己发送出去的消息置为确认,并从内存和硬盘上移除,不管消费者是否消费消息成功,适用于消息可靠性要求不高的场景
  • 手动确认:当设置autoAck参数为false时,RabbitMQ会等待消费者显示调用Basic.Ack命令,如果确认消费成功则进行删除消息操作,适用于消息可靠性较高的场景

当autoAck参数设置为false的时候,消息会被分为两部分:一部分是等待进行投递的消息,另一部分是已经投递但是还没有等到消费者回复的消息,其结构如下:

从RabbitMQ的Web管理平台也可以看到这两种状态:
image.png

1.2 手动确认方法

消费者在收到消息之后,可以进行确认应答,也可以进行拒绝确认,RabbitMQ也提供的不同的确认方法API,在消费者端可以使用channel的以下三种不同API进行应答:

  1. 肯定应答:channel.basicAck(long deliveryTag, boolean multiple)

表示消息已经被消费者正确处理,通知RabbitMQ可以将消息进行移除了
参数说明:

  • deliveryTag:是消息的唯一标识,是一个64位递增的长整数,该参数由每个channel进行单独维护,即在每个channel内部deliveryTag是不重复的
  • multiple:是否进行批量确认,在某些情况下为了减少网络传输带宽,可以对连续的多个deliveryTag进行批量确认,当值设置为true的时候则会将ack<=deliveryTag的消息全部确认;如果值设置为false则只会将对应deliveryTag的消息进行确认
  1. 否定确认:channel.basicReject(long deliveryTag, boolean requeue)

表示消费者拒绝该消息
参数说明:

  • deliveryTag:参考basicAck
  • requeue:表示拒绝该消息之后该消息如何处理,如果设置为true,则RabbitMQ会重新将该消息放入队列,以便投递给下一个订阅的消费者;如果设置为false,则RabbitMQ会将该消息从队列中移除
  1. 否定确认:channel.basicNack(long deliveryTag, boolean multiple, boolean requeue)

表示消费者拒绝该消息,并且可以批量拒绝消息
参数说明(参考上方)

1.3 代码演示

下面我们基于Spring-AMQP演示消息的确认机制,该确认机制有三种模式可以配置(需要注意与上述client模式有些不同):

1.3.1 NONE模式

该模式类似于上述讲的自动确认模式:即只要Broker将消息投递给消费者就会删除队列中的消息,而不管消费者有没有消费成功,可能会造成消息丢失场景!

  1. 配置确认机制为NONE模式:
spring:
  application:
    name: mq-advanced
  rabbitmq:
    username: guest
    password: guest
    host: 127.0.0.1
    port: 5672
    virtual-host: springboot-mq
    listener:
      simple:
        acknowledge-mode: NONE # NONE模式
  1. 发送消息
@RequestMapping("/none")
public String testNone() {
    rabbitTemplate.convertAndSend("", QueueConstant.ACK_QUEUE, "test none mode");
    return "消息发送成功!";
}
  1. 监听消息
@Component
public class AckListener {
    @RabbitListener(queues = QueueConstant.ACK_QUEUE)
    public void ackListener(Message message, Channel channel) {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        String body = new String(message.getBody(), StandardCharsets.UTF_8);
        System.out.println("接收到消息: " + body + " deliveryTag: " + deliveryTag);
        System.out.println("开始处理消息...");
        int ret = 3 / 0;
        System.out.println("消息处理完毕...");
    }
}

此时就会出现以下情况:在消费者中抛出异常,但是RabbitMQ中消息已经丢失!

1.3.2 AUTO模式(默认)

该模式作用如下:

  • 当消费者业务代码处理正常时就会对消息进行确认
  • 但是如果消费者业务代码中抛出了异常,就会对消息进行否定确认并重新投递
  1. 配置确认机制为AUTO模式:
spring:
  application:
    name: mq-advanced
  rabbitmq:
    username: guest
    password: guest
    host: 127.0.0.1
    port: 5672
    virtual-host: springboot-mq
    listener:
      simple:
        acknowledge-mode: AUTO # AUTO模式
  1. 发送消息
@RequestMapping("/none")
public String testNone() {
    rabbitTemplate.convertAndSend("", QueueConstant.ACK_QUEUE, "test none mode");
    return "消息发送成功!";
}
  1. 监听消息
@Component
public class AckListener {
    @RabbitListener(queues = QueueConstant.ACK_QUEUE)
    public void ackListener(Message message, Channel channel) {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        String body = new String(message.getBody(), StandardCharsets.UTF_8);
        System.out.println("接收到消息: " + body + " deliveryTag: " + deliveryTag);
        System.out.println("开始处理消息...");
        int ret = 3 / 0;
        System.out.println("消息处理完毕...");
    }
}

此时就会出现以下情况:在消费者中抛出异常,但是消息不会丢失,而是源源不断投递给可用的消费者!
image.png

1.3.3 MANUAL模式

该模式就可以进行手动确认:

  1. 配置确认机制为MANUAL模式:
spring:
  application:
    name: mq-advanced
  rabbitmq:
    username: guest
    password: guest
    host: 127.0.0.1
    port: 5672
    virtual-host: springboot-mq
    listener:
      simple:
        acknowledge-mode: MANUAL # MANUAL模式
  1. 发送消息
@RequestMapping("/none")
public String testNone() {
    rabbitTemplate.convertAndSend("", QueueConstant.ACK_QUEUE, "test none mode");
    return "消息发送成功!";
}
  1. 监听消息
@Component
public class AckListener {
    @RabbitListener(queues = QueueConstant.ACK_QUEUE)
    public void ackListener(Message message, Channel channel) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            String body = new String(message.getBody(), StandardCharsets.UTF_8);
            System.out.println("接收到消息: " + body + " deliveryTag: " + deliveryTag);
            System.out.println("开始处理消息...");
            int ret = 3 / 0;
            System.out.println("消息处理完毕...");
            channel.basicAck(deliveryTag, false);
        } catch (Exception e) {
            channel.basicReject(deliveryTag, true);
        }
    }
}

此时就会出现以下情况:如果处理成功,就会进行basicAck肯定确认,但是如果捕获到了异常就进行拒绝确认,并将消息重新入队投递给下一个消费者使用!
image.png

2. 持久化机制

2.1 介绍

我们再次回看RabbitMQ的消息流转图:

前面我们通过消息确认机制保证了Broker能够将消息可靠地投递给Consumer消费者端,但是现在还存在一个问题:当消息存储在Broker中,但是RabbitMQ服务器遇到断电重启的情况如何保证将消息恢复呢?RabbitMQ就提供了 持久化机制 ,在RabbitMQ中有以下三种持久化:

  1. 队列持久化
  2. 交换机持久化
  3. 消息持久化

2.2 队列持久化

队列的持久化是通过在声明队列的时候设置参数durable为true实现的

  • 如果队列不进行持久化,那么在重启的时候关于队列的元数据信息就会丢失(此时哪怕消息进行了持久化也无法恢复消息了,因为消息保存在队列中)
  • 如果将队列设置为持久化,此时队列相关的元数据就可以从硬盘上进行恢复,但是并不能保证内部的消息不丢失,如果想要让消息不丢失,还需要设置消息的持久化

我们之前所创建队列的代码默认设置为持久化:

/**
 * 声明持久化队列
 */
@Bean("persistQueue")
public Queue persistQueue() {
    return QueueBuilder
            .durable(QueueConstant.PERSIST_QUEUE)
            .build();
}

追踪durable方法源码:
image.png
继续追踪setDurable方法源码可以发现默认是进行持久化的!
image.png
如果我们想要设置队列为非持久化,可以使用如下代码:

/**
 * 声明非持久化队列
 */
@Bean("nonPersistQueue")
public Queue nonPersistQueue() {
    return QueueBuilder
            .nonDurable(QueueConstant.NON_PERSIST_QUEUE)
            .build();
}

2.3 交换机持久化

交换机的持久化是通过在声明交换机的时候设置参数durable为true实现的
同队列一样,只有设置为持久化,才会将有关交换机的元数据信息保存在硬盘上,在重启RabbitMQ服务器的时候才会读取然后恢复交换机数据信息,我们可以通过在声明交换机的时候设置durable(true | false)显示声明是否持久化:

/**
 * 声明持久化交换机
 */
@Bean("persistDirectExchange")
public DirectExchange persistDirectExchange() {
    return ExchangeBuilder
            .directExchange(ExchangeConstant.PERSIST_EXCHANGE)
            .durable(true)
            .build();
}
/**
 * 声明非持久化交换机
 */
@Bean("nonPersistDirectExchange")
public DirectExchange nonPersistDirectExchange() {
    return ExchangeBuilder
            .directExchange(ExchangeConstant.NON_PERSIST_EXCHANGE)
            .durable(false)
            .build();
}

2.4 消息持久化

如果想要让消息进行持久化,我们就需要设置消息的投递模式MessageProperties.deliveryModePERSISITENT,使用RabbitTemplate发送持久化消息代码如下:

@RestController
public class PersistController {
    @Resource
    private RabbitTemplate rabbitTemplate;

    @RequestMapping("/persist")
    public String sendPersist() {
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
        Message message = new Message("persist info".getBytes(), messageProperties);
        rabbitTemplate.convertAndSend(ExchangeConstant.PERSIST_EXCHANGE, "persist", message);
        return "发送成功!";
    }
}

如果想要设置消息的不持久化,则对应代码如下:

@RequestMapping("/nonPersist")
public String sendNonPersist() {
    MessageProperties messageProperties = new MessageProperties();
    messageProperties.setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);
    Message message = new Message("non-persist info".getBytes(), messageProperties);
    rabbitTemplate.convertAndSend(ExchangeConstant.NON_PERSIST_EXCHANGE, "non-persist", message);
    return "发送成功!";
}

3. 发送方确认机制

3.1 介绍

我们再次回看RabbitMQ的消息流转图:

现在我们通过消息确认机制保证从Broker到Consumer链路上消息可靠性,通过持久化机制保证Broker内部消息可靠性,但是此时还存在着问题:如果说消息在生产者投递给Broker过程中由于网络等问题导致消息丢失、或者Broker处于重启等服务不可用状态该怎么办呢?即生产者如何保证消息能够可靠到达RabbitMQ服务器?
RabbitMQ为了解决这个问题,提供了以下两种机制:

  1. 事务机制(性能较低,此处不介绍)
  2. 发送方确认机制(Publisher Confirm)

在发送方确认机制中,可以配置以下两种模式:

  1. confirm确认模式:

确认模式指的是在发送者发送消息时设置一个ConfirmCallback的监听器,无论消息是否到达对应的Exchange,这个监听都会执行。如果消息到达对应的Exchange,则对应ACK参数为true,反之没有到达Exchange则ACK参数为false

  1. return回退模式

我们期待Exchange能够依据特定的路由规则将消息投递给对应的队列,但是如果设置的路由键错误或者队列不存在时导致消息迟迟没有投递给队列,此时我们希望可以将消息退回给生产者,退回模式指的是在发送者发送消息时设置一个ReturnsCallback的监听器对退回的消息进行处理

🔑 总结:确认模式和退回模式并不是互斥的,两者可以同时设置!确认模式主要解决的是保证消息可靠到达Exchange的问题,而退回模式保证的是消息可靠到达Queue的问题

3.2 代码演示

3.2.1 Confirm确认模式

配置步骤如下:

  1. 进行confirm模式配置
  2. 在发送方设置ConfirmCallback并发送消息
  3. 测试

接下来看实现步骤:

  1. 配置confirm模式开启
spring:
  application:
    name: mq-advanced
  rabbitmq:
    username: guest
    password: guest
    host: 127.0.0.1
    port: 5672
    virtual-host: springboot-mq
    publisher-confirm-type: correlated # 开启发送者确认模式
  1. 声明队列与交换机
public interface ExchangeConstant {
    String CONFIRM_EXCHANGE = "confirm.exchange";
}
public interface QueueConstant {
    String CONFIRM_QUEUE = "confirm.queue";
}
@Configuration
public class RabbitMQConfig {
    /**
     * 声明发送者确认模式队列
     */
    @Bean("confirmQueue")
    public Queue confirmQueue() {
        return QueueBuilder
        .durable(QueueConstant.CONFIRM_QUEUE)
        .build();
    }
    /**
     * 声明发送者确认模式交换机
     */
    @Bean("confirmExchange")
    public DirectExchange confirmExchange() {
        return ExchangeBuilder
        .directExchange(ExchangeConstant.CONFIRM_EXCHANGE)
        .durable(true)
        .build();
    }
    /**
     * 声明发送者确认模式交换机
     */
    @Bean("confirmBinding")
    public Binding confirmBinding(@Qualifier("confirmExchange") DirectExchange exchange, @Qualifier("confirmQueue") Queue queue) {
        return BindingBuilder
        .bind(queue)
        .to(exchange)
        .with("confirm");
    }
}
  1. 编写发送者代码
@Configuration
public class RabbitTemplateConfig {
    @Bean("rabbitTemplate")
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        return new RabbitTemplate(connectionFactory);
    }

    @Bean("confirmRabbitTemplate")
    public RabbitTemplate confirmRabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        // 设置confirm回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean b, String s) {
                System.out.println("执行了confirm方法...");
                if (b) {
                    // 到达交换机
                    System.out.println("消息id: " + (correlationData == null ? null : correlationData.getId())  + "到达交换机");
                } else {
                    // 没有到达交换机
                    System.out.println("消息id: " + (correlationData == null ? null : correlationData.getId()) + "没有到达交换机, 原因是: " + s);
                }
            }
        });
        return rabbitTemplate;
    }
}
@RestController
public class ConfirmController {
    @Resource(name = "confirmRabbitTemplate")
    private RabbitTemplate rabbitTemplate;
    @RequestMapping("/confirm")
    public String confirm() {
        // 发送消息
        CorrelationData correlationData = new CorrelationData("1");
        rabbitTemplate.convertAndSend(ExchangeConstant.CONFIRM_EXCHANGE, "confirm", "test confirm...", correlationData);
        return "发送消息成功!";
    }
}
  1. 测试接口

image.png
发现如果交换机名称设置正确则当消息到达交换机时回调被执行,我们尝试设置一个不存在的交换机名称查看现象:
image.png
此时就会走没有到达交换机的逻辑,此处就可以进行重新投递消息等业务逻辑!

💡 答疑解惑:为什么此处我们明确注入一个自己创建出来的RabbitTemplate,而不使用Spring提供的呢?有以下两点原因:

  1. 这是因为Spring默认配置Bean为单例的,因此如果使用Spring提供的RabbitTemplate设置回调函数则会影响其余接口同样使用回调
  2. 我们不能重复在controller层代码中重复多次调用setConfirmCallback回调,因为明确规定每个RabbitTemplate只能设置一次ConfirmCallback

3.2.2 Return退回模式

配置步骤如下:

  1. 进行return模式配置
  2. 在发送方设置setMandatory(true)表示进行退回
  3. 设置ReturnsCallback回调逻辑并发送消息
  4. 测试

接下来看实现步骤:

  1. 配置return模式开启(同confirm模式一致)
spring:
  application:
    name: mq-advanced
  rabbitmq:
    username: guest
    password: guest
    host: 127.0.0.1
    port: 5672
    virtual-host: springboot-mq
    publisher-confirm-type: correlated # 开启发送者确认模式
  1. 设置ReturnsCallback回调逻辑并发送消息
@Configuration
public class RabbitTemplateConfig {
    @Bean("rabbitTemplate")
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        return new RabbitTemplate(connectionFactory);
    }

    @Bean("confirmRabbitTemplate")
    public RabbitTemplate confirmRabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        // 设置confirm回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean b, String s) {
                System.out.println("执行了confirm方法...");
                if (b) {
                    // 到达交换机
                    System.out.println("消息id: " + (correlationData == null ? null : correlationData.getId())  + "到达交换机");
                } else {
                    // 没有到达交换机
                    System.out.println("消息id: " + (correlationData == null ? null : correlationData.getId()) + "没有到达交换机, 原因是: " + s);
                }
            }
        });
        // 设置return回调
        rabbitTemplate.setMandatory(true);
        rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
            @Override
            public void returnedMessage(ReturnedMessage returnedMessage) {
                System.out.println("收到回退消息: " + returnedMessage);
            }
        });
        return rabbitTemplate;
    }
}
@RestController
public class ConfirmController {
    @Resource(name = "confirmRabbitTemplate")
    private RabbitTemplate rabbitTemplate;
    @RequestMapping("/confirm")
    public String confirm() {
        // 发送消息
        CorrelationData correlationData = new CorrelationData("1");
        rabbitTemplate.convertAndSend(ExchangeConstant.CONFIRM_EXCHANGE, "confirm", "test confirm...", correlationData);
        return "发送消息成功!";
    }

    @RequestMapping("/returns")
    public String returns() {
        // 发送消息
        CorrelationData correlationData = new CorrelationData("2");
        rabbitTemplate.convertAndSend(ExchangeConstant.CONFIRM_EXCHANGE, "confirm", "test return...", correlationData);
        return "发送消息成功!";
    }
}

  1. 下面进行测试(设置不存在的routingkey)

image.png
此时证明当消息长期存放在exchange中没有投递到queue的时候就会触发消息退回回调

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

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

相关文章

用 like concat 不用 like,为了防止sql注入;#{}和${}的区别和用法;#{}预防SQL注入的原理

一、like concat 和 like mybatis中为了防止sql注入&#xff0c;使用like语句时并不是直接使用&#xff0c;而是使用concat函数<if test"goodName ! null and goodName ! "> and good_name like concat(%, #{goodName}, %)</if> concat()函数1、功能&a…

Webbench1.5安装使用Ubuntu

1、安装依赖包 sudo apt-get update sudo apt-get install libtirpc-dev2、安装Webbench1.5 参考https://github.com/baiguo/webbench-1.5 # 可能需要root权限&#xff0c;我是切换到root用户才安装成功 wget http://home.tiscali.cz/~cz210552/distfiles/webbench-1.5.tar.…

APP.vue引入子组件进行页面展示

一.将vue项目启动服务器原始页面进行清空 打开APP.vue文件&#xff0c;将<template>标签里的内容和<style>标签里的内容 ctrl/ 选中进行注释&#xff0c;以及引入的Helloworld.vue文件内容代码进行注释 并且 ctrls 保存 服务器页面从原始页面 变为空白 二.在comp…

树莓派4B安装golang最新版(20210520)

前置条件&#xff1a; 树莓派4B 安装官方系统 Linux raspberrypi 5.10.17-v7l #1414 更换最新版的原因&#xff1a; 截至 2021.5.20 &#xff0c;Raspberry Pi OS 最新版系统中&#xff0c;默认安装golang1.11&#xff0c;但是使用 go get golang.org/x/crypto/ssh 时&#xff…

推荐系统实战(七)-多任务多场景(上)多任务

多任务Multi-Task&#xff0c;有时也被称为多目标Multi-Objective建模。比如说电商场景下&#xff0c;希望曝光的物料被多多点击&#xff0c;还希望商品被下单购买&#xff0c;因此同时建模三个目标&#xff1a;曝光到点击CTR&#xff0c;点击到购买转换率CVR&#xff0c;曝光到…

记一次对某佛教系统的漏洞挖掘

前言 简单记录一次漏洞挖掘&#xff0c;一个系统居然爆了这么多类型的洞&#xff0c;于是想记录哈。(比较基础&#xff0c;我是菜狗&#xff0c;大佬轻喷) 业务介绍 是一个某佛教的系统 有一些佛教的学习资源、一些佛教相关的实物商品可购买&#xff0c;有个人中心&#xff…

PyCharm中python语法要求——消去提示波浪线

PyCharm中python语法要求——消去提示波浪线 关闭代码规范检查 在Setting里边搜索pep&#xff0c;取消勾选pep8 coding style violation 问题产生 解决问题 按照下图操作&#xff0c;也可直接CtrlAlts弹出设置页面 在 Settings 中 &#xff1a; Editor > Color Sheame >…

设计模式26-解析器模式

设计模式26-解析器模式 动机定义与结构定义结构 C代码推导代码说明 优缺点应用总结 动机 在软件构建过程中&#xff0c;如果某一特定领域的问题比较复杂&#xff0c;类似结构会不断重复的出现。如果使用普通的编程方式来实现&#xff0c;将面临非常频繁的变化。 在这种情况下&…

二叉树算法算法【二叉树的创建、插入、删除、查找】

一、原理 1.1、二叉排序树的插入 1.2、二叉树的删除 &#xff08;1&#xff09;删除度为0的节点&#xff0c;就是最后的叶子节点&#xff0c;直接删除就可以了. &#xff08;2&#xff09;删除度为1的节点&#xff0c;就是爷爷节点接收孙子节点。 &#xff08;3&#xff09;删…

什么软件可以约束员工摸鱼行为?「5款软件助力企业管控员工上班摸鱼!」

你的企业是否也在面临这些问题&#xff1a; 1.工作效率下降&#xff1a;频繁的分心会打断工作连贯性&#xff0c;降低任务完成的质量和速度。 2.团队协作受损&#xff1a;个别员工的低效可能导致整个团队进度滞后&#xff0c;影响项目按时交付。 3.资源浪费&#xff1a;非工…

Git —— 1、Windows下安装配置git

Git简介 Git 是一个免费的开源分布式版本控制系统&#xff0c;旨在处理从小型到 快速高效的超大型项目。 Git 易于学习&#xff0c;占用空间小&#xff0c;性能快如闪电。 它超越了 Subversion、CVS、Perforce 和 ClearCase 等 SCM 工具 具有 cheap local branching、 方便的暂…

【分布式架构幂等性总结】

文章目录 幂等性什么场景需要幂等设计&#xff1f;产生幂等性的原因解决重复操作&#xff0c;实现幂等性 幂等性 接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的&#xff0c;不会因为多次点击而产生了副作用。比如&#xff1a;公交车刷卡&#xff0…

.NET8 Web 利用BAT命令 一键部署 IIS - CI-CD基础

1. Windows Server 前置准备 1.1 IIS安装好 1.2 .NET8 Sdk 运行时 安装 官方下载地址&#xff1a;https://dotnet.microsoft.com/zh-cn/download/dotnet/8.0 1.3 创建一个.NET8 WebMvc项目 生成发布包 微软MVC这个项目模板直接创建&#xff0c;发布 2. 利用 BAT 来一键部署…

特效与样式(5)——Timetables的使用

第一次使用timetables做学校课表的开发&#xff0c;里面的门道东西挺多的&#xff0c;比我想的要复杂很多。包括我现在也只是实现了课表的初级效果。 主要是标题部分&#xff0c;数据部分&#xff0c;还有颜色控制部分。每个表格都需要一个控制颜色&#xff0c;每次写数据的时候…

hyperf注解,自定义注解

注解是 Hyperf 非常强大的一项功能&#xff0c;可以通过注解的形式减少很多的配置&#xff0c;以及实现很多非常方便的功能。 结构 建立注解 在app下建立Annotation注解文件夹 在Annotation下建立Jim.php注解 下面的的Annotation 和 Target是全局注解&#xff0c;所以不需…

Go学习笔记(一)语法

标准库文档&#xff1a;Go语言标准库文档中文版 | Go语言中文网 | Golang中文社区 | Golang中国 B站课程&#xff1a;8小时转职Golang工程师(如果你想低成本学习Go语言) 课程作者语雀&#xff08;首页有更多内容&#xff09;&#xff1a;8小时转职Golang工程师 语雀 代码仓…

C语言基础(二十)

链表是一种常见的数据结构&#xff0c;通常用来存储一系列元素&#xff0c;每个元素由一个节点来表示。在链表中&#xff0c;每个节点包含两部分&#xff1a;数据元素本身和指向下一个节点的指针。这种结构使得链表中的元素在内存中不是连续存储的&#xff0c;而是通过指针连接…

可拖拽表单设计器都有哪些突出特点?

为了提高效率、降低开发成本&#xff0c;利用低代码技术平台的优势特点可以实现这一目标。究竟什么是低代码技术平台&#xff1f;都有哪些值得夸耀的特点和优势&#xff1f;今天&#xff0c;我们就带着这些问题&#xff0c;一起来了解低代码技术平台、可拖拽表单设计器的多个优…

香港站群服务器优势

香港站群服务器因其独特的地理位置和网络连接优势&#xff0c;在SEO优化、网站群管理和网络营销等方面受到广泛关注。其优势主要体现在以下几个方面&#xff0c;rak小编为您整理发布。 地理位置优越 连接亚洲国际市场&#xff1a;香港作为亚太地区的重要经济中心&#xff0c;具…

华为2024年秋招-结构与材料工程师-结构方向-机试题(四套)(每套四十题)

华为2024年招聘-结构与材料工程师-结构方向-机试题&#xff08;四套&#xff09;&#xff08;每套四十题&#xff09; 岗位——结构与材料工程师 岗位意向——结构 真题题目分享&#xff0c;完整版带答案(有答案和解析&#xff0c;答案非官方&#xff0c;未仔细校正&#xff…