当前位置: 首页 > news >正文

Kubernetes对象深入学习之四:对象属性编码实战

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

本篇概览

  • 本文是《Kubernetes对象深入学习》系列的第四篇,前面咱们读源码和文档,从理论上学习了kubernetes的对象相关的知识,是时候自己动手来实战操作了

  • 本篇的主要内容就是新建一个golang工程,里面运行一个基于client-go的Controller,监听两种资源类型的变化事件

  • 面对两种不同类型的资源对象,咱们开发一个通用的方法,使用该方法可以获取各种类型的对象的属性,以此来验证前文学习的知识点

  • 整个项目的功能如下图所示,其实挺简单的:用kubectl对资源做修改(修改label),api-server会向所有监听者发送变更事件,object-tutorials是个go开发的应用程序,里面使用client-go库,监听kubernetes上的pod和service所有变更事件,在收到事件后,object-tutorials中会对变更对象做一些读对象属性相关的操作
    在这里插入图片描述

在k8s部署service和deployment

  • 为了实战需要,首先请在kubernetes环境将service和deployment部署好,这里给出我的部署脚本作为参考
  • 所有要部署的内容我都集中在这个名为nginx-deployment-service.yaml脚本中了
---
apiVersion: apps/v1
kind: Deployment
metadata:namespace: client-go-tutorialsname: nginx-deploymentlabels:app: nginx-apptype: front-end
spec:replicas: 3selector:matchLabels:app: nginx-apptype: front-endtemplate:metadata:labels:app: nginx-apptype: front-end# 这是第一个业务自定义label,指定了mysql的语言类型是c语言language: c# 这是第二个业务自定义label,指定了这个pod属于哪一类服务,nginx属于web类business-service-type: webspec:containers:- name: nginx-containerimage: nginx:latestresources:limits:cpu: "0.5"memory: 128Mirequests:cpu: "0.1"memory: 64Mi
---
apiVersion: v1
kind: Service
metadata:namespace: client-go-tutorialsname: nginx-service
spec:type: NodePortselector:app: nginx-apptype: front-endports:- port: 80targetPort: 80nodePort: 30011
  • 先执行以下命令创建namespace
kubectl create namespace client-go-tutorials
  • 再执行以下命令即可完成资源的创建
kubectl apply -f nginx-deployment-service.yaml
  • 来查看一下资源情况,如下图,service和pod都创建好了,准备工作完成,可以开始编码了
    在这里插入图片描述

源码下载

  • 如果您不想编写代码,也可以从GitHub上直接下载,地址和链接信息如下表所示(https://github.com/zq2599/blog_demos):
名称链接备注
项目主页https://github.com/zq2599/blog_demos该项目在GitHub上的主页
git仓库地址(https)https://github.com/zq2599/blog_demos.git该项目源码的仓库地址,https协议
git仓库地址(ssh)git@github.com:zq2599/blog_demos.git该项目源码的仓库地址,ssh协议
  • 这个git项目中有多个文件夹,本篇的源码在object-tutorials文件夹下,如下图黄框所示:
    在这里插入图片描述

编码:准备工程

  • 执行命令名为go mod init object-tutorials,新建module
  • 确保您的goproxy是正常的
  • 执行命令go get k8s.io/client-go@v0.22.8,下载client-go的指定版本
  • 现在工程已经准备好了,接着就是具体的编码

编码:梳理

  • 咱们按照开发顺序开始写代码,如果您看过欣宸的《client-go实战》系列,此刻对使用client-go开发简易版controller应该很熟悉了,这里在简单提一下controller的基本套路
  1. 在整个controller中,核心是队列,或者说队列是唯一的一条线,其他知识点都是珍珠,被队列穿起来
  2. 当队列创建成功后,咱们接下来要做的就是往队列中生产数据,然后取出数据来消费
  3. 还要多考虑一些,例如多个协程并行消费,以及消费过程中发生异常时的处理逻辑
  4. 然后就是本篇的核心了:无视资源类型的不同,可以用同一段代买来处理各种资源对象的属性
  • 这些编码要实现的功能,如下图所示,队列为线,其他知识点为珍珠
    在这里插入图片描述

编码:数据结构

  • 新建controller.go文件,先定义数据结构
// 自定义controller数据结构,嵌入了真实的控制器
type Controller struct {// 本地缓存,关注的对象都会同步到这里indexer cache.Indexer// 消息队列,用来触发对真实对象的处理事件queue workqueue.RateLimitingInterface// 实际运行运行的控制器informer cache.Controller
}

编码:创建队列,生产数据

  • 首先要创建队列,然后对k8s的api-server建立list&watch(list取得全量数据,watch监听数据变化的通知),再让整个监听和响应的逻辑运行起来
  • 上述功能由以下两个方法组成,CreateAndStartController负责创建实例,并且队列的生产逻辑也在此方法中,Run方法负责让队列的生产和消费运转起来

// Run 开始常规的控制器模式(持续响应资源变化事件)
func (c *Controller) Run(threadiness int, stopCh chan struct{}) {defer runtime.HandleCrash()// Let the workers stop when we are donedefer c.queue.ShutDown()klog.Info("Starting Pod controller")go c.informer.Run(stopCh)// Wait for all involved caches to be synced, before processing items from the queue is started// 刚开始启动,从api-server一次性全量同步所有数据if !cache.WaitForCacheSync(stopCh, c.informer.HasSynced) {runtime.HandleError(fmt.Errorf("timed out waiting for caches to sync"))return}// 支持多个线程并行从队列中取得数据进行处理for i := 0; i < threadiness; i++ {go wait.Until(c.runWorker, time.Second, stopCh)}<-stopChklog.Info("Stopping Pod controller")
}// CreateAndStartController 为了便于外部使用,这里将controller的创建和启动封装在一起
func CreateAndStartController(c cache.Getter, objType objectruntime.Object, resource string, namespace string, stopCh chan struct{}) {// ListWatcher用于获取数据并监听资源的事件podListWatcher := cache.NewListWatchFromClient(c, resource, NAMESPACE, fields.Everything())// 限速队列,里面存的是有事件发生的对象的身份信息,而非对象本身queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())// 创建本地缓存并对指定类型的资源开始监听// 注意,如果业务上有必要,其实可以将新增、修改、删除等事件放入不同队列,然后分别做针对性处理,// 但是,controller对应的模式,主要是让status与spec达成一致,也就是说增删改等事件,对应的都是查到实际情况,令其与期望情况保持一致,// 因此,多数情况下增删改用一个队列即可,里面放入变化的对象的身份,至于处理方式只有一种:查到实际情况,令其与期望情况保持一致indexer, informer := cache.NewIndexerInformer(podListWatcher, objType, 0, cache.ResourceEventHandlerFuncs{AddFunc: func(obj interface{}) {key, err := cache.MetaNamespaceKeyFunc(obj)if err == nil {// 再次注意:这里放入队列的并非对象,而是对象的身份,作用是仅仅告知消费方,该对象有变化,// 至于有什么变化,需要消费方自行判断,然后再做针对性处理queue.Add(key)}},UpdateFunc: func(old interface{}, new interface{}) {key, err := cache.MetaNamespaceKeyFunc(new)if err == nil {queue.Add(key)}},DeleteFunc: func(obj interface{}) {key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)if err == nil {queue.Add(key)}},}, cache.Indexers{})controller := &Controller{informer: informer,indexer:  indexer,queue:    queue,}go controller.Run(1, stopCh)
}

编码:消费队列的数据

  • 这里先写好业务代码,就是知道某个对象发生变化时,具体的业务逻辑是什么,如下所示
// syncToStdout 这是业务逻辑代码,被调用意味着key对应的对象有变化(新增或者修改)
func (c *Controller) syncToStdout(key string) error {// 从本地缓存中取出完整的对象obj, exists, err := c.indexer.GetByKey(key)if err != nil {klog.Errorf("Fetching object with key %s from store failed with %v", key, err)return err}// 如果不存在,就表示这是个删除事件if !exists {fmt.Printf("Pod %s does not exist anymore\n", key)} else {// 这里无视了obj具体是什么类型的对象(deployment、pod这些都有可能),// 用meta.Accessor转换出metav1.Object对象后就能获取该对象的所有meta信息objMeta, err := meta.Accessor(obj)if err != nil {klog.Errorf("get meta accessor error, [%s], failed with %v", key, err)return err}// 取得资源的所有属性labels := objMeta.GetLabels()if labels == nil {klog.Infof("name [%s], namespace [%s], label is empty", objMeta.GetName(), objMeta.GetNamespace())return nil}// 遍历每个属性,打印出来for key, value := range labels {klog.Infof("name [%s], namespace [%s], key [%s], value [%s]",objMeta.GetName(),objMeta.GetNamespace(),key,value)}}return nil
}
  • 接下里要写的是从消费队列中的数据,即:取数据,然后调用上面的syncToStdout,这里由两个方法组成,runWorker负责建立一个无限循环,不断调用processNextItem方法
// processNextItem 不间断从队列中取得数据并处理
func (c *Controller) processNextItem() bool {// 注意,队列里面不是对象,而是key,这是个阻塞队列,会一直等待key, quit := c.queue.Get()if quit {return false}// Tell the queue that we are done with processing this key. This unblocks the key for other workers// This allows safe parallel processing because two pods with the same key are never processed in// parallel.defer c.queue.Done(key)// 注意,这里的syncToStdout应该是业务代码,处理对象变化的事件err := c.syncToStdout(key.(string))// 如果前面的业务逻辑遇到了错误,就在此处理c.handleErr(err, key)// 外面的调用逻辑是:返回true就继续调用processNextItem方法return true
}// runWorker 这是个无限循环,不断地从队列取出数据处理
func (c *Controller) runWorker() {for c.processNextItem() {}
}

编码:异常处理

  • 还有个handleErr方法,在业务消费队列数据失败时的处理逻辑,可见这里的做法是将key重新放入队列,让业务逻辑再消费一次,这就是失败重试逻辑,这样的重复次数被限定在5次以内,超过了就不再放入队列中了
// handleErr 如果前面的业务逻辑执行出现错误,就在此集中处理错误,本例中主要是重试次数的控制
func (c *Controller) handleErr(err error, key interface{}) {if err == nil {// Forget about the #AddRateLimited history of the key on every successful synchronization.// This ensures that future processing of updates for this key is not delayed because of// an outdated error history.c.queue.Forget(key)return}// 如果重试次数未超过5次,就继续重试if c.queue.NumRequeues(key) < 5 {klog.Infof("Error syncing pod %v: %v", key, err)// Re-enqueue the key rate limited. Based on the rate limiter on the// queue and the re-enqueue history, the key will be processed later again.c.queue.AddRateLimited(key)return}// 代码走到这里,意味着有错误并且重试超过了5次,应该立即丢弃c.queue.Forget(key)// 这种连续五次重试还未成功的错误,交给全局处理逻辑runtime.HandleError(err)klog.Infof("Dropping pod %q out of the queue: %v", key, err)
}

编码,主程序

  • 最后是main.go文件中的main方法,作用是加载kubernetes的配置文件,以及决定要监听哪些资源的变化,这里通过调用两次CreateAndStartController方法,对pod和service的变化建立了监听
package mainimport ("flag""path/filepath"v1 "k8s.io/api/core/v1""k8s.io/client-go/kubernetes""k8s.io/client-go/tools/clientcmd""k8s.io/client-go/util/homedir""k8s.io/klog/v2"
)const (NAMESPACE = "client-go-tutorials"
)func main() {var kubeconfig *stringvar master string// 试图取到当前账号的家目录if home := homedir.HomeDir(); home != "" {// 如果能取到,就把家目录下的.kube/config作为默认配置文件kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")master = ""} else {// 如果取不到,就没有默认配置文件,必须通过kubeconfig参数来指定flag.StringVar(kubeconfig, "kubeconfig", "", "absolute path to the kubeconfig file")flag.StringVar(&master, "master", "", "master url")flag.Parse()}config, err := clientcmd.BuildConfigFromFlags(master, *kubeconfig)if err != nil {klog.Fatal(err)}clientset, err := kubernetes.NewForConfig(config)if err != nil {klog.Fatal(err)}stop := make(chan struct{})defer close(stop)CreateAndStartController(clientset.CoreV1().RESTClient(), &v1.Pod{}, "pods", NAMESPACE, stop)CreateAndStartController(clientset.CoreV1().RESTClient(), &v1.Service{}, "services", NAMESPACE, stop)select {}
}

运行程序,验证效果

  • 现在将程序运行起来,用go build构建应用,或者直接用IDE运行,启动后可以看到输入如下,可见程序符合预期,将所有service和pod的label都在日志中打印出来了,值得注意的是service现在还没有任何label,这在日志中也有提示
Starting: /root/software/gopath/bin/dlv dap --listen=127.0.0.1:33405 --log-dest=3 from /root/github/blog_demos/tutorials/object-tutorials
DAP server listening at: 127.0.0.1:33405
Type 'dlv help' for list of commands.
I0723 08:20:14.695662 2256666 controller.go:131] Starting Pod controller
I0723 08:20:14.696133 2256666 controller.go:131] Starting Pod controller
I0723 08:20:14.796419 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-wpnt7], namespace [client-go-tutorials], key [business-service-type], value [web]
I0723 08:20:14.796620 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-wpnt7], namespace [client-go-tutorials], key [language], value [c]
I0723 08:20:14.796669 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-wpnt7], namespace [client-go-tutorials], key [pod-template-hash], value [78f6b696d9]
I0723 08:20:14.796704 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-wpnt7], namespace [client-go-tutorials], key [type], value [front-end]
I0723 08:20:14.796737 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-wpnt7], namespace [client-go-tutorials], key [app], value [nginx-app]
I0723 08:20:14.796792 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-wp4qf], namespace [client-go-tutorials], key [app], value [nginx-app]
I0723 08:20:14.796831 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-wp4qf], namespace [client-go-tutorials], key [business-service-type], value [web]
I0723 08:20:14.796865 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-wp4qf], namespace [client-go-tutorials], key [language], value [c]
I0723 08:20:14.796901 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-wp4qf], namespace [client-go-tutorials], key [pod-template-hash], value [78f6b696d9]
I0723 08:20:14.796960 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-wp4qf], namespace [client-go-tutorials], key [type], value [front-end]
I0723 08:20:14.797007 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-j98xj], namespace [client-go-tutorials], key [pod-template-hash], value [78f6b696d9]
I0723 08:20:14.797047 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-j98xj], namespace [client-go-tutorials], key [type], value [front-end]
I0723 08:20:14.797100 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-j98xj], namespace [client-go-tutorials], key [app], value [nginx-app]
I0723 08:20:14.797139 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-j98xj], namespace [client-go-tutorials], key [business-service-type], value [web]
I0723 08:20:14.797174 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-j98xj], namespace [client-go-tutorials], key [language], value [c]
I0723 08:20:14.797346 2256666 controller.go:82] name [nginx-service], namespace [client-go-tutorials], label is empty
  • 现在修改资源对象试试,首先修改service,执行以下命令进入vi编辑模式
kubectl edit service nginx-service -n client-go-tutorial
  • 下图红框中是新增的内容
    在这里插入图片描述
  • 保存退出,在程序的控制台可见以下日志输出,证明service的变更事件都被会咱们的object-tutorials程序响应,也能顺利取出service对象的属性并打印到日志中
    在这里插入图片描述
  • 接着修改一个pod的label,新增内容如下图黄色箭头所示
    在这里插入图片描述
  • 保存后,程序这边立即有日志输出,会打印该pod的所有label
    在这里插入图片描述
  • 至此,编码和验证都完成了,结果符合预期,meta.Accessor方法很好用,拿到属性对象后所有资源的属性信息都能轻易获取

你不孤单,欣宸原创一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 数据库+中间件系列
  6. DevOps系列

相关文章:

Kubernetes对象深入学习之四:对象属性编码实战

欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码)&#xff1a;https://github.com/zq2599/blog_demos 本篇概览 本文是《Kubernetes对象深入学习》系列的第四篇&#xff0c;前面咱们读源码和文档&#xff0c;从理论上学习了kubernetes的对象相关的知识&#xff…...

深度学习入门教程(2):使用预训练模型来文字生成图片TextToImageGenerationWithNetwork

本深度学习入门教程是在polyu HPCStudio 启发以及资源支持下进行的&#xff0c;在此也感谢polyu以及提供支持的老师。 本文内容&#xff1a;在GoogleColab平台上使用预训练模型来文字生成图片Text To Image Generation With Network &#xff08;1&#xff09;你会学到什么&a…...

ORA-38760: This database instance failed to turn on flashback database

早晨接一个任务&#xff0c;使用rman备份在虚拟化单机上恢复实例&#xff0c;恢复参数文件、控制文件和数据文件都正常&#xff0c;recover归档时报错如下&#xff1a; Starting recover at 2023-07-28 10:25:01 using channel ORA_DISK_1 starting media recovery media reco…...

避免低级错误:深入解析Java的ConcurrentModificationException异常

在软件开发中&#xff0c;我们常常会遇到各种错误和异常。其中有一类比较低级但又常见的错误就是ConcurrentModificationException异常。最近了我就写了个这种异常&#xff0c;这个异常通常发生在使用迭代器遍历集合时&#xff0c;同时对集合进行修改&#xff0c;从而导致迭代器…...

7.28

1.思维导图 2.qt的sever #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include<QTcpServer> //服务器类 #include<QTcpSocket> //客户端类 #include<QMessageBox> //对话框类 #include<QList> …...

java线程中的常见方法(详解)

方法简介 方法名 功能 说明 start() 启动一个新线程&#xff0c;在新的线程运行 run 方法中的代码 start 方法只是让线程进入就绪&#xff0c;里面代码不一定立刻运行&#xff08;CPU 的时间片还没分给它&#xff09;。每个线程对象的start方法只能调用一次&#xff0c;如…...

线程池参数配置

上次面试被人问到&#xff0c;如果是IO 密集型的任务&#xff0c;该如何配置合适的线程数&#xff0c;当初我说要按照IO具体的请求毫秒时间&#xff0c;来配置具体的线程数。 NthreadsNcpu*(1w/c) 公式中 W/C 为系统 阻塞率 w:等待时间 c:计算时间一般情况下&#xff0c;如果存…...

Spread for Winform 16.2.20231.0 (SP2) Crack

Spread for Winform 16.2.20231.0 (SP2)发布。此版本包含针对客户报告的问题的重要修复&#xff1a; 安装版本 16 后&#xff0c;FarPoint.Localization.dll 将丢失。 将数据绑定到 Spread 时会出现 InvalidOperationException。 通过 Spread Designer 设置的上标将不会保留。…...

Go程序结构

Go程序结构 1、名称 ​ 名称的开头是一个字母或下划线&#xff0c;且区分大小写。 实体第一个字母的大小写决定其可见性是否跨包&#xff1a; ​ 若名称以大写字母开头&#xff0c;它是导出的&#xff0c;对包外是可见和可访问的&#xff0c;可以被自己包以外的其他程序所引用…...

JAVA面试总结-Redis篇章(四)——双写一致性

JAVA面试总结-Redis篇章&#xff08;四&#xff09;——双写一致性 问&#xff1a;redis 做为缓存&#xff0c;mysql的数据如何与redis进行同步呢&#xff1f;第一种情况&#xff0c;如果你的项目一致性要求高的话 采用以下逻辑我们应该先删除缓存&#xff0c;再修改数据库&…...

赋能医院数字化转型,医院拍摄VR全景很有必要

医院有没有必要拍摄制作VR全景呢&#xff1f;近期也有合作商问我们这个问题&#xff0c;其实VR智慧医院是趋势、也是机遇。现在外面很多的口腔医院、医美机构等都开始引入VR全景技术了&#xff0c;力求打造沉浸式、交互式的VR智慧医院新体验&#xff0c;通过VR全景展示技术来助…...

Vue3项目中没有配置 TypeScript 支持,使用 TypeScript 语法

1.安装 TypeScript&#xff1a;首先&#xff0c;需要在项目中安装 TypeScript。在终端中运行以下命令 npm install typescript --save-dev2.创建 TypeScript 文件&#xff1a;在 Vue 3 项目中&#xff0c;可以创建一个以 .ts 后缀的文件&#xff0c;例如 MyComponent.ts。在这…...

数据可视化大屏拼接屏开发实录:屏幕分辨率测试工具

一、可视化大屏开发 在数据可视化大屏开发时&#xff0c;确定数据可视化大屏拼接屏的板块尺寸需要考虑以下几个因素&#xff1a; 屏幕分辨率&#xff1a;首先需要知道每个板块屏幕的分辨率&#xff0c;包括宽度和高度&#xff0c;这决定了每个板块上可以显示的像素数量。 数据…...

每日一题7.28 209

209. 长度最小的子数组 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl1, ..., numsr-1, numsr] &#xff0c;并返回其长度。如果不存在符合条件的子数组&#xff0c;返回 0 。 本题应该是用前缀…...

Python + Playwright 无头浏览器Chrome找不到元素

用Python Playwright调试时&#xff0c;发现不用无头浏览器&#xff08;即headlessFalse&#xff09;代码能够运行成功&#xff0c;但是一用无头浏览器时&#xff08;即headlessTrue&#xff09;就会报错&#xff0c;提示找不到元素。换成Firefox浏览器又不会有这个问题&#…...

C++信号量与共享内存实现进程间通信

关于信号量和共享内存的相关知识可参考下面链接&#xff1a; 进程间通信方式介绍_夜雨听萧瑟的博客-CSDN博客 C 创建共享内存_c共享内存_夜雨听萧瑟的博客-CSDN博客 信号量SytemV与Posix信号量的介绍与用法_夜雨听萧瑟的博客-CSDN博客 直接上代码&#xff0c;代码如下&#…...

[Tools: Camera Conventions] NeRF中的相机矩阵估计

参考&#xff1a;NeRF代码解读-相机参数与坐标系变换 - 知乎 在NeRF中&#xff0c;一个重要的步骤是确定射线&#xff08;rays&#xff09;的初始点和方向。根据射线的初始点和方向&#xff0c;和设定射线深度和采样点数量&#xff0c;可以估计该射线成像的像素值。估计得到的…...

【sgUpload】自定义上传组件,支持上传文件夹及其子文件夹文件、批量上传,批量上传会有右下角上传托盘出现,支持本地上传图片转换为Base64image

特性&#xff1a; 支持批量上传文件、文件夹可自定义headers可自定义过滤上传格式可自定义上传API接口支持drag属性开启可拖拽上传文件、文件夹 sgUpload源码 <template><div :class"$options.name" :dragenter"isDragenter"><!-- 上传按钮…...

Kafka 实时处理Stream与Batch的对比分析

Kafka 实时处理Stream与Batch的对比分析 一、简介1. Kafka的定义和特点2. Kafka实时处理基础架构 二、Stream和Batch1. Stream和Batch的区别2. 对比Stream和Batch的优缺点Stream的优缺点Batch的优缺点 三、使用场景1. 使用场景对比Batch使用场景Stream使用场景 2. 如何选择Stre…...

Andriod开发性能优化实践

文章目录 内存优化布局优化网络优化图片优化内存泄露绘制优化 内存优化 在Android开发中&#xff0c;有一些实践可以帮助进行内存优化&#xff0c;以减少应用程序的内存占用和提高性能。以下是一些常见的内存优化实践&#xff1a; 使用合适的数据结构和集合&#xff1a;选择合…...

【入坑系列】TiDB 强制索引在不同库下不生效问题

文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密

在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...

Golang dig框架与GraphQL的完美结合

将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用&#xff0c;可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器&#xff0c;能够帮助开发者更好地管理复杂的依赖关系&#xff0c;而 GraphQL 则是一种用于 API 的查询语言&#xff0c;能够提…...

vue3 字体颜色设置的多种方式

在Vue 3中设置字体颜色可以通过多种方式实现&#xff0c;这取决于你是想在组件内部直接设置&#xff0c;还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法&#xff1a; 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...

Python实现prophet 理论及参数优化

文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候&#xff0c;写过一篇简单实现&#xff0c;后期随着对该模型的深入研究&#xff0c;本次记录涉及到prophet 的公式以及参数调优&#xff0c;从公式可以更直观…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战

“&#x1f916;手搓TuyaAI语音指令 &#x1f60d;秒变表情包大师&#xff0c;让萌系Otto机器人&#x1f525;玩出智能新花样&#xff01;开整&#xff01;” &#x1f916; Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制&#xff08;TuyaAI…...

根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:

根据万维钢精英日课6的内容&#xff0c;使用AI&#xff08;2025&#xff09;可以参考以下方法&#xff1a; 四个洞见 模型已经比人聪明&#xff1a;以ChatGPT o3为代表的AI非常强大&#xff0c;能运用高级理论解释道理、引用最新学术论文&#xff0c;生成对顶尖科学家都有用的…...

ArcGIS Pro制作水平横向图例+多级标注

今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作&#xff1a;ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等&#xff08;ArcGIS出图图例8大技巧&#xff09;&#xff0c;那这次我们看看ArcGIS Pro如何更加快捷的操作。…...

全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比

目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec&#xff1f; IPsec VPN 5.1 IPsec传输模式&#xff08;Transport Mode&#xff09; 5.2 IPsec隧道模式&#xff08;Tunne…...

企业如何增强终端安全?

在数字化转型加速的今天&#xff0c;企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机&#xff0c;到工厂里的物联网设备、智能传感器&#xff0c;这些终端构成了企业与外部世界连接的 “神经末梢”。然而&#xff0c;随着远程办公的常态化和设备接入的爆炸式…...