k8s-scheduler 解析

学习文档

官网的k8s上关于scheduler的文档基本可以分为这两部分

介绍 scheduler 的基本概念

介绍 scheduler 的配置 KubeSchedulerConfiguration 的参数

介绍 scheduler 的命令行参数

调度框架解析

Scheduling-framework 解析

kube-scheduler 选择 node 通过下面这两步

  1. 过滤(Filtering)

  2. 打分(Scoring)

为了提高 kube-scheduler 的效率,我们其实没有必要遍历集群中所有的node之后再挑选出一个合适的node进行容器的调度,这里 KubeSchedulerConfiguration 提供了一个参数

apiVersion: kubescheduler.config.k8s.io/v1alpha1
kind: KubeSchedulerConfiguration
algorithmSource:provider: DefaultProvider...percentageOfNodesToScore: 50

percentageOfNodesToScore 就表示在集群中需要进行评分的node的比例,这里指定为 50 表示只需要扫描比较集群中一半的node即可

为了让所有的node都接受公平性调度,scheduler 调度节点按照轮询的策略进行,比如下面这些node

Zone 1: Node 1, Node 2, Node 3, Node 4
Zone 2: Node 5, Node 6

Scheduler 计算的策略是按照 node1 -> node5 -> node2 -> node6 -> node3 -> node4 来进行的

第一次筛选如果只到node6,则下次筛选会从 node3 进行,依次循环往复遍历到每个节点

Kube-schedluer 的参数,介绍几个关键的参数

--secure-port int     Default: 10259    # 默认的启动端口
--config string     # 配置文件的路径(指定KubeSchedulerConfiguration的路径)

配置参数

Kube-scheduler 的配置统一配置在 KubeSchedulerConfiguration 中

参考官方的一个模板

apiVersion: kubescheduler.config.k8s.io/v1beta2
kind: KubeSchedulerConfiguration
profiles:- schedulerName: multipoint-schedulerplugins:# Disable the default QueueSort pluginqueueSort:enabled:- name: 'CustomQueueSort'disabled:- name: 'DefaultQueueSort'# Enable custom Filter pluginsfilter:enabled:- name: 'CustomPlugin1'- name: 'CustomPlugin2'- name: 'DefaultPlugin2'disabled:- name: 'DefaultPlugin1'# Enable and reorder custom score pluginsscore:enabled:- name: 'DefaultPlugin2'weight: 1- name: 'DefaultPlugin1'weight: 3

调度框架

调度框架由一个个的插件组成,每个pod的调度模块分为两个部分 scheduling cyclebinding cycle

调度循环为一个 pod 选择合适的 node,而绑定循环来执行调度循环选择好的决策

scheduling cyclebinding cycle 共同组成了 scheduling context

注意:scheduling cycle 所属的 plugins 必须是线性运行的,一个接着一个运行;而 binding cycle 所属的 plugins 是可以并行执行的

整体架构如下
在这里插入图片描述

基本流程

编写一个自定义的 scheduler 可分为如下

  1. 编写插件

  2. 部署scheduler到集群里面

  3. 配置使用的插件列表

  4. 下发pod验证

自己部署一个scheduler

clone仓库

我们去 k8s 官网 clone 好对应版本的 k8s 集群的代码仓库
在这里插入图片描述

比如我的版本是1.23

检查编译是否能够通过

Clone 下来之后执行 makefile,注意这里的go版本不要高于当前你编译使用的go版本
在这里插入图片描述

开始编译
在这里插入图片描述

出现下面这个标识未报错则编译成功
在这里插入图片描述

进入到 编译后的产物的目录,找到对应的二进制文件 kube-scheduler 即代表成功
在这里插入图片描述

构建镜像

编写DockerFile

FROM busybox
ADD ./_output/local/bin/linux/amd64/kube-scheduler /usr/local/bin/kube-scheduler

推送到自己的镜像仓库
在这里插入图片描述

查看远程仓库是否有该镜像
在这里插入图片描述

镜像已经上传成功

部署自定义的scheduler

部署自定义的scheduler

apiVersion: v1
kind: ServiceAccount
metadata:name: my-schedulernamespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:name: my-scheduler-as-kube-scheduler
subjects:
- kind: ServiceAccountname: my-schedulernamespace: kube-system
roleRef:kind: ClusterRolename: system:kube-schedulerapiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:name: my-scheduler-as-volume-scheduler
subjects:
- kind: ServiceAccountname: my-schedulernamespace: kube-system
roleRef:kind: ClusterRolename: system:volume-schedulerapiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:name: my-scheduler-extension-apiserver-authentication-readernamespace: kube-system
roleRef:kind: Rolename: extension-apiserver-authentication-readerapiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccountname: my-schedulernamespace: kube-system
---
apiVersion: v1
kind: ConfigMap
metadata:name: my-scheduler-confignamespace: kube-system
data:my-scheduler-config.yaml: |apiVersion: kubescheduler.config.k8s.io/v1beta2kind: KubeSchedulerConfigurationprofiles:- schedulerName: my-schedulerleaderElection:leaderElect: false   
---
apiVersion: apps/v1
kind: Deployment
metadata:labels:component: schedulertier: control-planename: my-schedulernamespace: kube-system
spec:selector:matchLabels:component: schedulertier: control-planereplicas: 1template:metadata:labels:component: schedulertier: control-planeversion: secondspec:serviceAccountName: my-schedulercontainers:- command:- /usr/local/bin/kube-scheduler- --config=/etc/kubernetes/my-scheduler/my-scheduler-config.yamlimage: boomchao/scheduler:1.0livenessProbe:httpGet:path: /healthzport: 10259scheme: HTTPSinitialDelaySeconds: 15name: kube-second-schedulerreadinessProbe:httpGet:path: /healthzport: 10259scheme: HTTPSresources:requests:cpu: '0.1'securityContext:privileged: falsevolumeMounts:- name: config-volumemountPath: /etc/kubernetes/my-schedulerhostNetwork: falsehostPID: falsevolumes:- name: config-volumeconfigMap:name: my-scheduler-config

注意配置在cm里面的 KubeSchedulerConfiguration 的版本

apiVersion: kubescheduler.config.k8s.io/v1beta2    # 这里版本需要注意
kind: KubeSchedulerConfiguration
profiles:- schedulerName: my-scheduler    # 我们自定义的scheduler名称
leaderElection:leaderElect: false   

太高的版本可能报错如下
在这里插入图片描述

因为我们用的是 release-1.23 版本
在这里插入图片描述

用其他版本 alpha 或者 v1 很可能识别不出来

部署成功后查看日志
在这里插入图片描述

部署成功

尝试部署一个pod使用我们自定义的调度器

apiVersion: v1
kind: Pod
metadata:name: annotation-second-schedulerlabels:name: multischeduler-example
spec:schedulerName: my-schedulercontainers:- name: pod-with-second-annotation-containerimage: nginx

部署成功后查看pod上的对应的调度器的名称,可以看到是我们自定义的 scheduler
在这里插入图片描述

插件解析

基本概念

区分三个概念

  • Profiles:配置的调度策略

  • Extension points:细分的调度策略,比如有

    • queueSort:用来对 pending 的 pod(也就是待调度的pod)进行排序
    • preFilter:预过滤,用来在过滤之前对集群或者node做提前处理
    • filter:过滤,用来筛选出具体的 node 进行 pod 的派发
    • postFilter: 后过滤,主要用来应对筛选不成功的情况,也就是没有任何一个 pod 能够匹配上 node
    • score:打分,用来对上面筛选出的 node 进行打分,分数最高的 node 将会承担起运行pod的任务
    • bind:将 pod 绑定到具体的 node 上
  • plugins:用来实现上面具体的调度策略,每个 plugin 必定属于某一个特定的 Extension-points

注意上面的 Extension points,每个Extension point的特性是不同的,比如

  • queueSort 就只支持一个plugin
  • filter 就支持多个 plugin 依次执行
  • postFilter 只要有一个 plugin 对 pod 执行结果是 schedulable,剩余的插件便不会继续执行
  • bind 插件用来将pod与node对应,只要一个bind插件处理了pod,其他多余的bind插件便不会再执行

所有的 Extension points 参考 Scheduler Configuration

插件接口

插件的API接口如下


type Plugin interface {Name() string
}type QueueSortPlugin interface {PluginLess(*PodInfo, *PodInfo) bool
}type PreFilterPlugin interface {PluginPreFilter(CycleState, *v1.Pod) *Status
}

所有的插件都需要先定位到具体作用的位置(也就是调度过程中的哪个 extension-point ),比如是在 queueSort 阶段阶段就只需要实现上面的 QueueSortPlugin 接口即可;在 PreFilter 阶段只需要实现 上面的 PreFilterPlugin 接口即可

编写的插件必须统一注册到原始的 Registry 这个 map 里面

type PluginFactory = func(runtime.Unknown, FrameworkHandle) (Plugin, error)type Registry map[string]PluginFactoryfunc NewRegistry() Registry {return Registry{fooplugin.Name: fooplugin.New,barplugin.Name: barplugin.New,// New plugins are registered here.}
}

实际编写自定义插件的时候,只需要在启动参数指定即可,指定了之后便会自动注册到这个map里面,比如

import (scheduler "k8s.io/kubernetes/cmd/kube-scheduler/app"
)func main() {command := scheduler.NewSchedulerCommand(scheduler.WithPlugin("example-plugin1", ExamplePlugin1),scheduler.WithPlugin("example-plugin2", ExamplePlugin2))if err := command.Execute(); err != nil {fmt.Fprintf(os.Stderr, "%v\n", err)os.Exit(1)}
}

查看代码 scheduler.WithPlugin,实际调用的还是 Register 方法将其注册到总的map里面
在这里插入图片描述

配置文件启用插件

配置文件怎么配置插件开启,每个插件都对应 KubeSchedulerConfiguration 中的一个配置项,对应结构体如下

type KubeSchedulerConfiguration struct {// ... other fieldsPlugins      PluginsPluginConfig []PluginConfig
}type Plugins struct {QueueSort      []PluginPreFilter      []PluginFilter         []PluginPostFilter     []PluginPreScore       []PluginScore          []PluginReserve        []PluginPermit         []PluginPreBind        []PluginBind           []PluginPostBind       []Plugin
}type Plugin struct {Name   stringWeight int // Only valid for Score plugins
}type PluginConfig struct {Name stringArgs runtime.Unknown
}

特别要注意上面的 Plugin.Weight 字段,这个字段只有 score 插件才会有这个分数

默认开启的一些插件

QueueSort

// Less is the function used by the activeQ heap algorithm to sort pods.
// It sorts pods based on their priority. When priorities are equal, it uses
// PodQueueInfo.timestamp.
func (pl *PrioritySort) Less(pInfo1, pInfo2 *framework.QueuedPodInfo) bool {p1 := corev1helpers.PodPriority(pInfo1.Pod)p2 := corev1helpers.PodPriority(pInfo2.Pod)return (p1 > p2) || (p1 == p2 && pInfo1.Timestamp.Before(pInfo2.Timestamp))
}

可以看到 queuSort 的实现是先比较优先级,优先级高的先调度;否则优先级一样则按照时间顺序来进行的,默认越早创建的pod越早进行调度

Filter

NodeName 插件实现了 Filter

// Filter invoked at the filter extension point.
func (pl *NodeName) Filter(ctx context.Context, _ *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {if nodeInfo.Node() == nil {return framework.NewStatus(framework.Error, "node not found")}if !Fits(pod, nodeInfo) {return framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrReason)}return nil
}

比较 pod 的名字是否和 node 的名字相同

Score

ScorePlugin 我们仔细看其实现的接口

type ScorePlugin interface {Plugin// Score is called on each filtered node. It must return success and an integer// indicating the rank of the node. All scoring plugins must return success or// the pod will be rejected.Score(ctx context.Context, state *CycleState, p *v1.Pod, nodeName string) (int64, *Status)// ScoreExtensions returns a ScoreExtensions interface if it implements one, or nil if does not.ScoreExtensions() ScoreExtensions
}// ScoreExtensions is an interface for Score extended functionality.
type ScoreExtensions interface {// NormalizeScore is called for all node scores produced by the same plugin's "Score"// method. A successful run of NormalizeScore will update the scores list and return// a success status.NormalizeScore(ctx context.Context, state *CycleState, p *v1.Pod, scores NodeScoreList) *Status
}

Score 方法用来衡量指标具体的维度值,而 NormalizeScore 方法用来返回维度值具体的分数(1-100)

我们看一个具体的实现

// Score invoked at the Score extension point.
// The "score" returned in this function is the matching number of pods on the `nodeName`,
// it is normalized later.
func (pl *SelectorSpread) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) {.......// 获取节点信息nodeInfo, err := pl.sharedLister.NodeInfos().Get(nodeName)if err != nil {return 0, framework.AsStatus(fmt.Errorf("getting node %q from Snapshot: %w", nodeName, err))}// 返回节点上的pod的数量count := countMatchingPods(pod.Namespace, s.selector, nodeInfo)return int64(count), nil
}// NormalizeScore invoked after scoring all nodes.
// For this plugin, it calculates the score of each node
// based on the number of existing matching pods on the node
// where zone information is included on the nodes, it favors nodes
// in zones with fewer existing matching pods.
func (pl *SelectorSpread) NormalizeScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, scores framework.NodeScoreList) *framework.Status {if skipSelectorSpread(pod) {return nil}countsByZone := make(map[string]int64, 10)maxCountByZone := int64(0)maxCountByNodeName := int64(0)// 按照节点的地域来进行分数的区分;更新每个区域的pod的数量for i := range scores {if scores[i].Score > maxCountByNodeName {maxCountByNodeName = scores[i].Score}nodeInfo, err := pl.sharedLister.NodeInfos().Get(scores[i].Name)if err != nil {return framework.AsStatus(fmt.Errorf("getting node %q from Snapshot: %w", scores[i].Name, err))}zoneID := utilnode.GetZoneKey(nodeInfo.Node())if zoneID == "" {continue}countsByZone[zoneID] += scores[i].Score}for zoneID := range countsByZone {if countsByZone[zoneID] > maxCountByZone {maxCountByZone = countsByZone[zoneID]}}haveZones := len(countsByZone) != 0maxCountByNodeNameFloat64 := float64(maxCountByNodeName)maxCountByZoneFloat64 := float64(maxCountByZone)MaxNodeScoreFloat64 := float64(framework.MaxNodeScore)// 计算最终的分数// 1. 先对分数进行规范化// 2. 计算每个节点的最终分数,考虑到节点的Pod数量和节点所属区域内的pod总数量// 3. 二次平衡节点分数和区域分数for i := range scores {// initializing to the default/max node score of maxPriorityfScore := MaxNodeScoreFloat64if maxCountByNodeName > 0 {fScore = MaxNodeScoreFloat64 * (float64(maxCountByNodeName-scores[i].Score) / maxCountByNodeNameFloat64)}// If there is zone information present, incorporate itif haveZones {nodeInfo, err := pl.sharedLister.NodeInfos().Get(scores[i].Name)if err != nil {return framework.AsStatus(fmt.Errorf("getting node %q from Snapshot: %w", scores[i].Name, err))}zoneID := utilnode.GetZoneKey(nodeInfo.Node())if zoneID != "" {zoneScore := MaxNodeScoreFloat64if maxCountByZone > 0 {zoneScore = MaxNodeScoreFloat64 * (float64(maxCountByZone-countsByZone[zoneID]) / maxCountByZoneFloat64)}fScore = (fScore * (1.0 - zoneWeighting)) + (zoneWeighting * zoneScore)}}scores[i].Score = int64(fScore)}return nil
}

zoneWeight 的值为 2/3,也即代表着节点所属区域分数权重占比2/3,节点本身pod数量的分数权重占比 1/3

Bind

对应代码如下

// New creates a DefaultBinder.
func New(_ runtime.Object, handle framework.Handle) (framework.Plugin, error) {return &DefaultBinder{handle: handle}, nil
}// Bind binds pods to nodes using the k8s client.
func (b DefaultBinder) Bind(ctx context.Context, state *framework.CycleState, p *v1.Pod, nodeName string) *framework.Status {klog.V(3).InfoS("Attempting to bind pod to node", "pod", klog.KObj(p), "node", nodeName)binding := &v1.Binding{ObjectMeta: metav1.ObjectMeta{Namespace: p.Namespace, Name: p.Name, UID: p.UID},Target:     v1.ObjectReference{Kind: "Node", Name: nodeName},}err := b.handle.ClientSet().CoreV1().Pods(binding.Namespace).Bind(ctx, binding, metav1.CreateOptions{})if err != nil {return framework.AsStatus(err)}return nil
}// Handle provides data and some tools that plugins can use. It is
// passed to the plugin factories at the time of plugin initialization. Plugins
// must store and use this handle to call framework functions.
type Handle interface {....// IterateOverWaitingPods acquires a read lock and iterates over the WaitingPods map.IterateOverWaitingPods(callback func(WaitingPod))// GetWaitingPod returns a waiting pod given its UID.GetWaitingPod(uid types.UID) WaitingPod// RejectWaitingPod rejects a waiting pod given its UID.// The return value indicates if the pod is waiting or not.RejectWaitingPod(uid types.UID) bool// ClientSet returns a kubernetes clientSet.ClientSet() clientset.Interface// KubeConfig returns the raw kube config.KubeConfig() *restclient.Config// EventRecorder returns an event recorder.EventRecorder() events.EventRecorderSharedInformerFactory() informers.SharedInformerFactory....
}

代码也很直观,选择好node之后,将这个pod 的 NodeName 字段与传入的参数做绑定

这里我们注意一个参数就是 state *framework.CycleState 另外一个就是构造参数传入的 framework.Handle

CyCleState 其实就是为当前的scheduling context提供一个上下文,因为binding cycles是可以并行执行的,插件可以通过这个参数来区分是否在处理正确的请求;其次是可以通过这个变量在不同的 extension-point 之间传递一些必要的信息,类似于golang直接上下文传递的 context.WithValue

framework.Handle 的主要作用就是里面提供了访问k8s的API,比如 clientset,informer,以及提供接口用于 approve/deny pending 的 pod

一次完整的Scheduling-context流程

查看 scheduler 的方法

// Run begins watching and scheduling. It starts scheduling and blocked until the context is done.
func (sched *Scheduler) Run(ctx context.Context) {sched.SchedulingQueue.Run()wait.UntilWithContext(ctx, sched.scheduleOne, 0)sched.SchedulingQueue.Close()
}

可以看到整体调用流程就是启动一个队列,然后循环执行 schedule 过程,一直等待 ctx 结束然后关闭队列 这三个步骤即可

Kube-scheduler 整个调度流程大概如下
在这里插入图片描述

启动队列

  1. 定义一个优先队列
  2. 从 informer 里面获取相关的 pod 事件
  3. 塞入队列
  4. 供下游 scheduler 处理

源码分析

// Profiles are required to have equivalent queue sort plugins.
lessFn := profiles[c.profiles[0].SchedulerName].QueueSortFunc()
podQueue := internalqueue.NewSchedulingQueue(lessFn,c.informerFactory,.......
)

可以看到定义的 QueueSort 插件主要是用在了这个队列上

Scheduler 会启动一个 pod 的 Informer 然后触发 eventHandler 之后就会入队

func addAllEventHandlers(sched *Scheduler,informerFactory informers.SharedInformerFactory,dynInformerFactory dynamicinformer.DynamicSharedInformerFactory,gvkMap map[framework.GVK]framework.ActionType,
) {// scheduled pod cacheinformerFactory.Core().V1().Pods().Informer().AddEventHandler(cache.FilteringResourceEventHandler{FilterFunc: func(obj interface{}) bool {.....},// 这里添加到scheduler的缓存中Handler: cache.ResourceEventHandlerFuncs{AddFunc:    sched.addPodToCache,        UpdateFunc: sched.updatePodInCache,DeleteFunc: sched.deletePodFromCache,},},)// unscheduled pod queueinformerFactory.Core().V1().Pods().Informer().AddEventHandler(cache.FilteringResourceEventHandler{FilterFunc: func(obj interface{}) bool {......},// 这里添加到scheduler的队列中Handler: cache.ResourceEventHandlerFuncs{AddFunc:    sched.addPodToSchedulingQueue,UpdateFunc: sched.updatePodInSchedulingQueue,DeleteFunc: sched.deletePodFromSchedulingQueue,},},).....
}

开启调度流程

  1. 获取待调度的 pod

  2. 判断该pod是否是被删除的或者是AssumePod

  3. 为每个 pod 构造 Framework 以及 CycleState

  4. 进行节点的筛选获取最终的结果(也就是这个pod应该调度到具体哪个 node 上)

    1. 找到所有可行的node

      1. 运行 preFilter, filter,postFilter 等插件
    2. 对上面的Node进行打分

      1. 运行 preScore, score 等插件
    3. 按照分数选中最合适的node

  5. 修改 pod 的状态为 Assume,表示已经执行过 score 阶段了,避免被二次操作

  6. 运行后置插件

    1. 运行 reserve 插件
    2. 运行 permit 插件
    3. 异步执行绑定插件,执行 prebind、bind、postBind 等插件

具体是怎么应用插件的呢,我们来以一个 filter 插件来进行举例

// 主方法执行
feasibleNodes, diagnosis, err := g.findNodesThatFitPod(ctx, extenders, fwk, state, pod)func (g *genericScheduler) findNodesThatFitPod(ctx context.Context, extenders []framework.Extender, fwk framework.Framework, state *framework.CycleState, pod *v1.Pod) ([]*v1.Node, framework.Diagnosis, error) {...// 获取所有的node信息allNodes, err := g.nodeInfoSnapshot.NodeInfos().List()if err != nil {return nil, diagnosis, err}.......// 挑选合适的nodefeasibleNodes, err := g.findNodesThatPassFilters(ctx, fwk, state, pod, diagnosis, allNodes)if err != nil {return nil, diagnosis, err}feasibleNodes, err = findNodesThatPassExtenders(extenders, pod, feasibleNodes, diagnosis.NodeToStatusMap)if err != nil {return nil, diagnosis, err}return feasibleNodes, diagnosis, nil
}

仔细查看 findNodesThatPassFilters 代码

func (g *genericScheduler) findNodesThatPassFilters(........nodes []*framework.NodeInfo) ([]*v1.Node, error) {numNodesToFind := g.numFeasibleNodesToFind(int32(len(nodes)))// Create feasible list with enough space to avoid growing it// and allow assigning.feasibleNodes := make([]*v1.Node, numNodesToFind)....checkNode := func(i int) {// We check the nodes starting from where we left off in the previous scheduling cycle,// this is to make sure all nodes have the same chance of being examined across pods.nodeInfo := nodes[(g.nextStartNodeIndex+i)%len(nodes)]// 运行所有的过滤插件,这里会记录状态status := fwk.RunFilterPluginsWithNominatedPods(ctx, state, pod, nodeInfo)if status.Code() == framework.Error {errCh.SendErrorWithCancel(status.AsError(), cancel)return}// 如果运行成功才会把结果记录到下面的两个变量// 一个是可用的node数量加1// 一个是添加到可用的nodeSlice中if status.IsSuccess() {length := atomic.AddInt32(&feasibleNodesLen, 1)if length > numNodesToFind {cancel()atomic.AddInt32(&feasibleNodesLen, -1)} else {feasibleNodes[length-1] = nodeInfo.Node()}} else {....}}.........feasibleNodes = feasibleNodes[:feasibleNodesLen]if err := errCh.ReceiveError(); err != nil {statusCode = framework.Errorreturn nil, err}return feasibleNodes, nil
}

参考文档

https://kubernetes.io/docs/tasks/extend-kubernetes/configure-multiple-schedulers/

https://v1-29.docs.kubernetes.io/docs/reference/scheduling/config/#multiple-profiles

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

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

相关文章

前端简历1v1修改: 优化项目经验

今天有人找我优化前端简历,分享一下如何优化项目经验描述。这是修改前的版本:项目为Web前端开发,但描述为APP应用,包含某某功能。起初我感到困惑,因为前端技术栈使用Vue,为何项目类型是APP?后来…

K8S企业级应用与DaemonSet实战解析

目录 一、概述 二、YAML文件详解 三、企业应用案例 3.1 环境准备 3.2 扩缩容 3.3 滚动更新 3.4 回滚 四、自定义更新策略 4.1类型 4.2 设置方式 4.3 配置案例 一、 DaemonSet 概述 DaemonSet 工作原理 Daemonset 典型的应用场景 DaemonSet 与 Deployment 的区别…

Celery在Django中的应用

Celery在Django中的应用一、项目配置二、异步任务2.1 普通用法2.1.1 通过delay2.1.2 通过apply_async2.2 高级用法2.2.1 任务回调(Callback)2.2.2 任务链(Chaining)2.2.3 任务组(Group)2.2.4 任务和弦&…

DeepSeek生成的高精度大数计算器

# 高精度计算器(精确显示版)1. **精确显示优化**:- 新增print_mpfr()函数专门处理MPFR数值的打印- 自动移除多余的尾随零和小数点- 确保所有浮点结果都以完整十进制形式显示,不使用科学计数法2. **浮点精度修复**:- 所…

08--深入解析C++ list:高效操作与实现原理

1. list介绍1.1. list概述template < class T, class Alloc allocator<T> > class list;Lists are sequence containers that allow constant time insert and erase operations anywhere within the sequence, and iteration in both directions.概述&#xff1…

GraphQL从入门到精通完整指南

目录 什么是GraphQLGraphQL核心概念GraphQL Schema定义语言查询(Queries)变更(Mutations)订阅(Subscriptions)Schema设计最佳实践服务端实现客户端使用高级特性性能优化实战项目 什么是GraphQL GraphQL是由Facebook开发的一种API查询语言和运行时。它为API提供了完整且易于理…

使用 Dockerfile 与 Docker Compose 结合+Docker-compose.yml 文件详解

使用 Dockerfile 与 Docker Compose 结合的完整流程 Dockerfile 用于定义单个容器的构建过程&#xff0c;而 Docker Compose 则用于编排多个容器。以下是结合使用两者的完整方法&#xff1a; 1. 创建 Dockerfile 在项目目录中创建 Dockerfile 定义应用镜像的构建过程&#xff1…

15 ABP Framework 开发工具

ABP Framework 开发工具 概述 该页面详细介绍了 ABP Framework 提供的开发工具和命令行界面&#xff08;CLI&#xff09;&#xff0c;用于创建、管理和定制 ABP 项目。ABP CLI 是主要开发工具&#xff0c;支持项目脚手架、模块添加、数据库迁移管理及常见开发任务自动化。 ABP …

力扣top100(day02-01)--链表01

160. 相交链表 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode(int x) {* val x;* next null;* }* }*/ public class Solution {/*** 查找两个链表的相交节点* param headA 第一个…

LLM 中 语音编码与文本embeding的本质区别

直接使用语音编码,是什么形式,和文本的区别 直接使用语音编码的形式 语音编码是将模拟语音信号转换为数字信号的技术,其核心是对语音的声学特征进行数字化表征,直接承载语音的物理声学信息。其形式可分为以下几类: 1. 基于波形的编码(保留原始波形特征) 脉冲编码调制…

模型选择与调优

一、模型选择与调优在机器学习中&#xff0c;模型的选择和调优是一个重要的步骤&#xff0c;它直接影响到最终模型的性能1、交叉验证在任何有监督机器学习项目的模型构建阶段&#xff0c;我们训练模型的目的是从标记的示例中学习所有权重和偏差的最佳值如果我们使用相同的标记示…

vue+Django农产品推荐与价格预测系统、双推荐+机器学习预测+知识图谱

vueflask农产品推荐与价格预测系统、双推荐机器学习价格预测知识图谱文章结尾部分有CSDN官方提供的学长 联系方式名片 文章结尾部分有CSDN官方提供的学长 联系方式名片 关注B站&#xff0c;有好处&#xff01;编号: D010 技术架构: vueflaskmysqlneo4j 核心技术&#xff1a; 基…

数据分析小白训练营:基于python编程语言的Numpy库介绍(第三方库)(下篇)

衔接上篇文章&#xff1a;数据分析小白训练营&#xff1a;基于python编程语言的Numpy库介绍&#xff08;第三方库&#xff09;&#xff08;上篇&#xff09;&#xff08;十一&#xff09;数组的组合核心功能&#xff1a;一、生成基数组np.arange().reshape() 基础运算功能&…

负载因子(Load Factor) :哈希表(Hash Table)中的一个关键性能指标

负载因子&#xff08;Load Factor&#xff09; 是哈希表&#xff08;Hash Table&#xff09;中的一个关键性能指标&#xff0c;用于衡量哈希表的空间利用率和发生哈希冲突的可能性。一&#xff1a;定义负载因子&#xff08;通常用希腊字母 λ 表示&#xff09;的计算公式为&…

监控插件SkyWalking(一)原理

一、介绍 1、简介 SkyWalking 是一个 开源的 APM&#xff08;Application Performance Monitoring&#xff0c;应用性能监控&#xff09;和分布式追踪系统&#xff0c;主要用于监控、追踪、分析分布式系统中的调用链路、性能指标和日志。 它由 Apache 基金会托管&#xff0c;…

【接口自动化测试】---自动化框架pytest

目录 1、用例运行规则 2、pytest命令参数 3、pytest配置文件 4、前后置 5、断言 6、参数化---对函数的参数&#xff08;重要&#xff09; 7、fixture 7.1、基本用法 7.2、fixture嵌套&#xff1a; 7.3、请求多个fixture&#xff1a; 7.4、yield fixture 7.5、带参数…

Flink Stream API 源码走读 - socketTextStream

概述 本文深入分析了 Flink 中 socketTextStream() 方法的源码实现&#xff0c;从用户API调用到最终返回 DataStream 的完整流程。 核心知识点 1. socketTextStream 方法重载链 // 用户调用入口 env.socketTextStream("hostname", 9999)↓ 补充分隔符参数 env.socket…

待办事项小程序开发

1. 项目规划功能需求&#xff1a;添加待办事项标记完成/未完成删除待办事项分类或标签管理&#xff08;可选&#xff09;数据持久化&#xff08;本地存储&#xff09;2. 实现功能添加待办事项&#xff1a;监听输入框和按钮事件&#xff0c;将输入内容添加到列表。 标记完成/未完…

【C#】Region、Exclude的用法

在 C# 中&#xff0c;Region 和 Exclude 是与图形编程相关的概念&#xff0c;通常在使用 System.Drawing 命名空间进行 GDI 绘图时出现。它们主要用于定义和操作二维空间中的区域&#xff08;几何区域&#xff09;&#xff0c;常用于窗体裁剪、控件重绘、图形绘制优化等场景。 …

机器学习 - Kaggle项目实践(3)Digit Recognizer 手写数字识别

Digit Recognizer | Kaggle 题面 Digit Recognizer-CNN | Kaggle 下面代码的kaggle版本 使用CNN进行手写数字识别 学习到了网络搭建手法学习率退火数据增广 提高训练效果。 使用混淆矩阵 以及对分类出错概率最大的例子单独拎出来分析。 最终以99.546%正确率 排在 86/1035 …