Flink-1.19.0源碼詳解9-ExecutionGraph生成-后篇

? ? ? ? 《Flink-1.19.0源碼詳解8-ExecutionGraph生成-前篇》前篇已從Flink集群端調度開始解析ExecutionGraph生成的源碼,解析了ExecutionGraph的ExecutionJobVertex節點、ExecutionVertex節點、IntermediateResult數據集、IntermediateResultPartition數據集分區與封裝Task執行信息的Execution的創建完整過程。本篇接著前篇,繼續解析ExecutionGraph生成的后續源碼。

ExecutionGraph生成的完整源碼:

?

1.連接ExecutionJobVertex節點和前置的IntermediateResult數據集

? ? ? ? Flink在新版本(1.13后)取消了ExecutionEdge,用EdgeManager管理的vertexConsumedPartitions(Map<ExecutionVertexID, List<ConsumedPartitionGroup>>)和partitionConsumers(Map<IntermediateResultPartitionID, List<ConsumerVertexGroup>>)來保存IntermediateResultPartition數據集分區與ExecutionVertex節點的連接關系。

? ? ? ? 回到DefaultExecutionGraph的initializeJobVertex()方法,在完成ExecutionJobVertex的initialize()方法為每個ExecutionJobVertex節點創建其對應的ExecutionVertex節點和IntermediateResultPartition數據集分區后,DefaultExecutionGraph會繼續調用ExecutionJobVertex的connectToPredecessors()方法,連接ExecutionJobVertex節點(包括其每個并行度上的ExecutionVertex節點)和前置的IntermediateResult數據集(包括其每個并行度上的IntermediateResultPartition數據集分區)。

?源碼圖解:

DefaultExecutionGraph.initializeJobVertex()方法源碼:

public void initializeJobVertex(ExecutionJobVertex ejv,long createTimestamp,Map<IntermediateDataSetID, JobVertexInputInfo> jobVertexInputInfos)throws JobException {//...//初始化每個ExecutionJobVertexejv.initialize(executionHistorySizeLimit,rpcTimeout,createTimestamp,this.initialAttemptCounts.getAttemptCounts(ejv.getJobVertexId()));//連接ExecutionJobVertex和前置的IntermediateResultejv.connectToPredecessors(this.intermediateResults);//... 
}

? ? ? ? ExecutionJobVertex的connectToPredecessors()方法找到每個ExecutionJobVertex節點對應的JobVertex節點,從JobVertex節點中獲取每個輸入的JobEdge邊和其連接前置的IntermediateDataSet數據集,繼續調用EdgeManagerBuildUtil的connectVertexToResult()方法連接單個ExecutionJobVertex節點與IntermediateResult數據集。

ExecutionJobVertex.connectToPredecessors()方法源碼:

public void connectToPredecessors(Map<IntermediateDataSetID, IntermediateResult> intermediateDataSets)throws JobException {checkState(isInitialized());//從ExecutionJobVertex對應的JobVertex獲取所有入邊List<JobEdge> inputs = jobVertex.getInputs();//遍歷本節點所有入邊for (int num = 0; num < inputs.size(); num++) {//找出每個邊的IntermediateResultJobEdge edge = inputs.get(num);//...IntermediateResult ires = intermediateDataSets.get(edge.getSourceId());//...//連接ExecutionJobVertex和前置的IntermediateResultEdgeManagerBuildUtil.connectVertexToResult(this, ires);}
}

? ? ? ??EdgeManagerBuildUtil的connectVertexToResult()方法獲取了ExecutionJobVertex的DistributionPattern連接方式和由VertexInputInfoComputationUtils的computeVertexInputInfos()方法生成的JobVertexInputInfo輸入描述,并根據連接方式是POINTWISE還是ALL_TO_ALL,進行ExecutionJobVertex節點與IntermediateResult數據集的連接。

EdgeManagerBuildUtil.connectVertexToResult()方法源碼:

static void connectVertexToResult(ExecutionJobVertex vertex, IntermediateResult intermediateResult) {//獲取ExecutionJobVertex與IntermediateResult的連接方式(點對點、All對ALL)final DistributionPattern distributionPattern =intermediateResult.getConsumingDistributionPattern();//獲取輸入描述final JobVertexInputInfo jobVertexInputInfo =vertex.getGraph().getJobVertexInputInfo(vertex.getJobVertexId(), intermediateResult.getId());//根據不同連接方式(點對點、All對ALL)構建連接(相當于ExecutionEdge)switch (distributionPattern) {case POINTWISE:connectPointwise(vertex, intermediateResult, jobVertexInputInfo);break;case ALL_TO_ALL:connectAllToAll(vertex, intermediateResult, jobVertexInputInfo);break;default:throw new IllegalArgumentException("Unrecognized distribution pattern.");}
}

對于POINTWISE:

? ? ? ??EdgeManagerBuildUtil會根據JobVertexInputInfo為每個ExecutionVertex節點分配需連接的IntermediateResultPartition數據集分區,并調用connectInternal()方法具體創建連接。

ExecutionVertex.connectPointwise()方法源碼:

private static void connectPointwise(ExecutionJobVertex jobVertex,IntermediateResult result,JobVertexInputInfo jobVertexInputInfo) {Map<IndexRange, List<Integer>> consumersByPartition = new LinkedHashMap<>();//根據JobVertexInputInfo分配的為每個ExecutionVertex節點連接IntermediateResultPartition數據集。for (ExecutionVertexInputInfo executionVertexInputInfo :jobVertexInputInfo.getExecutionVertexInputInfos()) {int consumerIndex = executionVertexInputInfo.getSubtaskIndex();IndexRange range = executionVertexInputInfo.getPartitionIndexRange();consumersByPartition.compute(range,(ignore, consumers) -> {if (consumers == null) {consumers = new ArrayList<>();}consumers.add(consumerIndex);return consumers;});}//調用connectInternal()方法具體創建連接consumersByPartition.forEach((range, subtasks) -> {List<ExecutionVertex> taskVertices = new ArrayList<>();List<IntermediateResultPartition> partitions = new ArrayList<>();for (int index : subtasks) {taskVertices.add(jobVertex.getTaskVertices()[index]);}for (int i = range.getStartIndex(); i <= range.getEndIndex(); ++i) {partitions.add(result.getPartitions()[i]);}connectInternal(taskVertices,partitions,result.getResultType(),jobVertex.getGraph().getEdgeManager());});
}

對于ALL_TO_ALL:

? ? ? ? 對ExecutionVertex節點和IntermediateResultPartition數據集分區做全連接。

ExecutionJobVertex.connectToPredecessors()方法源碼:

private static void connectAllToAll(ExecutionJobVertex jobVertex,IntermediateResult result,JobVertexInputInfo jobVertexInputInfo) {// check the vertex input info is legal//ExecutionVertex對IntermediateResultPartition做全連接jobVertexInputInfo.getExecutionVertexInputInfos().forEach(executionVertexInputInfo -> {IndexRange partitionRange =executionVertexInputInfo.getPartitionIndexRange();checkArgument(partitionRange.getStartIndex() == 0);checkArgument(partitionRange.getEndIndex()== (result.getNumberOfAssignedPartitions() - 1));});connectInternal(Arrays.asList(jobVertex.getTaskVertices()),Arrays.asList(result.getPartitions()),result.getResultType(),jobVertex.getGraph().getEdgeManager());
}

?

2.調用ExecutionVertex.connectInternal()進行具體連接

? ? ? ? 無論是POINTWISE還是ALL_TO_ALL,在為每個ExecutionVertex節點分配號上游IntermediateResultPartition數據集分區后,都是通過調用ExecutionVertex.connectInternal()方法進行具體連接的。

? ? ? ? 在ExecutionVertex的connectInternal()方法中,首先創建consumedPartitionGroup封裝ExecutionVertex節點需要連接的IntermediateResultPartition數據集分區,并向EdgeManager的vertexConsumedPartitions(Map<ExecutionVertexID, List<ConsumedPartitionGroup>> )添加ExecutionVertex節點和對應的ConsumedPartitionGroup。

? ? ? ?然后繼續創建ConsumerVertexGroup封裝上游IntermediateResult數據集需連接的ExecutionVertex節點,并向EdgeManager的partitionConsumers (Map<IntermediateResultPartitionID, List<ConsumerVertexGroup>>)添加IntermediateResultPartition數據集分區和其對應的ConsumerVertexGroup。

源碼圖解:

ExecutionVertex.connectInternal()方法源碼:

private static void connectInternal(List<ExecutionVertex> taskVertices,List<IntermediateResultPartition> partitions,ResultPartitionType resultPartitionType,EdgeManager edgeManager) {checkState(!taskVertices.isEmpty());checkState(!partitions.isEmpty());//創建consumedPartitionGroup封裝ExecutionVertex需要連接的IntermediateResultPartitionConsumedPartitionGroup consumedPartitionGroup =createAndRegisterConsumedPartitionGroupToEdgeManager(taskVertices.size(), partitions, resultPartitionType, edgeManager);//向ExecutionJobVertex中所有ExecutionVertex添加ConsumedPartitionGroupfor (ExecutionVertex ev : taskVertices) {ev.addConsumedPartitionGroup(consumedPartitionGroup);}//創建ConsumerVertexGroup封裝上游IntermediateResult需連接的ExecutionVertexList<ExecutionVertexID> consumerVertices =taskVertices.stream().map(ExecutionVertex::getID).collect(Collectors.toList());ConsumerVertexGroup consumerVertexGroup =ConsumerVertexGroup.fromMultipleVertices(consumerVertices, resultPartitionType);//向IntermediateResult中所有IntermediateResultPartition添加ConsumerVertexGroup        for (IntermediateResultPartition partition : partitions) {partition.addConsumers(consumerVertexGroup);}consumedPartitionGroup.setConsumerVertexGroup(consumerVertexGroup);consumerVertexGroup.setConsumedPartitionGroup(consumedPartitionGroup);
}

? ? ? ? 因為在Flink1.13后取消了ExecutionEdge,ExecutionVertex與IntermediateResultPartition的連接關系由EdgeManager管理。

? ? ? ? 對于ExecutionJobVertex節點中所有ExecutionVertex節點,添加需要連接的IntermediateResultPartition數據集分區的ConsumedPartitionGroup,是調用ExecutionVertex節點的addConsumedPartitionGroup()方法,再進一步通過EdgeManager的connectVertexWithConsumedPartitionGroup()方法實現的。

ExecutionVertex.addConsumedPartitionGroup()方法源碼:

public void addConsumedPartitionGroup(ConsumedPartitionGroup consumedPartitions) {//向EdgeManager添加ConsumedPartitionGroupgetExecutionGraphAccessor().getEdgeManager().connectVertexWithConsumedPartitionGroup(executionVertexId, consumedPartitions);
}

? ? ? ? 最終EdgeManager從partitionConsumers中讀出ExecutionVertex節點對應IntermediateResultPartition數據集分區的List<ConsumerVertexGroup>,向EdgeManager的vertexConsumedPartitions(Map<ExecutionVertexID, List<ConsumedPartitionGroup>> )添加ConsumerVertexGroup。

EdgeManager.connectVertexWithConsumedPartitionGroup()方法源碼:

public void connectVertexWithConsumedPartitionGroup(ExecutionVertexID executionVertexId, ConsumedPartitionGroup consumedPartitionGroup) {checkNotNull(consumedPartitionGroup);//從partitionConsumers讀出本IntermediateResultPartition對應的List<ConsumerVertexGroup>,添加ConsumerVertexGroupfinal List<ConsumedPartitionGroup> consumedPartitions =getConsumedPartitionGroupsForVertexInternal(executionVertexId);consumedPartitions.add(consumedPartitionGroup);
}

? ? ? ? 同上,對于IntermediateResult數據集中所有IntermediateResultPartition數據集分區,添加要連接的ExecutionVertex節點的ConsumerVertexGroup,是調用IntermediateResultPartition的addConsumers()方法,再進一步通過EdgeManager的connectPartitionWithConsumerVertexGroup()方法實現的。

IntermediateResultPartition.addConsumers()方法源碼:

public void addConsumers(ConsumerVertexGroup consumers) {//向EdgeManager添加ConsumerVertexGroupgetEdgeManager().connectPartitionWithConsumerVertexGroup(partitionId, consumers);
}

? ? ? ? 最終EdgeManager從partitionConsumers中讀出IntermediateResultPartition數據集分區對應ExecutionVertex節點的List<ConsumerVertexGroup>,向EdgeManager的partitionConsumers (Map<IntermediateResultPartitionID, List<ConsumerVertexGroup>>)添加ConsumerVertexGroup。

EdgeManager.connectPartitionWithConsumerVertexGroup()方法源碼:

public void connectPartitionWithConsumerVertexGroup(IntermediateResultPartitionID resultPartitionId,ConsumerVertexGroup consumerVertexGroup) {checkNotNull(consumerVertexGroup);//從vertexConsumedPartitions讀出本IntermediateResultPartition對應的List<ConsumerVertexGroup>,添加ConsumerVertexGroupList<ConsumerVertexGroup> groups =getConsumerVertexGroupsForPartitionInternal(resultPartitionId);groups.add(consumerVertexGroup);
}

? ? ? ? 最終遍歷完所有ExecutionJobVertex節點,完成EdgeManager管理的vertexConsumedPartitions(Map<ExecutionVertexID, List<ConsumedPartitionGroup>>)和partitionConsumers(Map<IntermediateResultPartitionID, List<ConsumerVertexGroup>>)的創建,就完整保存了每個ExecutionJobVertex節點所有并行度上的ExecutionVertex節點與每個IntermediateResult數據集對應IntermediateResultPartition數據集分區的連接關系。

?

3.SchedulingPipelinedRegion劃分

? ? ? ??SchedulingPipelinedRegion是Flink獨立申請資源進行調度的單位,會把一系列通過流水線(pipelined)方式連接的算子組合起來,一起進行資源申請與調度。

? ? ? ? 當完成ExecutionJobVertex節點創建與初始化后,回到DefaultExecutionGraph的attachJobGraph()方法 ,繼續進行SchedulingPipelinedRegion的劃分。

源碼圖解:

?DefaultExecutionGraph.attachJobGraph()方法源碼:

public void attachJobGraph(List<JobVertex> verticesToAttach, JobManagerJobMetricGroup jobManagerJobMetricGroup)throws JobException {assertRunningInJobMasterMainThread();LOG.debug("Attaching {} topologically sorted vertices to existing job graph with {} "+ "vertices and {} intermediate results.",verticesToAttach.size(),tasks.size(),intermediateResults.size());//生成ExecutionJobVertexattachJobVertices(verticesToAttach, jobManagerJobMetricGroup);if (!isDynamic) {//初始化所有ExecutionJobVertexinitializeJobVertices(verticesToAttach);}//將ExecutionGraph的拓撲劃分Region// the topology assigning should happen before notifying new vertices to failoverStrategyexecutionTopology = DefaultExecutionTopology.fromExecutionGraph(this);partitionGroupReleaseStrategy =partitionGroupReleaseStrategyFactory.createInstance(getSchedulingTopology());
}

? ? ? ? 進入DefaultExecutionTopology.fromExecutionGraph()方法中,DefaultExecutionTopology創建了LogicalPipelinedRegion,并將LogicalPipelinedRegion轉換成SchedulingPipelinedRegion。

DefaultExecutionGraph.attachJobGraph()方法源碼:

public static DefaultExecutionTopology fromExecutionGraph(DefaultExecutionGraph executionGraph) {checkNotNull(executionGraph, "execution graph can not be null");//獲取EdgeManagerEdgeManager edgeManager = executionGraph.getEdgeManager();//創建LogicalPipelinedRegionDefaultExecutionTopology schedulingTopology =new DefaultExecutionTopology(() ->IterableUtils.toStream(executionGraph.getAllExecutionVertices()).map(ExecutionVertex::getID).collect(Collectors.toList()),edgeManager,//創建LogicalPipelinedRegioncomputeLogicalPipelinedRegionsByJobVertexId(executionGraph));//將LogicalPipelinedRegion轉換成SchedulingPipelinedRegionschedulingTopology.notifyExecutionGraphUpdated(executionGraph,IterableUtils.toStream(executionGraph.getVerticesTopologically()).filter(ExecutionJobVertex::isInitialized).collect(Collectors.toList()));return schedulingTopology;
}

? ? ? ? 進入DefaultExecutionTopology的computeLogicalPipelinedRegionsByJobVertexId()方法繼續分析LogicalPipelinedRegion的創建。首先DefaultExecutionTopology先對JobVertex節點進行排序,再根據JobVertex節點生成LogicalPipelinedRegion,最后再將把每個LogicalVertex關聯用其對于的LogicalPipelinedRegion。

DefaultExecutionTopology.computeLogicalPipelinedRegionsByJobVertexId()方法源碼:

private static Map<JobVertexID, DefaultLogicalPipelinedRegion>computeLogicalPipelinedRegionsByJobVertexId(final ExecutionGraph executionGraph) {//獲取拓撲排序后的JobVertex列表List<JobVertex> topologicallySortedJobVertices =IterableUtils.toStream(executionGraph.getVerticesTopologically()).map(ExecutionJobVertex::getJobVertex).collect(Collectors.toList());//通過JobVertex生成LogicalPipelinedRegionIterable<DefaultLogicalPipelinedRegion> logicalPipelinedRegions =DefaultLogicalTopology.fromTopologicallySortedJobVertices(topologicallySortedJobVertices).getAllPipelinedRegions();//把每個LogicalVertex關聯其LogicalPipelinedRegionMap<JobVertexID, DefaultLogicalPipelinedRegion> logicalPipelinedRegionsByJobVertexId =new HashMap<>();for (DefaultLogicalPipelinedRegion logicalPipelinedRegion : logicalPipelinedRegions) {for (LogicalVertex vertex : logicalPipelinedRegion.getVertices()) {logicalPipelinedRegionsByJobVertexId.put(vertex.getId(), logicalPipelinedRegion);}}return logicalPipelinedRegionsByJobVertexId;
}

? ? ? ? 通過JobVertex節點生成LogicalPipelinedRegion是依次調用DefaultLogicalTopology的getAllPipelinedRegions()方法、LogicalPipelinedRegionComputeUtil的computePipelinedRegions()方法,最終進入PipelinedRegionComputeUtil的buildRawRegions()方法。

DefaultLogicalTopology.getAllPipelinedRegions()方法源碼:

public Iterable<DefaultLogicalPipelinedRegion> getAllPipelinedRegions() {//繼續調用LogicalPipelinedRegionComputeUtil.computePipelinedRegions()final Set<Set<LogicalVertex>> regionsRaw =LogicalPipelinedRegionComputeUtil.computePipelinedRegions(verticesSorted);final Set<DefaultLogicalPipelinedRegion> regions = new HashSet<>();for (Set<LogicalVertex> regionVertices : regionsRaw) {regions.add(new DefaultLogicalPipelinedRegion(regionVertices));}return regions;
}

LogicalPipelinedRegionComputeUtil.computePipelinedRegions()方法源碼:

public static Set<Set<LogicalVertex>> computePipelinedRegions(final Iterable<? extends LogicalVertex> topologicallySortedVertices) {//繼續調用final Map<LogicalVertex, Set<LogicalVertex>> vertexToRegion =PipelinedRegionComputeUtil.buildRawRegions(topologicallySortedVertices,LogicalPipelinedRegionComputeUtil::getMustBePipelinedConsumedResults);// Since LogicalTopology is a DAG, there is no need to do cycle detection nor to merge// regions on cycles.return uniqueVertexGroups(vertexToRegion);
}

? ? ? 在PipelinedRegionComputeUtil的buildRawRegions()方法中,首先遍歷所有JobVertex節點,調用LogicalPipelinedRegionComputeUtil的getMustBePipelinedConsumedResults()方法判斷上下游節點是否連接關系是可以合并的,若可合并,且上下游節點不在一個Region,則直接合并。

PipelinedRegionComputeUtil.buildRawRegions()方法源碼:

static <V extends Vertex<?, ?, V, R>, R extends Result<?, ?, V, R>>Map<V, Set<V>> buildRawRegions(final Iterable<? extends V> topologicallySortedVertices,final Function<V, Iterable<R>> getMustBePipelinedConsumedResults) {final Map<V, Set<V>> vertexToRegion = new IdentityHashMap<>();//遍歷所有JobVertex節點// iterate all the vertices which are topologically sortedfor (V vertex : topologicallySortedVertices) {//把節點加入當前RegionSet<V> currentRegion = new HashSet<>();currentRegion.add(vertex);vertexToRegion.put(vertex, currentRegion);//調用LogicalPipelinedRegionComputeUtil的getMustBePipelinedConsumedResults()方法判斷上下游節點是否連接關系是可以合并的// Each vertex connected through not mustBePipelined consumingConstraint is considered// as a// single region.for (R consumedResult : getMustBePipelinedConsumedResults.apply(vertex)) {final V producerVertex = consumedResult.getProducer();final Set<V> producerRegion = vertexToRegion.get(producerVertex);if (producerRegion == null) {throw new IllegalStateException("Producer task "+ producerVertex.getId()+ " failover region is null"+ " while calculating failover region for the consumer task "+ vertex.getId()+ ". This should be a failover region building bug.");}//若可合并,且上下游節點不在一個Region,則直接合并// check if it is the same as the producer region, if so skip the merge// this check can significantly reduce compute complexity in All-to-All// PIPELINED edge caseif (currentRegion != producerRegion) {currentRegion =VertexGroupComputeUtil.mergeVertexGroups(currentRegion, producerRegion, vertexToRegion);}}}return vertexToRegion;
}

? ? ? ? 其中判斷是否可以合并的方法為LogicalPipelinedRegionComputeUtil的getMustBePipelinedConsumedResults()方法,判斷是根據本JobVertex節點與上游IntermediateDataSet數據集的連接關系的ResultPartitionType,來判斷是否可以Pipeline連接。

private static Iterable<LogicalResult> getMustBePipelinedConsumedResults(LogicalVertex vertex) {List<LogicalResult> mustBePipelinedConsumedResults = new ArrayList<>();//獲取本JobVertex與所有上游IntermediateDataSet數據集的連接關系for (LogicalResult consumedResult : vertex.getConsumedResults()) {//根據本JobVertex與上游IntermediateDataSet數據集的連接關系的ResultPartitionType判斷是否可以Pipeline連接if (consumedResult.getResultType().mustBePipelinedConsumed()) {mustBePipelinedConsumedResults.add(consumedResult);}}return mustBePipelinedConsumedResults;
}

? ? ? ? 當把ExecutionGraph劃分好LogicalPipelinedRegionComputeUtil并轉換為SchedulingPipelinedRegion后,JobMaster將依次為每個SchedulingPipelinedRegion向Flink的ResourceManager申請cpu內存資源,進行計算資源調度。

4.結語

? ? ? ? 至此,ExecutionGraph生成的完整源碼已解析完畢,本文解析了ExecutionGraph的ExecutionJobVertex節點、ExecutionVertex節點、IntermediateResult數據集、IntermediateResultPartition數據集分區與封裝Task執行信息的Execution的創建;解析了ExecutionJobVertex節點與前置的IntermediateResult數據集的連接,及SchedulingPipelinedRegion的劃分。本專欄的下篇博文將繼續從Flink JobMaster 依次為每個SchedulingPipelinedRegion進行計算資源調度分配,來繼續解析Flink的完整源碼。

?

?

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/92539.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/92539.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/92539.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

19、閾值分割+blob分析

目錄 一、仿射變換 1.變換矩陣 2.在矩陣的基礎上添加各種變換形式 3.開始變換 4.計算變換矩陣參數 新算子 二、閾值分割 新算子 三、blob分析案例 1.焊點 2.石頭 3.木材 4.車牌 5.骰子 新算子 一、仿射變換 1.變換矩陣 // 產生仿射變換矩陣hom_mat2d_identity…

破解 Django N+1 查詢困境:使用 select_related 與 prefetch_related 實踐指南

破解 Django N+1 查詢困境:使用 select_related 與 prefetch_related 實踐指南 開篇引入 數據庫查詢性能常常是 Web 應用性能瓶頸中的重中之重。Django ORM 以簡潔直觀的 API 層將 Python 代碼與數據庫打通,卻也可能因默認的惰性加載帶來 N+1 查詢問題,造成不必要的網絡往…

深入解析K-means聚類:從原理到調優實戰

一、聚類分析與K-means的核心價值在無監督學習領域&#xff0c;聚類分析是探索數據內在結構的核心技術。?K-means算法因其簡潔高效成為最廣泛使用的聚類方法&#xff0c;在客戶分群、圖像壓縮、生物信息學等領域應用廣泛。其核心目標是將數據集劃分為K個簇&#xff0c;實現“簇…

數據結構基礎:哈希表、排序和查找算法

目錄 一、哈希表 1.哈希算法 2.哈希碰撞 3.哈希表 4.哈希表相關操作 哈希表插入 哈希表遍歷 元素查找 哈希表銷毀 二、排序算法 1. 排序算法對比 2. 排序算法實現 冒泡排序 選擇排序 插入排序 希爾排序 快速排序 三、查找算法 1. 查找算法對比 2. 查找算法實…

Linux內核參數調優:為K8s節點優化網絡性能

在高并發微服務環境中&#xff0c;網絡性能往往成為K8s集群的瓶頸。本文將深入探討如何通過精細化的Linux內核參數調優&#xff0c;讓你的K8s節點網絡性能提升30%以上。引言&#xff1a;為什么網絡調優如此重要&#xff1f;作為一名在生產環境中維護過數千節點K8s集群的運維工程…

全家桶” 戰略如何重塑智能服務標準?無憂秘書 AI + 智腦 + 數字人協同模式的底層架構解析

在數字化浪潮的推動下&#xff0c;企業對智能化服務的需求日益增長。然而&#xff0c;單一的技術或產品往往難以滿足復雜場景下的多樣化需求。近年來&#xff0c;“全家桶”戰略成為科技行業的一大趨勢&#xff0c;通過整合多維度技術與服務&#xff0c;為企業提供全方位的支持…

前端后端之爭?JavaScript和Java的特性與應用場景解析

一、名字相似&#xff0c;本質迥異 1.1 歷史淵源與命名背景 在編程世界中&#xff0c;很少有兩種語言像JavaScript和Java這樣&#xff0c;僅僅因為名字的相似性就引發了無數初學者的困惑。然而&#xff0c;這種相似性純屬巧合——或者說是一種營銷策略的產物。 JavaScript誕…

【文獻分享】Machine learning models提供數據和代碼

數據輸入及前期信息&#xff1a;ChronoGauge 需要一個基因表達矩陣&#xff0c;其中包括來自多個時間進程 RNA-測序實驗的觀測數據&#xff0c;用于訓練&#xff0c;并且需要有關每個基因在連續光照&#xff08;LL&#xff09;條件下經過光暗&#xff08;LD&#xff09;周期調整…

PHP MySQL Delete 操作詳解

PHP MySQL Delete 操作詳解 引言 在Web開發中&#xff0c;數據庫是存儲和管理數據的重要工具。PHP作為一種流行的服務器端腳本語言&#xff0c;與MySQL數據庫結合使用可以高效地處理數據。本文將詳細介紹PHP中如何使用DELETE語句刪除MySQL數據庫中的數據。 什么是DELETE語句&am…

計組-大/小端存放區別

在計算機系統中&#xff0c;大端存放&#xff08;Big-Endian&#xff09;和小端存放&#xff08;Little-Endian&#xff09;是兩種不同的多字節數據存儲方式&#xff0c;主要區別在于字節在內存中的排列順序。理解它們對底層編程&#xff08;如網絡通信、二進制文件處理、硬件交…

線程同步相關知識

文章目錄一、線程同步的核心目標二、線程安全的判定條件三、同步方式一&#xff1a;synchronized 關鍵字1. 同步代碼塊2. 同步方法四、鎖的釋放與不釋放場景1. 自動釋放鎖的場景2. 不會釋放鎖的場景五、同步方式二&#xff1a;ReentrantLock&#xff08;顯式鎖&#xff09;1. 核…

Armoury Crate無法通過BIOS卸載

設備&#xff1a;天選4 Armoury Crate窗口反復彈出影響使用體驗&#xff0c;但無法通過BIOS關閉該怎么辦&#xff1f;本文以天選4為例提供解決方案。 Step1&#xff1a;進入服務支持官網 Armoury Crate-服務支持 下滑點擊”查看更多” 下載安裝卸載工具 得到Armoury_Crate_Un…

如何將視頻轉為GIF格式,3大視頻轉為GIF工具

在社交媒體和即時通訊盛行的當下&#xff0c;GIF 動圖以其獨特的魅力備受青睞。它能夠生動地捕捉視頻中的精彩瞬間&#xff0c;憑借體積小巧、無需復雜加載且可循環播放的特性&#xff0c;成為了人們在網絡交流中表達情感、分享趣事的得力工具。無論是制作詼諧幽默的表情包&…

開發避坑指南(22):Vue3響應式編程中this綁定機制與解決方案

錯誤信息 TypeError: Cannot read properties of undefined (reading find) TypeError: r.vnode.el.querySelector is not a function報錯背景 vue2項目升級到vue3后&#xff0c;原來的代碼報錯。 報錯代碼computed: {/** 計算列的顯示與隱藏*/columnVisible() {return functio…

AI學習筆記三十五:實時傳輸視頻

若該文為原創文章&#xff0c;轉載請注明原文出處。 目的是實現視頻的傳輸&#xff0c;只是個demo. 程序分為兩部分&#xff0c;視頻接收端和視頻發送端。 一、視頻接收端流程分析 主要流程&#xff1a; 初始化配置&#xff1a; 設置UDP端口&#xff08;5001&#xff09;和緩…

【ArcGIS】分區統計中出現Null值且Nodata無法忽略的問題以及shp擦除(erase)的使用——以NDVI去水體為例

需求 已有某地NDVI柵格、行政區shp以及水體shp&#xff0c;計算每個行政區的平均NDVI 問題 1.如果不剔除水體 負值NDVI會把平均值拉低 且水體NDVI并不全為負 需要通過shp剔除&#xff0c;Mask掩膜是提取水體本身而不是剩余部分 2.使用分區統計工具&#xff08;Zonal statis…

Linux中的內核同步源碼相關總結

什么是內核同步Linux 內核同步是指內核中用于解決并發執行單元&#xff08;如進程、中斷、內核線程等&#xff09;對共享資源&#xff08;如全局數據結構、硬件寄存器、鏈表等&#xff09;的競爭訪問的一系列機制和技術。其核心目標是保證多個并發單元在操作共享資源時的數據一…

WORD接受修訂,并修改修訂后文字的顏色

在 Word 中&#xff0c;接受修訂之后默認會采用正文的默認字體格式&#xff0c;不會保留修訂時設置的顏色&#xff0c;比如“插入內容是藍色字體”的設置會被清除。 如果你想要做到&#xff1a;? 接受所有修訂后仍然讓“原插入的文字”變為藍色字體保留下來你只能通過一些手動…

行業速覽:中國新能源汽車市場格局與關鍵趨勢

在全球汽車產業邁向綠色、低碳、智能化的變革浪潮中&#xff0c;新能源汽車已成為各國爭奪的戰略高地。中國&#xff0c;作為全球最大的汽車市場和新能源汽車制造國&#xff0c;正以強大的市場規模、完整的產業鏈體系以及快速提升的技術創新能力&#xff0c;在這場變革中不斷加…

【51單片機2個按鍵控制流水燈轉向】2022-10-25

緣由51單片機按鍵流水燈-嵌入式-CSDN問答 #include "REG52.h" sbit k1P3^0; sbit k2P3^1; void main() {unsigned char l0,xd0,ys10,ys20,z0;P1l;while(1){if(k10&&xd0){z0;while(k10);}if(k20&&xd0){z1;while(k20);}if(ys10)if(ys20){if(z0)if(l0)…