K8s源码分析(一)-K8s调度框架及调度器初始化介绍

本文首发在个人博客上,欢迎来踩!

文章目录

  • 调度框架介绍
  • K8s scheduler 介绍
  • K8s scheduler的初始化
    • Cobra介绍
    • K8s scheduler中初始化的源代码解析

调度框架介绍

这是官方对于v1.27调度框架的介绍文档:https://v1-27.docs.kubernetes.io/docs/concepts/scheduling-eviction/scheduling-framework/

将调度器的实现转化为插件的形式有助于加强调度器的拓展性、灵活性,同时也使得调度核心的实现更加的轻量、可维护。

下图展示了Pod的调度上下文以及调度框架暴露的扩展点。图中“Filter”相当于“Predicate”,“Scoring”相当于“Priority function”。

请添加图片描述

总体而言,首先新创建的Pod或还没有调度的Pod会存在队列中,然后经过调度周期的筛选得到符合条件的Node,然后在调度周期内再对各个符合条件的Node进行打分,最高分的Node就是需要调度到的Node,然后经过绑定周期将Pod放置到Node上。

各个拓展点的具体介绍建议参考上面提到的官方介绍文档,这里不再赘述。

K8s scheduler 介绍

首先需要明确的一个点,K8s中的scheduler是以pod的形式运行在系统中的,通过如下的命令能找到其对应的pod。

# kubectl get pod -n kube-system
NAME                                       READY   STATUS             RESTARTS         AGE
...
kube-scheduler-master                      1/1     Running            0                2d4h
...

Pod中的容器会存在一个scheduler程序并一直在前台运行,接收要调度的pod并给出调度结果。本文主要分析的也就是这个scheduler程序所对应的源代码。

这是官方对K8s scheduler代码层次结构的介绍文档:Scheduler code hierarchy overview。也很推荐观看!

整体的关键代码的结构如下所示:

.
├── cmd
│   └── kube-scheduler
│       └── app - 控制器代码位置以及命令行接口参数定义(遵循所有Kubernetes控制器的标准设置)
├── pkg
│   └── scheduler - 默认调度器代码库的根目录
│       ├── core - 默认调度算法的位置
│       ├── framework - 调度框架及其插件
│       └── internal - 缓存、队列和其他内部元素的实现
├── staging
│   └── src
│       └── k8s.io
│           └── kube-scheduler - ComponentConfig API类型的所在位置
└── test├── e2e│   └── scheduling - 端到端调度测试│├── integration├── scheduler - 调度器集成测试└── scheduler_perf - 调度性能基准测试

K8s scheduler的初始化

Cobra介绍

K8s中大部分组件其实都采用的是Cobra结构。Cobra是一个用于创建现代命令行应用程序的库,云原生中很多项目都采用了它,包括Kubernetes、Hugo、GitHub CLI等,目前都有36.2k个start了。而K8s中的scheduler实际上也是通过Cobra构建的。

Cobra的具体介绍可以参见万字长文——Go 语言现代命令行框架 Cobra 详解。

这边以一个小demo为例进行简单介绍。一个demo项目定义了一个名为hugo的命令行工具,代码如下所示:

.
├── cmd
│   ├── root.go
│   └── version.go
├── go.mod
├── go.sum
└── main.go

main.go的内容如下:

package mainimport ("hugo/cmd"
)func main() {cmd.Execute()
}

root.go的内容如下:

package cmdimport ("fmt""os""github.com/spf13/cobra"
)var rootCmd = &cobra.Command{Use:   "hugo",Short: "Hugo is a very fast static site generator",Long: `A Fast and Flexible Static Site Generator built withlove by spf13 and friends in Go.Complete documentation is available at https://gohugo.io`,RunE: func(cmd *cobra.Command, args []string) error {fmt.Println("run hugo...")return nil},
}func Execute() {if err := rootCmd.Execute(); err != nil {fmt.Println(err)os.Exit(1)}
}

version.go的内容如下:

package cmdimport ("fmt""github.com/spf13/cobra"
)var versionCmd = &cobra.Command{Use:   "version",Short: "Print the version number of Hugo",Long:  `All software has versions. This is Hugo's`,RunE: func(cmd *cobra.Command, args []string) error {fmt.Println("Hugo Static Site Generator v0.9 -- HEAD")return nil},
}func init() {rootCmd.AddCommand(versionCmd)
}

可以看到main.go的主要内容就是调用root.go中的Execute()函数,然后这个函数又是调用cobra定义的rootCmd对其进行执行。rootCmd是一个cobra.Command类,它定义时写了自己的说明文本,然后Run函数是最关键的,定义了自己的运行内容,也就是打印一句字符,这就是单独在命令行中输入hugo后需要执行的程序。如果想要进行命令嵌套,那么就得像version.go文件中的处理方法一样再定义另一个cobra的cmd变量versionCmd ,然后通过AddCommand函数就可以加入进去,如此之后就可以通过hugo version来运行versionCmd 中的Run对应的函数。

项目build之后得到执行文件hugo,运行结果如下

# ./hugo 
run hugo..
# ./hugo -h
A Fast and Flexible Static Site Generator built withlove by spf13 and friends in Go.Complete documentation is available at https://gohugo.ioUsage:hugo [flags]hugo [command]Available Commands:completion  Generate the autocompletion script for the specified shellhelp        Help about any commandversion     Print the version number of HugoFlags:-h, --help   help for hugoUse "hugo [command] --help" for more information about a command.
# ./hugo version
Hugo Static Site Generator v0.9 -- HEAD

K8s scheduler中初始化的源代码解析

K8s的scheduler也是类似于上面的hugo程序,只不过更加复杂。

首先在cmd/kube-scheduler/scheduler.go:29中我们能看见scheduler的入口函数:

func main() {command := app.NewSchedulerCommand()code := cli.Run(command)os.Exit(code)
}

这里也是通过app.NewSchedulerCommand得到了一个cobra.Command 类,然后让这个类运行起来。

具体看cmd/kube-scheduler/app/server.go:76

// NewSchedulerCommand creates a *cobra.Command object with default parameters and registryOptions
func NewSchedulerCommand(registryOptions ...Option) *cobra.Command {opts := options.NewOptions()cmd := &cobra.Command{Use: "kube-scheduler",Long: `The Kubernetes scheduler is a control plane process which assigns
Pods to Nodes. The scheduler determines which Nodes are valid placements for
each Pod in the scheduling queue according to constraints and available
resources. The scheduler then ranks each valid Node and binds the Pod to a
suitable Node. Multiple different schedulers may be used within a cluster;
kube-scheduler is the reference implementation.
See [scheduling](https://kubernetes.io/docs/concepts/scheduling-eviction/)
for more information about scheduling and the kube-scheduler component.`,RunE: func(cmd *cobra.Command, args []string) error {return runCommand(cmd, opts, registryOptions...)},Args: func(cmd *cobra.Command, args []string) error {for _, arg := range args {if len(arg) > 0 {return fmt.Errorf("%q does not take any arguments, got %q", cmd.CommandPath(), args)}}return nil},}//...return cmd
}

这里定义了一个cobra.Command,与之前的示例类似,主要的内容还是在runCommand中。

查看其对应的内容,cmd/kube-scheduler/app/server.go:121

// runCommand runs the scheduler.
func runCommand(cmd *cobra.Command, opts *options.Options, registryOptions ...Option) error {verflag.PrintAndExitIfRequested()// Activate logging as soon as possible, after that// show flags with the final logging configuration.if err := logsapi.ValidateAndApply(opts.Logs, utilfeature.DefaultFeatureGate); err != nil {fmt.Fprintf(os.Stderr, "%v\n", err)os.Exit(1)}cliflag.PrintFlags(cmd.Flags())ctx, cancel := context.WithCancel(context.Background())defer cancel()go func() {stopCh := server.SetupSignalHandler()<-stopChcancel()}()cc, sched, err := Setup(ctx, opts, registryOptions...)if err != nil {return err}// add feature enablement metricsutilfeature.DefaultMutableFeatureGate.AddMetrics()return Run(ctx, cc, sched)
}

前面的内容主要是一些配置文件,其中最主要的初始化配置函数是Setup(ctx, opts, registryOptions...) ,初始化完毕后就会返回一个scheduler。

具体的内容在cmd/kube-scheduler/app/server.go:309 ,对这部分代码的一些解释放在了注释里。

// Setup creates a completed config and a scheduler based on the command args and options
func Setup(ctx context.Context, opts *options.Options, outOfTreeRegistryOptions ...Option) (*schedulerserverconfig.CompletedConfig, *scheduler.Scheduler, error) {// 尝试获取默认的调度器配置if cfg, err := latest.Default(); err != nil {return nil, nil, err} else {opts.ComponentConfig = cfg // 如果没有错误,将配置赋值给opts}// 验证opts中的选项是否有效if errs := opts.Validate(); len(errs) > 0 {return nil, nil, utilerrors.NewAggregate(errs) // 如果有验证错误,返回它们}// 从opts创建一个调度器的配置对象c, err := opts.Config(ctx)if err != nil {return nil, nil, err}// 从调度器配置对象中获取完整的配置cc := c.Complete()// 创建一个用于存放外部插件的注册表outOfTreeRegistry := make(runtime.Registry)for _, option := range outOfTreeRegistryOptions {if err := option(outOfTreeRegistry); err != nil {return nil, nil, err}}// 获取事件记录器工厂recorderFactory := getRecorderFactory(&cc)// 创建一个空的调度器配置概要切片completedProfiles := make([]kubeschedulerconfig.KubeSchedulerProfile, 0)// 使用一系列参数和配置选项创建一个新的调度器实例sched, err := scheduler.New(cc.Client,                                 // 客户端对象cc.InformerFactory,                       // Informer工厂cc.DynInformerFactory,                    // 动态Informer工厂recorderFactory,                          // 事件记录器工厂ctx.Done(),                               // 上下文取消通道scheduler.WithComponentConfigVersion(cc.ComponentConfig.TypeMeta.APIVersion),  // 组件配置版本scheduler.WithKubeConfig(cc.KubeConfig),                                      // Kube配置scheduler.WithProfiles(cc.ComponentConfig.Profiles...),                       // 调度器配置概要scheduler.WithPercentageOfNodesToScore(cc.ComponentConfig.PercentageOfNodesToScore), // 节点评分百分比scheduler.WithFrameworkOutOfTreeRegistry(outOfTreeRegistry),                // 外部插件注册表scheduler.WithPodMaxBackoffSeconds(cc.ComponentConfig.PodMaxBackoffSeconds),  // Pod最大退避秒数scheduler.WithPodInitialBackoffSeconds(cc.ComponentConfig.PodInitialBackoffSeconds),  // Pod初始退避秒数scheduler.WithPodMaxInUnschedulablePodsDuration(cc.PodMaxInUnschedulablePodsDuration), // Pod在不可调度Pod列表中的最大持续时间scheduler.WithExtenders(cc.ComponentConfig.Extenders...),  // 扩展器scheduler.WithParallelism(cc.ComponentConfig.Parallelism),  // 并行度scheduler.WithBuildFrameworkCapturer(func(profile kubeschedulerconfig.KubeSchedulerProfile) {// 在框架实例化期间处理概要以设置默认插件和配置,并捕获它们以记录日志completedProfiles = append(completedProfiles, profile)}),)if err != nil {return nil, nil, err}// 记录或写入配置和概要信息if err := options.LogOrWriteConfig(klog.FromContext(ctx), opts.WriteConfigTo, &cc.ComponentConfig, completedProfiles); err != nil {return nil, nil, err}// 返回完整的配置和调度器实例return &cc, sched, nil
}

得到scheduler后运行的函数还是在后面的Run(ctx, cc, sched)里。

查看其对应的内容,cmd/kube-scheduler/app/server.go:150 ,补充了一部分解释放在代码的注释里。

// Run executes the scheduler based on the given configuration. It only returns on error or when context is done.
func Run(ctx context.Context, cc *schedulerserverconfig.CompletedConfig, sched *scheduler.Scheduler) error {logger := klog.FromContext(ctx) // 从上下文中获取日志记录器// 为了帮助调试,立即记录版本信息logger.Info("Starting Kubernetes Scheduler", "version", version.Get())// 记录 Golang 的设置,这些环境变量会影响 Go 运行时的行为logger.Info("Golang settings", "GOGC", os.Getenv("GOGC"), "GOMAXPROCS", os.Getenv("GOMAXPROCS"), "GOTRACEBACK", os.Getenv("GOTRACEBACK"))// Configz 注册,Configz 允许通过 HTTP 端点公开当前的配置if cz, err := configz.New("componentconfig"); err == nil {cz.Set(cc.ComponentConfig) // 设置调度器的组件配置} else {return fmt.Errorf("unable to register configz: %s", err) // 如果注册失败,返回错误}// 启动事件处理流水线cc.EventBroadcaster.StartRecordingToSink(ctx.Done()) // 开始录制事件defer cc.EventBroadcaster.Shutdown()                   // 延后关闭事件广播// 设置健康检查var checks []healthz.HealthCheckerif cc.ComponentConfig.LeaderElection.LeaderElect {checks = append(checks, cc.LeaderElection.WatchDog) // 如果启用了领导者选举,添加 WatchDog 健康检查}// 等待领导者选举的通道waitingForLeader := make(chan struct{})isLeader := func() bool {select {case _, ok := <-waitingForLeader:// 如果通道关闭,我们是领导者return !okdefault:// 通道是打开的,我们正在等待领导者return false}}// 启动健康检查服务器if cc.SecureServing != nil {// 构建处理函数链handler := buildHandlerChain(newHealthzAndMetricsHandler(&cc.ComponentConfig, cc.InformerFactory, isLeader, checks...), cc.Authentication.Authenticator, cc.Authorization.Authorizer)// 启动安全服务器,注意处理返回的 stoppedCh 和 listenerStoppedChif _, _, err := cc.SecureServing.Serve(handler, 0, ctx.Done()); err != nil {return fmt.Errorf("failed to start secure server: %v", err) // 如果启动失败,返回错误}}// 启动所有的 informercc.InformerFactory.Start(ctx.Done()) // 启动 informer 工厂// DynInformerFactory 可以在测试中为 nilif cc.DynInformerFactory != nil {cc.DynInformerFactory.Start(ctx.Done()) // 启动动态 informer 工厂}// 等待所有缓存同步后再进行调度cc.InformerFactory.WaitForCacheSync(ctx.Done()) // 等待 informer 工厂的缓存同步if cc.DynInformerFactory != nil {cc.DynInformerFactory.WaitForCacheSync(ctx.Done()) // 等待动态 informer 工厂的缓存同步}// 如果启用了领导者选举,通过 LeaderElector 运行直到完成并退出if cc.LeaderElection != nil {// 设置领导者选举的回调cc.LeaderElection.Callbacks = leaderelection.LeaderCallbacks{OnStartedLeading: func(ctx context.Context) {close(waitingForLeader) // 关闭等待领导者的通道,表示我们现在是领导者sched.Run(ctx)           // 运行调度器},OnStoppedLeading: func() {select {case <-ctx.Done():// 我们被请求终止。退出 0。logger.Info("Requested to terminate, exiting")os.Exit(0)default:// 我们失去了锁。logger.Error(nil, "Leaderelection lost")klog.FlushAndExit(klog.ExitFlushTimeout, 1)}},}// 创建新的领导者选举leaderElector, err := leaderelection.NewLeaderElector(*cc.LeaderElection)if err != nil {return fmt.Errorf("couldn't create leader elector: %v", err) // 如果创建失败,返回错误}leaderElector.Run(ctx) // 运行领导者选举return fmt.Errorf("lost lease") // 如果失去租约,返回错误}// 领导者选举被禁用,因此内联运行直到完成close(waitingForLeader) // 关闭等待领导者的通道sched.Run(ctx)           // 运行调度器return fmt.Errorf("finished without leader") // 如果没有领导者,返回错误
}

这个函数首先设置日志记录器,记录版本和 Golang 环境设置,然后注册配置以供调试使用。接着,它启动事件处理流水线,并设置健康检查和健康检查服务器。之后,函数启动 informer 并等待缓存同步。如果配置了领导者选举,它会通过领导者选举器运行调度器,否则直接运行调度器。如果在任何步骤中出现错误,函数会返回该错误。

查看sched.Run(ctx) 这部分调度器实际运行的内容,pkg/scheduler/scheduler.go:355 ,补充了一部分解释放在代码的注释里。

// Run begins watching and scheduling. It starts scheduling and blocked until the context is done.
func (sched *Scheduler) Run(ctx context.Context) {// 启动调度队列,这将允许调度器观察新的、需要调度的 Podssched.SchedulingQueue.Run()// We need to start scheduleOne loop in a dedicated goroutine,// because scheduleOne function hangs on getting the next item// from the SchedulingQueue.// If there are no new pods to schedule, it will be hanging there// and if done in this goroutine it will be blocking closing// SchedulingQueue, in effect causing a deadlock on shutdown.// 翻译:// 我们需要在一个独立的 goroutine 中启动 scheduleOne 循环,// 因为 scheduleOne 函数在从 SchedulingQueue 获取下一个项目时会挂起。// 如果没有新的 Pods 需要调度,它会在那里挂起,// 如果在这个 goroutine 中执行,它将阻止关闭 SchedulingQueue,// 从而在关闭时造成死锁。go wait.UntilWithContext(ctx, sched.scheduleOne, 0)// 当上下文完成(即 ctx.Done() 通道关闭)时,阻塞直到收到信号<-ctx.Done()// 关闭调度队列,这将停止调度器的事件循环sched.SchedulingQueue.Close()
}

可以看到到了这里就剩下了两个主要的实体:调度队列和调度算法。

  • 调度队列收集需要调度的Pod,然后提交给scheduler调度,具体将在后面进行介绍。

  • go wait.UntilWithContext(ctx, sched.scheduleOne, 0)启用了一个go协程,然后负责一个一个调度pod。注意一下go wait.UntilWithContext ,它 是 Kubernetes 项目中用于周期性运行函数的工具。它是一个包装了 time.Tickercontext.Context 的机制,允许在给定的时间间隔内重复执行某个函数,直到提供的上下文被取消。函数的基本签名如下:

    func UntilWithContext(ctx context.Context, f func(context.Context), period time.Duration)
    

    ctx.Done() 通道关闭时,wait.UntilWithContext 将停止执行其周期性的任务sched.scheduleOne0 表示两次迭代之间没有间隔,sched.scheduleOne 将尽可能快地被调用。具体sched.scheduleOne的介绍将在后面进行。

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

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

相关文章

使用vue3+ts+vite从零开始搭建bolg(五):layout(持续更新中)

五、layout搭建 5.1静态搭建 在src下创建如图文件夹 这里用logo举例&#xff0c;在scripts里export <script lang"ts">export default {name: Logo,}</script> 然后在layout里引入 //引入左侧菜单顶部用户信息 import Logo from ./logo/index.vue 接…

Spring AOP(概念,使用)

目录 Spring AOPAOP是什么什么是Spring AOPAOP实际开发流程1. 引入依赖2. 编写AOP程序 Spring AOP详解Spring AOP中的核心概念Spring AOP的通知类型六种类型PointCutOrder(切面优先级) Spring AOP AOP是什么 Aspect Oriented Programminig(面向切面编程)切面指的是某一类特定…

“Linux”目录结构and配置网络

了解完命令格式和vi、vim编辑器后&#xff0c;我们来认识一下目录的结构&#xff1a; 一、目录 &#xff08;1&#xff09;目录的特点 windows特点&#xff1a; Windows中有C、D、E盘&#xff0c;每个都是一个根系统 Linux特点&#xff1a; linux中只有一个根&#xff08;单…

C++auto关键字、范围for循环

一、auto关键字 1.1auto简介 在早期C/C中auto的含义是&#xff1a;使用auto修饰的变量&#xff0c;是具有自动存储器的局部变量。 C11中&#xff0c;标准委员会赋予了auto全新的含义即&#xff1a;auto不再是一个存储类型指示符&#xff0c;而是作为一个新的类型指示符来指示编…

记录用python转换headers

转换前 转换后效果 代码如下。注意需要在控制台切换到content.txt所在位置&#xff0c;不然运行代码会报file not found错误 # 假设txt文件内容如下 txt open(content.txt).read()# 使用splitlines()方法将txt内容分割为行&#xff0c;然后使用json.loads()方法将每一行转换为…

每日两题 / 437. 路径总和 III 105. 从前序与中序遍历序列构造二叉树(LeetCode热题100)

437. 路径总和 III - 力扣&#xff08;LeetCode&#xff09; 前序遍历时&#xff0c;维护当前路径&#xff08;根节点开始&#xff09;的路径和&#xff0c;同时记录路径上每个节点的路径和 假设当前路径和为cur&#xff0c;那么ans 路径和(cur - target)的出现次数 /*** D…

【吊打面试官系列】Java高并发篇 - 多线程的价值?

大家好&#xff0c;我是锋哥。今天分享关于 【多线程的价值&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; 多线程的价值&#xff1f; 1、发挥多核 CPU 的优势 多线程&#xff0c;可以真正发挥出多核 CPU 的优势来&#xff0c;达到充分利用 CPU 的目的&#…

Linux学习笔记(Socket)

Linux-Socket 1、基础知识2、服务端3、客户端4、读写操作4.1、读写函数4.2、阻塞IO和非阻塞IO 5、例程 1、基础知识 socket用于计算机之间的网络通信&#xff0c;无论是构建服务器还是客户端&#xff0c;我们仅需要三个信息&#xff0c;服务器的ip地址&#xff0c;对应进程的端…

openlayers 热力图 天地图

openlayers 实现热力图 样式可调 在https://blog.csdn.net/qq_36287830/article/details/131844745?spm1001.2014.3001.5501基础上改进来的 关键代码 如果你有数据可以不使用for循环 var blurInput document.getElementById("blur");var rediusInput document.g…

PyQt5编写的一个简易图像处理软件

文章目录 1. 简介2. 准备工作3. 主界面设计4. 功能构建5. 总结 1. 简介 通过编写简易图像处理软件&#xff0c;你可以学习如何使用 PyQt5 构建用户界面&#xff0c;以及如何与用户交互。同时&#xff0c;你还可以学习图像处理技术&#xff0c;如图像读取、傅里叶变换、滤波、增…

【NR学习一】NR中的带宽、子载波间隔、PRB数量、FFT点数与采样率之间的关系

NR中的带宽、子载波间隔、PRB数量、FFT点数与采样率之间的运算关系 在5G NR&#xff08;New Radio&#xff09;系统设计中&#xff0c;带宽&#xff08;Bandwidth&#xff09;、子载波间隔&#xff08;Subcarrier Spacing, SCS&#xff09;、资源块&#xff08;Resource Block…

仓库管理流程详解(附作业流程图)

仓库管理流程在企业的日常运营中至关重要。它不仅是物资流转的核心环节&#xff0c;更关乎着企业的运营效率、成本控制和客户服务水平。一个高效、规范的仓库管理流程能够确保货物从入库到出库的各个环节有序进行&#xff0c;减少资源浪费和时间成本&#xff0c;同时帮助企业实…

泽攸科技无掩模光刻机:引领微纳制造新纪元

在当今科技迅猛发展的时代&#xff0c;微纳制造技术正变得越来越重要。泽攸科技作为这一领域的先行者&#xff0c;推出了其创新的无掩模光刻机&#xff0c;这一设备在微电子制造、微纳加工、MEMS、LED、生物芯片等多个高科技领域展现出了其独特的价值和广泛的应用前景。 技术革…

数据分析(二)——导入外部数据,导入Excel数据,CSV文件,txt文件,HTML网页,数据抽取,DataFrame对象的loc属性与iloc属性

一.导入外部数据 1.导入.xIs或.xIsx文件 pd.read_ excel(io,sheet_ name,header) 1.1常用参数说明 ●io:表示.xIs或.xIsx文件路径或类文件对象 ●sheet name:表示工作表&#xff0c;取值如下表所示 ●header:默认值为0&#xff0c;取第一行的值为列名&#xff0c;数据为除列…

手撸XXL-JOB(四)——远程调用定时任务

Java Socket网络编程 网络编程是Java编程中的重要组成部分&#xff0c;包括服务端和客户端两部分内容。Socket是Java网络编程的基本组件之一&#xff0c;用于在应用程序之间提供双向通信&#xff0c;Socket提供了一种标准的接口&#xff0c;允许应用程序通过网络发送和接收数据…

数据中台管理系统原型

数据中台是一个通用性的基础平台&#xff0c;适用于各类行业场景&#xff0c;数据中台包含多元数据汇聚、数据标准化、数据开发、数据共享、数据智能、数据资产管理等功能&#xff0c;助力企业数字化转型。 数据汇聚 数据汇聚是将不同系统、不同类型的多元源数据汇聚至目标数据…

20.接口自动化-Git

1、Git和SVN–版本控制系统 远程服务出问题后&#xff0c;可以先提交commit到本地仓库&#xff0c;之后再提交push远程仓库 git有clone Git环境组成部分 常用Git代码仓库服务-远程仓库 GitHub-服务器在国外&#xff0c;慢 GitLab-开源&#xff0c;可以在自己服务器搭建&…

真JAVA代码审计之XSS漏洞

Part1 漏洞案例demo&#xff1a; 没有java代码审计XSS漏洞拿赏金的案例。 所以将就看看demo吧 漏洞原理&#xff1a;关于XSS漏洞的漏洞原理核心其实没啥好说的&#xff0c;网上一查一大堆。 反射性XSS漏洞 <% page language"java" contentType"text/ht…

2. 感知机算法和简单 Python 实现

目录 1. 感知机介绍 1.1 背景 1.2 定义 1.2.1 权重 1.2.2 阈值 1.2.3 偏置 1.3 逻辑处理&#xff1a;与门、非门、或门 2. 感知机实现 2.1 与门的 Python 实现 2.2 非门的 Python 实现 2.3 或门的 Python 实现 1. 感知机介绍 1.1 背景 感知机1957年由 Rosenblatt 提…

【全开源】JAVA国际版多语言语聊大厅语音聊天APP系统源码

国际版多语言语聊大厅语音聊天APP系统&#xff1a;跨越语言的界限&#xff0c;连接世界的声音 在全球化日益加速的今天&#xff0c;语言不再是沟通的障碍。我们很高兴地宣布&#xff0c;全新的“国际版多语言语聊大厅语音聊天APP系统”已经正式上线&#xff0c;旨在为全球用户…