17.2 ksm源码讲解

本节重点介绍 :

  • k8s资源对象的 buildStores构造函数注入MetricFamilies
  • k8s client-go 之 Reflector
    • listAndWatch 方法
    • watchHandler 监听更新,调用add等action

架构图总结

image.png

项目地址

  • 地址

go get

 go get -v  -d  k8s.io/kube-state-metrics/v2@v2.1.1

源码分析

main.go 中的主流程

  • 位置 D:\go_path\pkg\mod\k8s.io\kube-state-metrics\v2@v2.1.1\main.go

初始化store.Builder

	storeBuilder := store.NewBuilder()

注册registery

	ksmMetricsRegistry := prometheus.NewRegistry()ksmMetricsRegistry.MustRegister(version.NewCollector("kube_state_metrics"))durationVec := promauto.With(ksmMetricsRegistry).NewHistogramVec(prometheus.HistogramOpts{Name:        "http_request_duration_seconds",Help:        "A histogram of requests for kube-state-metrics metrics handler.",Buckets:     prometheus.DefBuckets,ConstLabels: prometheus.Labels{"handler": "metrics"},}, []string{"method"},)storeBuilder.WithMetrics(ksmMetricsRegistry)

解析命令行中启用的resource

	var resources []stringif len(opts.Resources) == 0 {klog.Info("Using default resources")resources = options.DefaultResources.AsSlice()} else {klog.Infof("Using resources %s", opts.Resources.String())resources = opts.Resources.AsSlice()}
  • 如果没有指定就用默认的 ,位置 D:\go_path\pkg\mod\k8s.io\kube-state-metrics\v2@v2.1.1\pkg\options\resource.go
	// DefaultResources represents the default set of resources in kube-state-metrics.DefaultResources = ResourceSet{"certificatesigningrequests":      struct{}{},"configmaps":                      struct{}{},"cronjobs":                        struct{}{},"daemonsets":                      struct{}{},"deployments":                     struct{}{},"endpoints":                       struct{}{},"horizontalpodautoscalers":        struct{}{},"ingresses":                       struct{}{},"jobs":                            struct{}{},"leases":                          struct{}{},"limitranges":                     struct{}{},"mutatingwebhookconfigurations":   struct{}{},"namespaces":                      struct{}{},"networkpolicies":                 struct{}{},"nodes":                           struct{}{},"persistentvolumes":               struct{}{},"persistentvolumeclaims":          struct{}{},"poddisruptionbudgets":            struct{}{},"pods":                            struct{}{},"replicasets":                     struct{}{},"replicationcontrollers":          struct{}{},"resourcequotas":                  struct{}{},"secrets":                         struct{}{},"services":                        struct{}{},"statefulsets":                    struct{}{},"storageclasses":                  struct{}{},"validatingwebhookconfigurations": struct{}{},"volumeattachments":               struct{}{},}

解析命令行中的启用的namespace

	if len(opts.Namespaces) == 0 {klog.Info("Using all namespace")storeBuilder.WithNamespaces(options.DefaultNamespaces)} else {if opts.Namespaces.IsAllNamespaces() {klog.Info("Using all namespace")} else {klog.Infof("Using %s namespaces", opts.Namespaces)}storeBuilder.WithNamespaces(opts.Namespaces)}
  • 如果没传入,则采集所有namespace的资源对象

根据命令行传入的 metrics 黑白名单进行设置

	allowDenyList, err := allowdenylist.New(opts.MetricAllowlist, opts.MetricDenylist)if err != nil {klog.Fatal(err)}err = allowDenyList.Parse()if err != nil {klog.Fatalf("error initializing the allowdeny list : %v", err)}klog.Infof("metric allow-denylisting: %v", allowDenyList.Status())storeBuilder.WithAllowDenyList(allowDenyList)

最为关键的一步

  • 具体干什么先不讲,先跳过
storeBuilder.WithGenerateStoresFunc(storeBuilder.DefaultGenerateStoresFunc())

创建kubeClient

	kubeClient, vpaClient, err := createKubeClient(opts.Apiserver, opts.Kubeconfig)if err != nil {klog.Fatalf("Failed to create client: %v", err)}storeBuilder.WithKubeClient(kubeClient)
根据apiserver地址+kubeconfig配置文件创建 或者 使用 restclient.InClusterConfig创建client
  • 位置 D:\go_path\pkg\mod\k8s.io\client-go@v0.21.2\tools\clientcmd\client_config.go
func BuildConfigFromFlags(masterUrl, kubeconfigPath string) (*restclient.Config, error) {if kubeconfigPath == "" && masterUrl == "" {klog.Warning("Neither --kubeconfig nor --master was specified.  Using the inClusterConfig.  This might not work.")kubeconfig, err := restclient.InClusterConfig()if err == nil {return kubeconfig, nil}klog.Warning("error creating inClusterConfig, falling back to default config: ", err)}return NewNonInteractiveDeferredLoadingClientConfig(&ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},&ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}}).ClientConfig()
}
  • 默认不传apiserver地址信息,采用inclusterconfig方式创建,启动日志如下
W0820 04:31:20.664175       1 client_config.go:543] Neither --kubeconfig nor --master was specified.  Using the inClusterConfig.  This might not work.
  • inclusterconfig方式验证 sa和token

oklogrun 启动metrichandler

	var g run.Groupm := metricshandler.New(opts,kubeClient,storeBuilder,opts.EnableGZIPEncoding,)// Run MetricsHandler{ctxMetricsHandler, cancel := context.WithCancel(ctx)g.Add(func() error {return m.Run(ctxMetricsHandler)}, func(error) {cancel()})}

metricshandler run

  • 位置 D:\go_path\pkg\mod\k8s.io\kube-state-metrics\v2@v2.1.1\pkg\metricshandler\metrics_handler.go
func (m *MetricsHandler) Run(ctx context.Context) error {autoSharding := len(m.opts.Pod) > 0 && len(m.opts.Namespace) > 0if !autoSharding {klog.Info("Autosharding disabled")m.ConfigureSharding(ctx, m.opts.Shard, m.opts.TotalShards)<-ctx.Done()return ctx.Err()}
  • 默认不开启分片,执行m.ConfigureSharding
func (m *MetricsHandler) ConfigureSharding(ctx context.Context, shard int32, totalShards int) {m.mtx.Lock()defer m.mtx.Unlock()if m.cancel != nil {m.cancel()}if totalShards != 1 {klog.Infof("configuring sharding of this instance to be shard index %d (zero-indexed) out of %d total shards", shard, totalShards)}ctx, m.cancel = context.WithCancel(ctx)m.storeBuilder.WithSharding(shard, totalShards)m.storeBuilder.WithContext(ctx)m.metricsWriters = m.storeBuilder.Build()m.curShard = shardm.curTotalShards = totalShards
}
  • 这里会根据 storeBuilder执行Build方法

请求metric时对应的serveHttp方法

  • 位置 D:\go_path\pkg\mod\k8s.io\kube-state-metrics\v2@v2.1.1\pkg\metricshandler\metrics_handler.go
func (m *MetricsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {m.mtx.RLock()defer m.mtx.RUnlock()resHeader := w.Header()var writer io.Writer = wresHeader.Set("Content-Type", `text/plain; version=`+"0.0.4")if m.enableGZIPEncoding {// Gzip response if requested. Taken from// github.com/prometheus/client_golang/prometheus/promhttp.decorateWriter.reqHeader := r.Header.Get("Accept-Encoding")parts := strings.Split(reqHeader, ",")for _, part := range parts {part = strings.TrimSpace(part)if part == "gzip" || strings.HasPrefix(part, "gzip;") {writer = gzip.NewWriter(writer)resHeader.Set("Content-Encoding", "gzip")}}}for _, w := range m.metricsWriters {w.WriteAll(writer)}// In case we gzipped the response, we have to close the writer.if closer, ok := writer.(io.Closer); ok {closer.Close()}
}
  • 其中最关键的是,遍历 m.metricsWriters 调用WriteAll
	for _, w := range m.metricsWriters {w.WriteAll(writer)}

metricsWriters是何方神圣

  • 是在ConfigureSharding中执行的m.storeBuilder.Build()
m.metricsWriters = m.storeBuilder.Build()

m.storeBuilder.Build()

  • 位置 D:\go_path\pkg\mod\k8s.io\kube-state-metrics\v2@v2.1.1\internal\store\builder.go
// Build initializes and registers all enabled stores.
// It returns metrics writers which can be used to write out
// metrics from the stores.
func (b *Builder) Build() []metricsstore.MetricsWriter {if b.allowDenyList == nil {panic("allowDenyList should not be nil")}var metricsWriters []metricsstore.MetricsWritervar activeStoreNames []stringfor _, c := range b.enabledResources {constructor, ok := availableStores[c]if ok {stores := constructor(b)activeStoreNames = append(activeStoreNames, c)if len(stores) == 1 {metricsWriters = append(metricsWriters, stores[0])} else {metricsWriters = append(metricsWriters, metricsstore.NewMultiStoreMetricsWriter(stores))}}}klog.Infof("Active resources: %s", strings.Join(activeStoreNames, ","))return metricsWriters
}
  • 其中的核心就是遍历 availableStores,执行他们的 buildXXXXStore函数
  • 比如 configmap对应的就是
	"configmaps":                      func(b *Builder) []*metricsstore.MetricsStore { return b.buildConfigMapStores() },
  • 对应就是
func (b *Builder) buildConfigMapStores() []*metricsstore.MetricsStore {return b.buildStoresFunc(configMapMetricFamilies, &v1.ConfigMap{}, createConfigMapListWatch)
}
  • 看到这里发现每个资源对象都会调用 b.buildStoresFunc 注入MetricFamilies

b.buildStoresFunc

  • 这个buildStoresFunc 对应的就是main中
	storeBuilder.WithGenerateStoresFunc(storeBuilder.DefaultGenerateStoresFunc())
  • 底层是 buildStores这个函数
func (b *Builder) buildStores(metricFamilies []generator.FamilyGenerator,expectedType interface{},listWatchFunc func(kubeClient clientset.Interface, ns string) cache.ListerWatcher,
) []*metricsstore.MetricsStore {metricFamilies = generator.FilterMetricFamilies(b.allowDenyList, metricFamilies)composedMetricGenFuncs := generator.ComposeMetricGenFuncs(metricFamilies)familyHeaders := generator.ExtractMetricFamilyHeaders(metricFamilies)if isAllNamespaces(b.namespaces) {store := metricsstore.NewMetricsStore(familyHeaders,composedMetricGenFuncs,)listWatcher := listWatchFunc(b.kubeClient, v1.NamespaceAll)b.startReflector(expectedType, store, listWatcher)return []*metricsstore.MetricsStore{store}}stores := make([]*metricsstore.MetricsStore, 0, len(b.namespaces))for _, ns := range b.namespaces {store := metricsstore.NewMetricsStore(familyHeaders,composedMetricGenFuncs,)listWatcher := listWatchFunc(b.kubeClient, ns)b.startReflector(expectedType, store, listWatcher)stores = append(stores, store)}return stores
}

每种资源都会调用这个 buildStores函数

func (b *Builder) buildConfigMapStores() []*metricsstore.MetricsStore {return b.buildStoresFunc(configMapMetricFamilies, &v1.ConfigMap{}, createConfigMapListWatch)
}

传入三个参数

  • metrics的metricFamilies信息
  • 资源对象结构体
  • 资源对象对应的 ListWatch方法

composedMetricGenFuncs metrics gen方法

  • 生成一个metricGen的方法
  • 然后构造一个MetricsStore
		store := metricsstore.NewMetricsStore(familyHeaders,composedMetricGenFuncs,)
  • 构造一个listWatcher,一并传入 startReflector
		listWatcher := listWatchFunc(b.kubeClient, v1.NamespaceAll)b.startReflector(expectedType, store, listWatcher)

reflector用来watch特定的k8s API资源

func (b *Builder) startReflector(expectedType interface{},store cache.Store,listWatcher cache.ListerWatcher,
) {instrumentedListWatch := watch.NewInstrumentedListerWatcher(listWatcher, b.listWatchMetrics, reflect.TypeOf(expectedType).String())reflector := cache.NewReflector(sharding.NewShardedListWatch(b.shard, b.totalShards, instrumentedListWatch), expectedType, store, 0)go reflector.Run(b.ctx.Done())
}

metrics 更新

  • D:\go_path\pkg\mod\k8s.io\kube-state-metrics\v2@v2.1.1\pkg\metrics_store\metrics_store.go
func (s *MetricsStore) Add(obj interface{}) error {o, err := meta.Accessor(obj)if err != nil {return err}s.mutex.Lock()defer s.mutex.Unlock()families := s.generateMetricsFunc(obj)familyStrings := make([][]byte, len(families))for i, f := range families {familyStrings[i] = f.ByteSlice()}s.metrics[o.GetUID()] = familyStringsreturn nil
}
  • 当有对象更新时,会调用generateMetricsFunc生成对应的指标,塞入map中

k8s 的client-go reflector.watchHandler 监听到资源变化时 调用add

  • 源码位置 D:\go_path\pkg\mod\k8s.io\client-go@v0.21.2\tools\cache\reflector.go
// watchHandler watches w and keeps *resourceVersion up to date.
func (r *Reflector) watchHandler(start time.Time, w watch.Interface, resourceVersion *string, errc chan error, stopCh <-chan struct{}) error {eventCount := 0// Stopping the watcher should be idempotent and if we return from this function there's no way// we're coming back in with the same watch interface.defer w.Stop()loop:for {select {case <-stopCh:return errorStopRequestedcase err := <-errc:return errcase event, ok := <-w.ResultChan():if !ok {break loop}if event.Type == watch.Error {return apierrors.FromObject(event.Object)}if r.expectedType != nil {if e, a := r.expectedType, reflect.TypeOf(event.Object); e != a {utilruntime.HandleError(fmt.Errorf("%s: expected type %v, but watch event object had type %v", r.name, e, a))continue}}if r.expectedGVK != nil {if e, a := *r.expectedGVK, event.Object.GetObjectKind().GroupVersionKind(); e != a {utilruntime.HandleError(fmt.Errorf("%s: expected gvk %v, but watch event object had gvk %v", r.name, e, a))continue}}meta, err := meta.Accessor(event.Object)if err != nil {utilruntime.HandleError(fmt.Errorf("%s: unable to understand watch event %#v", r.name, event))continue}newResourceVersion := meta.GetResourceVersion()switch event.Type {case watch.Added:err := r.store.Add(event.Object)if err != nil {utilruntime.HandleError(fmt.Errorf("%s: unable to add watch event object (%#v) to store: %v", r.name, event.Object, err))}case watch.Modified:err := r.store.Update(event.Object)if err != nil {utilruntime.HandleError(fmt.Errorf("%s: unable to update watch event object (%#v) to store: %v", r.name, event.Object, err))}case watch.Deleted:// TODO: Will any consumers need access to the "last known// state", which is passed in event.Object? If so, may need// to change this.err := r.store.Delete(event.Object)if err != nil {utilruntime.HandleError(fmt.Errorf("%s: unable to delete watch event object (%#v) from store: %v", r.name, event.Object, err))}case watch.Bookmark:// A `Bookmark` means watch has synced here, just update the resourceVersiondefault:utilruntime.HandleError(fmt.Errorf("%s: unable to understand watch event %#v", r.name, event))}*resourceVersion = newResourceVersionr.setLastSyncResourceVersion(newResourceVersion)if rvu, ok := r.store.(ResourceVersionUpdater); ok {rvu.UpdateResourceVersion(newResourceVersion)}eventCount++}}watchDuration := r.clock.Since(start)if watchDuration < 1*time.Second && eventCount == 0 {return fmt.Errorf("very short watch: %s: Unexpected watch close - watch lasted less than a second and no items received", r.name)}klog.V(4).Infof("%s: Watch close - %v total %v items received", r.name, r.expectedTypeName, eventCount)return nil
}

架构图总结

image.png

本节重点介绍 :

  • k8s资源对象的 buildStores构造函数注入MetricFamilies
  • k8s client-go 之 Reflector
    • listAndWatch 方法
    • watchHandler 监听更新,调用add等action

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【Godot4.3】自定义数列类NumList

概述 数列是一种特殊数组。之前写过等比、等差数列、斐波那契等数列的求取函数。今天就汇总到一起&#xff0c;并添加其他的一些数列&#xff0c;比如平方数、立方数、三角形数等。 这里我首先采用以前比较喜欢的静态函数库的写法&#xff0c;然后在其基础上改进为基于类继承…

大数据毕业设计选题推荐-国潮男装微博评论数据分析系统-Hive-Hadoop-Spark

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、PHP、.NET、Node.js、GO、微信小程序、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇…

[vulnhub] Jarbas-Jenkins

靶机链接 https://www.vulnhub.com/entry/jarbas-1,232/ 主机发现端口扫描 扫描网段存活主机&#xff0c;因为主机是我最后添加的&#xff0c;所以靶机地址是135的 nmap -sP 192.168.75.0/24 // Starting Nmap 7.93 ( https://nmap.org ) at 2024-09-21 14:03 CST Nmap scan…

Android中高级面试题笔记题理论知识大全(PDF免费下载)

Android中高级面试题笔记题理论知识大全(PDF免费下载) 基本上全覆盖了市面上中大厂的面试题&#xff0c;笔试题。而且持续更新。 而且现在市场行情非常不好&#xff0c;所以多学点&#xff0c;背点面试题&#xff0c;笔记题目总没有坏处&#xff0c;只有好处。想获取更多资料: …

手机上轻松解压并处理 JSON 文件

JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&#xff0c;在手机上有着广泛的应用场景。 首先&#xff0c;在数据传输方面&#xff0c;许多移动应用程序通过网络请求与后端服务器进行交互&#xff0c;而服务器端的 API 接口通常使用 JS…

[Redis][持久化][上][RDB]详细讲解

目录 0.前言1.RDB0.是什么&#xff1f;1.触发机制2.流程说明3.RDB文件的处理4.RDB的优缺点 0.前言 Redis ⽀持 RDB 和 AOF 两种持久化机制&#xff0c;持久化功能有效地避免因进程退出造成数据丢失问题&#xff0c;当下次重启时利⽤之前持久化的⽂件即可实现数据恢复 RDB ->…

Qt/C++ 了解NTFS文件系统,解析MFT主文件表中的常驻属性与非常驻属性

系列文章目录 整个专栏系列是根据GitHub开源项目NTFS-File-Search获取分区所有文件/目录列表的思路。 具体的如下: Qt/C 了解NTFS文件系统&#xff0c;了解MFT(Master File Table)主文件表&#xff08;一&#xff09; 介绍NTFS文件系统&#xff0c;对比通过MFT(Master File Tab…

16、斑马设备的ppocer-4进行文字识别,和opencv-mobile中文显示

基本思想:手上有个斑马设备,是客户的,简单记录一下开发过程和工程项目,同时记录跟着android小哥学习了很多anroid的知识,转ppocr-4参考之前的ppocr-3转换即可,整个框架仍然使用c++ ncnn jni框架推理和现实,图像库使用opencv-mobile 一、首先转paddle-cor-4 到ncnn的框架…

Pointnet++改进59:全网首发MogaBlock(2024最新模块)|用于在纯基于卷积神经网络的模型中进行判别视觉表示学习,具有良好的复杂性和性能权衡

简介:1.该教程提供大量的首发改进的方式,降低上手难度,多种结构改进,助力寻找创新点!2.本篇文章对Pointnet++特征提取模块进行改进,加入MogaBlock,提升性能。3.专栏持续更新,紧随最新的研究内容。 目录 1.理论介绍 2.修改步骤 2.1 步骤一 2.2 步骤二 2.3 步骤三 1.…

pdf怎么删除空白页?分享5个删除pdf页面的方法(批量删除法)

pdf文件因其跨平台、格式稳定的特性&#xff0c;已成为我们工作、学习中不可或缺的一部分。那么在编辑pdf格式文档中&#xff0c;总会遇到一些难题&#xff0c;比如说pdf怎么删除空白页 pdf与word一样&#xff0c;具备了多种编辑功能&#xff0c;只不过是word倾向于编辑&#x…

gitlab集成CI/CD,shell方式部署

目录 1.首先安装好gitlab和gitlab-runner&#xff0c;这两个&#xff0c;看我以往的教程 2.注册新的 Runner 3. 步骤 3.1 Enter the GitLab instance URL (for example, https://gitlab.com/): 3.2 Enter the registration token: 3.3 Enter a description for the runner: 3…

Python模块和包:自定义模块和包③

文章目录 一、模块1.1 什么是模块1.2 创建模块1.3 导入模块1.4 模块的命名空间 二、包2.1 什么是包2.2 创建包2.3 导入包2.4 包的命名空间 三、综合详细例子3.1 项目结构3.2 模块代码student.pycourse.pymanager.py 3.3 主程序代码main.py 3.4 运行结果 四、总结 Python模块和包…

Java 中的运算符重载

在这篇文章中&#xff0c;我们将深入探讨 Java 中 Operator 重载的迷人世界。尽管 Java 本身不支持运算符重载&#xff0c;但我们将发现 Manifold 如何使用该功能扩展 Java。我们将探讨它的好处、局限性和用例&#xff0c;尤其是在科学和数学代码方面。 我们还将探索 Manifold …

高清8k电脑壁纸分享

Hello&#xff01;欢迎各位新老朋友来看小弟博客&#xff0c;祝大家事业顺利&#xff0c;财源广进&#xff01;&#xff01; 主题&#xff1a;高清壁纸分享 文件太大上传不上去&#x1f605;&#x1f605;&#x1f605;&#x1f605;&#x1f605;&#xff0c;需要的朋友自取&…

国货美妆品牌整合营销多少钱?

合作咨询联系竑图 hongtu201988 化妆品品牌线上推广费用到底高不高&#xff0c;需要多少钱&#xff0c;是每个经营者最为关注的问题。结合中小美妆品牌在网络上的费用投入&#xff0c;有一个大致的范围&#xff1a;几千元到几十万不等/年&#xff01;品牌越大&#xff0c;投入越…

什么鬼?主备同步正常,备库查询表空间使用结果却是空的?

作者介绍&#xff1a;老苏&#xff0c;10余年DBA工作运维经验&#xff0c;擅长Oracle、MySQL、PG、Mongodb数据库运维&#xff08;如安装迁移&#xff0c;性能优化、故障应急处理等&#xff09; 公众号&#xff1a;老苏畅谈运维 欢迎关注本人公众号&#xff0c;更多精彩与您分享…

Centos Stream 9根目录扩容

要将 sda 的剩余空间扩展给 cs-root&#xff0c;可以按照以下步骤进行操作。假设你已经有剩余的未分配空间在 sda 上。 步骤 1&#xff1a;查看当前磁盘分区情况 首先&#xff0c;确保你有未分配的空间在 sda 上。 lsblk步骤 2&#xff1a;创建新的分区 使用 fdisk 或 par…

C++vector类的模拟实现

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 模拟实现vector类 收录于专栏【C语法基础】 本专栏旨在分享学习C的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 前置说明 1. vecto…

数据结构2——单链表

目录 1.链表 1.1链表的概念及结构 1.2 链表的分类 ​编辑2.无头单链表的实现 1. 节点 2.遍历链表 3.动态增加新节点 4.查找&#xff08;修改&#xff09; 5.插入 5.1 尾插 5.2 头插 5.3 在pos之前插入x 5.4 在pos之后插入x 6.删除 6.1 尾删 6.2 头删 6.3 删除…

YOLOv10改进,YOLOv10损失函数更换为Powerful-IoU(2024年最新IOU),助力高效涨点

改进前训练结果: 改进后的结果: 摘要 边界框回归(BBR)是目标检测中的核心任务之一,BBR损失函数显著影响其性能。然而,观察到现有基于IoU的损失函数存在不合理的惩罚因子,导致回归过程中锚框扩展,并显著减缓收敛速度。为了解决这个问题,深入分析了锚框扩展的原因。针…