当前位置: 首页 > news >正文

RocketMQ实现延迟队列精确到秒级实现

前言篇:

为了节约成本,决定通过自研来改造rocketmq,添加任意时间延迟的延时队列,开源版本的rocketmq只有支持18个等级的延迟时间,

其实对于大部分的功能是够用了的,但是以前的项目,全部都是使用了阿里云的rocketmq,原因是不同的供应商的订单的延时时间是不同的

(部分供应商的订单未支付30分钟取消,有些1个半小时取消,各种时间都有),

所以使用了大量的延时队列,但是开源版本不支持任意时间延时(希望官方支持这个功能)

为了实现这个功能,网上查询了不少资料,查询到不少相关的文章,主要实现都是基于时间轮来实现的,

但是比较少开源的代码实现(也许大家都没有这个需求吧)

debug实践篇:

1. 撸起袖子加油干,首先,下载源代码 https://github.com/apache/rocketmq.git,导入ide

运行mvn package 生成jar包,如果成功的话,会生成到distribution目录下面

2. 查看文档,发现要运行namesvr 和 broker

找到 src\main\java\org\apache\rocketmq\namesrv\NamesrvStartup.java ,开心的执行main方法,

哦哦哦哦哦,果然报错了,提示 rocketmq.home.dir 目录不存在,查看源码, 原来是从system.propeties读取的,

为了调试,我毫不犹豫的加上了配置文件,

再次运行,不报错了,控制台显示,成功啦( 生活是多么美好,空气是多么清晰! )

3.运行 broker ,打开 src\main\java\org\apache\rocketmq\broker\BrokerStartup.java,执行main方法,

添加 配置文件 ( D:\\mq\\rocketmq-rocketmq-all-4.9.2是我本地的路径,你要修改成自己的 )

1 System.setProperty("rocketmq.home.dir", "D:\\mq\\rocketmq-rocketmq-all-4.9.2\\rb");2 System.setProperty("user.home", "D:\\mq\\rocketmq-rocketmq-all-4.9.2\\rb\\home\\");

运行一下,成功了,开心的发一条消息,试试,哦哦哦哦哦。 发不出去哦( 人生最痛苦的事情是,快要成功了,却没有成功 ) 。

原来还要配置namesvr地址,在启动命令,添加 -n localhost:9876 ( 上面的namesvr 启动的ip和端口)

4.漫长的改造之路 (我们是勇敢的斯巴达勇士,一直勇往直前)

用了阿里云的延时队列,发现它的message 可以传一个时间过来(任意的延时时间)

来来来,我们复制一下( 不要告诉别人,我们一直是复制,粘贴的,没有原创, 嘘 ...... )

1/** 2      * 该类预定义一些系统键. 3      */4     static public class SystemPropKey {5         public static final String TAG = "__TAG";6         public static final String KEY = "__KEY";7         public static final String MSGID = "__MSGID";8         public static final String SHARDINGKEY = "__SHARDINGKEY";9         public static final String RECONSUMETIMES = "__RECONSUMETIMES";10         public static final String BORNTIMESTAMP = "__BORNTIMESTAMP";11         public static final String BORNHOST = "__BORNHOST";12/**13          * 设置消息的定时投递时间(绝对时间). <p>例1: 延迟投递, 延迟3s投递, 设置为: System.currentTimeMillis() + 3000; <p>例2: 定时投递,14          * 2016-02-01 11:30:00投递, 设置为: new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2016-02-0115          * 11:30:00").getTime()16          */17         public static final String STARTDELIVERTIME = "__STARTDELIVERTIME";18     }
/**     * <p> 设置消息的定时投递时间(绝对时间),最大延迟时间为7天. </p> <ol> <li>延迟投递: 延迟3s投递, 设置为: System.currentTimeMillis() + 3000;</li>     * <li>定时投递: 2016-02-01 11:30:00投递, 设置为: new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2016-02-01     * 11:30:00").getTime()</li> </ol>     */    public void setStartDeliverTime(final long value) {        putSystemProperties(SystemPropKey.STARTDELIVERTIME, String.valueOf(value));    }

5.既然要改造rocketmq,在哪里改呢,debug,debug,debug(一直到天荒地老),功夫不负有心人,找到啦,

找到 \src\main\java\org\apache\rocketmq\broker\processor\SendMessageProcessor.java, 发现

public RemotingCommand processRequest(ChannelHandlerContext ctx,                                          RemotingCommand request) throws RemotingCommandException {        SendMessageContext mqtraceContext;switch (request.getCode()) {case RequestCode.CONSUMER_SEND_MSG_BACK:return this.consumerSendMsgBack(ctx, request);default:                SendMessageRequestHeader requestHeader = parseRequestHeader(request);if (requestHeader == null) {return null;                }                mqtraceContext = buildMsgContext(ctx, requestHeader);                this.executeSendMessageHookBefore(ctx, request, mqtraceContext);                RemotingCommand response;if (requestHeader.isBatch()) {                    response = this.sendBatchMessage(ctx, request, mqtraceContext, requestHeader);                } else {                    response = this.sendMessage(ctx, request, mqtraceContext, requestHeader);                }                this.executeSendMessageHookAfter(response, mqtraceContext);return response;        }    }

继续debug,发现 sendMessage 就是处理发送消息的,

如果我们在这里判断是否延时消息就写入文件,然后返回成功到客户端,等到了时间就发送延迟消息,不就搞定了吗?

oh,yes,就是这么干的

//处理延迟消息 delay message        String startTime = msgInner.getProperty(Message.SystemPropKey.STARTDELIVERTIME);        boolean isDelayMsg = false;        long nextStartTime = 0;if (startTime != null && msgInner.getDelayTimeLevel() <= 0) {            nextStartTime = Long.parseLong(startTime);if (nextStartTime >= System.currentTimeMillis()) {                isDelayMsg = true;            }        }if (isDelayMsg) {return delayProcessor.handlePutMessageResultFuture(response, request, msgInner, ctx, queueIdInt, nextStartTime);        } else {if (traFlag != null && Boolean.parseBoolean(traFlag)) {if (this.brokerController.getBrokerConfig().isRejectTransactionMessage()) {                    response.setCode(ResponseCode.NO_PERMISSION);                    response.setRemark("the broker[" + this.brokerController.getBrokerConfig().getBrokerIP1()                                    + "] sending transaction message is forbidden");return response;                }                putMessageResult = this.brokerController.getTransactionalMessageService().prepareMessage(msgInner);            } else {                putMessageResult = this.brokerController.getMessageStore().putMessage(msgInner);            }return handlePutMessageResult(putMessageResult, response, request, msgInner, responseHeader, sendMessageContext, ctx, queueIdInt);        }    }

其中 delayProcessor.handlePutMessageResultFuture 是我们用来处理延迟消息的地方

我们按照每个时间一个文件夹来保存延时消息,等延时消息到达后,定时的写入延时队列里面。

详细原理,请查考 rocketmq 原理实现篇 https://www.cnblogs.com/tomj2ee/p/15815186.html

<em> </em>
package org.apache.rocketmq.broker.delay;import io.netty.channel.ChannelHandlerContext;import org.apache.commons.lang3.time.DateFormatUtils;import org.apache.rocketmq.broker.BrokerController;import org.apache.rocketmq.common.protocol.ResponseCode;import org.apache.rocketmq.common.protocol.header.SendMessageResponseHeader;import org.apache.rocketmq.logging.InternalLogger;import org.apache.rocketmq.logging.InternalLoggerFactory;import org.apache.rocketmq.remoting.protocol.RemotingCommand;import org.apache.rocketmq.store.MessageExtBrokerInner;import java.io.*;import java.net.InetSocketAddress;import java.net.SocketAddress;import java.util.Date;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.ThreadLocalRandom;public class DelayProcessor implements  Runnable {    protected static final InternalLogger log = InternalLoggerFactory.getLogger(DelayProcessor.class.getCanonicalName());    protected final BrokerController brokerController;    protected final SocketAddress storeHost;    private ExecutorService jobTaskExecute = Executors.newFixedThreadPool(16);    public DelayProcessor(final BrokerController brokerController) {        this.brokerController = brokerController;        this.storeHost = new InetSocketAddress(brokerController.getBrokerConfig().getBrokerIP1(), brokerController                .getNettyServerConfig().getListenPort());        Thread thread = new Thread(this);        thread.setName("delayProcessor-run---thread");        thread.setDaemon(true);new File(getDelayPath()).mkdirs();        thread.start();        Thread missCallThread = new Thread(() -> {            try {for(;;) {                    Thread.sleep(10 * 1000);                    sendMissCallMsg();                }            } catch (InterruptedException e) {                e.printStackTrace();            }        });        missCallThread.setName("delayProcessor-callback-thread");        missCallThread.start();        System.out.println("init delay success " +getDelayPath());    }    public RemotingCommand handlePutMessageResultFuture(RemotingCommand response,                                                        RemotingCommand request,                                                        MessageExtBrokerInner msgInner,                                                        ChannelHandlerContext ctx,int queueIdInt, long nextStartTime) {return handlePutMessageResult(response, request, msgInner, ctx, queueIdInt, nextStartTime);    }    private RemotingCommand handlePutMessageResult(RemotingCommand response,                                                   RemotingCommand request, MessageExtBrokerInner msg,                                                   ChannelHandlerContext ctx,int queueIdInt, long nextStartTime) {        boolean svOk = saveMsgFile(nextStartTime, msg);        SendMessageResponseHeader sendMessageResponseHeader = new SendMessageResponseHeader();        sendMessageResponseHeader.setQueueId(1);        sendMessageResponseHeader.setMsgId("0");        sendMessageResponseHeader.setQueueOffset(0l);        sendMessageResponseHeader.setTransactionId("");        RemotingCommand newCommand = RemotingCommand.createRequestCommand(ResponseCode.SUCCESS, sendMessageResponseHeader);if (svOk) {            newCommand.setCode(ResponseCode.SUCCESS);        } else {            newCommand.setCode(ResponseCode.SYSTEM_ERROR);            newCommand.setRemark("发送消息延迟失败!");        }        newCommand.setExtFields(request.getExtFields());        newCommand.setVersion(response.getVersion());        newCommand.setOpaque(response.getOpaque());        newCommand.setLanguage(response.getLanguage());        newCommand.setBody(request.getBody());if (!request.isOnewayRPC()) {            try {                ctx.writeAndFlush(newCommand);            } catch (Throwable e) {                log.error("DelayProcessor process request over, but response failed", e);                log.error(request.toString());                log.error(response.toString());            }        }return newCommand;    }    public void putMessage(MessageExtBrokerInner msgInner) {        this.brokerController.getMessageStore().putMessage(msgInner);    }    @Override    public void run() {for (; ; ) {            long curTime = System.currentTimeMillis() / 1000;            jobTaskExecute.submit(() -> sendMsg(curTime));            try {                Thread.sleep(1000);            } catch (InterruptedException e) {            }        }    }    private String getDelayPath() {        String delayPath = "./delay-store"+ File.separator + "delay";return delayPath;    }    private boolean saveMsgFile(long startTime, MessageExtBrokerInner msgInner) {        ObjectOutputStream objectOutputStream = null;        try {            String msgId =(startTime/1000 )+"-"+ System.currentTimeMillis() + "-" + ThreadLocalRandom.current().nextInt(99999999);            System.out.println( getCurrentTime()+"写入延迟消息 >>" + msgId);            String parentDir = getDelayPath() + File.separator + startTime / 1000;            File parentFile = new File(parentDir);if (!parentFile.exists()) {                parentFile.mkdirs();            }            String fileName = parentDir + File.separator + msgId;            FileOutputStream fos = new FileOutputStream(fileName);            BufferedOutputStream bos = new BufferedOutputStream(fos);            objectOutputStream = new ObjectOutputStream(bos);            objectOutputStream.writeObject(msgInner);returntrue;        } catch (Exception ex) {            log.error("saveMsgFile ex:", ex);returnfalse;        } finally {            try {if (objectOutputStream != null) {                    objectOutputStream.close();                }            } catch (Exception ex) {                log.error("saveMsgFile ex:", ex);            }        }    }    private MessageExtBrokerInner readFile(File f) {        ObjectInputStream ois = null;        try {            ois = new ObjectInputStream(new FileInputStream(f));return (MessageExtBrokerInner) ois.readObject();        } catch (Exception ex) {return null;        } finally {if (ois != null) {                try {                    ois.close();                } catch (IOException e) {                    e.printStackTrace();                }            }        }    }    private  void sendMissCallMsg() {        File lst = new File(getDelayPath());        File[] files = lst.listFiles();        long startTime = System.currentTimeMillis() / 1000 - 10 * 1000;for (File f : files) {            String name = f.getName();if (f.isDirectory() && !name.equals(".") && !name.equals("..")) {                try {                    Long fileTime = Long.parseLong(name);if (fileTime <= startTime) {                        sendMsg(fileTime);                    }                } catch (Exception ex) {                }            }        }    }    private String  getCurrentTime(){return  Thread.currentThread().getName()+ ">>["+DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss")+"] ";    }    private void sendMsg(long startTime) {        File lst = new File(getDelayPath() + File.separator + startTime);        File[] files = lst.listFiles();if (files != null) {for (File f : files) {                System.out.println( getCurrentTime()+"时间到发送>> "+ startTime+" to commitLog " + f.getName());                MessageExtBrokerInner msgInner = readFile(f);if (msgInner != null) {                    putMessage(msgInner);                    System.out.println( getCurrentTime()+"写入log >> "+ startTime+" to commitLog " + f.getName()+" success");                    f.delete();                }            }            lst.delete();        }    }}

总结:rocketmq延迟队列实现主要是通过时间轮和文件来保存延时消息,等到了时间后,再写入延时队列,来达到延时的目的。

总共有4种方式来实现延时队列,可以参考延时队列的实现原理篇

https://www.cnblogs.com/tomj2ee/p/15815157.html

开源rocketmq延迟队列实现:

https://gitee.com/venus-suite/rocketmq-with-delivery-time.git

相关文章:

RocketMQ实现延迟队列精确到秒级实现

前言篇&#xff1a;为了节约成本&#xff0c;决定通过自研来改造rocketmq&#xff0c;添加任意时间延迟的延时队列&#xff0c;开源版本的rocketmq只有支持18个等级的延迟时间&#xff0c;其实对于大部分的功能是够用了的&#xff0c;但是以前的项目&#xff0c;全部都是使用了…...

线性数据结构:数组 Array

一、前言数组是数据结构还是数据类型&#xff1f;数组只是个名称&#xff0c;它可以描述一组操作&#xff0c;也可以命名这组操作。数组的数据操作&#xff0c;是通过 idx->val 的方式来处理。它不是具体要求内存上要存储着连续的数据才叫数组&#xff0c;而是说&#xff0c…...

大数据开发-Hive

1、hive简介 hive是基于Hadoop的一个数据仓库工具&#xff0c;用于分析数据的。可以将结构化数据文件映射为一张数据库表&#xff0c;并提供类SQL查询功能 注&#xff1a;hive-SQL or HQL or类SQL 和标准SQL还是有一点点区别的 本质是SQL转换为MapReduce程序 用途&#xff1…...

《程序员新声》-Tech Lead 如何带领团队

收听本期播客 谢谢收听程序员新声&#xff0c;这是一款来自思特沃克&#xff08;Thoughtworks&#xff09;的播客节目。在这里&#xff0c;我们不仅讨论软件和技术领域的现状和未来&#xff0c;更关注程序员的成长世界。如何学习&#xff0c;如何晋升&#xff0c;如何带领团队…...

每日算法面试题

🧝‍♂️算法题 实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,xn)。 示例 1:输入:x = 2.00000, n = 10 输出:1024.00000示例 2:输入:x = 2.10000, n = 3 输出:9.26100示例 3:输入:x...

高质量前端之自动化测试

前端自动化测试&#xff1a;Testing Library 篇 引言 前端测试 静态测试 eslint、TypeScript 单元测试 jest、mocha 集成测试 enzyme、react-testing-library、mock 爬虫 前后端解耦 为什么要引入自动化测试 测试可以让开发者站在用户的角度考虑问题&#xff0c;通过测试的手…...

2023不伤人脉的全新商城分销,一劳永逸的消费分红

2023不伤人脉的全新商城分销&#xff0c;一劳永逸的消费分红 2023-02-24 11:52梦龙 2023不伤人脉的全新商城分销&#xff0c;一劳永逸的消费分红 如今是流量为王的时代&#xff0c;但是如何将流量转化为忠实客户是个难题。不再是单向的买卖关系&#xff0c;而是从对产品的关注…...

【代码随想录训练营】【Day21】第六章|二叉树|530.二叉搜索树的最小绝对差|501.二叉搜索树中的众数|236. 二叉树的最近公共祖先

二叉搜索树的最小绝对差 题目详细&#xff1a;LeetCode.530 这道题使我第一次了解到二叉树的双指针遍历法&#xff0c;详细可以先查看卡哥的讲解视频&#xff1a;《代码随想录 — 二叉搜索树中的众数》 利用二叉搜索树的特点&#xff1a; 中序遍历二叉搜索树得到一个有序序…...

leaflet 导出图片,打印图片(A4横版或竖版)

第093个 点击查看专栏目录 本示例的目的是介绍如何在vue+leaflet中打印图片导出图片。一个简单的leaflet插件示例,添加了一个图标来打印或导出地图。 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果. 文章目录 示例效果配置方式示例源代码(共85行)安装插…...

Java面向对象:继承特性的学习

本文介绍了面向对象的继承特性: 什么是继承 继承的概念 Java中继承的语法 在继承下父类成员的访问 super和this关键字 父类和子类构造方法 在继承下类中出现初始化代码的执行顺序 父类成员的访问权限对子类的可见性 Java的继承关系 final关键字 认识继承和组合关系 继承特性的学…...

问答系统(QA)调研

引言 智能问答系统广泛用于回答人们以自然语言形式提出的问题&#xff0c;经典应用场景包括&#xff1a;智能语音交互、在线客服、知识获取、情感类聊天等。根据QA任务&#xff0c;可以将QA大致分为5大类&#xff0c;分别为&#xff1a; 文本问答&#xff08;text-based QA&am…...

商务租车的三大优势吸引企业以租代购

随着社会机经济的高速发展&#xff0c;租车模式的日益盛行&#xff0c;租车不仅仅是受个体户的青睐&#xff0c;而作为环保经济的出行方式也让越来越多的企业开始选择以租代买&#xff0c;据调查统计&#xff0c;最早开始商务租车的群体是外企。而近几年&#xff0c;国内的很多…...

蓝桥杯的比赛流程和必考点

蓝桥杯的比赛流程和必考点 距省赛仅1个多月&#xff01;蓝桥杯的比赛流程和必考点&#xff0c;你还不清楚&#xff1f; “巷子里的猫很自由&#xff0c;却没有归宿&#xff1b;围墙里的狗有归宿&#xff0c;终身都得低头。人生这道选择题&#xff0c;怎么选都会有遗憾。” 但不…...

【数据结构】红黑树

红黑树一、红黑树的概念二、红黑树的接口2.1 插入三、验证四、源码一、红黑树的概念 红黑树也是一个二叉搜索树&#xff0c;他是通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;最长路径长度不超过最短路径长度的 2 倍保持近似平衡。他在每个节点添加了一…...

从C++的角度理解C#的Event

由于技术背景是C起家的&#xff0c;所以对于C的概念很清楚&#xff0c;遇到C#的EVENT时候&#xff0c;总感觉这个概念比较抽象&#xff0c;不容易理解&#xff0c;但是当使用函数指针和回调函数来理解EVENT的时候&#xff0c;这个概念就清晰了。 首先对于EVENT来讲&#xff0c…...

商城进货记录交易-课后程序(JAVA基础案例教程-黑马程序员编著-第七章-课后作业)

【实验7-2】商城进货记录交易 【任务介绍】 1.任务描述 每个商城都需要进货&#xff0c;而这些进货记录整理起来很不方便&#xff0c;本案例要求编写一个商城进货记录交易的程序&#xff0c;使用字节流将商场的进货信息记录在本地的csv文件中。程序具体要求如下&#xff1a; …...

【正点原子FPGA连载】第十七章双核AMP实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

1&#xff09;实验平台&#xff1a;正点原子MPSoC开发板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id692450874670 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第十七章双核AMP…...

内存管理框架---页(一)

文章目录物理内存的模型非一致内存访问--NUMA一致内存访问模型--UMA内存管理架构页页框管理页描述符页描述符字段flags字段详解gfp_mask 标志获得页alloc_pages__get_free_pages获得填充为0的页释放页kmallocvmalloc参考资料你用心写的每一篇文章&#xff0c;可能会带别人和自己…...

华为OD机试真题Python实现【流水线】真题+解题思路+代码(20222023)

流水线 题目 一个工厂有m条流水线 来并行完成n个独立的作业 该工厂设置了一个调度系统 在安排作业时,总是优先执行处理时间最短的作业 现给定流水线个数m 需要完成的作业数n 每个作业的处理时间分别为 t1,t2...tn 请你编程计算处理完所有作业的耗时为多少 当n > m时 首先…...

「JVM 编译优化」Graal 编译器

文章目录1. 历史背景2. 构建编译调试环境3. JVMCI 编译器接口4. 代码中间表示5. 代码优化与生成1. 历史背景 Graal 编译器在 JDK 9 以 Jaotc 提前编译工具的形式首次加入到官方的 JDK 中&#xff0c;JDK 10 开始提供替换&#xff08;得益于 HotSpot 编译器接口&#xff0c;Jav…...

浅谈 React Hooks

React Hooks 是 React 16.8 引入的一组 API&#xff0c;用于在函数组件中使用 state 和其他 React 特性&#xff08;例如生命周期方法、context 等&#xff09;。Hooks 通过简洁的函数接口&#xff0c;解决了状态与 UI 的高度解耦&#xff0c;通过函数式编程范式实现更灵活 Rea…...

web vue 项目 Docker化部署

Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段&#xff1a; 构建阶段&#xff08;Build Stage&#xff09;&#xff1a…...

FastAPI 教程:从入门到实践

FastAPI 是一个现代、快速&#xff08;高性能&#xff09;的 Web 框架&#xff0c;用于构建 API&#xff0c;支持 Python 3.6。它基于标准 Python 类型提示&#xff0c;易于学习且功能强大。以下是一个完整的 FastAPI 入门教程&#xff0c;涵盖从环境搭建到创建并运行一个简单的…...

高频面试之3Zookeeper

高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个&#xff1f;3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制&#xff08;过半机制&#xff0…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

七、数据库的完整性

七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲

文章目录 前言第一部分&#xff1a;体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分&#xff1a;体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...

动态 Web 开发技术入门篇

一、HTTP 协议核心 1.1 HTTP 基础 协议全称 &#xff1a;HyperText Transfer Protocol&#xff08;超文本传输协议&#xff09; 默认端口 &#xff1a;HTTP 使用 80 端口&#xff0c;HTTPS 使用 443 端口。 请求方法 &#xff1a; GET &#xff1a;用于获取资源&#xff0c;…...

FFmpeg:Windows系统小白安装及其使用

一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】&#xff0c;注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录&#xff08;即exe所在文件夹&#xff09;加入系统变量…...

R 语言科研绘图第 55 期 --- 网络图-聚类

在发表科研论文的过程中&#xff0c;科研绘图是必不可少的&#xff0c;一张好看的图形会是文章很大的加分项。 为了便于使用&#xff0c;本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中&#xff0c;获取方式&#xff1a; R 语言科研绘图模板 --- sciRplothttps://mp.…...