Hive SQL ——窗口函数源码阅读
前言
使用Starrocks引擎中的窗口函数 row_number() over( )对10亿的数据集进行去重操作,BE内存溢出问题频发(忘记当时指定的BE内存上限是多少了.....),此时才意识到,开窗操作,如果使用 不当,反而更容易引发性能问题。 下文是对Hive中的窗口函数底层源码进行初步学习,若有问题,请指正~
一、窗口函数的执行步骤
(1)将数据分割成多个分区;
(2)在各个分区上调用窗口函数;
由于窗口函数的返回结果不是一个聚合值,而是另一张表的格式(table-in, table-out),因此Hive社区引入分区表函数 Partitioned Table Function(PTF)。
简略的代码流转图:
hive会把QueryBlock,翻译成执行操作数OperatorTree,其中每个operator都会有三个重要的方法:
- initializeOp() :初始化算子
- process() :执行每一行数据
- forward() :把处理好的每一行数据发送到下个Operator
当遇到窗口函数时,会生成PTFOperator,PTFOperator依赖PTFInvocation 读取已经排好序的数据,创建相应的输入分区:PTFPartition inputPart;
WindowTableFunction 负责管理窗口帧、调用窗口函数(UDAF)、并将结果写入输出分区: PTFPartition outputPart。
二、源码分析
2.1 PTFOperator 类
是PartitionedTableFunction的运算符,继承Operator抽象类(Hive运算符基类)
重写process(Object row, int tag) 方法,该方法来处理一行数据Row
@Overridepublic void process(Object row, int tag) throws HiveException {if (!isMapOperator) {/** check if current row belongs to the current accumulated Partition:* - If not:* - process the current Partition* - reset input Partition* - set currentKey to the newKey if it is null or has changed.*/newKeys.getNewKey(row, inputObjInspectors[0]);//会判断当前row所属的Key(newKeys)是否等于当前正在累积数据的partition所属的key(currentKeys)boolean keysAreEqual = (currentKeys != null && newKeys != null) ?newKeys.equals(currentKeys) : false;// 如果不相等,就结束当前partition分区的数据累积,触发窗口计算if (currentKeys != null && !keysAreEqual) {// 关闭正在积累的分区ptfInvocation.finishPartition();}// 如果currentKeys为空或者被改变,就将newKeys赋值给currentKeysif (currentKeys == null || !keysAreEqual) {// 开启一个新的分区partitionptfInvocation.startPartition();if (currentKeys == null) {currentKeys = newKeys.copyKey();} else {currentKeys.copyKey(newKeys);}}} else if (firstMapRow) { // 说明当前row是进入的第一行ptfInvocation.startPartition();firstMapRow = false;}// 将数据row添加到分区中,积累数据ptfInvocation.processRow(row);}
上面的代码可以看出,所有数据应该是按照分区排好了序,排队进入process方法,当遇到进入的row和当前分区不是同一个key时,当前分区就可以关闭了,然后在打开下一个分区。
2.2 PTFInvocation类
PTFInvocation是PTFOperator类 的内部类
在PTFOperator的初始化方法中创建了实例。
@Overrideprotected void initializeOp(Configuration jobConf) throws HiveException {...ptfInvocation = setupChain();ptfInvocation.initializeStreaming(jobConf, isMapOperator);...}
它的主要作用是负责PTF 数据链中行( row)的流动,通过 ptfInvocation.processRow(row) 方法调用传递链中的每一行,并且通过ptfInvocation.startPartition()、ptfInvocation.finishPartition()方法来通知分区何时开始何时结束。
该类中包含TableFunction,用来处理分区数据。
PTFPartition inputPart; // inputPart理解为:分区对象,一直是在复用一个inputPart
TableFunctionEvaluator tabFn; // tabFn理解为:窗口函数的实例//向分区中添加一行数据
void processRow(Object row) throws HiveException {if (isStreaming()) {// tabFn是窗口函数的实例handleOutputRows(tabFn.processRow(row));} else {// inputPart就是当前正在累积数据的分区inputPart.append(row);}
}// 开启一个分区
void startPartition() throws HiveException {if (isStreaming()) {tabFn.startPartition();} else {if (prev == null || prev.isOutputIterator()) {if (inputPart == null) {// 创建新分区对象:PTFPartition对象createInputPartition();} else {// 重置分区inputPart.reset();}}}if (next != null) {next.startPartition();}
}// 关闭一个分区
void finishPartition() throws HiveException {if (isStreaming()) {handleOutputRows(tabFn.finishPartition());} else {if (tabFn.canIterateOutput()) {outputPartRowsItr = inputPart == null ? null :tabFn.iterator(inputPart.iterator());} else {// tabFn是窗口函数的实例,execute方法:执行窗口函数逻辑的计算,返回outputPart依旧是一个分区对象outputPart = inputPart == null ? null : tabFn.execute(inputPart);outputPartRowsItr = outputPart == null ? null : outputPart.iterator();}if (next != null) {if (!next.isStreaming() && !isOutputIterator()) {next.inputPart = outputPart;} else {if (outputPartRowsItr != null) {while (outputPartRowsItr.hasNext()) {next.processRow(outputPartRowsItr.next());}}}}if (next != null) {next.finishPartition();} else {if (!isStreaming()) {if (outputPartRowsItr != null) {while (outputPartRowsItr.hasNext()) {// 将窗口函数计算结果逐条输出到下一个Operator中forward(outputPartRowsItr.next(), outputObjInspector);}}}}
}
2.3 PTFPartition类
该类表示由TableFunction或WindowFunction来处理的行集合,使用PTFRowContainer来保存数据。
private final PTFRowContainer<List<Object>> elems; // 存放数据的容器public void append(Object o) throws HiveException {//在往PTFPartition中添加数据时,如果当前累计条数超过了Int最大值(21亿),会抛异常。if (elems.rowCount() == Integer.MAX_VALUE) {throw new HiveException(String.format("Cannot add more than %d elements to a PTFPartition",Integer.MAX_VALUE));}@SuppressWarnings("unchecked")List<Object> l = (List<Object>)ObjectInspectorUtils.copyToStandardObject(o, inputOI, ObjectInspectorCopyOption.WRITABLE);elems.addRow(l);
}
2.4 TableFunctionEvaluator类
该类负责对分区内的数据做实际的窗口计算
public abstract class TableFunctionEvaluator {
transient protected PTFPartition outputPartition; // transient瞬态变量,该属性可以不参与序列化// iPart理解为:分区对象
public PTFPartition execute(PTFPartition iPart)throws HiveException {if (ptfDesc.isMapSide()) {return transformRawInput(iPart);}PTFPartitionIterator<Object> pItr = iPart.iterator();PTFOperator.connectLeadLagFunctionsToPartition(ptfDesc.getLlInfo(), pItr);if (outputPartition == null) {outputPartition = PTFPartition.create(ptfDesc.getCfg(),tableDef.getOutputShape().getSerde(),OI, tableDef.getOutputShape().getOI());} else {outputPartition.reset();}// 入参1:输入PTFPartition转换的迭代器;入参2:输出PTFPartitionexecute(pItr, outputPartition);return outputPartition;
}protected abstract void execute(PTFPartitionIterator<Object> pItr, PTFPartition oPart) throws HiveException;
}
抽象方法 execute(PTFPartitionIterator pItr, PTFPartition oPart) 方法的具体实现在子类WindowingTableFunction中
public class WindowingTableFunction extends TableFunctionEvaluator {@Override
public void execute(PTFPartitionIterator<Object> pItr, PTFPartition outP) throws HiveException {ArrayList<List<?>> oColumns = new ArrayList<List<?>>();PTFPartition iPart = pItr.getPartition();StructObjectInspector inputOI = iPart.getOutputOI();WindowTableFunctionDef wTFnDef = (WindowTableFunctionDef) getTableDef();for (WindowFunctionDef wFn : wTFnDef.getWindowFunctions()) {// 这里是判断逻辑:如果该窗口定义是一个从第一行到最后一行的全局无限窗口就返回false,反之trueboolean processWindow = processWindow(wFn.getWindowFrame());pItr.reset();if (!processWindow) {Object out = evaluateFunctionOnPartition(wFn, iPart);if (!wFn.isPivotResult()) {out = new SameList(iPart.size(), out);}oColumns.add((List<?>) out);} else {oColumns.add(executeFnwithWindow(wFn, iPart));}}/** Output Columns in the following order* - the columns representing the output from Window Fns* - the input Rows columns*/for (int i = 0; i < iPart.size(); i++) {ArrayList oRow = new ArrayList();Object iRow = iPart.getAt(i);for (int j = 0; j < oColumns.size(); j++) {oRow.add(oColumns.get(j).get(i));}for (StructField f : inputOI.getAllStructFieldRefs()) {oRow.add(inputOI.getStructFieldData(iRow, f));}//最终将处理好的数据逐条添加到输出PTFPartition中outP.append(oRow);}
}// Evaluate the function result for each row in the partition
ArrayList<Object> executeFnwithWindow(WindowFunctionDef wFnDef,PTFPartition iPart)throws HiveException {ArrayList<Object> vals = new ArrayList<Object>();for (int i = 0; i < iPart.size(); i++) {// 入参:1.窗口函数、2.当前行的行号、3.输入PTFPartition对象Object out = evaluateWindowFunction(wFnDef, i, iPart);vals.add(out);}return vals;
}// Evaluate the result given a partition and the row number to process
private Object evaluateWindowFunction(WindowFunctionDef wFn, int rowToProcess, PTFPartition partition)throws HiveException {BasePartitionEvaluator partitionEval = wFn.getWFnEval().getPartitionWindowingEvaluator(wFn.getWindowFrame(), partition, wFn.getArgs(), wFn.getOI(), nullsLast);// 给定当前行,获取窗口的聚合return partitionEval.iterate(rowToProcess, ptfDesc.getLlInfo());
}}
注:WindowingTableFunction类中的execute方法 ,没怎么理解清楚,待补充~
三、Hive SQL窗口函数实现原理
window Funtion的使用语法:
select col1,col2,row_number() over (partition by col1 order by col2 窗口子句) as rnfrom tableA
上面的语句主要分两部分
-
window函数部分(window_func)
-
窗口定义部分
3.1 window函数部分
windows函数部分即是:在窗口上执行的函数。主要有count 、sum、avg聚合类窗口函数、还有常用的row_number、rank这样的排序函数。
3.2 窗口定义部分
即为: over里面的三部分内容(均可省略不写)
-
partition by 分区
-
order by 排序
-
(rows | range )between ... and ..... 窗口子句
ps :Hive 窗口函数的详细介绍:
(07)Hive——窗口函数详解_hive 窗口函数-CSDN博客
3.3 window Function实现原理
窗口函数的实现,主要借助 Partitioned Table Function (即PTF);
(1)PTF的输入可以是:表、子查询或另一个PTF函数输出;
(2)PTF输出是一张表。
写一个相对复杂的sql,来看一下执行窗口函数时,数据的流转情况:
select id,sq,cell_type,rank,row_number() over(partition by id order by rank ) as rn ,rank() over(partition by id order by rank) as r,dense_rank() over(partition by cell_type order by id) as dr from window_test_table group byid,sq,cell_type,rank;
数据流转如下图:
以上代码实现主要有三个阶段:
-
计算除窗口函数以外所有的其他运算,如:group by,join ,having等。上面的代码的第一阶段即为:
selectid, sq, cell_type, rank
from window_test_table
group byid, sq, cell_type, rank;
-
将第一步的输出作为第一个 PTF 的输入,计算对应的窗口函数值。上面代码的第二阶段即为:
select id,sq,cell_type,rank,rn,r
from
window(<w>,--将第一阶段输出记为wpartition by id, --分区order by rank, --窗口函数的order[rn:row_number(),r:rank()] --窗口函数调用)
由于row_number(),rank() 两个函数对应的窗口是相同的(partition by id order by rank),因此,这两个函数可以在一次shuffle中完成。
-
将第二步的输出结果作为 第二个PTF 的输入,计算对应的窗口函数值。上面代码的第三阶段即为:
select id,sq,cell_type,rank,rn,r,dr
from
window(<w1>,--将第二阶段输出记为w1partition by cell_type, --分区order by id, --窗口函数的order[dr:dense_rank()] --窗口函数调用)
由于dense_rank()的窗口与前两个函数不同,因此需要再partition一次,得到最终的输出结果。
总结:上述代码显示需要shuffle三次才能得到最终的结果(第一阶段的group by ,第二阶段,第三阶段的开窗操作)。对应到MapReduce程序,即需要经历三次 map->reduce组合;对应到spark sql上,需要Exchange三次,再加上中间排序操作,在数据量很大的情况下,效率上确实会有较大的影响。
四、窗口函数的性能问题
在使用Hive进行数据处理时,借助窗口函数可以对数据进行分组、排序等操作,但是在使用row_number这类窗口函数时,会遇到性能较慢的问题,j即比普通的聚合函数( sum,min,max等)运行成本更高,为啥?
4.1 性能问题产生原因
4.1.1 第一个版本
小破站一个up主给出的答案:
原因:
(1)开窗函数不能做预聚合 ,数据量很多,shuffle慢,计算慢,并且会有
数据倾斜的风险;
(2)开窗多一步order by ,更耗时间;
4.1.2 第二个版本
原因:
(1)普通的聚合函数语句,可以根据函数不同,采用partial + merge 的方式运行,也即是map端预聚合;但那是window 窗口语句只能在reduce 端一次性聚合,即只有complete 执行模式。
(2)普通聚合函数的物理执行计划分为SortBased和HashBased的;而window是SortBased。
(3)window语句作用于 对行,并为每行返回一个聚合结果,这决定了window在执行过程中需要更大的buffer 进行汇总。
4.2 性能问题的优化方法
4.2.1 用聚合函数替代 排序开窗函数
例如:假设需要求出历史至今用户粒度末次交易的sku名称或者交易金额等,这种情况下,可以将 交易时间和sku名称拼接起来,取max ,之后再将sku名称拆解开,即能达到预期效果。
在Hive 中,row_number是一个常用的窗口函数,用于为结果集中的每一行分配一个唯一的数字。通常会搭配over子句来指定窗口的范围和排序方式。例如:
select col1,col2,row_number() over (partition by col1 order by col2 窗口子句) as rnfrom tableA
上述示例row_number 函数将根据col1进行分组,并按照col2的值进行排序,为每一组数据分配一个唯一的行号。然而,在处理大规模数据时,使用row_number可能会导致性能下降,这是因为row_number 需要对数据进行排序和标记,而这些操作在大数据量下会消耗较多的计算资源。
注: 以下都是row_number() over () 开窗函数性能优化的几种方式:
4.2.2 减少数据量
一种最直接的优化方法是减少需要进行row_number计算的数据量。可以通过在where子句中添加条件、对数据进行分区等方式来减小数据规模,从而提升计算性能。
ps: 这种方式在生产环境中用过。
4.2.3 避免多次排序
在使用row_number时,尽量避免多次排序操作。可以将row_number 函数应用在子查询中,然后再进行排序操作,避免重复的排序过程。
selectcol1,col2,rn
from
( select col1,col2,row_number() over (partition by col1 order by col2) as rnfrom tableA) tmp1
order by col1,col2;
参考文章:
常用的SQL优化方式, 用聚合函数替代排序开窗求最值, sparksql, hivesql_哔哩哔哩_bilibili
https://blog.51cto.com/u_16213435/9877979
Hive学习(一)窗口函数源码阅读_hive 源码阅读-CSDN博客
https://mp.weixin.qq.com/s/WBryrbpHGO9jmzMp0e7jhw
相关文章:

Hive SQL ——窗口函数源码阅读
前言 使用Starrocks引擎中的窗口函数 row_number() over( )对10亿的数据集进行去重操作,BE内存溢出问题频发(忘记当时指定的BE内存上限是多少了.....),此时才意识到,开窗操作,如果使用 不当,反而…...

用python的Manim 创建大括号
Brace 是 Manim 中用于创建大括号(curly braces)的一个对象类。它有几个子类,自定义了不同的功能。下面是每个类的简要解释: 1. ArcBrace 功能: 创建一个环绕弧线的括号。适用于需要围绕弧形线条的场景。用法: 通常用于图形中有…...

白骑士的Matlab教学附加篇 5.2 代码规范与最佳实践
系列目录 上一篇:白骑士的Matlab教学附加篇 5.1 MATLAB开发工具 在 MATLAB 编程中,遵循良好的代码规范和最佳实践有助于提高代码的可读性、可维护性和可重用性。无论是变量命名、注释风格,还是代码格式化,合理的规…...

Javaweb--SpringBoot
1.SpringBoot入门 简化Spring开发的一个框架,Spring Boot 旨在帮助开发者快速搭建 Spring 框架。 整个Spring的一个合集,可以简化配置 2.微服务 (1)微服务就是一种架构风格 (2)微服务就是把一个项目拆…...

【数据结构】算法的时间复杂度与空间复杂度
计算机考研408-数据结构笔记本之——第一章 绪论 1.2 算法和算法评价 1.2.2 算法效率的度量 算法效率的度量是通过时间复杂度和空间复杂度来描述的。 1.空间复杂度 算法的空间复杂度S(n)定义为该算法所需的存储空间,它是问题规模n的函数,记为 S(n) …...

PyCharm环境python开发上位机
目录 前言: 一、pycharm新建工程 1、打开 pycharm软件,新建工程 二、配置UI界面 1、新建UI界面 1)创建 Main Window 2)拖动控件到 MainWindow 中 3)设置信号与槽 4)ctrlS 保存ui文件 2、将ui文件转…...

ROS 2 参数使用
ROS 2 参数使用 介绍 ROS 2 (Robot Operating System 2) 是一个为机器人开发提供支持的开源框架。它继承了 ROS 1 的优点,并且在架构上做了许多改进以支持分布式系统、实时性、安全性等要求。ROS 2 中的参数是用于配置节点行为的关键部分,允许我们动态…...

QT的Model-View实现大批量数据展示
一、完整源代码 1.项目结构 2.各文件代码展示 define.h #pragma once #include <QVector>//学生信息 typedef struct _STUDENT {QString name; //姓名int score1; //语文成绩int score2; //数学成绩int score3; //外语成绩_STUDENT(){name ""…...

2024年8月7日(mysql主从 )
回顾 主服务器 [rootmaster_mysql ~]# yum -y install rsync [rootmaster_mysql ~]# tar -xf mysql-8.0.33-linux-glibc2.12-x86_64.tar [rootmaster_mysql ~]# tar -xf mysql-8.0.33-linux-glibc2.12-x86_64.tar.xz [rootmaster_mysql ~]# cp -r mysql-8.0.33-linux-glibc2.…...

接口/自动化测试 面试集合
1. apache和nginx的区别? Nginx相对Apache的优点: 轻量级,同样起web服务,比apache占用更少的内存及资源; 抗并发,nginx处理请求是异步非阻塞的,支持更多的并发连接,而apache则是阻塞型的,在高 并发下ngi…...

菜鸡勇闯第136场双周赛
菜鸡鼓足了勇气报名了力扣双周赛(后来复盘才知道双周赛更难一点,我真是头铁。。) 没想到还拿了个竞赛名次哈哈哈哈哈还在前50%,小力它真的,我哭死 为什么我本科被高数老师忽悠,去打了两年数模o(≧口≦)o 每…...

趋动科技陈飞:从小模型到大模型,AI时代下的数据中心建设
自AI大模型横空出世,不断推动着AI从学术界到产业界向大众破圈,新的时代正在来临。11月15-16日,由CDCC主办的“2023第11届数据中心标准大会”在北京国家会议中心盛大开幕。 本届大会的主题围绕“AI时代 重塑未来”,聚焦数据中心领…...

yolo v8 + flask部署到云服务器,以及问题记录
环境安装 1、运行项目报错:no python application found, check your startup logs for errors 在云服务器pytorch版本安装错了,安装了GPU版本,需要安装CPU版本 # CPU only 使用下面这段代码避免出现第二个错误 pip install torch2.3.1 to…...

【科研必备插件】easyscholar如何使文章显示期刊影响因子与分区等级
简要介绍 EasyScholar 是一个微软 Edge 浏览器的扩展程序,可以显示会议、期刊等级。可支持在各大论文搜索网站,显示各种期刊、会议等级排名 要想你的知网页面如下图所示,快来获取安装,快速科研有方法 插件安装教程 ①打开浏览器…...

UE5 UrlEncode转换
调用接口时用到了 UFUNCTION(BlueprintPure, Category "FuncLib", meta (Keywords "URL1"))static FString StringToURLEncode(const FString& str1);FString UBasicFuncLib::StringToURLEncode(const FString& str1){return FGenericPlatformH…...

【QML】Qt.rgba()的正确使用方法
1. 问题 设置颜色 color: Qt.rgba(65,105,225,255) ,应该是蓝色,却显示白色。 2. 正确方法 //正确代码 color: Qt.rgba(65/255, 105/255, 225/255, 255/255)...

centos7.9 docker安装
1、不要通过yum直接安装 具体原因: CentOS 6 因内核太旧,即使支持安装 docker,但会有各种问题,不建议安装CentOS 7 的 extras 源虽然可以安装 docker,但包比较旧,建议从官方源或镜像源站点下载安装 docke…...

spring操作数据库
xml版 程序结构 配置文件 dbUtil-阿帕奇提供操作数据库的插件 核心类:QueryRunner .query() 查询 .update() 增删改 <dependencies><!-- spring --><dependency><groupId>org.springframework</groupId><artifactId>spri…...

Apache Flink中TaskManager,SubTask,TaskSlot,并行度之间的关系
Apache Flink 中Application 与 Job 一个完整的Flink Application 一般组成如下: Source 数据来源Transformation 数据转换处理等Sink 数据传输 Flink 中一个或者多个Operator(算子)组合对数据进行转换形成一个 Transformation,一…...

马斯克xAI新计划:人工智能模型Grok 2测试版即将发布
特斯拉CEO马斯克在X平台上表示,人工智能模型Grok 2测试版即将发布。Grok,作为xAI公司的明星大语言模型,其首代产品Grok 1已凭借神经演化计算与深度学习技术的深度融合,展现了超乎想象的学习速度与智能深度,赢得了业界的…...

【机器人学】6-4.六自由度机器人运动学参数辨识-机器人精度验证【附MATLAB代码】
前言 前两个章节以及完成了机器人参数辨识。 【机器人学】6-1.六自由度机器人运动学参数辨识-辨识数学模型的建立 【机器人学】6-2.六自由度机器人运动学参数辨识-优化方法求解辨识参数 这里我们认为激光测量仪测量到的数据为机器人实际到达的位置,而机器人理论到…...

分销商城小程序系统渠道拓展
线上卖货渠道很多,想要不断提高营收和新客获取,除了自己和工具本身努力外,还需要其他人的帮助来提高商城店铺的整体销量。 搭建saas商城系统网站/小程序,后台上货,设置支付、配送、营销、精美模板商城装修等内容&…...

WPF篇(14)-ProgressBar进度条+Calendar日历控件+DatePicker日期控件
ProgressBar进度条 ProgressBar进度条通常在我们执行某个任务需要花费大量时间时使用,这时可以采用进度条显示任务或线程的执行进度,以便给用户良好的使用体验。 ProgressBar类定义 public class ProgressBar : RangeBase {public static readonly De…...

链表高频题目和必备技巧
链表高频题目和必备技巧 1. 链表类题目注意点 1,如果笔试中空间要求不严格,直接使用容器来解决链表问题 2,如果笔试中空间要求严格、或者在面试中面试官强调空间的优化,需要使用额外空间复杂度**O(1)**的方法 3,最…...

Vue3详细介绍,正则采集器所用前端框架
Vue3 引入了一个全新的响应式系统,它是基于ES6的Proxy特性构建的。这个系统使得 Vue 能够更加高效地追踪数据的变化,并在数据发生变化时自动更新DOM。响应式系统的核心是"可观察",当数据变化时,视图会响应这些变化并重新…...

数据集--COCO2017(快速下载)
1、数据集介绍 数据集官网:https://cocodataset.org/#home COCO(Common Objects in Context)数据集是计算机视觉领域中最广泛使用的数据集之一,主要用于目标检测、分割和图像标注任务。COCO 数据集由 Microsoft 发布,…...

【管理咨询宝藏159】顶级咨询公司人力三支柱建设方案思路
阅读完整版报告内容,请搜索VV号“管理咨询宝藏”。 【管理咨询宝藏159】顶级咨询公司人力三支柱建设方案思路 【格式】PDF版本 【关键词】人力咨询、三支柱、人力体系 【核心观点】 - 集团总部制定全集团共享中心总体规划路径,组织并负责实施与推广。各…...

跨时钟域总结
跨时钟域总结 秋招学习跨时钟域 总结一下吧 异步电路 设计中有两个频率不同的时钟(也可能多个),而有数据在两组时钟之间传输 单bit跨时钟域 慢时钟域数据-> 快时钟域 方法 : 使用两个锁存器 (打两拍) 数据跨时钟域同步过程中,脉冲宽度会改变,不影响同步结…...

富婆和富公子都在看的负载均衡和Haproxy大全
一.负载均衡 1.1:什么是负载均衡 负载均衡: Load Balance ,简称 LB ,是一种服务或基于硬件设备等实现的高可用反向代理技术,负载均 衡将特定的业务(web 服务、网络流量等 ) 分担给指定的一个或多个后端特定的服务器或…...

VScode找python环境 (conda)
第一步 CtrlshiftP 第二步 框框里输入:Python:Select Interpreter...