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
英文地址,請注明出處:http://en.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 …