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

informer中的indexer机制的实现分析与源码解读

1. 背景

client-go工具下的tools/cache.indexer为informer提供缓存与索引的能力。可以实现快速通过索引找到对应的对象(pod, deployment,secret,configmap等)。
indexer再informer机制中的使用图示:
indexer包括2部分: 一部分是store用于实际数据的存储,也就是图中thread safe store部分。另一部分是index,用于为数据制作索引。 建立索引后,就可以实现从store中快速检索出向要的数据。否则
从store中检索数据,需要依次对store中的数据进行全遍历查询。 indexer简单图示就是大概这样:
这个机制可以类比mysql数据库的索引机制。mysql的利用B+树建立索引,之后利用索引就能快速检索数据,而不需要全表遍历查询。
当然indexer的索引机制不是使用B+数,那具体是如何实现呢?接下来我们分析下indexer的索引实现方法。

2. indexer的源码分析

找到indexer源码,位于k8s.io/client-go/tools/cach/index.go
 

type Indexer interface {Store// Index returns the stored objects whose set of indexed values// intersects the set of indexed values of the given object, for// the named indexIndex(indexName string, obj interface{}) ([]interface{}, error)// IndexKeys returns the storage keys of the stored objects whose// set of indexed values for the named index includes the given// indexed valueIndexKeys(indexName, indexedValue string) ([]string, error)// ListIndexFuncValues returns all the indexed values of the given indexListIndexFuncValues(indexName string) []string// ByIndex returns the stored objects whose set of indexed values// for the named index includes the given indexed valueByIndex(indexName, indexedValue string) ([]interface{}, error)// GetIndexer return the indexersGetIndexers() Indexers// AddIndexers adds more indexers to this store.  If you call this after you already have data// in the store, the results are undefined.AddIndexers(newIndexers Indexers) error
}

// Index maps the indexed value to a set of keys in the store that match on that value
type Index map[string]sets.String
// Indexers maps a name to a IndexFunc
type Indexers map[string]IndexFunc
// Indices maps a name to an Index
type Indices map[string]Index
indexer是一个接口,包括二部分,一部分是存储数据的store,后面可以进一步看到store也是一个接口。另一部分是实现索引的几个方Index(),IndexKeys(),ListIndexFuncValues,ByIndex(),GetIndexers(),AddIndexers(),以及三个map结构类型Index,Indexers,Indices。
可以看到涉及的名称和概念很多,需要一步一步的拆解分析,才能搞清楚各个概念的含义与作用,这里先简单介绍下。
(1) IndexFunc, 是一个函数,输入obj对象,输出对象在这个索引函数匹配后字段。
例如,一个pod对象,给pod 打个label, "city"="shenzhen"。可以定义一个IndexFunc,输入一个pod对象,输出这个pod已经定义的label: "city"的值:"shenzhen"
func cityIndexFunc(obj interface{}) ([]string, error) {pod := obj.(*corev1.Pod)psaId := pod.Labels["city"]return []string{psaId}, nil
}
(2) Index, map结构,这里我们姑且称为"索引表",索引机制用到的索引就存储在index类型里面。key是索引值,value是索引出的对象名(默认是<ns>/<meta.name>格式的对象)
(3) Indexers, map结构,可能IndexFunc索引函数有很多,那么可以给每个indexFunc起一个名字indexName,再把indexName: indexFunc的映射关系用一个map结构保存 。
(4) Indices, map结构,indexFunc有很多,每一个indexFunc都对应一个index索引表,所以indices就是indexName与index的映射关系表
indexer的定义简单图示下,便于理解
反复说,Indexer由两部分组成,"存储"+"索引"。我们先看看第一步分存储数据的store如何实现.

3. store.go 源码分析

找到store源码,位于k8s.io/client-go/tools/cach/store.go
store接口的定义

type Store interface {Add(obj interface{}) error           // 往存储里面添加一个对象Update(obj interface{}) error        // 更新存储里面的一个对象Delete(obj interface{}) error        // 删除存储里面的一个对象List() []interface{}                 // 提取存储里面所有对象ListKeys() []string                  // 提取存储里面所有对象的keyGet(obj interface{}) (item interface{}, exists bool, err error) // 获取存储里面的一个对象GetByKey(key string) (item interface{}, exists bool, err error) // 通过key来获取存储里面的一个对象// Replace will delete the contents of the store, using instead the// given list. Store takes ownership of the list, you should not reference// it after calling this function.Replace([]interface{}, string) error // 替换存储里面的所有对象Resync() error
}
store的构造函数与indexer的构造函数:

// NewStore returns a Store implemented simply with a map and a lock.
func NewStore(keyFunc KeyFunc) Store {return &cache{cacheStorage: NewThreadSafeStore(Indexers{}, Indices{}),keyFunc:      keyFunc,}
}
// NewIndexer returns an Indexer implemented simply with a map and a lock.
func NewIndexer(keyFunc KeyFunc, indexers Indexers) Indexer {return &cache{cacheStorage: NewThreadSafeStore(indexers, Indices{}),keyFunc:      keyFunc,}
}
NewStore()返回的是一个Store对象,只能存储数据,不具有索引能力。NewIndexer()返回的是一个Indexer,通过上分析知道Indexer是既有"存储"能力,也有"索引"能力的类型。
另外,从构造函数可以看到NewStore()与NewIndexer()都返回的是一个&cache{}对象,那还得继续看看cache类型的定义
cache类型的定义

// cache responsibilities are limited to:
//	1. Computing keys for objects via keyFunc
//  2. Invoking methods of a ThreadSafeStorage interface
type cache struct {// cacheStorage bears the burden of thread safety for the cachecacheStorage ThreadSafeStore  // ThreadSafeStore 是存数据的地方// keyFunc is used to make the key for objects stored in and retrieved from items, and// should be deterministic.keyFunc KeyFunc               // 作用把一个object计算出一个key出来
}
var _ Store = &cache{}
从cache类型的定义看,包括2部分,一个是ThreadSafeStore类型,一个keyFunc. 其中keyFunc,这个方法的作用把object计算出一个key,通常用的keyFunc是MetaNamespaceKeyFunc, 可以输入一个obj,返回结果是<ns>/<name>,如果没有namespace的对象,返回<name>.
// MetaNamespaceKeyFunc is a convenient default KeyFunc which knows how to make
// keys for API objects which implement meta.Interface.
// The key uses the format <namespace>/<name> unless <namespace> is empty, then
// it's just <name>.
//
// TODO: replace key-as-string with a key-as-struct so that this
// packing/unpacking won't be necessary.
func MetaNamespaceKeyFunc(obj interface{}) (string, error) {if key, ok := obj.(ExplicitKey); ok {return string(key), nil}meta, err := meta.Accessor(obj)if err != nil {return "", fmt.Errorf("object has no meta: %v", err)}if len(meta.GetNamespace()) > 0 {return meta.GetNamespace() + "/" + meta.GetName(), nil // 如果有namespace的资源类型,返回ns+name,比如pod,configmap等}return meta.GetName(), nil                                     // 如果没有namespace的资源类型,返回ns,比如node,pv等
}

而且cache类型实现了store接口定义的所有方法 Add(), Update(), Delete(), List(), ListKeys(), Get(), GetByKey(), Replace(), Resync() ,具体代码略。
store是抽象接口类,cache是store的具体实现类型。
接下来看看cache类型定义包括的另一部分,ThreadSafeStore的分析

4. ThreadSafeStore.go 源码分析

找到ThreadSafeStore源码,位于k8s.io/client-go/tools/cach/ThreadSafeStore.go

type ThreadSafeStore interface {Add(key string, obj interface{})Update(key string, obj interface{})Delete(key string)Get(key string) (item interface{}, exists bool)List() []interface{}ListKeys() []stringReplace(map[string]interface{}, string)Index(indexName string, obj interface{}) ([]interface{}, error)IndexKeys(indexName, indexKey string) ([]string, error)ListIndexFuncValues(name string) []stringByIndex(indexName, indexKey string) ([]interface{}, error)GetIndexers() Indexers// AddIndexers adds more indexers to this store.  If you call this after you already have data// in the store, the results are undefined.AddIndexers(newIndexers Indexers) errorResync() error
}
定义了一个ThreadSafeStore的接口类型。可以看到ThreadSafeStore定义的方法,包括"存储"能力部分对应的函数,与"索引"能力部分对应的函数。
threadSafeMap类型的定义

// threadSafeMap implements ThreadSafeStore
type threadSafeMap struct {lock  sync.RWMutex              // 保证对items map表操作的线程安全items map[string]interface{}    // 真正存储数据的map表结构// indexers maps a name to an IndexFuncindexers Indexers                         // 保存IndexFunc索引函数的map结构// indices maps a name to an Indexindices Indices                           // 保存Index索引表的map结构
}

threadSafeMap类型实现了ThreadSafeStore接口类型。
threadSafeMap类型定义中的lock为了保证操作map时线程安全。items是一个map,用于真正存储数据的结构; indexers 是一个map,保存IndexFunc与这个函数的命名(即indexName); indices也是一个map,map表里面保存了很多的index索引表.
threadSafeMap类型定义的方法包括 Add(), Update(), Delete(), List(), ListKeys(), Get() , Replace(), Resync(), Index() , IndexKeys(), ListIndexFuncValues(), ByIndex(), GetIndexers(), AddIndexers().
大致分为实现存储数据的方法函数:  Add(), Update(), Delete(), List(), ListKeys(), Get() , Replace(), Resync()
与实现索引的方法函数: Index() , IndexKeys(), ListIndexFuncValues(), ByIndex(), GetIndexers(), AddIndexers()
到此为止我们总结下:
经过层层剥解,我们知道indexer接口类型,调用了store接口类型,store接口类型调用了cache普通类型,cache类型的定义中包括threadSafeStore接口类型, 而threadSafeMap普通类型是threadSafeStore接口类型的实现。 所以indexer接口类型的的存储与索引能力,是利用底层的是threadSafeMap类型实现的.
threadSafeMap类型包括2部分,一部分是一个名叫到items的map,是存储数据的结构。另一部分是2个map,分别是indexers、indices,其中indexers是用于存放indexFunc即索引函数的集合,indices是存放index的集合,index保存的是索引后的值。

5. 举例说明

 我们参考应官方文档的测试用例k8s.io/client-go/tools/cache/index_test.go,写一个测试代码.
为了更好的理解源码与下面的测试用例,这里再重申下几个概念,源码中会多次涉及到。
obj: object表示k8s中的一个资源对象runtime.object,比如命名空间为"public"下,名为"one"的pod
key: 是由函数MetaNamespaceKeyFunc(obj)对某个obj资源对象处理后,返回的一个string,是有<ns>/<name>组成,比如上面那个pod对象,对应的Key是"public/one"
indexer: 可以理解为是一个能实现"存储"+“索引”能力的对象。
indexFunc:   称为"索引函数"
indexName:  索引函数也需要一个名字,就叫indexName
indexValue:  由索引函数处理Obj后,返回的值。比如下面例子中的“shenzhen”,"chengdu","beijing"
index:  map结构,也叫索引表,真正存储索引的地方
indices:  每一个indexFunc都有一个对应的index,indices用于保存indexName与index的对应关系。可以通过indexName找到对应的Index
测试的完整代码

func cityIndexFunc(obj interface{}) ([]string, error) {pod := obj.(*corev1.Pod)psaId := pod.Labels["city"]return []string{psaId}, nil
}
func TestIndexer(t *testing.T) {// 用NewIndexer构造函数,创建一个indexer对象indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"cityIndex": cityIndexFunc,})// 造数据,添加pods到indexer中pod1 := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "one", Namespace: "public", Labels: map[string]string{"city": "shenzhen"}}}pod2 := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "two", Namespace: "public", Labels: map[string]string{"city": "chengdu"}}}pod3 := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "tre", Namespace: "public", Labels: map[string]string{"city": "beijing"}}}pod4 := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "for", Namespace: "public", Labels: map[string]string{"city": "shenzhen"}}}indexer.Add(pod1)indexer.Add(pod2)indexer.Add(pod3)indexer.Add(pod4)fmt.Println("显示索引表的所有数据: ")for k, v := range indexer.List() {fmt.Println(k, v.(*corev1.Pod).Name, v.(*corev1.Pod).Labels)}// 显示indexer中的所有索引值values := indexer.ListIndexFuncValues("cityIndex")fmt.Println("values: ", values) // values:  [chengdu beijing shenzhen]// 查询索引值为shenzhen的pod// ByIndex 根据索引函数名与索引值,检索出匹配的obj对象foundPods2, err := indexer.ByIndex("cityIndex", "shenzhen")if err != nil {fmt.Printf("unexpected error: %v\n", err)}fmt.Println("pod have label shenzhen: ")for _, pod2 := range foundPods2 {fmt.Println(pod2.(*corev1.Pod).Namespace, pod2.(*corev1.Pod).Name) // 结果是 public for; public one}// IndexKeys 根据索引名与索引值,检索出匹配的obj的key(key是由ns/name组成)keys, err := indexer.IndexKeys("cityIndex", "shenzhen")if err != nil {t.Error(err)}for _, key := range keys {fmt.Println("key: ", key) // 结果是: public/one;public/for}// 查询所有obj中,用索引函数匹配的索引值ss := indexer.ListIndexFuncValues("cityIndex")fmt.Println("indexFuncValue: ", ss) // indexFuncValue:  [chengdu beijing shenzhen]// 返回与输入obj有同样索引的objress, err := indexer.Index("cityIndex", pod1)if err != nil {return}fmt.Println(len(ress))for _, pod := range ress {fmt.Println(pod.(*corev1.Pod).Name, pod.(*corev1.Pod).Namespace) // one public,for public}
}
测试功能说明: 需要通过label快速检索出对应对象obj
创建一个索引函数: cityIndexFunc,函数输入是一个对象obj, 返回label是"city"的值。
加入一个pod,打上label: "city"="shenzhen". 将这个pod作为参数输入到这个函数,输出就是"city"对应的值"shenzhen"
func cityIndexFunc(obj interface{}) ([]string, error) {pod := obj.(*corev1.Pod)psaId := pod.Labels["city"]return []string{psaId}, nil
}
使用NewIndexer()构造函数, 创建一个Indexer对象出来。构造函数入参需要keyFunc函数 与  indexers类型。
keyFunc使用常用的MetaNamespaceKeyFunc函数,Indexers类型,需要一个indexFunc,这里就使用上面的cityIndexFunc函数。 索引函数名是"cityIndex",索引函数是"cityIndexFunc"

indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"cityIndex": cityIndexFunc,})
进一步看下NewIndexer()函数,返回的cache对象中,用NewThreadSafeStore(indexers, Indices{},构造了一个NewThreadSafeStore对象

func NewIndexer(keyFunc KeyFunc, indexers Indexers) Indexer {return &cache{cacheStorage: NewThreadSafeStore(indexers, Indices{}),keyFunc:      keyFunc,}
}
再进一步看下NewThreadSafeStore()构造函数,返回了一个threadSafeMap对象,该对象中又创建了一个名"items"的map[string]interface{},它就是正是的索引表;indexers是索引函数的集合; indices是索引表index的集合。

// NewThreadSafeStore creates a new instance of ThreadSafeStore.
func NewThreadSafeStore(indexers Indexers, indices Indices) ThreadSafeStore {return &threadSafeMap{items:    map[string]interface{}{},indexers: indexers,indices:  indices,}
}
通过构造4个pod对象,再用Add()方法,将pod对象添加到存储。Indexer的Add()方法。

// 造数据,添加pods到indexer中pod1 := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "one", Namespace: "public", Labels: map[string]string{"city": "shenzhen"}}}pod2 := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "two", Namespace: "public", Labels: map[string]string{"city": "chengdu"}}}pod3 := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "tre", Namespace: "public", Labels: map[string]string{"city": "beijing"}}}pod4 := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "for", Namespace: "public", Labels: map[string]string{"city": "shenzhen"}}}indexer.Add(pod1)indexer.Add(pod2)indexer.Add(pod3)indexer.Add(pod4)
Indexer的Add()方法,是用store接口类型的Add()实现的。 方法定义如下:

// Add inserts an item into the cache.
func (c *cache) Add(obj interface{}) error {key, err := c.keyFunc(obj)             // 现有keyFunc也就是MetaNamespaceKeyFunc方法,计算出obj的key(由<ns>/<name>表示)if err != nil {return KeyError{obj, err}}c.cacheStorage.Add(key, obj)           // 再调用ThreadSafeStore接口类型的Add()方法return nil
}
ThreadSafeStore接口类型的Add()方法,调用的是ThreadSafeMap的Add()方法
ThreadSafeMap的Add()方法的实现如下,包括2部分,一是存储数据,二是更新索引。

func (c *threadSafeMap) Add(key string, obj interface{}) {c.lock.Lock()defer c.lock.Unlock()oldObject := c.items[key]              // 通过key获取存储内原来的obj对象即oldObjectc.items[key] = obj                     // 新的obj存到items表中c.updateIndices(oldObject, obj, key)   // 使用updateIndices()更新索引
}
那么c.updateIndices(oldObject, obj, key)是如何更新索引的呢?

// updateIndices modifies the objects location in the managed indexes, if this is an update, you must provide an oldObj
// updateIndices must be called from a function that already has a lock on the cache
func (c *threadSafeMap) updateIndices(oldObj interface{}, newObj interface{}, key string) {// if we got an old object, we need to remove it before we add it againif oldObj != nil {c.deleteFromIndices(oldObj, key)          // 如存储里面,已经有obj的老数据,先把老数据的索引删除}for name, indexFunc := range c.indexers {indexValues, err := indexFunc(newObj)     // 通过indexFunc获取到newObj的索引值indexValuesif err != nil {panic(fmt.Errorf("unable to calculate an index entry for key %q on index %q: %v", key, name, err))}index := c.indices[name]                 // 通过indexName索引函数名,找到对应index索引表if index == nil {                        // 如果indexName索引函数名,还没有对应的索引表Index,就index{}新创建一个索引表index = Index{}c.indices[name] = index          // 把新创建的索引表index,加到indices表中}for _, indexValue := range indexValues {set := index[indexValue]          // 在index索引表中,用indexValue值找对应的值,值是一个set.string{}类型if set == nil {                   // 如果在index索引表,没有找到indexValue值时,就新建一个set.string{}类型set = sets.String{}       index[indexValue] = set   // indexValue与set对应的数据,存放到index索引表}set.Insert(key)                   // 如果index表中,已经有indexValue值的set.string{}数据,就将key加到这个set.string{}集合中去}}
}
简单的理解updateIndices ()函数的逻辑,如何oldObj已经在存储里面,就先删除oldObj对应的索引。 从indexers从遍历出,索引函数名name与索引函数indexFunc,用name从indices找到对应index索引表. 最后处理索引值indexValue,如果indexValue已经存储在与index索引表中,就将indexValue添加到index表中去。如果不存在,就新加一个键值对应健为index[indexValue],值为sets.string{}.
set.string{}是一个map结果类型,利用map建的唯一性,实现一个集合set类型, set的特点就是元素无重复。具体set.string{}的实现这里就不展开了,详情可以查看源码/k8s.io/apimachinery/pkg/util/sets
// sets.String is a set of strings, implemented via map[string]struct{} for minimal memory consumption.
type String map[string]Empty
type Empty struct{}
接下来我们看看indexer的几个主要函数
indexer.List()返回indexer中存储的所有obj对象
fmt.Println("显示索引表的所有数据: ")for k, v := range indexer.List() {fmt.Println(k, v.(*corev1.Pod).Name, v.(*corev1.Pod).Labels)}
indexer.ListIndexFuncValues("cityIndex") 用于返回匹配了indexFunc索引函数为"cityIndex"对应的索引值

// 显示indexer中的所有索引值
values := indexer.ListIndexFuncValues("cityIndex")
fmt.Println("values: ", values) // values:  [chengdu beijing shenzhen]
indexer.ByIndex("cityIndex", "shenzhen"),根据索引函数名与索引值,检索出匹配的对象obj

// 查询索引值为shenzhen的pod
// ByIndex 根据索引函数名与索引值,检索出匹配的obj对象
foundPods2, err := indexer.ByIndex("cityIndex", "shenzhen")
if err != nil {fmt.Printf("unexpected error: %v\n", err)
}
fmt.Println("pod have label shenzhen: ")
for _, pod2 := range foundPods2 {fmt.Println(pod2.(*corev1.Pod).Namespace, pod2.(*corev1.Pod).Name) // 结果是 public for; public one
}
indexer.IndexKeys("cityIndex", "shenzhen"),根据索引函数名与索引值,检索出匹配的key(这里key默认是<ns>/<name>组合的字符串).

// IndexKeys 根据索引名与索引值,检索出匹配的obj的key(key是由ns/name组成)
keys, err := indexer.IndexKeys("cityIndex", "shenzhen")
if err != nil {t.Error(err)
}
for _, key := range keys {fmt.Println("key: ", key) // 结果是: public/one;public/for
}
再补充下,ByIndex(),IndexKeys()很类似,输入都是一样,但一个返回的是对象obj,一个是返回的对象obj的key.
indexer.Index("cityIndex", pod1), 先通过obj对象"pod1"找到indexkeys: "shenzhen", 再通过indexName: "cityIndex"与indexKey: "shenzhen",  接下来的逻辑同IndexKeys()函数

// 返回与输入obj有同样索引的obj
ress, err := indexer.Index("cityIndex", pod1)
if err != nil {return
}
fmt.Println(len(ress))
for _, pod := range ress {fmt.Println(pod.(*corev1.Pod).Name, pod.(*corev1.Pod).Namespace) // one public,for public
}
最后为了更深入理解index,indices,items三个map中具体存储的什么类型,默认可以在源码k8s.io/client-go/tools/cach/ThreadSafeStore.go中,找到ByIndex()函数下添加打印信息。
在运行测试用例"go test -run "^TestIndexer" -v ",输出如下:
items的内容:  map[public/for:&Pod{ObjectMeta:{for  public     此次输出省略},} public/one:&Pod{ObjectMeta:{one  public     此次输出省略},} public/tre:&Pod{ObjectMeta:{tre  public     此次输出省略},} public/two:&Pod{ObjectMeta:{two  public   此次输出省略}]
index的内容:  map[beijing:map[public/tre:{}] chengdu:map[public/two:{}] shenzhen:map[public/for:{} public/one:{}]]
indices的内容:  map[cityIndex:map[beijing:map[public/tre:{}] chengdu:map[public/two:{}] shenzhen:map[public/for:{} public/one:{}]]] 
为了便于理解再整理下格式:
items的内容:

map["public/for": &pod{},"public/one": &pod{},"public/tre": &pod{},"public/two": &pod{},
]
items的内容是key与obj的对应,也就是实际存储的数据. 重述下,key的格式是ns/name
index的内容:

map["shenzhen": map["public/for": {},"public/one": {}]"beijing": map["public/tre":{}]"chengdu": map["public/two":{}]
]
可以看出index,的键是"",value是一个map, 这个map里面,只有键,没有value,这里不用list,我猜想是为了能保证数据唯一
indices的内容
map["cityIndex": index]map["cityIndex": map["beijing": map["public/tre": {}]"chengdu": map["public/two": {}]"shenzhen": map["public/for":{} "public/one":{}]
]
indices的键是indexFunc的名称即: indexName,value为一个Index  

相关文章:

informer中的indexer机制的实现分析与源码解读

1. 背景 client-go工具下的tools/cache.indexer为informer提供缓存与索引的能力。可以实现快速通过索引找到对应的对象(pod, deployment,secret,configmap等)。 indexer再informer机制中的使用图示&#xff1a; indexer包括2部分: 一部分是store用于实际数据的存储&#xff0c;…...

英特尔宣布针对对Llama 3.1进行优化 以提升所有产品的性能

日前Meta正式发布了Llama 3.1开源大模型&#xff0c;以其庞大的参数量和卓越性能&#xff0c;首次在多项基准测试中击败了GPT-4o等业界领先的闭源模型。允许开发者自由地进行微调、蒸馏&#xff0c;甚至在任何地方部署&#xff0c;这种开放性为AI技术的普及和创新提供了无限可能…...

Python3网络爬虫开发实战(1)爬虫基础

一、URL 基础 URL也就是网络资源地址&#xff0c;其满足如下格式规范 scheme://[username:password]hostname[:port][/path][;parameters][?query][#fragment] scheme&#xff1a;协议&#xff0c;常用的协议有 Http&#xff0c;https&#xff0c;ftp等等&#xff1b;usern…...

Redis的五种数据类型与命令

目录 引言 一 Redis的特性 二 Redis的安装 三 Redis的优点 四 Redis的五种数据类型与命令 五 Redis的配置文件 引言 Redis是什么&#xff1f; Remote Dictionary Service(远程字典服务器) Redis 是一个开源的(BSD许可)的&#xff0c;C语言编写的&#xff0c;高性能的数…...

RocketMQ的详细讲解(四种mq的对比(activeMq、rabbitmq、rocketmq、kafka))

20240729 RocketMQ1 mq的三大作用 异步、削峰限流、解耦合2. 四种mq的对比&#xff08;activeMq、rabbitmq、rocketmq、kafka&#xff09;3 rocketmq特点1. 平台无关2. 能提供什么样的功能 4 rocketMq4.1 broker中的标题&#xff0c;来约束读和写4.2 rocketmq的结构4.3 读和写的…...

除了GPT,还有哪些好用的AI工具?

最强AI视频生成&#xff1a;小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频百万播放量https://aitools.jurilu.com/ 多得很&#xff0c;这20个免费的国产AI工具&#xff0c;打工人必备&#xff0c;除了比chatGPT好用&#xff0c;甚至还可以用来变现…...

04 | 深入浅出索引(上)

此系列文章为极客时间课程《MySQL 实战 45 讲》的学习笔记&#xff01; 索引的常见模型 可以提供查询效率的数据结构有很多&#xff0c;常见的有三种&#xff1a;哈希表、有序数组、搜索数。 哈希表是一种以 key-value 形式存储的数据结构。输入一个 key&#xff0c;通过固定…...

Linux的yum源安装MySQL5.7

linux的yum源安装MySQL5.7 一、MySQL 1、简介 MySQL 是一种流行的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;由瑞典公司 MySQL AB 开发&#xff0c;后来被 Oracle Corporation 收购。它是一个开源软件&#xff0c;提供了高效、稳定和可靠的数据管理解决…...

基于深度学习的音频自监督学习

基于深度学习的音频自监督学习&#xff08;Self-Supervised Learning, SSL&#xff09;是一种利用未标注的音频数据&#xff0c;通过设计自监督任务进行特征学习的方法。这种方法在需要大量标注数据的音频处理任务&#xff08;如语音识别、情感分析等&#xff09;中&#xff0c…...

用uniapp 及socket.io做一个简单聊天app1

####相关的表结构&#xff0c;用的是mysql 用户表&#xff08;Users&#xff09; 存储用户的基本信息。 CREATE TABLE Users (id INT AUTO_INCREMENT PRIMARY KEY,username VARCHAR(50) NOT NULL UNIQUE,password VARCHAR(100) NOT NULL,email VARCHAR(100) UNIQUE,created_a…...

在Postman中引用JS库

前言 在做接口测试时&#xff0c;出于安全因素&#xff0c;请求参数需要做加密或者加上签名才能正常请求&#xff0c;例如&#xff1a;根据填写的请求参数进行hash计算进行签名。postman作为主流的接口调试工具也是支持请求预处理的&#xff0c;即在请求前使用JavaScript脚本对…...

学习笔记-系统框图简化求传递函数公式例题

简化系统结构图求系统传递函数例题 基础知识回顾 第四讲 控制系统的方框图 (zhihu.com) 「自控原理」2.3 方框图的绘制及化简_方框图化简-CSDN博客 自动控制原理笔记-结构图及其等效变换_结构图等效变换-CSDN博客 例子一 「自控原理」2.3 方框图的绘制及化简_方框图化简-CS…...

postgrsql——事务概述

事务概述 事务的特性 原子性&#xff08;Atomicity&#xff09;&#xff1a; 事务被视为一个整体&#xff0c;其中的操作要么全部执行成功&#xff0c;要么全部不执行&#xff0c;即不存在部分执行的情况。这确保了事务的完整性和一致性。一致性&#xff08;Consistency&…...

1.Spring Boot 简介(Spring MVC+Mybatis-plus)

文章目录 一&#xff0c;Spring Boot 简介二&#xff0c;搭建springboot项目并整合mybatis-plus框架1.pom导依赖2.添加启动项3.配置文件.yml 三&#xff0c;springboot集成 Spring MVC1.springmvc定义2.应用注解 一&#xff0c;Spring Boot 简介 SpringBoot是Spring的子工程(或…...

《计算机网络》(学习笔记)

目录 一、计算机网络体系结构 1.1 计算机网络概述 1.1.1 计算机网络的概念 1.1.2 计算机网络的组成 1.1.3 计算机网络的功能 1.1.4 电流交换、报文交换和分组交换 1.1.5 计算机网络的分类 1.1.6 计算机网络的性能指标 1.2 计算机网络体系结构与参考模型 1.2.1 计算机…...

指针函数和函数指针

函数名在表达式中应该如何被解读&#xff1f;答&#xff1a;函数名可以在表达式中被解读成“指向该函数的指针”。 函数指针和指针函数有什么区别&#xff1f;答&#xff1a;函数指针是一个指向函数的指针&#xff1b;指针函数是一个返回指针变量的函数。 一个函数能否有时候…...

Elasticsearch跨集群搜索

Elasticsearch&#xff08;简称ES&#xff09;是一种基于Lucene的搜索引擎&#xff0c;以其高性能、可扩展性和实时搜索能力而广受欢迎。在大型分布式系统中&#xff0c;跨集群搜索成为了一个重要的需求&#xff0c;它允许用户从多个Elasticsearch集群中联合查询数据&#xff0…...

基于FPGA的数字信号处理(19)--行波进位加法器

1、10进制加法是如何实现的&#xff1f; 10进制加法是大家在小学就学过的内容&#xff0c;不过在这里我还是帮大家回忆一下。考虑2个2位数的10进制加法&#xff0c;例如&#xff1a;15 28 43&#xff0c;它的运算过程如下&#xff1a; 个位两数相加&#xff0c;结果为5 8 1…...

树莓派下,centos7操作系统, TensorFlow java版实现植物分类功能

在树莓派上运行CentOS 7,并使用TensorFlow Java版本实现植物分类功能可以通过以下步骤实现。以下是详细的指导: 一、安装和设置环境 1. 更新系统并安装基本工具 确保你的CentOS 7系统是最新的,并安装必要的工具: sudo yum update -y sudo yum install -y wget unzip gi…...

开源一个react路由缓存库

Github仓库 背景 产品希望可以像浏览器那样每打开一个路由&#xff0c;会多一个tab&#xff0c;用户可以切换tab访问之前加载过的页面&#xff0c;且不会重新加载。真就产品一句话…… Github上有轮子了吗 Github上开箱即用的轮子是基于react-router-dom V5实现的&#xff…...

go-kratos 学习笔记(7) 服务发现服务间通信grpc调用

服务发现 Registry 接口分为两个&#xff0c;Registrar 为实例注册和反注册&#xff0c;Discovery 为服务实例列表获取 创建一个 Discoverer 服务间的通信使用的grpc&#xff0c;放到data层&#xff0c;实现的是从uses服务调用orders服务 app/users/internal/data.go 加入 New…...

SPSS个人版是什么软件

SPSS是一款数据统计、分析软件&#xff0c;它由IBM公司出品&#xff0c;这款软件平台提供了文本分析、大量的机器学习算法、数据分析模型、高级统计分析功能等&#xff0c;软件易学且功能非常强大&#xff0c;可以使用SPSS制作图表&#xff0c;例如柱状、饼状、折线等图表&…...

Minos 多主机分布式 docker-compose 集群部署

参考 docker-compose搭建多主机分布式minio - 会bk的鱼 - 博客园 (cnblogs.com) 【运维】docker-compose安装minio集群-CSDN博客 Minio 是个基于 Golang 编写的开源对象存储套件&#xff0c;虽然轻量&#xff0c;却拥有着不错的性能 中文地址&#xff1a;MinIO | 用于AI的S3 …...

Unity + Hybridclr + Addressable + 微信小程序 热更新报错

报错时机&#xff1a; Generate All 怎么All 死活就是报错 生成微信小程序&#xff0c;并启动后 报错内容&#xff1a; MissingMethodException:AoT generic method notinstantiated in aot.assembly:Unity.ResourceManager:dll, 原因&#xff1a; Hybridclr 开发文档 解…...

鸿蒙开发—黑马云音乐之Music页面

目录 1.外层容器效果 2.信息区-发光效果 3.信息区-内容布局 4.播放列表布局 5.播放列表动态化 6.模拟器运行并配置权限 效果&#xff1a; 1.外层容器效果 Entry Component export struct MuiscPage {build() {Column() {// 信息区域Column() {}.width(100%)// .backgroun…...

IsaacLab | 如何在Manipulation任务中添加新的目标(target)

如是我闻&#xff1a; 终于让我给摸索出来了&#xff0c;在这里描述一下问题场景。 假使说我们有一个机械臂操作的任务&#xff0c;这样婶的 Isaac Lab | Push 我们想做多目标的任务&#xff0c;这时候需要向环境中添加第二个目标&#xff0c;像这样 Isaac Lab | Add target 那…...

【Python从入门到进阶】61、Pandas中DataFrame对象的操作(二)

接上篇《60、Pandas中DataFrame对象的操作&#xff08;一&#xff09;》 上一篇我们讲解了DataFrame对象的简介、基本操作及数据清洗相关的内容。本篇我们来继续讲解DataFrame对象的统计分析、可视化以及数据导出与保存相关内容。 一、DataFrame的统计分析 在数据分析和处理中…...

Linux(虚拟机)的介绍

Linux介绍 常见的操作系统 Windows&#xff1a;微软公司开发的一款桌面操作系统&#xff08;闭源系统&#xff09;。版本有dos&#xff0c;win98&#xff0c;win NT&#xff0c;win XP , win7, win vista. win8, win10&#xff0c;win11。服务器操作系统&#xff1a;winserve…...

CSS(九)——CSS 轮廓(outline)

CSS 轮廓&#xff08;outline&#xff09; 轮廓&#xff08;outline&#xff09;是绘制于元素周围的一条线&#xff0c;位于边框边缘的外围&#xff0c;可起到突出元素的作用。 轮廓&#xff08;outline&#xff09;属性指定元素轮廓的样式、颜色和宽度。 让我们用一个图来看…...

Unity Timeline:构建复杂动画序列的利器

Unity的Timeline是一个强大的动画工具&#xff0c;它允许开发者创建复杂的动画序列&#xff0c;将动画、音频和事件整合到一个统一的时间轴上。Timeline的可视化编辑界面使得动画制作变得更加直观和灵活。本文将介绍Unity Timeline的基本概念、功能以及如何使用它来实现动画。 …...

C# 与C++ cli

cli CLI&#xff08;Command Line Interface&#xff09;是一种通过命令行界面与计算机系统进行交互的方式。它提供了一种以文本形式输入命令和接收系统输出的方法&#xff0c;用于执行各种操作和管理计算机系统。以下是CLI的详细解释&#xff1a; 一、定义与基本概念 定义&…...

Linux文件编程--打开及创建

...

Vue3点击按钮实现跳转页面并携带参数

前提&#xff1a;有完整的路由规则 1.源页面 <template><div><h1>源页面</h1><!--通过js代码跳转--><template #default"scope"><button click"toTargetView(scope.row)">点击跳转携带参数</button><…...

探索Linux-1-虚拟机远程登陆XShell6远程传输文件Xftp6

Linux是什么&#xff1f; Linux是一个开源的操作系统内核&#xff0c;由林纳斯托瓦兹&#xff08;Linus Torvalds&#xff09;于1991年首次发布。它基于Unix操作系统&#xff0c;但提供了更多的自由和灵活性。Linux内核是操作系统的核心部分&#xff0c;负责管理系统资源、处理…...

SpringBoot中使用监听器

1.定义一个事件 /*** 定义事件* author hrui* date 2024/7/25 12:46*/ public class CustomEvent extends ApplicationEvent {private String message;public CustomEvent(Object source, String message) {super(source);this.message message;}public String getMessage() …...

mybatise全接触-面试宝典-知识大全

1 . 简述什么是Mybatis和原理 &#xff1f; Mybatis工作原理&#xff1a; &#xff08;1&#xff09;Mybatis是一个半ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;它内部封装了JDBC&#xff0c;加载驱动、创建连接、创建statement等繁杂的过程&#xff0c;开发者…...

Catalyst优化器:让你的Spark SQL查询提速10倍

目录 1 逻辑优化阶段 2.1 逻辑计划解析 2.2 逻辑计划优化 2.2.1 Catalys的优化过程 2.2.2 Cache Manager优化 2 物理优化阶段 2.1 优化 Spark Plan 2.1.1 Catalyst 的 Join 策略 2.1.2 如何决定选择哪一种 Join 策略 2.2 Physical Plan 2.2.1 EnsureRequirements 规则 3 相关文…...

【Hot100】LeetCode—416. 分割等和子集

目录 题目1- 思路2- 实现⭐152. 乘积最大子数组——题解思路 3- ACM 实现 题目 原题连接&#xff1a;416. 分割等和子集 1- 思路 理解为背包问题 思路&#xff1a; 能否将均分的子集理解为一个背包&#xff0c;比如对于 [1,5,11,5]&#xff0c;判断能否凑齐背包为 11 的容量…...

前端开发知识-vue

大括号里边放键值对&#xff0c;即是一个对象。 一、vue可以简化前端javascript的操作。 主要特点是可以实现视图、数据的双向绑定。 使用vue主要分为三个步骤&#xff1a; 1.javascript中引入vue.js 可以src中可以是vue的网址&#xff0c;也可以是本地下载。 2.在javasc…...

【嵌入式硬件】快衰减和慢衰减

1.引语 在使用直流有刷电机驱动芯片A4950时,这款芯片采用的是PWM控制方式,我发现他的正转、反转有两种控制方式,分别是快衰减和慢衰减。 2.理解 慢衰减:相当于加在电机(感性原件)两端电压消失,将电机两端正负短接。 快衰减:相当于加在电机(感性原件)两端电压消失,将电机…...

C语言 | Leetcode C语言题解之第275题H指数II

题目&#xff1a; 题解&#xff1a; int hIndex(int* citations, int citationsSize) {int left 0, right citationsSize - 1;while (left < right) {int mid left (right - left) / 2;if (citations[mid] > citationsSize - mid) {right mid - 1;} else {left mi…...

速盾:网络安全和 CDN 之间的关系是怎样的?

网络安全和内容交付网络&#xff08;CDN&#xff09;之间有着密切的关系。网络安全主要涉及保护网络和系统免受各种威胁和攻击&#xff0c;而CDN是一种用于提供更快速、高效和可靠的内容交付服务的技术。在当今数字化和云计算时代&#xff0c;网络安全和CDN之间的关系变得更加紧…...

数据库安全:MySQL安全配置,MySQL安全基线检查加固

「作者简介」:冬奥会网络安全中国代表队,CSDN Top100,就职奇安信多年,以实战工作为基础著作 《网络安全自学教程》,适合基础薄弱的同学系统化的学习网络安全,用最短的时间掌握最核心的技术。 这一章节我们需要知道MySQL的安全基线标准和加固方式。 MySQL基线检查 1、更新…...

【SpringBoot】参数传递

1.定义URL变量 RequestMapping("/user/{username}") ResponseBody public String userProfile(PathVariable String username){ return "user:"username; } 2.定义多个URL变量 RequestMapping("/user/{username}/blog/{blogId}") Response…...

Unity 骨骼动画(Skinned Mesh Renderer): 角色动画的高级渲染

在Unity中&#xff0c;骨骼动画(Skinned Mesh Renderer)是一种用于高级角色动画渲染的组件。它允许开发者将复杂的3D模型和动画应用到游戏角色上&#xff0c;实现逼真的视觉效果。本文将探讨Skinned Mesh Renderer的基本概念、使用方法以及如何优化性能。 Skinned Mesh Render…...

花几千上万学习Java,真没必要!(三十四)

1、泛型类&#xff1a; 测试代码&#xff1a; 创建一个Box类; package settest.com; public class Box<T> { // T stands for "Type" - T是一个占位符&#xff0c;用于表示具体的类型 // 类的内部可以使用T作为类型声明变量 private T t; // 构造方法&am…...

【代码】Python3|Scrapy框架初探(汽车之家大连市二手车车辆数据爬取、清洗与可视化)

本篇主要是整个项目的介绍&#xff0c;没提到太多琐碎的技术细节&#xff0c;以后有空的话会整理一下 Scrapy 和原生爬虫的差异&#xff0c;还有它坑人的一些地方&#xff0c;单发出来。 开源地址&#xff1a;https://github.com/shandianchengzi/car_home_spider 使用说明&a…...

C#中的new以及类

new关键字的用法 实例化对象&#xff1a;使用 new 关键字可以创建一个类的实例。例如&#xff1a; ​ MyClass obj new MyClass(); 指定构造函数&#xff1a;如果类有多个构造函数&#xff0c;可以使用 new 关键字指定使用哪一个构造函数来创建对象。例如&#xff1a; ​ MyC…...

Hbase简介和快速入门

一 Hbase简介 1 HBase定义 Apache HBase™ 是以hdfs为数据存储的&#xff0c;一种分布式、可扩展的NoSQL数据库。 2 HBase数据模型 HBase的设计理念依据Google的BigTable论文&#xff0c;论文中对于数据模型的首句介绍。Bigtable 是一个稀疏的、分布式的、持久的多维排序map…...

【AI落地应用实战】Amazon Bedrock +Amazon Step Functions实现链式提示(Prompt Chaining)

一、链式提示 Prompt Chaining架构 Prompt Chaining 是一种在生成式人工智能&#xff08;如大型语言模型&#xff09;中广泛使用的技术&#xff0c;它允许用户通过一系列精心设计的提示&#xff08;Prompts&#xff09;来引导模型生成更加精确、丰富且符合特定需求的内容。 P…...