【Kubernetes】Operator开发之kubebuilder实战(一)

news2025/1/11 0:40:49

1 什么是Operator

k8s原生提供了很多资源类型,像用于无状态应用的Deployment、ReplicaSet和有状态应用的StatefulSet,用户可以通过向k8s提交yaml文件的方式进行资源的管理,但是,有时候,原生的这些资源无法满足我们的需求,例如,当用户希望在k8s中部署prometheus进行业务监控。

在prometheus的监控体系中,除了二进制程序,重要的就是几个配置文件:prometheus自身要抓取数据的目标、获取到数据后的检测规则等。因此,一种实现方式是将prometheus部署为Pod,并将它们的配置文件放到ConfigMap,prometheus从ConfigMap挂载,当用户修改ConfigMap时,在prometheus的Pod中加一个sidecar容器,该容器检测ConfigMap挂载的文件的变化,如果该文件变化,则调用prometheus接口让prometheus热加载配置,以此来完成配置的变更和加载操作。这种方式的问题是,对于不熟悉prometheus的用户还需要花时间去学习如何配置prometheus,而且,对于业务数据采集的场景下,prometheus的部署和维护可能是运维人员,用户通常只希望提供业务数据采集接口进行接入即可,最重要的是,这种方式不够k8s

k8s提供了一种特殊的CustomResourceDefinition资源类型,用户可以通过这种资源类型向k8s增加新的资源类型,如上例,可以增加PrometheusScrapeTarget、PrometheusRule等资源类型,用户可以用k8s的方式创建这些资源。但是,创建这些资源只是让k8s知道有这个新的资源类型,没有任何组件去驱动这些资源生效,例如,当用户创建PrometehsuScrapeTarget时,在该资源中声明需要采集的Pod的selector,那么,就需要将这些Pod的IP和采集接口放到prometheus的配置文件中并加载,这就需要靠Controller。

因此,用更加k8s的方式实现上述功能,在需要告诉k8s新增资源类型的同时,还需要额外开发Controller,让新增的资源能够生效,这就是Operator,可以简单理解为Operator = CRD + Controller

2 环境准备

2.1 安装golang

在golang下载对应平台的golang压缩包,然后解压到/usr/local目录下:

tar -xf go1.21.3.linux-amd64.tar.gz -C /usr/local/ 

然后在/etc/profile配置环境变量:

export GOROOT=/usr/local/go
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
export GOPROXY=https://goproxy.cn

然后执行. /etc/profile,再执行go version可以查看golang版本:

root@ubuntu:~# go version
go version go1.21.3 linux/amd64

/root/go_projects目录下编写一个简单的helloworld程序测试:

// helloworld.go
package main

import "fmt"

func main()  {
	fmt.Println("Hello, World!")
}

直接go run helloworld.go可以查看输出结果。

2.2 安装kubebuilder
curl -L -o kubebuilder "https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)"
chmod +x kubebuilder && mv kubebuilder /usr/local/bin/

然后执行kubebuilder version查看版本:

root@ubuntu:~/kubebuilder# kubebuilder version
Version: main.version{KubeBuilderVersion:"3.12.0", KubernetesVendor:"1.27.1", GitCommit:"b48f95cd5384eadcdfd02a47a02910f72ddc7ea8", BuildDate:"2023-09-06T06:04:11Z", GoOs:"linux", GoArch:"amd64"}

配置命令自动补全:

# 安装bash-completion
apt install bash-completion

echo "source <(kubebuilder completion bash)" >> /etc/profile

然后在使用kubebuilder过程中就可以通过tab键进行自动补全。

3 Operator Demo

通常一个资源的字段可能会比较多,实现起来会比较复杂,这里为了简化整个开发流程,会使用简单的例子说明。这里开发的是一个类似ReplicaSet的资源,它会负责创建一定数量的Pod,并监控Pod的状态。

3.1 初始化项目
kubebuilder init --domain tutorial.kubebuilder.io --repo github.com/demo
  • GVK:Group(api组,例如apps、batch)、Version(api组的版本,例如v1、v1beta1)、Kind(例如Deployment、CronJob)
  • GVR:Group(api组,例如apps、batch)、Version(api组的版本,例如v1、v1beta1)、Resource(例如deployments、cronjobs),大部分场景下,Kind和Resource是1对1的关系
3.2 创建API
kubebuilder create api --group batch --version v1 --kind Demo

执行上面的命令后会自动创建以下目录和文件:

  • api/v1/
    * demo_types.go Demo的类型定义
    * groupversion_info.go group和version的定义
    * zz_generated.deepcopy.go 深拷贝实现,是runtime.Object接口的自动实现
  • bin/
    * controller-gen
  • config/crd/
    * kustomization.yaml
    * kustomizeconfig.yaml
    * patches/cainjection_in_demos.yaml
    * patches/webhook_in_demos.yaml
  • config/samples/
    * batch_v1_demo.yaml 测试用例
    * kustomization.yaml
  • internal/controller/
    * demo_controller.go 控制器的调谐逻辑
    * suite_test.go 单元测试

1 api/v1/demo_types.go

该文件包含CRD的定义,Demo的结构体跟常规的yaml配置文件对应,一种资源包含4个字段:

  • metav1.TypeMeta 指定GVK(apiVersion和kind)
  • metav1.ObjectMeta metadata部分,包含一些公共属性
  • DemoSpec 用户期望的状态
  • DemoStatus 资源当前实际的状态

2 api/v1/groupversion_info.go

Group和Version的定义以及注册。

3 api/v1/zz_generated.deepcopy.go

runtime.Object接口的实现,主要是DeepCopyObject()的实现。

type Object interface {
    GetObjectKind() schema.ObjectKind
    DeepCopyObject() Object
}

2 internal/controller/demo_controller.go

controller的实现,里面主要的内容是Reconcile()方法:

func (r *DemoReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    _ = log.FromContext(ctx)

    // your logic here

    return ctrl.Result{}, nil
}

创建完成api后,就需要填充Demo的字段以及控制器的实现。

3.3 填充CRD字段

这里实现Demo的简单版本,demo.spec中只有pod的模板和副本数:

type DemoSpec struct {
       // pod模板 
	Template corev1.PodTemplateSpec `json:"template"`

       // 副本数
	Replicas *int64 `json:"replicas"`

       // pod选择器
	Selector map[string]string `json:"selector"`
}

而demo.status中只记录当前副本的数量:

type DemoStatus struct {
	CurrentReplicas *int64 `json:"currentReplicas"`
}

其他的Demo和DemoList不做修改。接下来就需要实现控制器。

3.4 实现控制器

根据Demo的字段定义以及对ReplicaSet的理解,可以设想Demo Controller的逻辑如下:

  • 根据selector在Demo所在命名空间查找Pod,假设查找到N个Pod
  • 如果N < replicas,则依据template模板创建replicas-N个Pod
  • 如果N > replicas,则按照时间顺序删除旧的Pod
  • 将N更新到demo.status.currentReplicas

首先看下kubebuilder给我们生成的脚手架代码。

跟k8s中常规代码一样,控制器的入口在cmd/main.go

func main() {
    // 获取命令行参数
	var metricsAddr string
	var enableLeaderElection bool
	var probeAddr string
	flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
	flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
	flag.BoolVar(&enableLeaderElection, "leader-elect", false,
		"Enable leader election for controller manager. "+
			"Enabling this will ensure there is only one active controller manager.")
	opts := zap.Options{
		Development: true,
	}
	opts.BindFlags(flag.CommandLine)
	flag.Parse()

	ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))

    // 创建控制器管理器,同时传入上面的命令行参数
	mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
		Scheme:                 scheme,
		Metrics:                metricsserver.Options{BindAddress: metricsAddr},
		HealthProbeBindAddress: probeAddr,
		LeaderElection:         enableLeaderElection, // 是否启动选主逻辑,当运行多个控制器时需要开启
		LeaderElectionID:       "3d4b5da1.tutorial.kubebuilder.io",
	})
	if err != nil {
		setupLog.Error(err, "unable to start manager")
		os.Exit(1)
	}

    // 将Demo的控制器加入到控制器管理器
	if err = (&controller.DemoReconciler{
		Client: mgr.GetClient(),
		Scheme: mgr.GetScheme(),
	}).SetupWithManager(mgr); err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "Demo")
		os.Exit(1)
	}
	//+kubebuilder:scaffold:builder

    // 下面两个接口用于Pod的探针配置
    // 控制器增加healthz接口
	if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
		setupLog.Error(err, "unable to set up health check")
		os.Exit(1)
	}

    // 控制器增加ready接口
	if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
		setupLog.Error(err, "unable to set up ready check")
		os.Exit(1)
	}

    // 注册SIGTERM和SIGINT信号,当收到两次信号时程序就会退出
	setupLog.Info("starting manager")
	if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
		setupLog.Error(err, "problem running manager")
		os.Exit(1)
	}
}

这里与我们的控制器相关的代码就是(&controller.DemoReconciler{}).SetupWithManager(mgr)

// SetupWithManager sets up the controller with the Manager.
func (r *DemoReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		For(&batchv1.Demo{}).
		Complete(r)
}

在Complete()中会调用Build(),这里就负责创建Controller和执行ListAndWatch操作:

func (blder *Builder) Build(r reconcile.Reconciler) (controller.Controller, error) {
        if r == nil {
                return nil, fmt.Errorf("must provide a non-nil Reconciler")
        }
        if blder.mgr == nil {
                return nil, fmt.Errorf("must provide a non-nil Manager")
        }
        if blder.forInput.err != nil {
                return nil, blder.forInput.err
        }

        // 创建Controller
        if err := blder.doController(r); err != nil {
                return nil, err
        }

        // 执行watch
        if err := blder.doWatch(); err != nil {
                return nil, err
        }

        return blder.ctrl, nil
}

下面就可以实现我们的控制器:

// req是变化的资源的名称,因此,在调谐逻辑开始时通常会通过名称获取对应的资源
// 返回值除了常规的error,还有一个Result,Result中包含两个元素:
// Requeue bool:表明是否重新入队列处理
// RequeueAfter time.Duration:多久之后重新入队列,如果设置了RequeueAfter则不需要设置Requeue
func (r *DemoReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	_ = log.FromContext(ctx)

	// 1 获取demo资源对象
	var demo batchv1.Demo
	if err := r.Get(ctx, req.NamespacedName, &demo); err != nil {
		log.Log.Error(err, "unable to fetch demo", "demo_name", req.Name)
		// 忽略掉 not-found 错误,通常出现在资源已经被删除时
		return ctrl.Result{}, client.IgnoreNotFound(err)
	} else {
		log.Log.Info("fetch demo success", "demo_name", req.Name)
	}

	// 2 根据资源对象获取Pod数量
	var podList corev1.PodList
	if err := r.List(ctx, &podList, client.InNamespace(req.Namespace), &client.ListOptions{
		LabelSelector: labels.SelectorFromSet(demo.Spec.Selector),
	}); err != nil {
		log.Log.Error(err, "unable to list child Pods", "demo_name", req.Name)
		return ctrl.Result{}, err
	} else {
		log.Log.Info("list child Pods success", "demo_name", req.Name, "pod_cnt", len(podList.Items))
	}

	// 3 对比当前Pod数量和期望数量
	var podCnt int64
	podCnt = int64(len(podList.Items))
	if podCnt < *demo.Spec.Replicas {
		// 当前Pod数量少于期望数量,则创建Pod
		name := fmt.Sprintf("%s-%d", req.Name, time.Now().UnixMicro())

		pod := &corev1.Pod{
			ObjectMeta: metav1.ObjectMeta{
				Labels:      make(map[string]string),
				Annotations: make(map[string]string),
				Name:        name,
				Namespace:   req.Namespace,
			},
			Spec: *demo.Spec.Template.Spec.DeepCopy(),
		}

             // 这里本来想用demo.Spec.template.ObjectMeta.Labels,发现是空的
		for k, v := range demo.Spec.Selector {
			//pod.Labels[k] = v
			pod.ObjectMeta.Labels[k] = v
		}

		if err := r.Create(ctx, pod); err != nil {
			log.Log.Error(err, "unable to create Pod", "pod_name", name)
			return ctrl.Result{}, err
		} else {
			log.Log.Info("create Pod success", "pod_name", name)
			podCnt += 1
		}
	} else {
		// 当前Pod数量大于期望数量,则需要删除多余的Pod
		// 由于可能有正在删除的Pod,因此,在进行数量判断时,需要对正在删除的Pod进行筛选
		podCnt = 0
		var pod_tmp corev1.Pod
		for _, pod_tmp = range podList.Items {
			annotations := pod_tmp.GetAnnotations()
			if annotations != nil {
				flag := false
				for k, v := range annotations {
					if k == "demo-deleting" && v == req.Name {
						flag = true
						break
					}
				}
				if flag == true {
					continue
				}
			}

			if pod_tmp.Status.Phase == corev1.PodRunning {
				podCnt += 1
			}
		}

		if podCnt > *demo.Spec.Replicas {
			// 在删除Pod之前,给Pod加一个Annotations,然后再删除Pod,后续就可以用该Annotations判断Pod是否正在删除中
			annotations := pod_tmp.GetAnnotations()
			if annotations == nil {
				annotations = make(map[string]string)
			}
			annotations["demo-deleting"] = req.Name
			pod_tmp.SetAnnotations(annotations)

			if err := r.Update(ctx, &pod_tmp, &client.UpdateOptions{}); err != nil {
				log.Log.Error(err, "update Pod annotations failed", "pod_name", pod_tmp.Name)
				return ctrl.Result{RequeueAfter: time.Second}, nil
			} else {
				log.Log.Info("update Pod annotations success", "pod_name", pod_tmp.Name)
			}

			if err := r.Delete(ctx, &pod_tmp); err != nil {
				log.Log.Error(err, "delete Pod failed", "pod_name", pod_tmp.Name)
			} else {
				log.Log.Info("delete Pod success", "pod_name", pod_tmp.Name)
				podCnt -= 1
			}

			return ctrl.Result{RequeueAfter: time.Second}, nil
		}
	}

	// 4 更新status状态
	demo.Status.CurrentReplicas = &podCnt
	if err := r.Status().Update(ctx, &demo); err != nil {
		log.Log.Error(err, "unable to update Demo status")
		return ctrl.Result{}, err
	} else {
		log.Log.Info("update Demo status success", "demo_name", demo.Name)
	}

	return ctrl.Result{}, nil
}
3.5 部署测试

代码开发完成后,就需要进行部署测试,其实正常情况下,在未填充任何代码时也应该先部署测试,保证kubebuilder生成的代码可以在当前版本的k8s中使用。

首先看下生成的Makefile提供了哪些命令,执行make help可以看到支持的子命令:

开发:

  • manifests 生成Webhook、ClusterRole和CRD对象,例如config/crd/bases下的CRD文件和config/crd/rbac/下面的文件
  • generate 重新生成zz_generated.deepcopy.go文件
  • fmt 运行go fmt格式化代码
  • vet 运行go vet检查语法错误
  • test 运行项目中的测试代码,主要是控制器目录的test文件

构建:

  • build 构建控制器二进制
  • run 在本地启动控制器
  • docker-build 构建控制器镜像
  • docker-push 推送控制器镜像
  • docker-buildx 构建跨平台的镜像

部署:

  • install 在集群中安装CRD
  • uninstall 在集群中卸载CRD
  • deploy 在集群中部署controller
  • undeploy 在集群中卸载controller

下面就对我们开发的程序部署,跟前面说的一样,Operator主要就是CRD和Controller,部署也是一样:

  • 生成CRD和RBAC:make manifests
  • 为Demo资源重新生成zz_generated.deepcopy.go:make generate
  • 在k8s中创建CRD:make install
  • 本地运行Controller:make run

与常规的Controller不同,这种方式就会在本地启动Controller,该控制器通过~/.kube/config文件去调用k8s api的接口进行交互。

此时,可以在k8s中看到Demo资源:

请添加图片描述

然后可以修改config/samples/batch_v1_demo.yaml文件:

apiVersion: batch.tutorial.kubebuilder.io/v1
kind: Demo
metadata:
  labels:
    app.kubernetes.io/name: demo
    app.kubernetes.io/instance: demo-sample
    app.kubernetes.io/part-of: demo
    app.kubernetes.io/managed-by: kustomize
    app.kubernetes.io/created-by: demo
  name: demo-sample
spec:
  replicas: 2
  selector:
    app: demo-kubebuilder
  template:
    metadata:
      labels:
        app: demo-kubebuilder
    spec:
      containers:
      - name: nginx
        image: nginx

接下来可以将该yaml提交到k8s创建Demo对象,然后就可以查看Controller的日志:

请添加图片描述

从这里面可以看出make run其实就是make manifests + make generate + make fmt + make vet + go run ./cmd/main.go,因此,如果是在本地运行时如果已经将资源对象提交到k8s,后续又没有修改资源对象的字段,只是调整了Controller的代码,可以直接执行make run

执行kubectl edit demo demo-sample修改replicas字段会发现Pod的数量也会随之变化。

3.6 两个问题

对上面的代码测试会发现两个问题:

  • 修改Demo的replicas字段发现Pod的数量会与期望保持一致,但是如果删除Pod,并没有新的Pod重建,这是因为当前Controller只监听了Demo的变化,没有监听Pod的变化
  • Pod的删除是通过先设置Annotations,这种方式不够优雅

4 总结

本文用实现类似ReplicaSet的Demo资源讲解了Operator的开发过程,虽然是很简单的功能,还是需要考虑很多问题,实际的Operator开发更加复杂,考虑的问题更多,虽然k8s提供了Operator这种机制用于扩展资源,但是开发的门槛还是有些高,幸运的是,OperatorHub提供了大量可用的Operator,在开发Operator之前可以先在这里查找下是否已经有现成的Operator可以使用。

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

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

相关文章

【Python】图像和办公文档的处理

图像和办公文档处理 用程序来处理图像和办公文档经常出现在实际开发中&#xff0c;Python的标准库中虽然没有直接支持这些操作的模块&#xff0c;但我们可以通过Python生态圈中的第三方模块来完成这些操作。 操作图像 计算机图像相关知识 颜色。如果你有使用颜料画画的经历&…

Day5力扣打卡

打卡记录 对角线上不同值的数量差&#xff08;矩阵对角线遍历 前缀和&#xff09; 链接 思路&#xff1a;由于任意行 i 与 列 j&#xff0c;满足对角线上 i j t 的关系&#xff0c;t 的范围为 [1 - n, m - 1]&#xff0c;设 s t n&#xff0c;可以得到 s的范围为 [1, n …

vue3后台管理系统之顶部tabbar组件搭建

1.1静态页面搭建 <template><div class"tabbar"><div class"tabbar_left"><!-- 面包屑 --><Breadcrumb /></div><div class"tabbar_right"><!-- 设置 --><Setting /></div></di…

有源RFID标签系统SOC单发芯片——Si24R2E

Si24R2E是针对有源RFID市场设计的低功耗、高性能的2.4GHz标签系统的SoC单芯片&#xff0c;集成嵌入式2.4GHz无线射频发射器模块、128次可编程NVM存储器模块以及自动发射控Z器模块等。 Si24R2E采用GFSK/FSK数字调制与解调技术。数据传输速率可配置&#xff0c;支持2Mbps、1Mbps和…

UML(Unified Modeling Language)统一建模语言,及工具介绍、使用

1. UML介绍&#xff1a; UML&#xff08;Unified Modeling Language&#xff09;统一建模语言。是一种图形化语言。 在UML 2.5 中共包含14种图形&#xff1a;类图、用例图、活动图、对象图、时序图、交互概述图、包图、配置文件图、部署图、组件图、组合结构图、状态机图、通…

[ Windows-Nginx ]Windows服务器,Tomcat容器部署项目,整合Nginx

一、官网下载Nginx http://nginx.org/en/download.html 稳定版&#xff1a;windows的stable版本 注意&#xff1a;Nginx安装包不要放在中文目录下 二、conf目录下&#xff0c;修改nginx.conf文件 修改Nginx服务端口&#xff1a; 默认端口为80&#xff0c;即外界访问的入口…

图论01-【无权无向】-图的基本表示-邻接矩阵/邻接表

文章目录 1. 代码仓库2. 图的基本表示的比较3. 邻接矩阵&#xff1a;Array和TreeSet3.1 图示3.2 Array主要代码解析3.3 测试输出3.4 使用TreeSet的代码 4. 邻接表&#xff1a;LinkedList4.1 图示4.2 LinkedList主要代码解析4.3 测试输出 5. 完整代码5.1 邻接表 - Array5.2 邻接…

Hadoop3教程(二十七):(生产调优篇)HDFS读写压测

文章目录 &#xff08;146&#xff09;HDFS压测环境准备&#xff08;147&#xff09;HDFS读写压测写压测读压测 参考文献 &#xff08;146&#xff09;HDFS压测环境准备 对开发人员来讲&#xff0c;压测这个技能很重要。 假设你刚搭建好一个集群&#xff0c;就可以直接投入生…

屏幕录像推荐:Apeaksoft Screen Recorder 中文 for mac

Apeaksoft Screen Recorder 是一款功能强大的屏幕录制软件&#xff0c;它允许用户在 Windows 和 Mac 系统上捕捉和录制屏幕活动。无论是记录游戏过程、创建教学视频、制作演示文稿还是捕捉在线流媒体内容&#xff0c;该软件都提供了丰富的功能和工具。 以下是 Apeaksoft Scree…

【LeetCode每日一题合集】2023.10.9-2023.10.15(贪心⭐位运算的应用:只出现一次的数字)

文章目录 2578. 最小和分割&#xff08;贪心&#xff09;2731. 移动机器人&#xff08;脑筋急转弯排序统计&#xff09;2512. 奖励最顶尖的 K 名学生&#xff08;哈希表排序&#xff09;&#xff08;练习Java语法&#xff09;代码风格1代码风格2 2562. 找出数组的串联值&#x…

基于Linux的驱动开发:内核模块传参、内核到处符号表、字符设备驱动

内核模块传参 内核模块&#xff1a; int a , b; 安装内核模块时&#xff1a;insmod demo.ko a 100 b 10; 1.内核模块传参的意义 在安装内核模块时给内核模块中的变量进行数值传递&#xff0c;这样可以让我们的内核模块向上兼容更为复杂的应用程序&#xff0c;向下适配多种硬件…

网络协议--ARP:地址解析协议

4.1 引言 本章我们要讨论的问题是只对TCP/IP协议簇有意义的IP地址。数据链路如以太网或令牌环网都有自己的寻址机制&#xff08;常常为48 bit地址&#xff09;&#xff0c;这是使用数据链路的任何网络层都必须遵从的。一个网络如以太网可以同时被不同的网络层使用。例如&#…

解剖—单链表相关OJ练习题

目录 一、移除链表元素 二、找出链表的中间节点 三、合并两个有序链表 四、反转链表 五、求链表中倒数第k个结点 六、链表分割 七、链表的回文结构 八、判断链表是否相交 九、判断链表中是否有环(一) 十、 判断链表中是否有环(二) 注&#xff1a;第六题和第七题牛…

三网话费余额查询的API系统 基于thinkphp6.0框架

本套系统是用thinkphp6.0框架开发的&#xff0c;PHP需大于8.2&#xff0c;系统支持用户中心在线查询和通过API接口对接发起查询&#xff0c;用户余额充值是对接usdt接口&#xff0c;源码全开源&#xff0c;支持懂技术的人二次开发~搭建教程1、源码上传后&#xff0c;吧运行目录…

异常数据检测 | Python基于Hampel的离群点检测

文章目录 文章概述模型描述源码分享文章概述 在时间序列数据分析领域,识别和处理异常点是至关重要的任务。异常点或离群点是明显偏离预期模式的数据点,可能表明存在错误、欺诈或有价值的见解。 应对这一挑战的一种有效技术是汉普尔过滤器(Hampel Filter)。 模型描述 汉…

Manacher学习笔记

Manacher 算法&#xff0c;俗称马拉车算法&#xff0c;是一种解决最长回文子串问题的算法。 在 Ybt 的哈希章节中出现了这个&#xff1a; 数据范围 1 0 6 10^6 106&#xff0c;数据相对于马拉车模板较弱。 对于一个普通的字符串&#xff0c;我们要想求出它的回文子串需要考虑它…

聊天机器人语料在开发中的重要性

语料在聊天机器人的开发中起着至关重要的作用&#xff0c;使其能够有效理解和回应用户的查询。语料是聊天机器人的训练数据&#xff0c;通过分析和学习这个语料&#xff0c;聊天机器人可以提高对用户意图的准确理解&#xff0c;并生成恰当的回应。 | 一、聊天机器人语料好在哪&…

Unity 单例-接口模式

单例-接口模式 使用接口方式实现的单例可以继承其它类&#xff0c;更加方便 using System.Collections; using System.Collections.Generic; using UniRx; using UniRx.Triggers; using UnityEngine; namespace ZYF {public interface ISingleton<TMono> where TMono : M…

Redis-Sentinel高可用架构学习

Redis-Sentinel高可用架构 Redis主从复制过程&#xff1a; 主从同步原理 Redis Sentinel&#xff08;哨兵&#xff09;高可用集群方案&#xff1a;Redis-Sentinel是Redis官方推荐的高可用性(HA)解决方案。 当用Redis做Master-slave的高可用方案时&#xff0c;假如master宕机了…

微信小程序之后台首页交互

目录 一.与后台数据进行交互&request封装 后台准备 测试结果 ​编辑 前端 测试结果 二.wxs的介绍以及入门 测试结果 一.与后台数据进行交互&request封装 后台准备 pom.xml文件编写 <?xml version"1.0" encoding"UTF-8"?> <proj…