RabbitMQ系列(9)--RabbitMQ预取值及利用预取值实现不公平分发

news2024/12/24 3:39:00

概念:RabbitMQ的默认分发消息机制是轮询分发,但在消费者之间处理任务速度不同时,这种分发消息机制会导致任务的处理效率低下,处理任务速度快的消费者很大一部分的时间处于空闲状态,速度慢的消费者则一直在干活,所以这种情况下需要改变下RabbitMQ分发消息的策略,实现不公平分发。

1、预取值

预取值指消费者管道的缓冲区大小,即是信道中可以存储未应答消息的最大值

2、利用预取值实现不公平分发

如果设置预取值为1,则表示每个消费者允许未确认的消息最大值为1条,在消费者未完全处理完这1条消息并应答时,由于设置的预取值为1,队列不会再分配新的消息给它处理,那处理快的消费者很快就处理完消息,然后队列根据预取值为1,立马就给快的消费者分配新的1条消息,然后快的消费者可以继续处理下一条,慢的消费者还得继续处理原来未完成的消息,利用这个思路就能实现不公平分发,实现不公平分发需要在消费者端设置basicQos方法的参数为1,即channel.basicQos(1);(不设置时默认为0,即是轮询分发),

Task02代码如下:

注:RabbitMqUtils工具类的实现在我的另一篇文章里,有需要的同学可以查看参考

https://blog.csdn.net/m0_64284147/article/details/129465871

package com.ken.ack;

import com.ken.utils.RabbitMqUtils;
import com.rabbitmq.client.Channel;

import java.util.Scanner;

public class Task02 {

    //队列名称(用于指定往哪个队列接收消息)
    public  static final String QUEUE_NAME = "my_queue";

    public static void main(String[] args) throws Exception{
        Channel channel = RabbitMqUtils.getChannel();
        /**
         * 创建队列
         * 第一个参数:队列名称
         * 第二个参数:服务器重启后队列是否还存在,即队列是否持久化,true为是,false为否,默认false,即消息存储在内存中而不是硬盘中
         * 第三个参数:该队列是否只供一个消费者进行消费,是否进行消息共享,true为只允许一个消费者进行消费,false为允许多个消费者对队列进行消费,默认false
         * 第四个参数:是否自动删除,最后一个消费者断开连接后该队列是否自动删除,true自动删除,false不自动删除
         * 第五个参数:其他参数
         */
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //从控制台读取要发送的信息
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            String message = scanner.next();
            /**
             * 用信道对消息进行发布
             * 第一个参数:发送到哪个交换机
             * 第二个参数:路由的Key值是哪个,本次是队列名
             * 第三个参数:其他参数信息
             * 第四个参数:发送消息的消息体
             */
            channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
            System.out.println("消息发送成功:" + message);
        }
    }

}

Worker03代码如下:

package com.ken.ack;

import com.ken.utils.RabbitMqUtils;
import com.ken.utils.SleepUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

/**
 *  手动应答的第一个消费者
 */
public class Worker03 {

    //队列名称(用于指定往哪个队列接收消息)
    public  static final String QUEUE_NAME = "my_queue";

    //进行接收操作
    public static void main(String[] args) throws Exception{
        //通过工具类获取信道
        Channel channel = RabbitMqUtils.getChannel();

        /**
         * 声明消费者接收消息后的回调方法(由于回调方法DeliverCallback是函数式接口,所以需要给DeliverCallback赋值一个函数,为了方便我们这里使用Lambda表达式进行赋值)
         * 为什么要这样写呢,是因为basicConsume方法里的参数deliverCallback的类型DeliverCallback用 @FunctionalInterface注解规定DeliverCallback是一个函数式接口,所以要往deliverCallback参数传的值要是一个函数
         *
         * 以下是DeliverCallback接口的源代码
         *  @FunctionalInterface
         *  public interface DeliverCallback {
         *      void handle (String consumerTag, Delivery message) throws IOException;
         *  }
         */
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            //沉睡1S,用于模拟业务处理需要1s的时间(处理任务速度快)
            SleepUtils.sleep(1);
            System.out.println("接收的消息:" + new String(message.getBody()));
            /**
             * 手动应答
             *  第一个参数:表示消息的标记Tag(每个消息都有标记Tag)
             *  第二个参数:是否批量应答,true表示批量,false表示不批量
             */
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
        };

        /**
         * 声明消费者取消接收消息后的回调方法(由于回调方法CancelCallback是函数式接口,所以需要给CancelCallback赋值一个函数,为了方便我们这里使用Lambda表达式进行赋值)
         * 为什么要这样写呢,是因为basicConsume方法里的参数cancelCallback的类型CancelCallback用 @FunctionalInterface注解规定CancelCallback是一个函数式接口,所以要往cancelCallback参数传的值要是一个函数
         *
         *  @FunctionalInterface
         *  public interface CancelCallback {
         *      void handle (String consumerTag) throws IOException;
         *  }
         *
         */
        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("取消消费消息:" + consumerTag);
        };

        //设置预取值为1,实现不公平分发
        channel.basicQos(1);

        /**
         * 用信道对消息进行接收(采用手动应答)
         * 第一个参数:消费的是哪一个队列的消息
         * 第二个参数:消费成功后是否要自动应答,true代表自动应当,false代表手动应答
         * 第三个参数:消费者接收消息后的回调方法
         * 第四个参数:消费者取消接收消息后的回调方法(正常接收不调用)
         */
        System.out.println("Work03等待接收消息...");
        channel.basicConsume(QUEUE_NAME,false,deliverCallback,cancelCallback);
    }

}

Worker04代码如下:

package com.ken.ack;

import com.ken.utils.RabbitMqUtils;
import com.ken.utils.SleepUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

/**
 *  手动应答的第二个消费者
 */
public class Worker04 {

    //队列名称(用于指定往哪个队列接收消息)
    public  static final String QUEUE_NAME = "my_queue";

    //进行接收操作
    public static void main(String[] args) throws Exception{
        //通过工具类获取信道
        Channel channel = RabbitMqUtils.getChannel();

        /**
         * 声明消费者接收消息后的回调方法(由于回调方法DeliverCallback是函数式接口,所以需要给DeliverCallback赋值一个函数,为了方便我们这里使用Lambda表达式进行赋值)
         * 为什么要这样写呢,是因为basicConsume方法里的参数deliverCallback的类型DeliverCallback用 @FunctionalInterface注解规定DeliverCallback是一个函数式接口,所以要往deliverCallback参数传的值要是一个函数
         *
         * 以下是DeliverCallback接口的源代码
         *  @FunctionalInterface
         *  public interface DeliverCallback {
         *      void handle (String consumerTag, Delivery message) throws IOException;
         *  }
         */
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            //沉睡30S,用于模拟业务处理需要30s的时间(处理任务速度慢)
            SleepUtils.sleep(30);
            System.out.println("接收的消息:" + new String(message.getBody()));
            /**
             * 手动应答
             *  第一个参数:表示消息的标记Tag(每个消息都有标记Tag)
             *  第二个参数:是否批量应答,true表示批量,false表示不批量
             */
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
        };

        /**
         * 声明消费者取消接收消息后的回调方法(由于回调方法CancelCallback是函数式接口,所以需要给CancelCallback赋值一个函数,为了方便我们这里使用Lambda表达式进行赋值)
         * 为什么要这样写呢,是因为basicConsume方法里的参数cancelCallback的类型CancelCallback用 @FunctionalInterface注解规定CancelCallback是一个函数式接口,所以要往cancelCallback参数传的值要是一个函数
         *
         *  @FunctionalInterface
         *  public interface CancelCallback {
         *      void handle (String consumerTag) throws IOException;
         *  }
         *
         */
        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("取消消费消息:" + consumerTag);
        };

        //设置预取值为1,实现不公平分发
        channel.basicQos(1);

        /**
         * 用信道对消息进行接收(采用手动应答)
         * 第一个参数:消费的是哪一个队列的消息
         * 第二个参数:消费成功后是否要自动应答,true代表自动应当,false代表手动应答
         * 第三个参数:消费者接收消息后的回调方法
         * 第四个参数:消费者取消接收消息后的回调方法(正常接收不调用)
         */
        System.out.println("Work04等待接收消息...");
        channel.basicConsume(QUEUE_NAME,false,deliverCallback,cancelCallback);
    }

}

2、测试分发效果

在上述的代码里,我们把Worker03处理任务的速度设置为了1s,模拟成处理任务速度快的那个消费者,把Worker04处理任务的速度设置为了30s,模拟成处理任务速度慢的那个消费者

(1)把Task02、Worker03、Worker04都启动起来

(2)往队列发送多条消息

(3)观察消费者消费消息的情况

效果图:

Worker03

Worker04

由上述图片的两个消费者处理消息的数量对比得知我们设置的不公平分发策略生效了,Worker03处理任务速度快,从而承担了处理更多任务的责任,而Worker04处理任务的速度慢,从而导致处理任务的条数没有Worker03多

3、利用预取值来实现向指定消费者侧重分发消息

除了上述的不公平分发,我们也可以手动的为不同的消费者设置不同的预取值,从而实现向指定的消费者侧重分发消息

Task02代码如下:

package com.ken.ack;

import com.ken.utils.RabbitMqUtils;
import com.rabbitmq.client.Channel;

import java.util.Scanner;

public class Task02 {

    //队列名称(用于指定往哪个队列接收消息)
    public  static final String QUEUE_NAME = "my_queue";

    public static void main(String[] args) throws Exception{
        Channel channel = RabbitMqUtils.getChannel();
        /**
         * 创建队列
         * 第一个参数:队列名称
         * 第二个参数:服务器重启后队列是否还存在,即队列是否持久化,true为是,false为否,默认false,即消息存储在内存中而不是硬盘中
         * 第三个参数:该队列是否只供一个消费者进行消费,是否进行消息共享,true为只允许一个消费者进行消费,false为允许多个消费者对队列进行消费,默认false
         * 第四个参数:是否自动删除,最后一个消费者断开连接后该队列是否自动删除,true自动删除,false不自动删除
         * 第五个参数:其他参数
         */
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //从控制台读取要发送的信息
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            String message = scanner.next();
            /**
             * 用信道对消息进行发布
             * 第一个参数:发送到哪个交换机
             * 第二个参数:路由的Key值是哪个,本次是队列名
             * 第三个参数:其他参数信息
             * 第四个参数:发送消息的消息体
             */
            channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
            System.out.println("消息发送成功:" + message);
        }
    }

}

Worker03代码如下:

package com.ken.ack;

import com.ken.utils.RabbitMqUtils;
import com.ken.utils.SleepUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

/**
 *  手动应答的第一个消费者
 */
public class Worker03 {

    //队列名称(用于指定往哪个队列接收消息)
    public  static final String QUEUE_NAME = "my_queue";

    //进行接收操作
    public static void main(String[] args) throws Exception{
        //通过工具类获取信道
        Channel channel = RabbitMqUtils.getChannel();

        /**
         * 声明消费者接收消息后的回调方法(由于回调方法DeliverCallback是函数式接口,所以需要给DeliverCallback赋值一个函数,为了方便我们这里使用Lambda表达式进行赋值)
         * 为什么要这样写呢,是因为basicConsume方法里的参数deliverCallback的类型DeliverCallback用 @FunctionalInterface注解规定DeliverCallback是一个函数式接口,所以要往deliverCallback参数传的值要是一个函数
         *
         * 以下是DeliverCallback接口的源代码
         *  @FunctionalInterface
         *  public interface DeliverCallback {
         *      void handle (String consumerTag, Delivery message) throws IOException;
         *  }
         */
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            //沉睡1S,用于模拟业务处理需要1S的时间
            SleepUtils.sleep(1);
            System.out.println("接收的消息:" + new String(message.getBody()));
            /**
             * 手动应答
             *  第一个参数:表示消息的标记Tag(每个消息都有标记Tag)
             *  第二个参数:是否批量应答,true表示批量,false表示不批量
             */
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
        };

        /**
         * 声明消费者取消接收消息后的回调方法(由于回调方法CancelCallback是函数式接口,所以需要给CancelCallback赋值一个函数,为了方便我们这里使用Lambda表达式进行赋值)
         * 为什么要这样写呢,是因为basicConsume方法里的参数cancelCallback的类型CancelCallback用 @FunctionalInterface注解规定CancelCallback是一个函数式接口,所以要往cancelCallback参数传的值要是一个函数
         *
         *  @FunctionalInterface
         *  public interface CancelCallback {
         *      void handle (String consumerTag) throws IOException;
         *  }
         *
         */
        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("取消消费消息:" + consumerTag);
        };

        //设置不公平分发
        channel.basicQos(2);

        /**
         * 用信道对消息进行接收(采用手动应答)
         * 第一个参数:消费的是哪一个队列的消息
         * 第二个参数:消费成功后是否要自动应答,true代表自动应当,false代表手动应答
         * 第三个参数:消费者接收消息后的回调方法
         * 第四个参数:消费者取消接收消息后的回调方法(正常接收不调用)
         */
        System.out.println("Work03等待接收消息...");
        channel.basicConsume(QUEUE_NAME,false,deliverCallback,cancelCallback);
    }

}

Worker04代码如下

package com.ken.ack;

import com.ken.utils.RabbitMqUtils;
import com.ken.utils.SleepUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

/**
 *  手动应答的第二个消费者
 */
public class Worker04 {

    //队列名称(用于指定往哪个队列接收消息)
    public  static final String QUEUE_NAME = "my_queue";

    //进行接收操作
    public static void main(String[] args) throws Exception{
        //通过工具类获取信道
        Channel channel = RabbitMqUtils.getChannel();

        /**
         * 声明消费者接收消息后的回调方法(由于回调方法DeliverCallback是函数式接口,所以需要给DeliverCallback赋值一个函数,为了方便我们这里使用Lambda表达式进行赋值)
         * 为什么要这样写呢,是因为basicConsume方法里的参数deliverCallback的类型DeliverCallback用 @FunctionalInterface注解规定DeliverCallback是一个函数式接口,所以要往deliverCallback参数传的值要是一个函数
         *
         * 以下是DeliverCallback接口的源代码
         *  @FunctionalInterface
         *  public interface DeliverCallback {
         *      void handle (String consumerTag, Delivery message) throws IOException;
         *  }
         */
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            //沉睡30S,用于模拟业务处理需要30S的时间
            SleepUtils.sleep(30);
            System.out.println("接收的消息:" + new String(message.getBody()));
            /**
             * 手动应答
             *  第一个参数:表示消息的标记Tag(每个消息都有标记Tag)
             *  第二个参数:是否批量应答,true表示批量,false表示不批量
             */
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
        };

        /**
         * 声明消费者取消接收消息后的回调方法(由于回调方法CancelCallback是函数式接口,所以需要给CancelCallback赋值一个函数,为了方便我们这里使用Lambda表达式进行赋值)
         * 为什么要这样写呢,是因为basicConsume方法里的参数cancelCallback的类型CancelCallback用 @FunctionalInterface注解规定CancelCallback是一个函数式接口,所以要往cancelCallback参数传的值要是一个函数
         *
         *  @FunctionalInterface
         *  public interface CancelCallback {
         *      void handle (String consumerTag) throws IOException;
         *  }
         *
         */
        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("取消消费消息:" + consumerTag);
        };

        //设置不公平分发
        channel.basicQos(5);

        /**
         * 用信道对消息进行接收(采用手动应答)
         * 第一个参数:消费的是哪一个队列的消息
         * 第二个参数:消费成功后是否要自动应答,true代表自动应当,false代表手动应答
         * 第三个参数:消费者接收消息后的回调方法
         * 第四个参数:消费者取消接收消息后的回调方法(正常接收不调用)
         */
        System.out.println("Work04等待接收消息...");
        channel.basicConsume(QUEUE_NAME,false,deliverCallback,cancelCallback);
    }

}

4、测试分发效果

在上述的代码里,我们把Worker03处理任务的速度设置为了1s,模拟成处理任务速度快的那个消费者,把Worker04处理任务的速度设置为了30s,模拟成处理任务速度慢的那个消费者

(1)把Task02、Worker03、Worker04都启动起来

(2)快速的往队列发送多条消息(注意要快速,不然就被Worker03消费完了,看不出效果)

(3)查看RabbitMQ的管理页面,可以看到我们发送了这么多条消息,最大的消费消息上限是我们设置的5条

也可以在Channels里查看,可以分别看到我们为消费者设置的预取值

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

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

相关文章

SpringBoot教学资料6-SpringBoot登录注册功能实现(带简单前端)

项目样式: SQL: CREATE TABLE t_user (id int(11) NOT NULL AUTO_INCREMENT,username varchar(32) NOT NULL,password varchar(32) NOT NULL,PRIMARY KEY (id),UNIQUE KEY username (username) ) ENGINEInnoDB AUTO_INCREMENT5 DEFAULT CHARSETutf8项目结构&#xf…

数据结构(排序)

文章目录 一、排序的概念二、插入排序1. 基本思想2. 直接插入排序3. 希尔排序(缩小增量排序) 三、选择排序1. 基本思想2. 直接选择排序3. 堆排序 四、交换排序1. 基本思想2. 冒泡排序3. 快速排序 五、归并排序六、其他排序6.1 计数排序6.2 基数排序6.3 桶排序 一、排序的概念 …

记一次 .NET 某医院预约平台 非托管泄露分析

一:背景 1. 讲故事 前几天有位朋友找到我,说他的程序有内存泄露,让我帮忙排查一下,截图如下: 说实话看到 32bit, 1.5G 这些关键词之后,职业敏感告诉我,他这个可能是虚拟地址紧张所…

Docker快速部署Hadoop环境

Docker安装部署Hadoop环境,通过三个容器来模拟三个节点,最后只保留Master节点实现搭建。 安装环境 Ubuntu 22.04.1 LTS 和Docker 23.0.1 安装过程 拉取镜像 docker pull registry.cn-hangzhou.aliyuncs.com/hadoop_test/hadoop_base在Docker中创建网…

供应链管理系统有哪些模块?

先弄搞清楚:供应链管理的概念与定义 供应链管理(Supply Chain Management ,简称SCM):就是指在满足一定的客户服务水平的条件下,为了使整个供应链系统成本达到最小而把供应商、制造商、仓库、配送中心和渠道商等有效地组织在一起来进行的产品…

Kubernetes(k8s)容器编排Pod调度策略

目录 1 节点调度1.1 创建资源清单1.2 应用部署1.3 删除pod 2 定向调度(标签调度)2.1 创建标签2.1.1 添加标签2.1.2 显示标签 2.3 创建资源清单2.4 应用部署2.5 删除pod 1 节点调度 ​ 一般而言pod的调度都是通过RC、Deployment等控制器自动完成,但是仍可以通过手动配…

自然语言处理的分词与词嵌入

1 分词 1.1 什么是分词 分词是把自然语言语句进行数字化的过程。 1.2 为什么要分词 自然语言是字符串序列,机器没办法直接处理, 需要处理成数字的形式。 1.3 如何进行分词 以英文为例: 1 按空格划分 这应该是最简单也最直观的做法了。这…

Vue3setup的参数说明

setup的两个参数 setup包含两个参数&#xff0c;一个为props、一个为context &#xff08;均为形参&#xff09; props&#xff1a;值为对象&#xff0c;包含&#xff1a;组件外部传递过来&#xff0c;且组件内部声明接收了的属性。context&#xff1a;上下文对象 <scrip…

26-DOM常见的操作(了解)

一、DOM &#x1f37f;&#x1f37f;&#x1f37f;文档对象模型 (DOM) 是 HTML 和 XML 文档的编程接口 它提供了对文档的结构化的表述&#xff0c;并定义了一种方式可以使从程序中对该结构进行访问&#xff0c;从而改变文档的结构&#xff0c;样式和内容 例如&#xff1a;随着…

2023再更新下百度蜘蛛最新UA(User Agent)

其实百度蜘蛛的UA一直没什么变化&#xff0c;有不少朋友以为百度蜘蛛修改特征了&#xff0c;我查了下日志&#xff0c;把最新的UA整理出来给大家。 百度UA信息&#xff1a; Mozilla/5.0 (compatible; Baiduspider/2.0; http://www.baidu.com/search/spider.html) 神码ai在了…

LVS负载均衡群集——DR直接路由模式

一.LVS数据包流向分析 1.数据包流向 &#xff08;1&#xff09;客户端发送请求到 Director Server&#xff08;负载均衡器&#xff09;&#xff0c;请求的数据报文&#xff08;源 IP 是 CIP,目标 IP 是 VIP&#xff09;到达内核空间。 &#xff08;2&#xff09;Director Ser…

Spring Bean 的生命周期快速记忆

引言 “请你描述下 Spring Bean 的生命周期&#xff1f;”&#xff0c;这是面试官考察 Spring 的常用问题&#xff0c;可见是 Spring 中很重要的知识点。 我之前在准备面试时&#xff0c;去网上搜过答案&#xff0c;大多以下图给出的流程作为答案。 如何记忆 Spring Bean 的…

电影《消失的她》观后感

上周看了电影《消失的她》&#xff0c;也许是和朋友一起看的原因吧&#xff0c;这次电影的体验感觉比以往更好&#xff0c;这或许就是共同经历的缘故&#xff0c;同时看完电影&#xff0c;还可以大家一起讨论下。本部电影讲述一个富商国外旅游&#xff0c;女友莫名消失&#xf…

LVS负载均衡群集博客

文章目录 LVS负载均衡群集一.什么是集群1.群集的含义 二.集群使用在那个场景三.集群的分类1.负载均衡器群集2.高可用群集3.高性能运算群集 四.负载集群的架构1.第一层&#xff0c;负载调度器2.第二层&#xff0c;服务器池3.第三层&#xff0c;共享存储 五.负载均衡集群的工作模…

人工智能在航天领域中有哪些应用?

随着科技的不断进步&#xff0c;人工智能已经成为各个领域中的重要驱动力。在航天领域中&#xff0c;人工智能的应用正日益展现出巨大的潜力。航天领域对精确性、自动化和高效性的需求&#xff0c;使得人工智能成为实现这些目标的关键技术之一。人工智能正在以其独特的优势和算…

使用MQL4编写自己的交易策略:技巧与经验分享

随着技术的发展&#xff0c;越来越多的投资者开始使用程序化交易系统进行交易&#xff0c;其中MQL4语言是广泛应用于MetaTrader 4平台上编写交易策略的一种语言。本文将分享一些技巧和经验&#xff0c;帮助读者利用MQL4编写自己的交易策略。 策略开发流程 首先&#xff0c;我…

如何用ChatGPT写Shell脚本

因为最近下班前都要拿机子搞压测&#xff0c;所以这段时间对shell脚本比较感兴趣&#xff0c;用chatGPT写shell脚本很方便。 如下是一些案列 比如我需要写一个批处理&#xff1a;写一个批处理在当前文件夹下建立20个文件夹每个文件夹里面有一个文本文档文本文档的第一句话是hel…

实现分类标签展示的魔力——gradio库中的Label模块

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

第40节:cesium 温度场效果(含源码+视频)

结果示例: 完整源码: <template><div class="viewer"><vc-viewer @ready="ready" :logo="false"><!

JAVA POI 图片插入excel保存导出,可多图,多种插入样式

JAVA POI 图片插入excel保存导出,可多图,多种插入样式 JAVA POI 图片插入excel保存导出,可多图,多种插入样式 import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.util.Date;i…