[k8s源码]4.informer

Informer 是 client-go 库中的一个核心组件,它提供了一种高效的方式来监视 Kubernetes 集群中资源的变化。Informer 通过 Watch 机制与 API Server 建立长连接,初次同步时会获取资源的完整列表,之后只接收增量更新,大大减少了网络流量。

使用informer可以减少 API 调用: 避免频繁轮询,降低 API Server 压力。

  1. 实时性: 能快速获知资源变化
  2. 缓存: Informer 在本地维护资源缓存,减少查询延迟
  3. 事件处理: 提供 Add/Update/Delete 事件的回调机制

从下图我们可以看到 Informer 包含2个蓝色组件,分别是Reflector,Indexer。其中 Reflector 是用来和 apiserver 建立链接,实时获取最新的数据并交给 Informer,Informer 拿到数据存放到 Indexer 中并通知下游 controller。

代码工厂

这里的podinformer是一个专门用来监视和更新pod信息的informer,是通过工厂informer来实例化的一个informer,以下是一个实例来解释代码工厂的概念:

这里的studentInformer是一种特殊的informer, 里面有一个informerfactory和一个自有的函数,这里使用函数类型func()作为字段,可以在结构体中保存一些动态生成的逻辑或者回调函数,使得结构体的行为可以根据这些函数的不同实现而变化。这种方式可以帮助实现更灵活和可扩展的程序设计。

package main

import (
    "fmt"
)

type Student struct {
    Name string
    Age  int
}

type InformerFactory struct {
    // 一些工厂的字段
}

type StudentInformer struct {
    factory InformerFactory
    defaultInformer func() string
}

func (s *StudentInformer) Informer() string {
    return s.factory.InformerFor(s.defaultInformer)
}

func (f InformerFactory) InformerFor(informerFunc func() string) string {
    // 这里我们简单返回调用的结果
    return informerFunc()
}

func defaultStudentInformer() string {
    return "Student Informer Initialized"
}

func main() {
    factory := InformerFactory{}
    studentInformer := StudentInformer{
        factory: factory,
        defaultInformer: defaultStudentInformer,
    }
    
    result := studentInformer.Informer()
    fmt.Println(result) // 输出: Student Informer Initialized
}

这里InformerFor 方法需要在未来根据不同的情况返回不同的 informer 结果,而不是简单地调用一个预定义的函数,这种设计就显得更加合理。此外,通过 InformerFor 方法,可以对 InformerFactory 进行更多的管理和配置,比如添加日志记录、性能监控等逻辑,而不需要修改 StudentInformer 结构体本身。所以这里使用了informer和informerFor两个方法。

 PodInformer

通过上面的例子,可以更好的理解这里的代码逻辑,PodInformer使用了一个工厂方法internalinterfaces.SharedInformerFactory,他的informer方法调用了SharedInformerFactory中的informerFor方法。

type PodInformer interface {
	Informer() cache.SharedIndexInformer
	Lister() v1.PodLister
}

type podInformer struct {
	factory          internalinterfaces.SharedInformerFactory
	tweakListOptions internalinterfaces.TweakListOptionsFunc
	namespace        string
}

// 返回一个 sharedIndexInformer ,本质上是调用了 sharedInformerFactory 的 InformerFor 方法,
//我在下面贴出来了,sharedInformerFactory 是个工厂方法,这里初始化了 podInformer 并存储在 //factory 的 informers map 中
func (f *podInformer) Informer() cache.SharedIndexInformer {
	return f.factory.InformerFor(&corev1.Pod{}, f.defaultInformer)
}

// 返回一个 PodList , 可以用来列出 indexer 中的数据,这里的 indexer 是本地的,
//因此是拿出的pod是只读的,无法修改
func (f *podInformer) Lister() v1.PodLister {
	return v1.NewPodLister(f.Informer().GetIndexer())
}

// 根据传入的对象类型 obj 和创建函数 newFunc,返回一个对应的 SharedIndexInformer 实例。
//如果已经存在相同类型的 informer,则直接返回现有的实例;否则,根据参数创建一个新的实例,
//并在需要时进行存储,以便后续重复使用,从而提高效率和资源利用率。
func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer {
	f.lock.Lock()
	defer f.lock.Unlock()

	informerType := reflect.TypeOf(obj)
	informer, exists := f.informers[informerType]
	if exists {
		return informer
	}

	resyncPeriod, exists := f.customResync[informerType]
	if !exists {
		resyncPeriod = f.defaultResync
	}

	informer = newFunc(f.client, resyncPeriod)
	informer.SetTransform(f.transform)
	f.informers[informerType] = informer

	return informer
}
Informer构造方法

构造方法(NewPodInformer 和 NewFilteredPodInformer)是创建实际 Informer 的地方,而 podInformer 结构体则是对这个 Informer 的一个封装,提供了更高层次的抽象和额外的功能(如 Lister)。podInformer 的 defaultInformer 方法是连接这两部分的桥梁,它在需要时调用构造方法来创建新的 Informer。这种设计允许灵活地创建和管理 Informer,同时提供了一致的接口(PodInformer)供客户端代码使用。

这里的NewPodInformer其实是调用了另一个informer但是自动将 tweakListOptions 参数设为 nil

func NewPodInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
    return NewFilteredPodInformer(client, namespace, resyncPeriod, indexers, nil)
}

 这里的 NewFilteredPodInformer方法定义了wach和list两个函数,核心是client.CoreV1().Pods(namespace).Watch(context.TODO(), options),client.CoreV1().Pods(namespace).List(context.TODO(), options)

&cache.ListWatch{...} 创建一个 ListWatch 结构体的指针。
ListFunc: 是 ListWatch 结构体的一个字段,它期望一个函数作为值。
func(options metav1.ListOptions) (runtime.Object, error) { ... } 是一个匿名函数,它:
接受一个 metav1.ListOptions 参数
返回一个 runtime.Object 和一个 error
这个函数的内容:
如果 tweakListOptions 不为 nil,就调用它来修改 options
然后调用 client.CoreV1().Pods(namespace).List() 来获取 Pod 列表

func NewFilteredPodInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
	return cache.NewSharedIndexInformer(
		&cache.ListWatch{
			ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
				if tweakListOptions != nil {
					tweakListOptions(&options)
				}
				return client.CoreV1().Pods(namespace).List(context.TODO(), options)
			},
			WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
				if tweakListOptions != nil {
					tweakListOptions(&options)
				}
				return client.CoreV1().Pods(namespace).Watch(context.TODO(), options)
			},
		},
		&corev1.Pod{},
		resyncPeriod,
		indexers,
	)
}
func (f *podInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
	return NewFilteredPodInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}

&corev1.Pod{},
        resyncPeriod,
        indexers,

这三行简单的代码本身并不直接完成所有复杂的操作,而是为 cache.NewSharedIndexInformer 函数提供了必要的参数。这个函数内部会使用这些参数来设置和配置 Informer。
&corev1.Pod{}:这只是一个类型指示器。NewSharedIndexInformer 函数内部会使用反射来检查这个类型,以确定如何处理从 API 服务器接收的对象。
resyncPeriod:这个参数被传递给 Informer 的内部定时器。Informer 会创建一个 goroutine,定期触发重新同步操作。
indexers:这个参数用于初始化 Informer 的索引器。例如像这样:

cache.Indexers{
    cache.NamespaceIndex: cache.MetaNamespaceIndexFunc,
}

这个 Indexers 映射会被传递给 Informer 的内部存储(通常是一个 cache.ThreadSafeStore),用于设置索引函数。 

可以看到最后定义了defaultInformer,是上面构造podInformer的时候,Informer方法中调用InformerFor的时候传递的一个函数。

总结以上内容

这段代码定义了 PodInformer 接口及其实现,采用了工厂模式和延迟初始化策略来管理 Kubernetes Pod 资源的 Informer。核心是 Informer() 方法,它通过调用 factory.InformerFor() 来获取或创建 cache.SharedIndexInformer 实例。这个过程的本质是构造一个 cache.SharedIndexInformer 类型的 informer。

具体流程如下:Informer() 方法调用 InformerFor(),并传递一个 defaultInformer 函数作为参数。这个 defaultInformer 函数实际上返回一个 NewFilteredPodInformer,而 NewFilteredPodInformer 在构造时会创建一个 cache.SharedIndexInformer 类型的对象。InformerFor() 函数首先检查是否已经存在对应类型的 informer 实例。如果已存在,直接返回该实例;如果不存在,则使用提供的 defaultInformer 函数创建新的实例。这种设计确保了 Informer 的单一实例,避免重复创建,从而提高了资源利用效率。

sharedIndexInformer

它包含了以下重要组件:
indexer: 用于存储和索引对象的本地缓存。
controller: 负责管理 Reflector,处理对象的添加、更新和删除。
processor: 处理事件分发给注册的事件处理器。
listerWatcher: 用于列出和监视资源的接口。
objectType: 指定了 Informer 处理的对象类型。
resyncCheckPeriod 和 defaultEventHandlerResyncPeriod: 控制重新同步的周期。
started 和 stopped: 控制 Informer 的生命周期。
watchErrorHandler: 处理监视过程中的错误。
transform: 允许在将对象添加到存储之前对其进行转换。
这个结构体的定义揭示了 SharedIndexInformer 的内部工作机制:
它维护了一个本地缓存(indexer),用于快速检索对象。
通过 controller 和 listerWatcher,它能够持续监视 Kubernetes API 服务器的变化。
使用 processor 来分发事件,确保所有注册的处理器都能接收到对象的变更通知。
通过 resync 机制,它可以周期性地重新同步所有对象,确保本地缓存与服务器状态一致。 

type sharedIndexInformer struct {
	indexer    Indexer
	controller Controller

	processor             *sharedProcessor
	cacheMutationDetector MutationDetector

	listerWatcher ListerWatcher

	// objectType is an example object of the type this informer is expected to handle. If set, an event
	// with an object with a mismatching type is dropped instead of being delivered to listeners.
	objectType runtime.Object

	// objectDescription is the description of this informer's objects. This typically defaults to
	objectDescription string

	// resyncCheckPeriod is how often we want the reflector's resync timer to fire so it can call
	// shouldResync to check if any of our listeners need a resync.
	resyncCheckPeriod time.Duration
	// defaultEventHandlerResyncPeriod is the default resync period for any handlers added via
	// AddEventHandler (i.e. they don't specify one and just want to use the shared informer's default
	// value).
	defaultEventHandlerResyncPeriod time.Duration
	// clock allows for testability
	clock clock.Clock

	started, stopped bool
	startedLock      sync.Mutex

	// blockDeltas gives a way to stop all event distribution so that a late event handler
	// can safely join the shared informer.
	blockDeltas sync.Mutex

	// Called whenever the ListAndWatch drops the connection with an error.
	watchErrorHandler WatchErrorHandler

	transform TransformFunc
}

除了这个结构,sharedIndexInformer 还实现了许多重要的方法来完成其功能。如:Run(stopCh <-chan struct{}),这是启动 informer 的主要方法。它会启动底层的 controller 和 processor。AddEventHandler(handler ResourceEventHandler)用于添加事件处理器,这些处理器会在资源发生变化时被调用。等等。

package main

import (
    "time"

    "k8s.io/client-go/kubernetes"
    "k8s.io/client-go/tools/cache"
    "k8s.io/client-go/util/workqueue"
)

func main() {
    // 假设已经有一个 Kubernetes 客户端的实例 clientset
    var clientset *kubernetes.Clientset

    // 创建一个 sharedInformerFactory 实例
    informerFactory := NewSharedInformerFactory(clientset, time.Minute*10)

    // 创建一个 podInformer 实例
    podInformer := informerFactory.Core().V1().Pods()

    // 启动 informer 以监听 Pod 资源变化
    stopCh := make(chan struct{})
    defer close(stopCh)
    podInformer.Informer().Run(stopCh)
}
Channel

<-chan struct{} 类型的 channel 在协程间传递简单的停止信号非常实用。它能够高效地实现协程间的同步和通信。在下面的代码中,创建了一个通道,这个通道将用来从集群获得informer的信息,struct{} 是 Go 语言中最小的数据类型,只占用 1 个字节的内存。相比于使用其他更复杂的数据类型作为 channel 元素,struct{} 可以减少内存开销。<-chan struct{} 类型的 channel 发送和接收数据都非常简单,因为 struct{} 类型没有任何有意义的值。无需进行复杂的数据处理或转换。

Go 语言中,chan 是一个数据类型,用于实现协程之间的通信和同步。它是 Go 语言并发编程的核心概念之一。

channel 有以下几个重要的特点:

无缓冲 vs 有缓冲:

无缓冲 channel: 发送和接收操作必须是同步的,发送者会一直阻塞到有接收者接收数据。

有缓冲 channel: 发送操作会先将数据存入缓冲区,直到缓冲区满时才会阻塞。接收操作会从缓冲区取出数据。

方向性:

单向 channel: 只能用于发送或接收数据,如 chan<- int 和 <-chan int。

双向 channel: 既可以用于发送又可以用于接收数据,如 chan int。

阻塞和非阻塞:

在没有准备好接收或发送数据时,channel 操作会阻塞当前协程,直到有其他协程准备好为止。

可以使用 select 语句来非阻塞地处理多个 channel。

InformerDemo

Informer 在监听到相关事件时,会自动调用 EventNil 类型实现的 OnAdd 方法,并将事件对应的对象(在这个例子中是 v1.Pod 类型)传递给 obj interface{} 参数。也就是说,这个 obj interface{} 参数并不是从 EventNil 结构体中获取的,而是由 Informer 在调用 OnAdd 方法时动态传入的。Informer 知道 EventNil 实现了 cache.ResourceEventHandler 接口,所以在触发事件时会自动调用 EventNil 类型的 OnAdd、OnUpdate 和 OnDelete 方法,并传入适当的参数。

package main

import (
	"flag"
	"fmt"
	"time"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/labels"
	kubeInformers "k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"
)

func main() {
	var kubeconfig *string // 声明为指针
	kubeconfig = flag.String("kubeconfig", "C:/Users/gx/.kube/config", "absolute path to the kubeconfig file")
	flag.Parse() // 需要解析命令行参数

	config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
	if err != nil { // 'error' 改为 'err','nul' 改为 'nil'
		panic(err.Error())
	}

	// create the clientset
	kubeClient, err := kubernetes.NewForConfig(config)
	if err != nil { // 需要检查错误
		panic(err.Error())
	}

	// 使用 kubeClient ...
	kubeInformerfactory := kubeInformers.NewSharedInformerFactory(kubeClient, 10*time.Second)
	podLister := kubeInformerfactory.Core().V1().Pods().Lister()
	//deploymentLister = kubeInformerfactory.Apps().V1().Deployments().Lister()

	pl := podLister.Pods("monitor-sa")
	i := kubeInformerfactory.Core().V1().Pods().Informer()
	e := &EventNil{}

	stopCh := make(chan struct{})
	kubeInformerfactory.Start(stopCh)
	kubeInformerfactory.WaitForCacheSync(stopCh)
	i.AddEventHandler(e)
	pods, err := pl.List(labels.Everything())
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(len(pods))
	for _, pod := range pods {
		fmt.Println("Pod Name:", pod.Name)
	}

	select {}
}

type EventNil struct {
}

func (receiver *EventNil) OnAdd(obj interface{}, isInInitialList bool) {
	pod, ok := obj.(*v1.Pod)
	if !ok {
		fmt.Println("Failed to cast obj to *v1.Pod")
		return
	}
	fmt.Println("add \n", pod.Name)
}
func (receiver *EventNil) OnUpdate(oldObj, newObj interface{}) {
}

func (receiver *EventNil) OnDelete(obj interface{}) {
	pod, ok := obj.(*v1.Pod)
	if !ok {
		fmt.Println("Failed to cast obj to *v1.Pod")
		return
	}
	fmt.Println("delete \n", pod.Name)
}

可以看到这里的type EventNil的struct是在函数外定义的。

通过实验发现,删除deployment的一个pod的时候,一般是先创建新的pod,再删除旧的pod。可以看到删除demo-deployment-7fd4444d-bdt8j的时候,先创建了新的pod

add 

 demo-deployment-7fd4444d-j5hzr

delete

 demo-deployment-7fd4444d-bdt8j

[root@master ~]# kubectl get pods
NAME                             READY   STATUS    RESTARTS   AGE
demo-deployment-7fd4444d-bdt8j   1/1     Running   0          19s
demo-deployment-7fd4444d-slwqm   1/1     Running   0          5m47s
[root@master ~]# kubectl delete pod demo-deployment-7fd4444d-bdt8j
pod "demo-deployment-7fd4444d-bdt8j" deleted
[root@master ~]# kubectl get pods
NAME                             READY   STATUS    RESTARTS   AGE
demo-deployment-7fd4444d-j5hzr   1/1     Running   0          12s
demo-deployment-7fd4444d-slwqm   1/1     Running   0          7m35s

kubeInformerFactory.Start(stopCh)
Start(stopCh) 会启动所有通过 factory 创建的 informer。通道 stopCh 会被传递给这些 informer。
当 stopCh 关闭时,所有这些 informer 都会停止运行。
kubeInformerFactory.WaitForCacheSync(stopCh)
WaitForCacheSync(stopCh) 会阻塞等待,直到所有 informer 的缓存都被同步(即 informer 从 Kubernetes 集群中获取到初始数据)。
如果 stopCh 关闭,则会提前退出等待。 

接口实现

在 Go 语言中,接口是一种抽象类型,它定义了一组方法签名,但不包含方法的具体实现。一个接口只描述了对象应该做什么,而不关心对象是如何做的通过接口,代码的不同部分可以被解耦合。实现者不需要了解调用者的具体实现,只需遵循接口定义的方法签名。 例如下面的例子里面,一个接口就有两个不同的实现方式。

package main

import "fmt"

// 定义接口
type ResourceEventHandler interface {
	OnAdd(obj interface{})
	OnUpdate(oldObj, newObj interface{})
	OnDelete(obj interface{})
}
// 实现接口的结构体
type EventLogger struct{}
func (e *EventLogger) OnAdd(obj interface{}) {
	fmt.Println("Object added:", obj)
}
func (e *EventLogger) OnUpdate(oldObj, newObj interface{}) {
	fmt.Println("Object updated from", oldObj, "to", newObj)
}
func (e *EventLogger) OnDelete(obj interface{}) {
	fmt.Println("Object deleted:", obj)
}
// 另一个实现接口的结构体
type EventCounter struct {
	AddCount    int
	UpdateCount int
	DeleteCount int
}
func (c *EventCounter) OnAdd(obj interface{}) {
	c.AddCount++
}
func (c *EventCounter) OnUpdate(oldObj, newObj interface{}) {
	c.UpdateCount++
}
func (c *EventCounter) OnDelete(obj interface{}) {
	c.DeleteCount++
}
func main() {
	// 使用 EventLogger
	var handler ResourceEventHandler = &EventLogger{}
	handler.OnAdd("Pod1")
	handler.OnUpdate("Pod1", "Pod1 v2")
	handler.OnDelete("Pod1")

	// 使用 EventCounter
	counter := &EventCounter{}
	handler = counter
	handler.OnAdd("Pod2")
	handler.OnUpdate("Pod2", "Pod2 v2")
	handler.OnDelete("Pod2")

	fmt.Printf("Add: %d, Update: %d, Delete: %d\n", counter.AddCount, counter.UpdateCount, counter.DeleteCount)
}

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【概率论三】参数估计

文章目录 一. 点估计1. 矩估计法2. 极大似然法1. 似然函数2. 极大似然估计 3. 评价估计量的标准2.1. 无偏性2.2. 有效性2.3. 一致性 三. 区间估计1. 区间估计的概念2. 正态总体参数的区间估计 参数估计讲什么 由样本来确定未知参数参数估计分为点估计与区间估计 一. 点估计 所…

EE trade:强平和爆仓的区别

在金融交易市场中&#xff0c;杠杆交易的引入&#xff0c;让投资者可以用少量的资金撬动更大的头寸&#xff0c;获取更大的收益。然而&#xff0c;杠杆交易也带来了更大的风险&#xff0c;一旦市场波动&#xff0c;投资者可能会面临强平或爆仓的风险。了解强平和爆仓的区别&…

51单片机(STC8H8K64U/STC8051U34K64)_RA8889驱动大屏_硬件SPI4_参考代码(v1.3)

单片机实际不限&#xff0c;这里采用的STC最新、主推的型号&#xff0c;比如STC8H8K64U、STC8051U34K64进行实验测试&#xff0c;您可以换用不同型号。目前测试这两个系列&#xff0c;显示速度均相当不错&#xff0c;软件设计也是极为简单。各篇文章下方均提供源码供参考下载。…

ctfshow-web入门-php特性(web123、web125、web126)

目录 1、web123 2、web125 3、web126 1、web123 要求存在 post 传入 CTF_SHOW 和 CTF_SHOW.COM&#xff0c;不能存在 get 传入 fl0g。 正则匹配过滤掉了一些符号&#xff0c;符合则会执行 eval 函数&#xff0c;其中 c 来自 post 传入的 fun。 我们先说非预期解&#xff0c…

.net dataexcel 脚本公式 函数源码

示例如: ScriptExec(""sum(1, 2, 3, 4)"") 结果等于10 using Feng.Excel.Builder; using Feng.Excel.Collections; using Feng.Excel.Interfaces; using Feng.Script.CBEexpress; using Feng.Script.Method; using System; using System.Collections.Gen…

js执行机制----事件循环

前言 问题 一般情况下,我们都认为js是顺序执行的 但是遇到下列情况 setTimeout(function(){console.log(定时器开始啦) });new Promise(function(resolve){console.log(马上执行for循环啦);for(var i 0; i < 10000; i){i 99 && resolve();} }).then(function(…

Hadoop-32 ZooKeeper 分布式锁问题 分布式锁Java实现 附带案例和实现思路代码

章节内容 上节我们完成了&#xff1a; ZooKeeper的Leader选举机制ZooKeeper的选举过程ZooKeeper的ZAB协议 背景介绍 这里是三台公网云服务器&#xff0c;每台 2C4G&#xff0c;搭建一个Hadoop的学习环境&#xff0c;供我学习。 之前已经在 VM 虚拟机上搭建过一次&#xff0…

如何让LabVIEW程序框图的图标简化,从而节省空间?

再点击选项 取消掉箭头所示的√即可。 这样就可以将生成的图标从下面所示&#xff1a; 变成简化的图标&#xff0c;如下所示&#xff1a;

QT实现滑动页面组件,多页面动态切换

这篇文章主要介绍了Qt实现界面滑动切换效果&#xff0c;对大家的学习或工作具有一定的参考借鉴价值&#xff0c;需要的朋友可以参考下。 一、简述 一个基于Qt的动态滑动页面组件。 二、 设计思路 1、自定义StackWidget类&#xff0c;继承自QWidget&#xff0c;实现一个堆叠…

持续集成04--Jenkins结合Gitee创建项目

前言 在持续集成/持续部署&#xff08;CI/CD&#xff09;的旅途中&#xff0c;Jenkins与版本控制系统的紧密集成是不可或缺的一环。本篇“持续集成03--Jenkins结合Gitee创建项目”将引导如何将Jenkins与Gitee&#xff08;一个流行的Git代码托管平台&#xff09;相结合&#xff…

SpringBoot使用本地缓存——Caffeine

SpringBoot使用本地缓存——Caffeine 缓存&#xff0c;想必大家都用过&#xff0c;将常用的数据存储在缓存上能在一定程度上提升数据存取的速度。这正是局部性原理的应用。之前用的缓存大多是分布式的&#xff0c;比如Redis。使用Redis作为缓存虽然是大多数系统的选择&#xf…

vue2学习笔记7 - Vue中的MVVM模型

MVVM Model-View-viewModel是一种软件架构模式&#xff0c;用于将用户界面&#xff08;View&#xff09;与业务逻辑&#xff08;Model&#xff09;分离&#xff0c;并通过ViewModel进行连接和协调。MVVM模式的目标是实现视图与模型的解耦&#xff0c;提高代码的可读性、可维护…

力扣Hot100之两数之和

解法一&#xff1a; 双层循环暴力求解&#xff0c;先在数组的一个位置定住然后在这个位置的后续位置进行判断&#xff0c;如果两个数加起来等于目标和那么就返回 class Solution:def twoSum(self, nums: List[int], target: int) -> List[int]:for i,num in enumerate(num…

react Jsx基础概念和本质

什么是jsx jsx是JavaScript和XML(HTML)的缩写&#xff0c;表示在js代码中编写HTML模板结构&#xff0c;它是react中编写UI模板的方式 const message this is message function App(){return (<div><h1>this is title</h1>{message}</div>) } jsx优…

js补环境系列之剖析:原型、原型对象、实例对象三者互相转化(不讲废话、全是干货)

【作者主页】&#xff1a;小鱼神1024 【擅长领域】&#xff1a;JS逆向、小程序逆向、AST还原、验证码突防、Python开发、浏览器插件开发、React前端开发、NestJS后端开发等等 思考下&#xff1a;js补环境中&#xff0c;什么场景会用到原型、原型对象、实例对象&#xff1f; 举…

通过docker构建基于LNMP的WordPress项目

目录 1.准备nginx 2.准备mysql 3.准备php 4.构建各镜像 5.运行wordpress 1、项目环境&#xff1a; 1.1 &#xff08;1&#xff09;公司在实际的生产环境中&#xff0c;需要使用Docker 技术在一台主机上创建LNMP服务并运行Wordpress网站平台。然后对此服务进行相关的性能…

《昇思25天学习打卡营第07天|qingyun201003》

日期 心得 越往后&#xff0c;越看不懂&#xff0c;只能说是有了解到如何去训练模型代码&#xff0c;对于模型代码该如何去保存&#xff0c;如何通过网络模型去训练。只能一步步来&#xff0c;目前来说是推进度&#xff0c;等后面全部有了认知&#xff0c;再回来重新学习 昇思…

防火墙NAT地址转换和智能选举综合实验

一、实验拓扑 目录 一、实验拓扑 二、实验要求&#xff08;接上一个实验要求后&#xff09; 三、实验步骤 3.1办公区设备可以通过电信链路和移动链路上网(多对多的NAT&#xff0c;并且需要保留一个公网IP不能用来转换) 3.2分公司设备可以通过总公司的移动链路和电信链路访…

网易天音:网易云音乐推出的一站式AI音乐创作工具

网易天音是一款由网易云音乐推出的AI音乐创作工具&#xff0c;它为音乐爱好者和专业歌手提供了一个便捷高效的创作平台。用户可以通过输入灵感&#xff0c;利用AI技术辅助完成作词、作曲、编曲和演唱&#xff0c;生成初稿后还可以进行词曲协同调整&#xff0c;以满足个性化的音…

MySQL 执行引擎 事务 锁 日志

MySQL 执行引擎 事务 锁 日志 一、执行引擎二、事务三、锁四、日志 一、执行引擎 1、查询设置引擎 -- 查询当前数据库支持的存储引擎&#xff1a;默认的执行引擎是innoDB 支持事务&#xff0c;行级锁定和外键 show engines;-- 查看当前的默认存储引擎&#xff1a; show var…