July 19, 2022
简介 # client-go是k8s的一个基础组件库,是用于与API-Server交互的http客户端。K8s中大部分组件都使用了这个库实现与API-Server的通信功能。除了能够对资源对象的增删改查,还可Watch一个对象、升级成websocket链接等等功能。
client-go支持四种客户端:RESTClient、ClientSet、DynamicClient、DiscoveryClient。这几个client可以相互转换。
RESTClient # RESTClient是最基础的客户端,相当于最底层的基础结构,可以直接通过RESTClient提供的RESTful方法如Get()、Put()、Post()、Delete()进行交互。
一般而言,为了更为优雅的处理,需要进一步封装,通过Clientset封装RESTClient,然后再对外提供接口和服务。
可以通过ClientSet客户端获得:
client := cli.CoreV1().RESTClient().(*rest.RESTClient) ClientSet # Clientset是调用Kubernetes资源对象最常用的client,可以操作所有的资源对象,包含RESTClient。需要制定Group、Version,然后根据Resource获取。
clientset,err := kubernetes.NewForConfig(config) sa, err := clientset.CoreV1().ServiceAccounts("kube-system").Get("kube-shell-admin", metav1.GetOptions{}) DynamicClient # Dynamic client是一种动态的client,它能处理kubernetes所有的资源。不同于clientset,dynamic client返回的对象是一个map[string]interface{}。
dynamicClient,err := dynamic.NewForConfig(config) gvr := schema.GroupVersionResource{Version: "v1",Resource: "pods"} unstructObjList,err := dynamicClient.Resource(gvr).Namespace("dev").List(context.TODO(),metav1.ListOptions{Limit: 100}) DiscoveryClient # DiscoveryClient是发现客户端,主要用于发现kubernetes API Server所支持的资源组、资源版本、资源信息。除此之外,还可以将这些信息存储到本地,用户本地缓存,以减轻对Kubernetes API Server访问的压力。 kubectl的api-versions和api-resources命令输出也是通过DisconversyClient实现的。
discoveryClient,err := discovery.NewDiscoveryClientForConfig(config) APIGroup,APIResourceListSlice,err := discoveryClient.ServerGroupsAndResources() 这几种客户端的初始化都涉及到了入参config,即*rest.Config,这个是用于初始化客户端的所有配置信息。
rest.Config初始化 # 创建client前,需要先从初始化*rest.Config,这个*rest.Config可以从集群外的kubeconfig文件或者集群内部的 tokenFile 和 CAFile初始化(通过ServiceAcount自动挂载)。有以下几种方式:
集群外通过kubeconfig初始化 # BuildConfigFromFlags方法从给定的url或者kubeconfig文件的文件夹路径去初始化config,如果不成功则会使用集群内部方法初始化config,如果不成功则返回一个默认的config。
// "k8s.io/client-go/tools/clientcmd" config, err := clientcmd.
...
May 16, 2021
前言 # 前面🔗说过,Cond实现了一个条件变量,是等待或宣布一个事件发生的goroutines的汇合点。
就是说,使用sync.Cond可以做到多个协程等待某个协程通知的场景。
使用channel可以实现一读一写的场景,而Cond则实现多读一写的场景。
源码解析 # 简化版方法签名:
// Cond结构体 type Cond struct {} // NewCond 返回带Locker的Cond,这个Locker可以是 // *Mutex 或 *RWMutex func NewCond(l Locker) *Cond {} // 等待L的解锁并挂起goroutine func (c *Cond) Wait() {} // 唤醒1个因c阻塞的goroutine, // 如果在Signal之后才Wait会导致all goroutines are asleep - deadlock func (c *Cond) Signal() {} // 唤醒所有因c阻塞的goroutine // 如果在Broadcast之后才Wait会导致all goroutines are asleep - deadlock func (c *Cond) Broadcast() {} 因此,在Signal或者Broadcast前要先保证目标的协程已经进入了Wait状态,否则会导致死锁。因为Signal或者Broadcast只唤醒当前正在被Wait阻塞的协程。
Cond的定义:
// Copyright 2011 The Go Authors. All rights reserved.
...
May 11, 2021
Overview # 包链接🔗
sync包提供基本的同步原语,例如互斥锁。
除了Once和WaitGroup类型外,大多数都是供低级库例程使用的。
更高层次的同步最好通过channels和通信来完成。
从代码看,sync提供了几种类型:
Cond:条件变量 Locker:锁的接口定义 Map:协程并发安全的Map Mutex:互斥锁 Once:单次执行 Pool:池 RWMutex:读写锁 WaitGroup:等待组 几个类型分别对应不同的使用场景。
sync.Cond # Cond实现了一个条件变量,是等待或宣布一个事件发生的goroutines的汇合点。
通俗的说,sync.Cond用来协调那些访问共享资源的goroutine,当共享资源发生变化时,通知被阻塞goroutine。
sync.Cond 经常用在多个 goroutine 等待一个 goroutine 通知(事件发生)的场景。
sync.Map # Map就像Go中的map[interface{}]interface{},但对于多个goroutine的并发使用是安全的,不需要额外的锁或协调。
使用map + sync.Mutex或者sync.RWMutex的方式也可以实现与sync.Map类似的功能,但是在某些场景下,sync.Map具有更高的性能:
Map类型针对两种常见用例进行了优化:
当给定key的条目仅被写入一次却被读取多次时,例如在仅增长的高速缓存中 当多个goroutine读取,写入和覆盖的key都不相关时 在这两种情况下,与与单独的Mutex或RWMutex + map 相比,使用Map可以显着减少锁争用。
sync.Mutex # Mutex是一个相互排斥的锁。Mutex的零值是一个解锁的Mutex。
当调用Lock方法进行加锁时,如果锁已在使用中,则goroutine会阻塞,直到锁可用为止。 当调用UnLock方法进行解锁时,如果锁没有在使用,则会出现运行时错误。
锁定的互斥锁与特定的goroutine没有关联。允许一个goroutine锁定Mutex,然后安排另一个goroutine对其进行解锁。
sync.RWMutex # RWMutex是一个读写器相互排斥的锁。 该锁可以由任意数量的读者或单一的写者持有。RWMutex的零值是一个解锁的mutex。
读读不互斥,读写互斥,写写互斥。
sync.Once # Once的Do(f)方法保证只运行一次,即使f发生panic。 这常用在单例模式,配置文件加载,初始化这些场景下。
sync.Pool # Pool是一组可以单独保存和检索的临时对象。 储存在池子里的任何对象都可能在任何时候被自动删除,而无需通知。 池可以安全地同时被多个goroutine使用。
Pool的作用是缓存已分配但未使用的项目,以便以后再使用,减轻垃圾收集器的压力。也就是说,它使建立高效、线程安全的自由列表变得容易。
池的一个适当的用途是管理一组临时项目,这些临时项目在包的独立客户端之间默默地共享,并可能被重复使用。Pool提供了一种在许多客户端之间分摊分配开销的方法。
当然,Pool并不适用于一些短命的对象池化。
相当于拿出来,做操作,再放回去,操作过的东西放回去的时候是啥样,拿出来的时候就是啥样的。也就是说,拿出来用的时候需要初始化数据或者清空。 如Gin的源码:https://github.com/gin-gonic/gin/blob/v1.7.1/gin.go#L439
// ServeHTTP conforms to the http.
...
May 8, 2021
题目 # 实现如下结构体的深拷贝。
type Node struct { Data int Fields []*Node } 即指针指向的内存也需要Copy一份。
解析 # 观察结构体,由于Fields字段里存放的是指向Node结构体的指针切片,深拷贝时要考虑循环引用的问题,如:
struct a : data: 1 fields: b, c struct b: data: 2 fields: c struct c: data: 3 fields: a // 这里循环引用了a, c->a->b, c->a 可以考虑使用map[*Node]*Node来判断是否有环的情况,即用map[src] = dst来保存拷贝过的节点。
代码 # 代码如下:
package main import ( "go/ast" "go/token" ) type Node struct { Data int Fields []*Node } // deep copy var M map[*Node]*Node func Dup(src *Node) *Node { if src == nil { return nil } node := &Node{ Data: src.
...
April 29, 2021
注:go version 1.16.x
Overview # 从网站pkg.go.dev上可以看到,对应的解释。
atomic包提供了用于实现同步算法的低级原子内存原语。
可以分为几类操作:
Add操作:加减操作 CAS操作:先比较后赋值操作 Swap操作:赋值操作 Load操作:从某个地址中取值 Store操作:往某个地址赋值 Value类型:对任意类型的Load/Store操作封装 操作分类 # Add操作 # 由AddT函数实现的加法操作在原子上等效于:
*addr += delta \\ 加上步长 正负数都可以 return *addr \\ 反回加后的结果 相关的方法有:
func AddInt32(addr *int32, delta int32) (new int32) func AddUint32(addr *uint32, delta uint32) (new uint32) func AddInt64(addr *int64, delta int64) (new int64) func AddUint64(addr *uint64, delta uint64) (new uint64) func AddUintptr(addr *uintptr, delta uintptr) (new uintptr) CAS操作 # CAS即CompareAndSwap,这个函数主要就是先比较一下当前传入的地址的值是否和 old 值相等,如果相等,就赋值新值返回 true,如果不相等就返回 false.
...