Kubernetes基础(二十六)-kubernetes的eviction机制

eviction,即驱赶的意思,意思是当节点出现异常时,kubernetes将有相应的机制驱赶该节点上的Pod。eviction在openstack的nova组件中也存在。

目前kubernetes中存在两种eviction机制,分别由kube-controller-manager和kubelet实现。

1 kube-controller-manager实现的eviction

kube-controller-manager主要由多个控制器构成,而eviction的功能主要由node controller这个控制器实现。

kube-controller-manager提供了以下启动参数控制eviction

  • pod-eviction-timeout:即当节点宕机该事件间隔后,开始eviction机制,驱赶宕机节点上的Pod,默认为5min
  • node-eviction-rate: 驱赶速率,即驱赶Node的速率,由令牌桶流控算法实现,默认为0.1,即每秒驱赶0.1个节点,注意这里不是驱赶Pod的速率,而是驱赶节点的速率。相当于每隔10s,清空一个节点
  • secondary-node-eviction-rate: 二级驱赶速率,当集群中宕机节点过多时,相应的驱赶速率也降低,默认为0.01
  • unhealthy-zone-threshold:不健康zone阈值,会影响什么时候开启二级驱赶速率,默认为0.55,即当该zone中节点宕机数目超过55%,而认为该zone不健康
  • large-cluster-size-threshold:大集群法制,当该zone的节点多余该阈值时,则认为该zone是一个大集群。大集群节点宕机数目超过55%时,则将驱赶速率降为0.0.1,假如是小集群,则将速率直接降为0

node-controller代码主要位于pkg/controller/node目录下。

1.1 zone

为了控制eviction,kubernete将节点划分为不同的zone,主要通过给节点加label实现

  • failure-domain.beta.kubernetes.io/zone
  • failure-domain.beta.kubernetes.io/region

zone名称由上述的zone和region标签组合而成,两个节点zone和region分别相同,则位于同一个zone,否则不同zone。假如二者都为空,就位于default zone

zone有四种不同的状态

  • stateInitial
  • stateNormal
  • stateFullDisruption
  • statePartialDisruption

初始化状态比较好理解,假如节点刚刚加入集群,它所在的zone刚刚被发现,则该zone的状态是initial,这是一个非常短暂的时间,其余的状态,由以下函数决定:

func (nc *NodeController) ComputeZoneState(nodeReadyConditions []*v1.NodeCondition) (int, zoneState) {readyNodes := 0notReadyNodes := 0for i := range nodeReadyConditions {if nodeReadyConditions[i] != nil && nodeReadyConditions[i].Status == v1.ConditionTrue {readyNodes++} else {notReadyNodes++}}       switch {    case readyNodes == 0 && notReadyNodes > 0:return notReadyNodes, stateFullDisruptioncase notReadyNodes > 2 && float32(notReadyNodes)/float32(notReadyNodes+readyNodes) >= nc.unhealthyZoneThreshold:return notReadyNodes, statePartialDisruptiondefault:return notReadyNodes, stateNormal}
}

注意这里统计的某个zone下面的节点状态,而不是所有。当该zone下面ready的节点为0,而notReady节点大于0时,即认为所有节点都宕机了,所以状态为stateFullDisruption;当notReady节点大于两个,而且notReady节点占比超过unhealthyZoneThreshold,即0.55时,认为是statePartialDisruption,其他情况则认为stateNormal。

 这四种状态会如何影响eviction速度呢?看如下函数:

func (nc *NodeController) setLimiterInZone(zone string, zoneSize int, state zoneState) {switch state {case stateNormal:if nc.useTaintBasedEvictions {nc.zoneNotReadyOrUnreachableTainer[zone].SwapLimiter(nc.evictionLimiterQPS)} else {nc.zonePodEvictor[zone].SwapLimiter(nc.evictionLimiterQPS)}    case statePartialDisruption:if nc.useTaintBasedEvictions {nc.zoneNotReadyOrUnreachableTainer[zone].SwapLimiter(nc.enterPartialDisruptionFunc(zoneSize))} else {nc.zonePodEvictor[zone].SwapLimiter(nc.enterPartialDisruptionFunc(zoneSize))}    case stateFullDisruption:if nc.useTaintBasedEvictions {nc.zoneNotReadyOrUnreachableTainer[zone].SwapLimiter(nc.enterFullDisruptionFunc(zoneSize))} else {nc.zonePodEvictor[zone].SwapLimiter(nc.enterFullDisruptionFunc(zoneSize))}    }    
}//其中enterPartialDisruptionFunc就是函数ReducedQPSFunc
func (nc *NodeController) ReducedQPSFunc(nodeNum int) float32 {if int32(nodeNum) > nc.largeClusterThreshold {return nc.secondaryEvictionLimiterQPS}return 0
} //而enterFullDisruptionFunc是函数HealthyQPSFunc
func (nc *NodeController) HealthyQPSFunc(nodeNum int) float32 {return nc.evictionLimiterQPS 
}   

即加入zone状态是normal,那么速率为0.1,假如zone状态是FullDisruption,速率也是0.1;假如zone是PartialDisruption,假如是大集群,速率为0.0.1,小集群则直接降为0。

1.2 两种不同的eviction方法

目前node controller存在两种不同的eviction方法,即通过taint或者传统方法

if nc.useTaintBasedEvictions {go wait.Until(nc.doTaintingPass, nodeEvictionPeriod, wait.NeverStop)
} else {go wait.Until(nc.doEvictionPass, nodeEvictionPeriod, wait.NeverStop)
}

其中nodeEvictionPeriod为100ms,即每隔100ms就会执行doEvictionPass或doTaintingPass。

1.2.1 传统eviction方法

zonePodEvictor类型为map[string]*RateLimitedTimedQueue,即每个zone都有一个队列,队列带了流控算法,里面存储的是unready的节点,节点上的pod需要被eviction。

func (nc *NodeController) doEvictionPass() {nc.evictorLock.Lock()defer nc.evictorLock.Unlock()for k := range nc.zonePodEvictor {nc.zonePodEvictor[k].Try(func(value TimedValue) (bool, time.Duration) {node, err := nc.nodeLister.Get(value.Value)...nodeUid, _ := value.UID.(string)remaining, err := deletePods(nc.kubeClient, nc.recorder, value.Value, nodeUid, nc.daemonSetStore)...if remaining {glog.Infof("Pods awaiting deletion due to NodeController eviction")}return true, 0})  }
}

deletePods是驱逐节点的主要函数:

func deletePods(kubeClient clientset.Interface, recorder record.EventRecorder, nodeName, nodeUID string, daemonStore extensionslisters.DaemonSetLister) (bool, error) {remaining := falseselector := fields.OneTermEqualSelector(api.PodHostField, nodeName).String()options := metav1.ListOptions{FieldSelector: selector}pods, err := kubeClient.Core().Pods(metav1.NamespaceAll).List(options)var updateErrList []error...for _, pod := range pods.Items {// Defensive check, also needed for tests.if pod.Spec.NodeName != nodeName {continue}// 设置Pod终止理由if _, err = setPodTerminationReason(kubeClient, &pod, nodeName); err != nil {if errors.IsConflict(err) {updateErrList = append(updateErrList,fmt.Errorf("update status failed for pod %q: %v", format.Pod(&pod), err))continue}}// 该Pod正在被删除,忽略if pod.DeletionGracePeriodSeconds != nil {remaining = truecontinue}// 假如该节点是又daemonset管理,则忽略_, err := daemonStore.GetPodDaemonSets(&pod)if err == nil {continue}if err := kubeClient.Core().Pods(pod.Namespace).Delete(pod.Name, nil); err != nil {return false, err}remaining = true}...return remaining, nil
}

底层其实就是delete节点上的Pod,假如Pod是由daemonset管理,则忽略,因为即使删除了,daemonset还是会在该节点上重建。

1.2.2 taint机制

taint机制还处于试验状态,默认不开启,假如要开始,则要在所有组件上设置–feature-gates TaintNodesByCondition=true

当节点状态为unready时,打上node.alpha.kubernetes.io/notReady的taint 当节点状态为unknown时,打上node.alpha.kubernetes.io/unreachable的taint

打上taint后,必然有相应的控制器去处理:

// Run starts NoExecuteTaintManager which will run in loop until `stopCh` is closed.
func (tc *NoExecuteTaintManager) Run(stopCh <-chan struct{}) {   go func(stopCh <-chan struct{}) {for {            item, shutdown := tc.nodeUpdateQueue.Get()if shutdown {break    }            nodeUpdate := item.(*nodeUpdateItem) select {     case <-stopCh:            break    case tc.nodeUpdateChannel <- nodeUpdate:}}}(stopCh)            go func(stopCh <-chan struct{}) {for {item, shutdown := tc.podUpdateQueue.Get()if shutdown {break}podUpdate := item.(*podUpdateItem)select {     case <-stopCh:            breakcase tc.podUpdateChannel <- podUpdate:}}}(stopCh)for {                select {         case <-stopCh:   break        case nodeUpdate := <-tc.nodeUpdateChannel:tc.handleNodeUpdate(nodeUpdate)case podUpdate := <-tc.podUpdateChannel: // If we found a Pod update we need to empty Node queue first.priority:for {        select {case nodeUpdate := <-tc.nodeUpdateChannel:tc.handleNodeUpdate(nodeUpdate)default:break priority            }}// After Node queue is emptied we process podUpdate.tc.handlePodUpdate(podUpdate)}}
}func deletePodHandler(c clientset.Interface, emitEventFunc func(types.NamespacedName)) func(args *WorkArgs) error {return func(args *WorkArgs) error {ns := args.NamespacedName.Namespacename := args.NamespacedName.Nameglog.V(0).Infof("NoExecuteTaintManager is deleting Pod: %v", args.NamespacedName.String())if emitEventFunc != nil { emitEventFunc(args.NamespacedName)}var err error    for i := 0; i < retries; i++ {err = c.Core().Pods(ns).Delete(name, &metav1.DeleteOptions{})if err == nil {           break}time.Sleep(10 * time.Millisecond)}return err}                    
}

本质上还是讲带eviction节点上的pod加入到删除队列上。

2 kubelet eviction机制

kube-controller-manager的eviction机制是粗粒度的,即驱赶一个节点上的所有pod,而kubelet则是细粒度的,它驱赶的是节点上的某些Pod,驱赶哪些Pod与之前讲过的Pod的Qos机制有关。

kubelet的eviction机制,只有当节点内存和磁盘资源紧张时,才会开启,他的目的就是为了回收node节点的资源。之前提过,kubelet还有oom-killer可以回收资源,那为什么还需要eviction呢?这是因为oom-killer将Pod杀掉后,假如Pod的RestartPolicy设置为Always,则kubelet隔段时间后,仍然会在该节点上启动该Pod。而kublet eviction则会将该Pod从该节点上删除。

kubelet提供了以下参数控制eviction

  • eviction-hard:一系列的阈值,比如memory.available<1Gi,即当节点可用内存低于1Gi时,会立即触发一次pod eviction
  • eviction-max-pod-grace-period:eviction-soft时,终止Pod的grace时间
  • eviction-minimum-reclaim:表示每一次eviction必须至少回收多少资源
  • eviction-pressure-transition-period:默认为5分钟,脱离pressure condition的时间,超过阈值时,节点会被设置为memory pressure或者disk pressure,然后开启pod eviction
  • eviction-soft:与hard相对应,也是一系列法制,比如memory.available<1.5Gi。但它不会立即执行pod eviction,而会等待eviction-soft-grace-period时间,假如该时间过后,依然还是达到了eviction-soft,则触发一次pod eviction
  • eviction-soft-grace-period:默认为90秒

2.1 核心代码

kubelet eviction的核心代码就是如下,里面的synchronize就是核心函数。

func (m *managerImpl) Start(diskInfoProvider DiskInfoProvider, podFunc ActivePodsFunc, podCleanedUpFunc PodCleanedUpFunc, nodeProvider NodeProvider, monitoringInterval time.Duration) {// start the eviction manager monitoringgo func() {for {if evictedPods := m.synchronize(diskInfoProvider, podFunc, nodeProvider); evictedPods != nil {glog.Infof("eviction manager: pods %s evicted, waiting for pod to be cleaned up", format.Pods(evictedPods))m.waitForPodsCleanup(podCleanedUpFunc, evictedPods)} else {time.Sleep(monitoringInterval)}       }       }()     
}

2.2 何时检测触发eviction的条件

目前主要由两种机制检测触发eviction的条件

  • 第一种就是定时触发,前面的synchronize位于一个for循环,其中monitoringInterval为10s,也就是每隔10s会去检测出发条件
  • 通过cgroup订阅而触发,也就是假如内存低于阈值,cgroup就会通知kubelet去执行synchronize,内核层通知应用层,通过eventfd实现
if m.config.KernelMemcgNotification && !m.notifiersInitialized { glog.Infof("eviction manager attempting to integrate with kernel memcg notification api")m.notifiersInitialized = true// start soft memory notificationerr = startMemoryThresholdNotifier(m.config.Thresholds, observations, false, func(desc string) {glog.Infof("soft memory eviction threshold crossed at %s", desc)// TODO wait grace period for soft memory limitm.synchronize(diskInfoProvider, podFunc, nodeProvider)})if err != nil {  glog.Warningf("eviction manager: failed to create hard memory threshold notifier: %v", err)}// start hard memory notificationerr = startMemoryThresholdNotifier(m.config.Thresholds, observations, true, func(desc string) { glog.Infof("hard memory eviction threshold crossed at %s", desc)m.synchronize(diskInfoProvider, podFunc, nodeProvider)})if err != nil {glog.Warningf("eviction manager: failed to create soft memory threshold notifier: %v", err)}} 

2.3 资源的回收

kubelet的eviction主要会回收两种资源,内存和磁盘

  • 磁盘回收:主要通过删除已经终止的容器和未使用的镜像
  • 内存回收:主要通过终止正在运行的Pod

2.4 Qos对Eviction的影响

eviction manager会获取该节点上所有的容器,然后根据一定的算法对Pod进行排序,这里看看针对内存如何排序。

// rankMemoryPressure orders the input pods for eviction in response to memory pressure.
func rankMemoryPressure(pods []*v1.Pod, stats statsFunc) {orderedBy(qosComparator, memory(stats)).Sort(pods)
}// qosComparator compares pods by QoS (BestEffort < Burstable < Guaranteed)
func qosComparator(p1, p2 *v1.Pod) int {qosP1 := v1qos.GetPodQOS(p1)qosP2 := v1qos.GetPodQOS(p2)// its a tieif qosP1 == qosP2 {return 0}   // if p1 is best effort, we know p2 is burstable or guaranteedif qosP1 == v1.PodQOSBestEffort {return -1}// we know p1 and p2 are not besteffort, so if p1 is burstable, p2 must be guaranteedif qosP1 == v1.PodQOSBurstable {if qosP2 == v1.PodQOSGuaranteed {  return -1}return 1}// ok, p1 must be guaranteed.return 1
}

从qosComparator函数可以看出,Pod排列顺序为:PodQOSBestEffort < PodQOSBurstable < PodQOSGuaranteed,即首先会回收BestEffort的Pod,然后回收Burstable,最后才会回收Guranteed。

2.5 Eviction的本质

// we kill at most a single pod during each eviction intervalfor i := range activePods {pod := activePods[i]      // If the pod is marked as critical and static, and support for critical pod annotations is enabled,// do not evict such pods. Static pods are not re-admitted after evictions.// https://github.com/kubernetes/kubernetes/issues/40573 has more details.if utilfeature.DefaultFeatureGate.Enabled(features.ExperimentalCriticalPodAnnotation) &&kubelettypes.IsCriticalPod(pod) && kubepod.IsStaticPod(pod) {continue     }status := v1.PodStatus{   Phase:   v1.PodFailed,    Message: fmt.Sprintf(message, resourceToReclaim),Reason:  reason,          }// record that we are evicting the podm.recorder.Eventf(pod, v1.EventTypeWarning, reason, fmt.Sprintf(message, resourceToReclaim))gracePeriodOverride := int64(0)if softEviction {gracePeriodOverride = m.config.MaxPodGracePeriodSeconds}// this is a blocking call and should only return when the pod and its containers are killed.err := m.killPodFunc(pod, status, &gracePeriodOverride)if err != nil {  glog.Warningf("eviction manager: error while evicting pod %s: %v", format.Pod(pod), err)}                return []*v1.Pod{pod}     }

每次最多回收1个Pod。

假如是hardEviction,则PodDeleteGracePeriod设置为0,即立即删除,否则设置为MaxPodGracePeriodSeconds。然后调用killPodFunc删除Pod。

需要注意的是:当kubernetes驱赶Pod的时候,kubernetes并不会重新创建Pod,假如要重新创建Pod,需要借助replicationcontroller、relicaset和deployment等机制。也就是说假如,你直接创建一个Pod,当它被kubernetes驱赶时,该Pod直接被删除了,不会重建。而利用replicationcontroller等机制,由于少了一个Pod,这些控制器就会重新创建一个Pod

 

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

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

相关文章

Delphi之后的接班人?国产可视化编程工具重塑经典

Delphi&#xff0c;这个名字对于许多80后的程序员来说&#xff0c;无疑是一种深深的情怀。它曾是可视化编程的王者&#xff0c;承载着无数开发者的青春记忆。 在Pascal语言盛行的年代&#xff0c;Delphi以其独特的魅力&#xff0c;迅速在编程界崭露头角。当时流传着这样一句话&…

Java实现公司货物订单管理系统 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 客户管理模块2.2 商品维护模块2.3 供应商管理模块2.4 订单管理模块 三、系统展示四、核心代码4.1 查询供应商信息4.2 新增商品信息4.3 查询客户信息4.4 新增订单信息4.5 添加跟进子订单 五、免责说明 一、摘要 1.1 项目…

开启数字内容创作的新时代

目录 技术解析 未来展望 技术解析 Sora是一款由OpenAI开发的先进AI视频模型&#xff0c;其技术架构基于深度学习和自然语言处理技术。该模型的核心算法原理包括使用深度神经网络进行视频内容的理解、生成和互动。 在技术架构方面&#xff0c;Sora采用了一种混合的神经网络结…

架构设计:流式处理与实时计算

引言 随着大数据技术的不断发展&#xff0c;流式处理和实时计算在各行各业中变得越来越重要。那么什么是流式处理呢&#xff1f;我们又该怎么使用它&#xff1f;流式处理允许我们对数据流进行实时分析和处理&#xff0c;而实时计算则使我们能够以低延迟和高吞吐量处理数据。本…

怎么向最厉害的人偷师?

用户孙振楠William&#xff1a; 自己做重大决策&#xff0c;心里常常没底。你觉得有必要跟身边的长辈或者是公司里的上级有意识地建立导师关系吗&#xff1f; 选谁成为人生导师呢&#xff1f; 回答&#xff1a;做好决策的确是一件不容易的事情&#xff0c;你需要懂得决策的基本…

在Spring Boot启动时禁止自动配置数据源相关的组件、@SpringBootApplication

一、SpringBootApplication(exclude {DataSourceAutoConfiguration.class})注解 在Spring Boot启动时禁止自动配置数据源相关的组件。 SpringBootApplication(exclude {DataSourceAutoConfiguration.class})注解的使用案例 这个注解通常应该写在微服务项目的主启动类上&…

Java 面试题基础(四)

Java 面试题基础&#xff08;四&#xff09; 前言1、获取Class对象的构造方法&#xff1f;2、获取Class对象的成员变量&#xff1f;3、获取Class对象的成员方法&#xff1f;4、简述一下你了解的设计模式&#xff1f;5、java中fail-fast和fail-safe的区别说明&#xff1f;6、Jsp…

C#_事件简述

事件模型简述 C#中事件的运行模式为"发布订阅模型"&#xff0c;事件触发者称为"发布者"&#xff0c;事件处理者称为"订阅者" 事件模型的五个组成部分 事件&#xff08;成员&#xff09;事件的拥有者&#xff08;类/对象&#xff09;事件的响应…

【MySQL高可用集群】MySQL的MGR搭建

前情提要&#xff1a; MySQL官方在 5.7.17版本正式推出组复制&#xff08;MySQL Group Replication&#xff0c;简称MGR&#xff09;&#xff0c;使用类似 zookeeper 的多于一半原则。在一个集群由 2N1 个节点共同组成一个复制组&#xff0c;一个事务的提交&#xff0c;必须经过…

蛋白结构预测模型评价指标

欢迎浏览我的CSND博客&#xff01; Blockbuater_drug …点击进入 文章目录 前言一、蛋白结构预测模型评价指标TM-scorelDDT 二、Alphafold中的评价指标pLDDTpTMPAE 三、AlphaFold-multimer 蛋白结构的评价指标DockQipTM 总结参考资料 前言 本文汇总了AlphaFold和AlphaFold-mul…

tigramite教程(二)生物地球科学案例研究

文章目录 数据生成与绘图因果发现分析平稳性假设、确定性、潜在混杂因素结构假设参数假设使用PCMCIplus的滑动窗口分析聚合因果图非参数因果效应估计假设的图形和调整集干预的真实情况假设的参数模型和因果效应的估计使用关于图的不同假设进行估计非因果估计项目地址 这个文件…

android 13.0 屏蔽所有电话来电功能

1.概述 在13.0系统rom定制化开发中,最近项目需要开发需求是屏蔽来电功能,需要根据标志位 屏蔽一切来电功能 就是去掉通话功能,这就需要从通话流程进行分析,然后实现功能 ,而我们知道所有的来电去掉都是CallManager.java来负责监听管理的。 2.屏蔽所有电话来电功能的核心代…

LeetCode_Java_动态规划(2)(题目+思路+代码)

131.分割回文串 给你一个字符串 s&#xff0c;请你将 s 分割成一些子串&#xff0c;使每个子串都是 回文串 。返回 s 所有可能的分割方案。 回文串 是正着读和反着读都一样的字符串。 示例 1&#xff1a; 输入&#xff1a;s "aab" 输出&#xff1a;[["a&qu…

算法简介:查找与算法运行时间

文章目录 1. 二分查找与简单查找1.1 运行时间 2. 旅行商问题 算法是一组完成任务的指令。任何代码片段都可以视为算法。 1. 二分查找与简单查找 二分查找是一种算法&#xff0c;其输入是一个有序的元素列表&#xff0c;如果要查找的元素包含在列表中&#xff0c;二分查找返回…

ActiveMq PUT任意文件上传漏洞(CVE-2016-3088)漏洞复现

ActiveMQ ActiveMQ Web控制台分为三个应用程序&#xff1a;其中admin&#xff0c;api和fileserver&#xff0c;其中admin是管理员页面&#xff0c;api是界面&#xff0c;fileserver是用于存储文件的界面&#xff1b;admin和api需要先登录才能使用&#xff0c;fileserver不需要…

门面模式(Facade Pattern)

定义 门面模式&#xff08;Facade Pattern&#xff09;是一种结构型设计模式&#xff0c;旨在为一个复杂子系统提供一个简单的接口。它提供了一个高层接口&#xff0c;使得客户端可以更容易地使用这个子系统&#xff0c;而不需要了解其内部的复杂性。 示例 #include <ios…

Pytorch安装遇到网络问题

在运行 conda install pytorch1.11.0 torchvision cudatoolkit -c pytorch出现问题 (GAN) D:\0code\vision>conda install pytorch1.11.0 torchvision cudatoolkit -c pytorch Collecting package metadata (current_repodata.json): - DEBUG:urllib3.connectionpool:Star…

元素实现拖拽效果react-dnd+react-dnd-html5-backend

做完案例还是很懵逼 // useDrag 可以让一个 DOM 元素实现拖拽效果 // useDrop 可以让一个 DOM 元素能够放置拖拽元素 参考资料1 //React DnD 参考资料2 https://www.cnblogs.com/dtux/p/17468866.html import React, { useState } from react; import { useDrop, useDra…

Vue开发日志:宏观布局

Vue开发日志&#xff1a;宏观布局 总纲拆分组件化开发&#xff1a;拆分页面模块化开发&#xff1a;拆分功能两者的关系 集成组件传参父组件向子组件传参子组件接收参数子组件向父组件传参父组件接收参数场记 总纲 不识庐山真面目&#xff0c;只缘身在此山中 跳出三界之外&…

TypeScript基础知识点详解

TypeScript基础知识点详解 引言&#xff1a; 在现代前端开发中&#xff0c;TypeScript作为一种静态类型的JavaScript超集&#xff0c;越来越受到开发者的青睐。它提供了类型检查、编译时错误提示、代码重构和智能提示等功能&#xff0c;使得代码更加健壮、可维护。本文将详细介…