Java 延迟消息

news2024/11/14 11:55:31

场景

6S后执行任务

7天后发送订单

从现有时间算延后多少时间开始执行,当然也可以转换为在以后某个时间执行。

Timer类

Java中的Timer类是一个定时器,它可以用来实现延时消息的功能。

import java.util.Timer;
import java.util.TimerTask;


public class TimerDemo {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("Hello, world!");
            }
        }, 5000);
   }
}

Timer类的使用非常简单,但是它存在一些问题。首先,Timer类是单线程的,如果有多个任务需要执行,它们会被放到同一个队列中,按照先后顺序依次执行。如果某个任务的执行时间过长,会影响后续任务的执行。其次,Timer类不够灵活,无法满足一些复杂的需求。

ScheduledExecutorService 周期线程池

Java中的ScheduledExecutorService接口是一个可调度的线程池,它可以用来实现延时消息的功能。

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;


public class ScheduledExecutorServiceDemo {
    public static void main(String[] args) {
        ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
        executor.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello, world!");
            }
        }, 5, TimeUnit.SECONDS);
    }
}

与Timer类相比,ScheduledExecutorService接口更加灵活。它可以支持多个任务同时执行,可以设置任务的执行周期,可以设置任务的执行优先级等等。但是,它也存在一些问题。比如,如果任务的执行时间过长,会影响后续任务的执行,因为它也是单线程的。

Quartz 定时任务

POM:

<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>***</version>
</dependency>

示例:

JobDetail job = newJob(SimpleJob.class).withIdentity("job1", "group1").build();
SimpleTrigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(runDate)
        .withSchedule(simpleSchedule().withIntervalInHours(1).repeatForever()).modifiedByCalendar("holidays").build();

sched.scheduleJob(job, trigger);

DelayQueue 延时队列

DelayQueue的定义

public class DelayQueue<E extends Delayed> extends AbstractQueue<E> implements BlockingQueue<E>

DelayQueue是一个无界的BlockingQueue,是线程安全的(无界指的是队列的元素数量不存在上限,队列的容量会随着元素数量的增加而扩容,阻塞队列指的是当队列内元素数量为0的时候,试图从队列内获取元素的线程将被阻塞或者抛出异常)

以上是阻塞队列的特点,而延迟队列还拥有自己如下的特点:

DelayQueue中存入的必须是实现了Delayed接口的对象(Delayed定义了一个getDelay的方法,用来判断排序后的元素是否可以从Queue中取出,并且Delayed接口还继承了Comparable用于排序),插入Queue中的数据根据compareTo方法进行排序(DelayQueue的底层存储是一个PriorityQueue,PriorityQueue是一个可排序的Queue,其中的元素必须实现Comparable接口的compareTo方法),并通过getDelay方法返回的时间确定元素是否可以出队,只有小于等于0的元素(即延迟到期的元素)才能够被取出

延迟队列不接收null元素

代码

import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Data
public class OrderDelayTask implements Delayed {

    private Long orderId;

    private long delayTime;

    public OrderDelayTask(Long orderId, long delayTime) {
        this.orderId = orderId;
        // 延迟时间加当前时间
        this.delayTime = System.currentTimeMillis() + delayTime;
    }

    // 获取任务剩余时间
    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(delayTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }

    @Override
    public int compareTo(Delayed o) {
        return Long.compare(delayTime, ((OrderDelayTask) o).delayTime);
    }

    public static void main(String[] args) {
        DelayQueue<OrderDelayTask> orderDelayQueue = new DelayQueue<>();
        //发起订单下单的时候将订单演示对象放入orderDelayQueue
        orderDelayQueue.add(
            new OrderDelayTask(
                123456l, // 订单id
                3 * 1000 // 延迟时间:3s
            )
        );

        new Thread(() -> {
            try {
                while (true) {
                    OrderDelayTask task = orderDelayQueue.take();
                    // 当队列为null的时候,poll()方法会直接返回null, 不会抛出异常,但是take()方法会一直等待,因此会抛出一个InterruptedException类型的异常。(当阻塞方法收到中断请求的时候就会抛出InterruptedException异常)
                    Long orderId = task.getOrderId();
                    // 执行业务
                    System.out.println(orderId);
                }
            } catch (InterruptedException e) {
                // 因为是重写Runnable接口的run方法,子类抛出的异常要小于等于父类的异常。而在Runnable中run方法是没有抛异常的。所以此时是不能抛出InterruptedException异常。如果此时你只是记录日志的话,那么就是一个不负责任的做法,因为在捕获InterruptedException异常的时候自动的将是否请求中断标志置为了false。在捕获了InterruptedException异常之后,如果你什么也不想做,那么就将标志重新置为true,以便栈中更高层的代码能知道中断,并且对中断作出响应。
                Thread.currentThread().interrupt();
            }
        }).start();
    }
}

时间轮

原理

时间轮算法可以类比于时钟,如上图箭头(指针)按某一个方向按固定频率轮动,每一次跳动称为一个 tick。

这样可以看出定时轮由个3个重要的属性参数,ticksPerWheel(一轮的tick数),tickDuration(一个tick的持续时间)以及 timeUnit(时间单位)

例如当ticksPerWheel=60,tickDuration=1,timeUnit=秒,这就和现实中的始终的秒针走动完全类似了。

如果当前指针指在1上面,我有一个任务需要4秒以后执行,那么这个执行的线程回调或者消息将会被放在5上。

那如果需要在20秒之后执行怎么办,由于这个环形结构槽数只到8,如果要20秒,指针需要多转2圈。位置是在2圈之后的5上面(20 % 8 + 1)

实现:NettyHashedWheelTimer

import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import io.netty.util.Timer;
import io.netty.util.TimerTask;
import java.util.concurrent.TimeUnit;

public class test1 {
    static class MyTimerTask implements TimerTask {
        boolean flag;
        public MyTimerTask(boolean flag) {
            this.flag = flag;
        }
        public void run(Timeout timeout) throws Exception {
            System.out.println("要去数据库删除订单了。。。。");
            this.flag = false;
        }
    }

    public static void main(String[] argv) {
        MyTimerTask timerTask = new MyTimerTask(true);
        Timer timer = new HashedWheelTimer();
        //此处设置在时间轮第几个执行(本代码设置为第3格)
        timer.newTimeout(timerTask, 3, TimeUnit.SECONDS);

        int i = 1;
        while (timerTask.flag) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(i +"秒过去了");
            i++;
        }
    }
}

自定义实现

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class DelayExecUtil {

    private static final int CORE_POOL_SIZE = 2;

    private static final int MAX_POOL_SIZE = 20;

    private static final int QUEUE_CAPACITY = 1000;

    private static final long KEEP_ALIVE_TIME = 60;

    private static final BlockingQueue<Runnable> DELAY_TASK_QUEUE = new ArrayBlockingQueue<>(QUEUE_CAPACITY);

    private static final int LENGTH = 3600;

    private static final Set<DelayTaskContext>[] TIMERS = new Set[LENGTH];

    private static Set<DelayTaskContext> currentDelayTaskSet;

    private static List<DelayTaskContext> executeDelayTaskList = new ArrayList<>();

    private static final long DELAY = 3 * 1000l;

    private static final long PERIOD = 1 * 1000l;

    private static int currentTimeIndex = 0;

    private static ThreadPoolExecutor executor;

    // 这种场景应该写少读多的场景
    private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    private static void indexIncrease() {
        currentTimeIndex = (currentTimeIndex + 1) % LENGTH;
    }

    private static class DelayTaskContext {
        public int cycle;

        public DelayTask delayTask;
    }

    public interface DelayTask {
        void execute();
    }

    private static final Timer TIMER = new Timer("delay-time-main");

    static {
        // 创建执行线程
        executor = new ThreadPoolExecutor(CORE_POOL_SIZE
            , MAX_POOL_SIZE
            , KEEP_ALIVE_TIME
            , TimeUnit.SECONDS
            , DELAY_TASK_QUEUE
            , r -> new Thread(r, "delay-time-handle")
            // 当线程池已达到maxPoolSize之后,不在新线程中执行任务,而是由调用者所在线程来执行,即异步变同步
            , new ThreadPoolExecutor.CallerRunsPolicy());

        // 启动定时器
        TIMER.schedule(new TimerTask() {
            @Override
            public void run() {
                try {
                    lock.readLock().lock();
                    indexIncrease();
                    currentDelayTaskSet = TIMERS[currentTimeIndex];
                    log.info("index:" + currentTimeIndex);
                    if (currentDelayTaskSet != null && currentDelayTaskSet.size() > 0) {
                        executeDelayTaskList = new ArrayList<>();
                        for (DelayTaskContext task : currentDelayTaskSet) {
                            if (task.cycle == 0) {
                                executeDelayTaskList.add(task);
                            }
                            if (task.cycle > 0) {
                                task.cycle--;
                            }
                        }

                        // 清理可执行任务
                        if (executeDelayTaskList.size() > 0) {
                            currentDelayTaskSet.removeAll(executeDelayTaskList);

                            for (DelayTaskContext taskContext : executeDelayTaskList) {
                                executor.execute(() -> {
                                    taskContext.delayTask.execute();
                                });
                            }
                            executeDelayTaskList.clear();
                        }
                    }
                } finally {
                    lock.readLock().unlock();
                }
            }
        }, DELAY, PERIOD);
    }

    /**
     * 添加延迟任务
     *
     * @param delayTask 任务对象
     * @param delay     延迟多少s 会给3s的延迟
     */
    public static void addDelayTask(DelayTask delayTask, int delay) {
        if (delay < 0) {
            throw new IllegalArgumentException("Negative delay.");
        }
        if (delayTask == null) {
            throw new IllegalArgumentException("Empty task.");
        }

        try {
            lock.writeLock().lock();

            int cycle = delay / LENGTH;
            int remainder = delay % LENGTH;

            remainder = remainder + currentTimeIndex + 1;
            if (TIMERS[remainder] == null) {
                TIMERS[remainder] = new HashSet<>();
            }

            DelayTaskContext task = new DelayTaskContext();
            task.cycle = cycle;
            task.delayTask = delayTask;
            log.info("add task cycle:{},remainder:{},delay:{}", cycle, remainder, delay);
            TIMERS[remainder].add(task);

        } finally {
            lock.writeLock().unlock();
        }

    }

    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(1000 * 5);
        DelayExecUtil.addDelayTask(() -> {
            log.info("task execute");
        }, 3);
    }
}

RocketMQ延迟消息

参考:https://help.aliyun.com/zh/apsaramq-for-rocketmq/cloud-message-queue-rocketmq-4-x-series/developer-reference/send-and-receive-scheduled-messages-and-delayed-messages

import java.util.Date;

import org.apache.rocketmq.acl.common.AclClientRPCHook;
import org.apache.rocketmq.acl.common.SessionCredentials;
import org.apache.rocketmq.client.AccessChannel;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.RPCHook;
import org.apache.rocketmq.remoting.common.RemotingHelper;

public class RocketMQProducer {
    /**
     * 替换为您阿里云账号的AccessKey ID和AccessKey Secret。
     * 请确保环境变量ALIBABA_CLOUD_ACCESS_KEY_ID和ALIBABA_CLOUD_ACCESS_KEY_SECRET已设置。
     */
    private static RPCHook getAclRPCHook() {
        return new AclClientRPCHook(new SessionCredentials(System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID"),
            System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")));
    }

    public static void main(String[] args) throws MQClientException {
        /**
         *创建Producer,并开启消息轨迹。设置为您在阿里云消息队列RocketMQ版控制台创建的Group ID。
         *如果不想开启消息轨迹,可以按照如下方式创建:
         *DefaultMQProducer producer = new DefaultMQProducer("YOUR GROUP ID", getAclRPCHook());
         */
        DefaultMQProducer producer = new DefaultMQProducer("YOUR GROUP ID", getAclRPCHook(), true, null);
        /**
         *设置使用接入方式为阿里云,在使用云上消息轨迹的时候,需要设置此项,如果不开启消息轨迹功能,则运行不设置此项。
         */
        producer.setAccessChannel(AccessChannel.CLOUD);
        /**
         *设置为您从阿里云消息队列RocketMQ版控制台获取的接入点信息,类似“http://MQ_INST_XXXX.aliyuncs.com:80”。
         */
        producer.setNamesrvAddr("YOUR ACCESS POINT");
        producer.start();

        for (int i = 0; i < 128; i++) {
            try {
                /*设置为您在消息队列RocketMQ版控制台创建的Topic。*/
                Message msg = new Message("YOUR TOPIC",
                    /*设置消息的Tag。*/
                    "YOUR MESSAGE TAG",
                    /*消息内容。*/
                    "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET));
                /*发送延时消息,需要设置延时时间,单位毫秒(ms),消息将在指定延时时间后投递,例如消息将在3秒后投递。*/
                longdelayTime = System.currentTimeMillis() + 3000;
                msg.putUserProperty("__STARTDELIVERTIME", String.valueOf(delayTime));

                /**
                 *若需要发送定时消息,则需要设置定时时间,消息将在指定时间进行投递,例如消息将在2021-08-10 18:45:00投递。
                 *定时时间格式为:yyyy-MM-dd HH:mm:ss,若设置的时间戳在当前时间之前,则消息将被立即投递给Consumer。
                 * longtimeStamp=newSimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2021-08-10 18:45:00").getTime();
                 * msg.putUserProperty("__STARTDELIVERTIME",String.valueOf(timeStamp));
                 */
                SendResult sendResult = producer.send(msg);
                System.out.printf("%s%n", sendResult);
            } catch (Exception e) {
                //消息发送失败,需要进行重试处理,可重新发送这条消息或持久化这条数据进行补偿处理。
                System.out.println(new Date() + " Send mq message failed.");
                e.printStackTrace();
            }
        }

        //在应用退出前,销毁Producer对象。
        //注意:如果不销毁也没有问题。
        producer.shutdown();
    }
}

Redis实现

zset

利用redis的zset,zset是一个有序集合,每一个元素(member)都关联了一个score,通过score排序来取集合中的值

Keyspace Notifications键空间机制

使用redis的Keyspace Notifications(键空间机制),就是利用该机制可以在key失效之后,提供一个回调,实际上是redis会给客户端发送一个消息。

参考博客

Java延时消息的实现

https://blog.csdn.net/jam_yin/article/details/131001180

1分钟实现“延迟消息”功能

https://www.w3cschool.cn/architectroad/architectroad-delay-message.html

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

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

相关文章

uniapp微信小程序本地和真机调试文件图片上传成功但体验版不成功

文章目录 导文是因为要添加服务器域名&#xff01; 导文 uniapp微信小程序本地和真机调试文件图片上传成功但体验版不成功 uniapp微信小程序体验版上传图片不成功 微信小程序本地和真机调试文件图片上传成功但体验版不成功 是因为要添加服务器域名&#xff01; 先看一下 你小程…

android13 第三方桌面不能使用后台历史任务问题 任务键功能失效问题

总纲 android13 rom 开发总纲说明 目录 1.前言 2.复现现象 3.问题分析 4.解决方法 5.编译运行 6.彩蛋 1.前言 随着Android 13操作系统的发布,用户现在可以更加自由地选择和使用第三方Launcher来定制自己的设备。本文将介绍在Android 13上安装和使用第三方Launcher导致…

工信部哪些证书可以考,含金量高吗

随着科技的快速发展和行业的不断变化&#xff0c;市场对人才的需求也在不断更新。技能提升可以帮助个人适应这些变化&#xff0c;满足新的岗位要求。同时学习新技能可以拓宽思维&#xff0c;激发创新意识&#xff0c;帮助我们在工作中找到新的解决方案。 泰迪智能科技专注…

楼宇智能化仿真实训室解决方案

在信息技术的浪潮中&#xff0c;智慧城市作为未来城市发展的新形态&#xff0c;正以前所未有的速度在全球范围内兴起。其中&#xff0c;楼宇智能化作为智慧城市的关键构成&#xff0c;扮演着举足轻重的角色。它不仅提升了建筑的能源效率、安全性与舒适度&#xff0c;还促进了城…

SQL Server 端口配置

目录 默认端口 更改端口 示例&#xff1a;更改 TCP 端口 示例&#xff1a;验证端口设置 远程连接测试 示例&#xff1a;使用 telnet 测试连接 配置防火墙 示例&#xff1a;Windows 防火墙设置 远程连接测试 示例&#xff1a;使用 telnet 测试连接 默认端口 TCP/IP: …

【Github】Github 上commit后 contribution 绿格子不显示 | Github绿格子 | Github贡献度不显示

一、Github 消失的绿点 1、贡献值为什么没了&#xff1f; 2、选择要显示的贡献 如下配置 二、如何解决消失的绿点&#xff1f; 1、添加邮箱 确保邮箱的设置必须选择一个邮箱邮箱 2、git config 添加邮箱 设置邮箱如下&#xff1a; git config --local user.email 316434776…

Tomcat IntelliJ IDEA整合

一、下载及安装Tomcat 下载官网&#xff1a;Apache Tomcat - Welcome! 1.点击红色框中的任意一个版本 2.点击下载 3.解压后放在任意路径&#xff08;我的是放在D盘&#xff09; 4.在bin目录下找到startup.bat&#xff0c;点击启动Tomcat 5.如果双击启动后&#xff0c;终端出…

NC 缺失的第一个正整数

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 给定一个无重…

无人机之交通管理篇

无人机技术已经渗透到社会的各个领域&#xff0c;其中交通监控与管理便是其应用的重要方向之一。无人机凭借其独特的优势&#xff0c;如高效性、灵活性、实时性等&#xff0c;为交通监控与管理带来了革命性的变革。 一、无人机在交通监控中的应用 1、实时监控与数据采集 无人…

cdga|数据治理策略:击破壁垒,迈向纵向一体化的新纪元

在当今这个数据驱动的时代&#xff0c;企业数据的价值日益凸显&#xff0c;它不仅是企业决策的重要依据&#xff0c;更是推动业务创新、优化运营流程、增强市场竞争力的关键要素。然而&#xff0c;随着数据量的爆炸性增长和数据来源的多样化&#xff0c;企业面临着数据孤岛、数…

时常在面试中被问到的JVM问题

文章目录 JVM 和 JDK、JRE 有什么区别&#xff1f;JVM 是如何工作的&#xff1f;JVM 主要组件JVM 执行流程JVM 的工作示例 说一下类加载机制类加载器&#xff08;Class Loader&#xff09;示例 什么是双亲委派模型&#xff1f;&#xff08;Parent Delegation Model&#xff09;…

时间同步的原理

1.问题来源&#xff1a; 设备A想要给设备B同步时间&#xff0c;最直接的办法&#xff0c;A发送当前时间到B&#xff0c;但这个问题会带来一些问题。 1.1 例子&#xff1a; 设备A&#xff08;后面叫Master设备&#xff09;现在拥有准确时间9点整, 设备B&#xff08;后面叫Sla…

TL-SEJ 方法:有效对抗语音伪造攻击

关键词&#xff1a;语音增强、迁移学习、模型鲁棒性、U-Net模型 随着人工智能技术的快速发展&#xff0c;基于深度学习的语音转换&#xff08;Voice Conversion, VC&#xff09;和文本到语音&#xff08;Text-to-Speech, TTS&#xff09;技术取得了显著的进步。这些语音合成技术…

性价比最高的蓝牙耳机有哪些推荐?四款高性价比蓝牙耳机盘点

目前蓝牙耳机已成为我们日常出行、工作乃至休闲娱乐的必备伴侣&#xff0c;它们不仅让我们摆脱了线缆的束缚&#xff0c;更以卓越的音质、高效的降噪能力和舒适的佩戴体验&#xff0c;极大地提升了我们的听觉享受&#xff0c;不过市面上耳机众多&#xff0c;性价比最高的蓝牙耳…

巴黎奥运会 为啥这么抠?

文&#xff5c;琥珀食酒社 作者 | 朱珀 你是不是挺无语的 这奥运会还没有开始呢 吐槽大会就停不下来了 接近40度的高温 公寓没有空调 奥运巴士也没空调 连郭晶晶老公霍启刚 这种见惯大场面的也破防了 你可能会问 好不容易搞个奥运会 干嘛还要抠抠搜搜的呀 在咱们看…

AI定制招聘策略:企业、候选人与市场三者融合之道

一、引言 在数字化时代&#xff0c;人工智能&#xff08;AI&#xff09;已成为企业招聘的重要工具&#xff0c;能够根据企业需求、候选人特征和市场趋势制定个性化招聘策略。本文旨在探讨AI在招聘过程中的应用&#xff0c;分析它如何精准匹配企业与候选人&#xff0c;从而提高招…

根据空域图信息构造飞机航线图以及飞行轨迹模拟matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 4.1 航路网络建模 4.2 航线图构建 4.3 飞行轨迹模拟的具体步骤 5.完整程序 1.程序功能描述 空域图是指航空领域中的一种图形表示方式&#xff0c;它涵盖了空中交通管理所需要的各种信息&a…

Studying-代码随想录训练营day48| 739. 每日温度、496.下一个更大元素 I、503.下一个更大元素II

第48天&#xff0c;单调栈part01&#xff0c;栈的特殊应用场所&#xff01;编程语言&#xff1a;C 目录 739. 每日温度 496.下一个更大元素 I 503.下一个更大元素II 总结&#xff1a; 739. 每日温度 文档讲解&#xff1a;代码随想录每日温度 视频讲解&#xff1a;手撕每日…

龙迅LT8711GX 高性能Type-C/DP1.4/EDP转HDMI2.1转换器,内置MCU,支持8K30HZ

龙迅LT8711GX描述&#xff1a; LT8711GX是一个高性能的Type-C/DP1.4a到HDMI2.1转换器&#xff0c;设计用于连接一个USB Type-C源或一个DP1.4a源到一个HDMI2.1接收器。LT8711GX集成了一个与DP1.4a兼容的接收器&#xff0c;和一个与HDMI2.1兼容的发射机。此外&#xff0c;还包括…

【Kubernetes】二进制部署k8s集群(中)之cni网络插件flannel和calico

&#xff01;&#xff01;&#xff01;继续上一篇实验部署&#xff01;&#xff01;&#xff01; 目录 一.k8s的三种网络模式 1.Pod 内容器与容器之间的通信 2.同一个 Node 内 Pod 之间的通信 3.不同 Node 上 Pod 之间的通信 二.k8s的三种接口 三.Flannel 网络插件 1.U…