RocketMQ客户端实现多种功能

目录

RocketMQ客户端基本流程

消息确认机制

1、消息生产端采用消息确认加多次重试的机制保证消息正常发送到RocketMQ

单向发送

同步发送

异步发送

2、消息消费者端采用状态确认机制保证消费者一定能正常处理对应的消息

3、消费者也可以自行指定起始消费位点

广播消息

顺序消息机制

延迟消息

批量消息

过滤消息


RocketMQ客户端基本流程

引入依赖

        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-client</artifactId>
            <version>4.9.5</version>
        </dependency>

消息生产者代码

public class Producer {
    public static void main(String[] args) throws MQClientException, InterruptedException {
        //初始化一个消息生产者
        DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
        // 指定nameserver地址
        producer.setNamesrvAddr("127.0.0.1:9876");
        // 启动消息生产者服务
        producer.start();
        for (int i = 0; i < 2; i++) {
            try {
                // 创建消息。消息由Topic,Tag和body三个属性组成,其中Body就是消息内容
                Message msg = new Message("TopicTest","TagA",("Hello RocketMQ " +i).getBytes(RemotingHelper.DEFAULT_CHARSET));
                //发送消息,获取发送结果
                SendResult sendResult = producer.send(msg);
                System.out.printf("%s%n", sendResult);
            } catch (Exception e) {
                e.printStackTrace();
                Thread.sleep(1000);
            }
        }
        //消息发送完后,停止消息生产者服务。
        producer.shutdown();
    }
}

消息消费者代码

public class Consumer {
    public static void main(String[] args) throws InterruptedException, MQClientException {
        //构建一个消息消费者
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_4");
        //指定nameserver地址
       consumer.setNamesrvAddr("127.0.0.1:9876");
       consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
        // 订阅一个感兴趣的话题,这个话题需要与消息的topic一致
        consumer.subscribe("TopicTest", "*");
        // 注册一个消息回调函数,消费到消息后就会触发回调。
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,ConsumeConcurrentlyContext context) {
    msgs.forEach(messageExt -> {
                    try {
                        System.out.println("收到消息:"+new String(messageExt.getBody(), RemotingHelper.DEFAULT_CHARSET));
                    } catch (UnsupportedEncodingException e) {}
                });
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        //启动消费者服务
        consumer.start();
        System.out.print("Consumer Started");
    }
}

RocketMQ的客户端编程模型相对比较固定,基本都有一个固定的步骤。

消息生产者的固定步骤

1.创建消息生产者producer,并指定生产者组名
2.指定Nameserver地址
3.启动producer。这个步骤比较容易忘记。可以认为这是消息生产者与服务端建立连接的过程。
4.创建消息对象,指定主题Topic、Tag和消息体
5.发送消息
6.关闭生产者producer,释放资源。

消息消费者的固定步骤

1.创建消费者Consumer,必须指定消费者组名
2.指定Nameserver地址
3.订阅主题Topic和Tag
4.设置回调函数,处理消息
5.启动消费者consumer。消费者会一直挂起,持续处理消息。

       关键是NameServer。可以看到,RocketMQ的客户端只需要指定NameServer地址,而不需要指定具体的Broker地址。


消息确认机制

       RocketMQ要支持互联网金融场景,那么消息安全是必须优先保障的。而消息安全有两方面的要求,一方面是生产者要能确保将消息发送到Broker上。另一方面是消费者要能确保从Broker上争取获取到消息。

1、消息生产端采用消息确认加多次重试的机制保证消息正常发送到RocketMQ

有三种发送消息的方式。

单向发送

​       单向发送方式下,消息生产者只管往Broker发送消息,而全然不关心Broker端有没有成功接收到消息。这就好比生产者向Broker发一封电子邮件,Broker有没有处理电子邮件,生产者并不知道。

public class OnewayProducer {
    public static void main(String[] args)throws Exception{
        DefaultMQProducer producer = new DefaultMQProducer("producerGroup");
        producer.start();
        Message message = new Message("Order","tag","order info : orderId = xxx".getBytes(StandardCharsets.UTF_8));
        producer.sendOneway(message);
        Thread.sleep(50000);
        producer.shutdown();
    }
}

​       sendOneway方法没有返回值,如果发送失败,生产者无法补救。​单向发送有一个好处,就是发送消息的效率更高。适用于一些追求消息发送效率,而允许消息丢失的业务场景。比如日志。

同步发送

       同步发送方式下,消息生产者在往Broker端发送消息后,会阻塞当前线程,等待Broker端的相应结果。这就好比生产者给Broker打了个电话。通话期间生产者就停下手头的事情,直到Broker明确表示消息处理成功了,生产者才继续做其他的事情。

SendResult sendResult = producer.send(msg);

​       SendResult来自于Broker的反馈。producer在send发出消息,到Broker返回SendResult的过程中,无法做其他的事情。

​       在SendResult中有一个SendStatus属性,这个SendStatus是一个枚举类型,其中包含了Broker端的各种情况。

public enum SendStatus {
    SEND_OK,
    FLUSH_DISK_TIMEOUT,
    FLUSH_SLAVE_TIMEOUT,
    SLAVE_NOT_AVAILABLE,
}

       在这几种枚举值中,SEND_OK表示消息已经成功发送到Broker上。至于其他几种枚举值,都是表示消息在Broker端处理失败了。使用同步发送的机制,我们就可以在消息生产者发送完消息后,对发送失败的消息进行补救。例如重新发送。

       这种同步发送的机制能够很大程度上保证消息发送的安全性。但是,这种同步发送机制的发送效率比较低。毕竟,send方法需要消息在生产者和Broker之间传输一个来回后才能结束。如果网速比较慢,同步发送的耗时就会很长。

异步发送

       异步发送机制下,生产者在向Broker发送消息时,会同时注册一个回调函数。接下来生产者并不等待Broker的响应。当Broker端有响应数据过来时,自动触发回调函数进行对应的处理。这就好比生产者向Broker发电子邮件通知时,另外找了一个代理人专门等待Broker的响应。而生产者自己则发完消息后就去做其他的事情去了。

	producer.send(msg, new SendCallback() {
                    @Override
                    public void onSuccess(SendResult sendResult) {
                        countDownLatch.countDown();
                        System.out.printf("%-10d OK %s %n", index, sendResult.getMsgId());
                    }
                    @Override
                    public void onException(Throwable e) {
                        countDownLatch.countDown();
                        System.out.printf("%-10d Exception %s %n", index, e);
                        e.printStackTrace();
                    }
                });

       在SendCallback接口中有两个方法,onSuccess和onException。当Broker端返回消息处理成功的响应信息SendResult时,就会调用onSuccess方法。当Broker端处理消息超时或者失败时,就会调用onExcetion方法,生产者就可以在onException方法中进行补救措施。

       此时同样有几个问题需要注意。一是与同步发送机制类似,触发了SendCallback的onException方法同样并不一定就表示消息不会向消费者推送。如果Broker端返回响应信息太慢,超过了超时时间,也会触发onException方法。超时时间默认是3秒,可以通过producer.setSendMsgTimeout方法定制。而造成超时的原因则有很多,消息太大造成网络拥堵、网速太慢、Broker端处理太慢等都可能造成消息处理超时。

​        二是在SendCallback的对应方法被触发之前,生产者不能调用shutdown()方法。如果消息处理完之前,生产者线程就关闭了,生产者的SendCallback对应方法就不会触发。这是因为使用异步发送机制后,生产者虽然不用阻塞下来等待Broker端响应,但是SendCallback还是需要附属于生产者的主线程才能执行。如果Broker端还没有返回SendResult,而生产者主线程已经停止了,那么SendCallback的执行线程也就会随主线程一起停止,对应的方法自然也就无法执行了。

       这种异步发送的机制能够比较好的兼容消息的安全性以及生产者的高吞吐需求,是很多MQ产品都支持的方式。RabbitMQ和Kafka都支持这种异步发送的机制。但是异步发送机制也并不是万能的,毕竟异步发送机制对消息生产者的主线业务是有侵入的。具体使用时还是需要根据业务场景考虑。

       RocketMQ提供的这三种发送消息的方式,并不存在绝对的好坏之分。我们更多的是需要根据业务场景进行选择。例如在电商下单这个场景,我们就应该尽量选择同步发送或异步发送,优先保证数据安全。然后,如果下单场景的并发比较高,业务比较繁忙,就应该尽量优先选择异步发送的机制。这时,我们就应该对下单服务的业务进行优化定制,尽量适应异步发送机制的要求。这样就可以尽量保证下单服务能够比较可靠的将用户的订单消息发送到RocketMQ了。

2、消息消费者端采用状态确认机制保证消费者一定能正常处理对应的消息

consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        这个返回值是一个枚举值,有两个选项CONSUME_SUCCESS和RECONSUME_LATER。如果消费者返回CONSUME_SUCCESS,那么消息自然就处理结束了。但是如果消费者没有处理成功,返回的是RECONSUME_LATER,Broker就会过一段时间再发起消息重试。

       Broker不可能无限制的向消费失败的消费者推送消息。如果消费者一直没有恢复,Broker显然不可能一直无限制的推送,这会浪费集群很多的性能。所以,Broker会记录每一个消息的重试次数。如果一个消息经过很多次重试后,消费者依然无法正常处理,那么Broker会将这个消息推入到消费者组对应的死信Topic中。死信Topic相当于windows当中的垃圾桶。你可以人工介入对死信Topic中的消息进行补救,也可以直接彻底删除这些消息。RocketMQ默认的最大重试次数是16次。

       为了让这些重试的消息不会影响Topic下其他正常的消息,Broker会给每个消费者组设计对应的重试Topic。MessageQueue是一个具有严格FIFO特性的数据结构。如果需要重试的这些消息还是放在原来的MessageQueue中,就会对当前MessageQueue产生阻塞,让其他正常的消息无法处理。RocketMQ的做法是给每个消费者组自动生成一个对应的重试Topic。在消息需要重试时,会先移动到对应的重试Topic中。后续Broker只要从这些重试Topic中不断拿出消息,往消费者组重新推送即可。这样,这些重试的消息有了自己单独的队列,就不会影响到Topic下的其他消息了。

       Broker端最终只通过消费者组返回的状态来确定消息有没有处理成功。至于消费者组自己的业务执行是否正常,Broker端是没有办法知道的。因此,在实现消费者的业务逻辑时,应该要尽量使用同步实现方式,保证在自己业务处理完成之后再向Broker端返回状态。而应该尽量避免异步的方式处理业务逻辑。

3、消费者也可以自行指定起始消费位点

       Broker端通过Consumer返回的状态来推进所属消费者组对应的Offset。但是,这里还是会造成一种分裂,消息最终是由Consumer来处理,但是消息却是由Broker推送过来的,也就是说,Consumer无法确定自己将要处理的是哪些消息。

       对消息队列也一样。虽然Offset完全由Broker进行维护,但是,RocketMQ也允许Consumer自己去查账,自己指定消费位点。核心代码是在Consumer中设定了一个属性ConsumeFromWhere,表示在Consumer启动时,从哪一条消息开始进行消费。Consumer当然不可能精确的知道Offset的具体参数,所以这个ConsumerFromWhere并不是直接传入Offset位点,而是可以传入一个ConsumerFromWhere对象,这是一个枚举值。名字一目了然。

public enum ConsumeFromWhere {
    CONSUME_FROM_LAST_OFFSET, //从队列的第一条消息开始重新消费
    CONSUME_FROM_FIRST_OFFSET, //从上次消费到的地方开始继续消费
    CONSUME_FROM_TIMESTAMP; //从某一个时间点开始重新消费
}

​        另外,如果指定了ConsumerFromWhere.CONSUME_FROM_TIMESTAMP,这就表示要从一个具体的时间开始。具体时间点,需要通过Consumer的另一个属性ConsumerTimestamp。这个属性可以传入一个表示时间的字符串。

consumer.setConsumerTimestamp("20131223171201");

广播消息

应用场景:

       广播模式和集群模式是RocketMQ的消费者端处理消息最基本的两种模式。集群模式下,一个消息,只会被一个消费者组中的多个消费者实例共同处理一次。广播模式下,一个消息,则会推送给所有消费者实例处理,不再关心消费者组。

​ 消费者核心代码

consumer.setMessageModel(MessageModel.BROADCASTING);

启动多个消费者,广播模式下,这些消费者都会消费一次消息。

实现思路:

       默认模式(也就是集群模式)下,Broker端会给每个ConsumerGroup维护一个统一的Offset,这个Offset可以保证一个消息,在同一个ConsumerGroup内只会被消费一次。而广播模式的实现方式,是将Offset转移到消费者端自行保管,这样Broker端只管向所有消费者推送消息,而不用负责维护消费进度。

注意点:

1、Broker端不维护消费进度,意味着,如果消费者处理消息失败了,将无法进行消息重试。

2、消费者端维护Offset的作用是可以在服务重启时,按照上一次消费的进度,处理后面没有消费过的消息。丢了也不影响服务稳定性。


顺序消息机制

应用场景:

​        每一个订单有从下单、锁库存、支付、下物流等几个业务步骤。每个业务步骤都由一个消息生产者通知给下游服务。可以通过顺序消费机制保证对每个订单的业务处理顺序不乱。

示例代码:

​ 生产者核心代码:

for (int i = 0; i < 10; i++) {
                int orderId = i;
                for(int j = 0 ; j <= 5 ; j ++){
                    Message msg =
                            new Message("OrderTopicTest", "order_"+orderId, "KEY" + orderId,
                                    ("order_"+orderId+" step " + j).getBytes(RemotingHelper.DEFAULT_CHARSET));
                    SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
                        @Override
                        public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
                            Integer id = (Integer) arg;
                            int index = id % mqs.size();
                            return mqs.get(index);
                        }
                    }, orderId);
                    System.out.printf("%s%n", sendResult);
                }
            }

通过MessageSelector,将orderId相同的消息,都转发到同一个MessageQueue中。

​ 消费者核心代码:

consumer.registerMessageListener(new MessageListenerOrderly() {
            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
                context.setAutoCommit(true);
                for(MessageExt msg:msgs){
                    System.out.println("收到消息内容 "+new String(msg.getBody()));
                }
                return ConsumeOrderlyStatus.SUCCESS;
            }
        });

​ 注入一个MessageListenerOrderly实现。

                

​ 1、生产者只有将一批有顺序要求的消息,放到同一个MesasgeQueue上,Broker才有可能保持这一批消息的顺序。

​ 2、消费者只有一次锁定一个MessageQueue,拿到MessageQueue上所有的消息,


延迟消息

生产者端核心代码:

msg.setDelayTimeLevel(3);

​ 只要给消息设定一个延迟级别就行了。

RocketMQ给消息定制了18个默认的延迟级别,分别对应18个不同的预设好的延迟时间。

 实现思路:

       延迟消息的难点其实是性能,需要不断进行定时轮训。全部扫描所有消息是不可能的,RocketMQ的实现方式是预设一个系统Topic,名字叫做SCHEDULE_TOPIC_XXXX。在这个Topic下,预设18个延迟队列。然后每次只针对这18个队列里的消息进行延迟操作,这样就不用一直扫描所有的消息了。


批量消息

应用场景:

​       生产者要发送的消息比较多时,可以将多条消息合并成一个批量消息,一次性发送出去。这样可以减少网络IO,提升消息发送的吞吐量。

示例代码:

生产者核心代码:

        List<Message> messages = new ArrayList<>();
        messages.add(new Message(topic, "Tag", "OrderID001", "Hello world 0".getBytes()));
        messages.add(new Message(topic, "Tag", "OrderID002", "Hello world 1".getBytes()));
        messages.add(new Message(topic, "Tag", "OrderID003", "Hello world 2".getBytes()));

        producer.send(messages);

注意点:

​       批量消息的使用非常简单,但是要注意RocketMQ做了限制。同一批消息的Topic必须相同,另外,不支持延迟消息。还有批量消息的大小不要超过1M,如果太大就需要自行分割。


过滤消息

应用场景:

同一个Topic下有多种不同的消息,消费者只希望关注某一类消息。

示例代码:

生产者端需要在发送消息时,增加Tag属性。

        String[] tags = new String[] {"TagA", "TagB", "TagC"};

        for (int i = 0; i < 15; i++) {
            Message msg = new Message("TagFilterTest",
                tags[i % tags.length],
                "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET));
            SendResult sendResult = producer.send(msg);
            System.out.printf("%s%n", sendResult);
        }

消费者端就可以通过这个Tag属性订阅自己感兴趣的内容。核心代码:

        consumer.subscribe("TagFilterTest", "TagA");

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

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

相关文章

Java多线程:`Thread`类

&#x1f451;专栏内容&#xff1a;Java⛪个人主页&#xff1a;子夜的星的主页&#x1f495;座右铭&#xff1a;前路未远&#xff0c;步履不停 目录 一、Thread的常见构造方法二、Thread 的常见属性三、Thread的常用方法1、start方法2、中断一个线程Ⅰ、通过共享标记Ⅱ、调用in…

大模型基础架构的变革:剖析Transformer的挑战者(下)

上一篇文章中&#xff0c;我们介绍了UniRepLKNet、StripedHyena、PanGu-π等有可能会替代Transformer的模型架构&#xff0c;这一篇文章我们将要介绍另外三个有可能会替代Transformer的模型架构&#xff0c;它们分别是StreamingLLM、SeTformer、Lightning Attention-2&#xff…

蓝桥杯省赛模板构建——uart

打开CubeMX 串口的发送是跟调试器放一起的&#xff0c;通过PA9和PA10来接收发送 选择异步通讯 波特率配置为9600 打开串口中断&#xff0c;因为单片机接收数据需要用到中断 生成代码 添加底层驱动代码 打开在main.h打开uart定义 uart时钟配置&#xff0c;由于uart是用PCLK时钟…

STM32的ADC电压采集

时间记录&#xff1a;2024/2/9 一、ADC相关知识点 &#xff08;1&#xff09;STM32的ADC时钟不要超过14MHz&#xff0c;不然结果的准确率将下降 &#xff08;2&#xff09;ADC分为规则组和注入组&#xff0c;规则组相当于正常运行的程序&#xff0c;注入组相当于中断可以打断…

飞天使-linux操作的一些技巧与知识点8-zabbix6.0 容器搭建

文章目录 安装docker安装步骤mysql下载镜像安装zabbix 使用zabbix非host模式创建 测试效果 安装docker 1. 配置官方 yum 源$ sudo yum install -y yum-utils $ sudo yum-config-manager \--add-repo \https://download.docker.com/linux/centos/docker-ce.repo2. 安装 Docker$ …

CSGO游戏搬砖项目靠谱吗?是不是骗人的

很多地方都在大肆宣扬说CSGO游戏搬砖项目有二三十个点的利润&#xff0c;但我觉得他们看待问题太片面了&#xff0c;没有从全局上去分析这个项目。 这些人为了能割到小白的韭菜真是无所不用其极&#xff0c;什么牛都能吹得出来&#xff01;至少要实事求是吧&#xff0c;这不睁…

应用ANN+SMOTE+Keras Tuner算法进行信用卡交易欺诈侦测

目录 SMOTE&#xff1a; ANN&#xff1a;ANN(MLP) 三种预测-CSDN博客 Keras Tuner&#xff1a;CNN应用Keras Tuner寻找最佳Hidden Layers层数和神经元数量-CSDN博客 数据&#xff1a; 建模&#xff1a; SMOTE Sampling&#xff1a; Keras Tuner&#xff1a; SMOTE&…

【洛谷题解】B2056 求整数的和与均值

题目链接&#xff1a;求整数的和与均值 - 洛谷 题目难度&#xff1a;入门 涉及知识点&#xff1a;求和&#xff0c;平均值 题意&#xff1a; 输入样例&#xff1a; 4 344 222 343 222 输出样例&#xff1a; 1131 282.75000 分析&#xff1a;直接累加&#xff0c;再求平…

【Boost】:http_server模块(六)

http_server模块 一.安装cpp-httplib库二.基本使用服务器 一.安装cpp-httplib库 可以自己写一个http服务器&#xff0c;但比较麻烦&#xff0c;这里直接使用库。 在gitee上搜索cpp-httplib&#xff0c;任意找一个即可&#xff08;建议使用0.7.15版本&#xff09;。例如&#xf…

双重OSPF + OSPF综合实验

一、实验要求 1.R4为ISP&#xff0c;所连接的所有物理接口为公有网段&#xff0c;任意指定IP即可。 2.R1-2-3 构建一个星型结构的MGRE结构&#xff0c;其中R1为中心点&#xff0c;假设R1的公有IP为固定地址。 3.R1-5-6 构建另一个全连网状的MGRE网络&#xff0c;其中R1/5均为中…

STM32输出PWM波控制180°舵机

时间记录&#xff1a;2024/2/8 一、PWM介绍 &#xff08;1&#xff09;脉冲宽度调制 &#xff08;2&#xff09;占空比&#xff1a;高电平时间占整个周期时间的比例 &#xff08;3&#xff09;STM32通过定时器实现PWM时具有两种模式 PWM1模式&#xff1a;向上计数模式下&…

【错误收录】ohpm ERROR: Install failed FetchPackageInfo: @ohos/hypium failed

创建APP的时候出现这样一个错误&#xff0c;是代理没有配置的原因 ohpm.bat install --registry https://repo.harmonyos.com/ohpm/ ohpm WARN: ETIMEDOUT Failed to search for package "ohos/hypium" from "https://repo.harmonyos.com/ohpm/", request…

【初中生讲机器学习】6. 分类算法中常用的模型评价指标有哪些?here!

创建时间&#xff1a;2024-02-07 最后编辑时间&#xff1a;2024-02-09 作者&#xff1a;Geeker_LStar 你好呀~这里是 Geeker_LStar 的人工智能学习专栏&#xff0c;很高兴遇见你~ 我是 Geeker_LStar&#xff0c;一名初三学生&#xff0c;热爱计算机和数学&#xff0c;我们一起加…

【Java IO】同步异步和阻塞非阻塞真正的区别!!!

先上结论&#xff1a; 同步异步和阻塞非阻塞真正的区别&#xff01;&#xff01;&#xff01; 假设某个进程正在运行下面这段代码&#xff1a; ...... operatorA......; read(); operatorB......; operatorC......;当进程执行完operatorA后开始进行read系统调用&#xff0c;…

[word] word2019段落中创建纵横混排的方法图解教程 #知识分享#其他#职场发展

word2019段落中创建纵横混排的方法图解教程 有时候在word文档中需要让文字纵横混排&#xff0c;word2019正好为我们带来了纵横混排的功能了&#xff0c;今天我们就来给大家介绍一下word2019段落中创建纵横混排的方法。 步骤1&#xff1a;打开Word文档&#xff0c;选中需要纵向…

单片机学习笔记---蜂鸣器工作原理

目录 蜂鸣器介绍 蜂鸣器的驱动方式 ULN2003D芯片工作原理 实战预备知识&#xff1a;基础乐理 音名的分组 全音和半音的关系 音高的表示 五线谱中的符号定义 简谱上的符号定义 C调音符与频率对照表 相关计算 蜂鸣器介绍 蜂鸣器是一种将电信号转换为声音信号的器件&a…

【SpringBootStarter】自定义全局加解密组件

【SpringBootStarter】 目的 了解SpringBoot Starter相关概念以及开发流程实现自定义SpringBoot Starter(全局加解密)了解测试流程优化 最终引用的效果&#xff1a; <dependency><groupId>com.xbhog</groupId><artifactId>globalValidation-spring…

2.7日学习打卡----初学RabbitMQ(二)

2.7日学习打卡 JMS 由于MQ产品很多&#xff0c;操作方式各有不同&#xff0c;于是JAVA提供了一套规则 ——JMS&#xff0c;用于操作消息中间件。JMS即Java消息服务 &#xff08;JavaMessage Service&#xff09;应用程序接口&#xff0c;是一个Java平台中关于面 向消息中间件的…

数据结构-->线性表-->单链表

链表的定义 链表&#xff1a;链表是一种物理存储结构上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的。 与顺序表不同的是&#xff0c;链表里的每节都是独立申请下来的空间&#xff0c;我们称之为“节点、结点”。 节点的组成主要由…

横扫Spark之 - 22个常见的转换算子

水善利万物而不争&#xff0c;处众人之所恶&#xff0c;故几于道&#x1f4a6; 文章目录 1. map()2. flatMap()3. filter()4. mapPartitions()5. mapPartitionsWithIndex()6. groupBy()7. distinct()8. coalesce()9. repartition()10. sortBy()11. intersection()12.union()13.…