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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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)…