我常用的 vscode 快捷键

因为我vim是必装的,所以某些vscode的快捷键就不用了。

以下是我常用的快捷键:

  • Ctrl+Shift+P打开命令面板

    虽然要敲一些字符,因为我们不会记录特别多的快捷键,所以这个方式也是很常用的。

    image-20210908160544988

    • > GO: GUT 生成单元测试

    image-20210908160557282

  • Ctrl+P 快速搜索(Mac:cmd+P),输入不同字符,进行不同操作:

    • ?:列出当前可执行的动作;

    • !:显示Errors或Warnings;直接快捷键Ctrl+Shift+M;

    • ::跳转到指定行;直接快捷键Ctrl+G;

    • @:查询本文件的 Symbol;

      image-20210908170533240

    • #:查询整个工程的Symbol;

  • Cmd+T,搜索Function,其实就是上一个快捷键+#

    image-20220815100037037

  • F5 debug

  • 窗口管理(分割编辑窗口):

    • Ctrl+ ~: 快速打开终端命令行
    • Ctrl+\:分割出新的窗口;
    • Ctrl+'数字':切换窗口,如Ctrl+1为第一个窗口;
    • :q:关闭当前窗口(标准模式下vim的快捷键);
    • Ctrl+- : 后退
    • Ctrl + Shift + -:前进
  • 代码折叠:

    • Ctrl+Shift+[:折叠当前区域(代码块);
    • Ctrl+Shift+]:展开当前区域(代码块);
  • 代码格式化

    • On Windows:Shift + Alt + F.
    • On Mac:Shift + Option + F.
    • On Ubuntu:Ctrl + Shift + I.
  • 设置:

    • Ctrl+,:快速打开 vscode 设置;

      image-20210908165436318


go 零散笔记

我从17年开始写go代码,到现在断断续续写了四年有余,其实比较惭愧,目前对go的认识非常浅薄。

究其原因,一个是我使用go的开发只是工作上粘合使用,每年写go代码的时间也不足1个月,基本上是在原有框架上做一些新功能的开发。得益于过去多年在laravel上的经验,看api文档和谷歌能力还是不错的,socket交互、orm、mongodb、k8s client-go等东西上手并不难,看半天基本上也就明白了如何使用。

二个是我工作中心更多是放在k8s这套系统以及偏网络方向上,虽然也是研发,更多是行业和架构层面的。

从今年下半年开始,我的工作重心转到了go的开发上来,而我个人也倾向于使用go作为我未来的主力开发语言。接下来这段时间我会记录更多关于 go 的基础知识。

这篇文章没什么重点,记录一些只言片语吧。

一、学习书籍

当遇到看不懂的内容时,有可能是作者的思考回路和我们的有差别。

不必纠结,跳过去,当看到同样内容不同作者的描述,你可能会豁然开朗。

入门:

入门时要注重理解go的设计理念和语言机制(Language Mechanics),

语言机制包括Go语言的句法、数据结构、解耦。

  • 《Go 程序设计语言》——许式伟译(英文原版翻译的,感觉细看浪费时间,快速浏览/后期针对性溯源就行。)
  • 《Go 语言编程》——许式伟(感觉适合基础入门,以补全理论概念铺垫为主,实战代码可以后期再看)

熟练:

熟练时要理解软件设计,研究并发,Go协程(Goroutine)、数据竞赛、多个channel和不用模式和用模式下的操作

高级:

了解基本单元测试、表测试、自测试等发测试方法,以及常见的标准等,还有各种包(Packages)。

更多选看:

二、Go 概念只言片语

这一部分大多来自 《Go 语言编程》——许式伟

2. 1 一些网站:

2. 2 基础类型:

  • int/unit/string/bool

  • 大整数big.Rat/浮点数fload/复数complex (math包)

  • string (strings包/strconv包/fmt包/utf8包/unicode包/regexp包)

  • 字符类型rune

  • 错误类型error

2. 3 组合类型:

  • 指针

  • 数组

  • slice切片

  • map(哈希表/字典)

  • 通道channel

  • struct 结构体

  • interface接口

    • 指定一组方法,抽象的,不可以实例化。接口的名字,默认以er结尾。接口可以嵌入。

    • 空接口 interface{}, 可以表示任意值,相当于指向任意类型的指针。

2. 4 流程控制:

  • 选择
    • 条件语句 if else
    • 选择语句 switch case/select
      • break continue fallthrough
  • 循环 for 和 range
  • 跳转 goto

2. 5 函数调用:

  • 大小写区分:
    • 小写字母开头的函数只在本包内可见,大写字母开头的函数才能被其他包使用。
    • 这个规则也适用于类型和变量的可见性。
  • 函数可以像普通变量一样被传递或使用

  • 不定参数:

    • func myfunc(args ...int)
    • func Printf(format string, args ...interface{})
  • 多返回值:

    • func (file *File) Read(b []byte) (n int, err Error)
  • 匿名函数/闭包:

    • 闭包内可以引用父方法的变量
    • 父方法为闭包提供绑定的计算环境(作用域)
  • 错误处理:

    • type error interface {
      Error() string
      }
      
    • defer

      • 先进后出的原则,延迟执行,一般用于文件、通道chan关闭、错误处理等。
    • panic/recover
  • init()和main()

image-20210819162920319

  • 动态函数
  • 泛型函数
  • 高阶函数
  • 纯记忆函数

2. 6 类型系统:

image-20210823113029415

2. 7 面向对象:

  • 构造函数,以NewXXX 来命名,表示“构造函数”

    func NewRect(x, y, width, height float64) *Rect {
    	return &Rect{x, y, width, height}
    }
    
  • 有继承,直接在struct引用父struct就ok了(匿名组合)。可以重写覆盖父方法。

  • 方法/变量的可见性,用大小写表示public/private。

  • 接口,隐式声明。

    • 变量类型查询: v1.(type)

2. 8 并发编程:

使用场景:

image-20210823142347960

实现方式:

image-20210823142544454

协程:

image-20210823143544145

goroutine:

  • go Add(1, 1)
    
  • 并发通信

    • 共享数据(c/c++,加锁)
    • 消息通信(go)
  • channel是类型相关的,只能传递一种类型的值,这个类型需要在声明时指定。

    • var chanName chan ElementType // 定义
      c := make(chan int, 1024) // 初始化,带缓冲区
          
      ch <- value // 将一个数据写入(发送)至channel的语法
      value := <-ch // 从channel中读取数据
          
      // 不带缓冲区时,向channel写入数据和读取数据会导致程序阻塞,直到有其他goroutine从这个channel中读取数据/写入数据为止。
          
      close(ch) //关闭channel
      
    • 单向channel:

      // 基于 ch4 ,通过类型转换初始化了两个单向channel:单向读的 ch5 和单向写的 ch6 。
          
      ch4 := make(chan int)
      ch5 := <-chan int(ch4) // ch5就是一个单向的读取channel
      ch6 := chan<- int(ch4) // ch6 是一个单向的写入channel
      
    • demo:

      image-20210823144823672

    • 高阶用法:

      • 底层多核并行:还没支持?

      • 出让时间片:Gosched()

      • 同步锁:sync.Mutex 、 sync.RWMutex

      • 多次只运行一次:once.Do()

      • 代码示例:

        image-20210823162559527

2. 9 网络编程:

  • Socket
  • http
  • rpc
  • json
  • net/http包

2. 10 安全编程:

  • pki
  • hash函数
    • MD5
    • SHA-1
  • https

2.11 代码规范:

  • 任何需要对外暴露的名字必须以大写字母开头、不需要对外暴露的则应该以小写字母开头

  • Go语言明确宣告了拥护骆驼命名法而排斥下划线法

  • 代码块中,左花括号{ 必须跟在同一行

  • 工程结构:

    README
    LICENSE
    bin/
    pkg/
    src/
    

2. 12 工程构建与命令行:

命令行主要完成以下这几类工作:

  • 代码格式化

  • 代码质量分析和修复

  • 单元测试与性能测试

    • 创建以_test结尾的go文件,形如[^.]*_test.go

    • 以 Test 和 Benchmark 为函数名前缀并以 *testing.T 为单一参数的函数。

      func TestAdd1(t *testing.T)
      func BenchmarkAdd1(t *testing.T)
      
  • 工程构建

  • 代码文档的提取和展示

  • 跨平台开发、编译

2. 13 高阶话题

  • 反射(reflection)

  • 多语言

    • cgo
  • 协程goroutine原理

  • 标准库

    go 标准库,导入使用unix风格。导入包的使用惯例,pkg.item

    https://books.studygolang.com/The-Golang-Standard-Library-by-Example/

    输入输出 (Input/Output)
    文本
    数据结构与算法
    日期与时间
    数学计算
    文件系统
    数据持久存储与交换
    数据压缩与归档
    测试
    进程、线程与 goroutine
    网络通信与互联网 (Internet)
    email
    应用构建 与 debug
    运行时特性
    底层库介绍
    同步
    加解密
    

2. 14 其它

方法与函数的区别:

函数是指不属于任何结构体、类型的方法,也就是说函数是没有接收者的;

方法是有接收者的,我们说的方法要么是属于一个结构体的,要么属于一个新定义的类型的。

方法在定义的时候,会在func和方法名之间增加一个参数,这个参数就是接收者,这样我们定义的这个方法就和接收者绑定在了一起,称之为这个接收者的方法。

type person struct {
	name string
}
 
func (p person) String() string{
	return "the person name is "+p.name
}

用户自定义类型,也应该实现Len()和Cap()方法。

Go语言的符号(symbol)一样,以大写字母开头的常量/函数在包外可见。

方法func

常量const(字面量)

变量var,赋予一内存块名字,该内存块保存特定的数据类型。可以匿名(返回值,等号左侧填_)

指针:保存了另一个变量内存地址的变量。

& ,取址操作符。

*,解引用操作符。

如果一个函数/方法返回超过4/5个值,最好使用一个切片/指向结构体的指针来传递,成本较低。

三、Go 实操笔记

这一部分大多来自:《Go 语言学习笔记》——雨痕

  • 运行时runtime、编译时Combile-time。

    • 静态类型语言都需要编译,Go是静态类型语言,不能在运行时改变变量类型。
  • 变量

    • 使用关键字 var 定义变量,自动初始化为零值。
    • 在函数内部,可用更简略的 “:=” 方式定义变量。
    • 可一次定义多个变量。
    • 特殊只写变量 “_“,用于忽略值占位。
  • 常量

    • 必须是编译期可确定的数字、字符串、布尔值。

    • 如不提供类型和初始化值,那么值与上一常量相同。

    • iota是常量计数器,在定义枚举时很有用。

      type AudioOutput int
          
      const (
          OutMute AudioOutput = iota // 0
          OutMono                    // 1
          OutStereo                  // 2
          _
          _
          OutSurround                // 5
      )
      
  • 引用类型

    • 包括 slice、map 和 channel。
    • 内置函数 new 计算类型大小,为其分配零值内存,返回指针。
    • make 会被编译器翻译成具体的创建函数,由其分配内存和初始化成员结构,返回对象而非指针。
  • 字符串

    • 字符串是不可变值类型,内部用指针指向 UTF-8 字节数组。

    • 默认值是空字符串 ““。

    • 用索引号访问某字节,如 s[i]。

    • 不能用索引号获取字节元素指针,&s[i] 非法。

    • 不可变类型,无法修改字节数组。

    • 字节数组尾部不包含 NULL。

    • 使用 “`” 定义不做转义处理的原始字符串,支支持跨行行。

    • 连接跨行字符串时,”+” 必须在上一行末尾,否则编译错误。

    • 支持用两个索引号返回子串。子串依然指向原字节数组,仅修改了指针和长度属性。

      s := "Hello, World!"
      s1 := s[:5] // Hello
      s2 := s[7:] // World!
      s3 := s[1:5] // ello
      
    • rune 是 int32 的别名,几乎在所有方面等同于int32,用于区分字符值和整数值。

    • golang 中的字符有两种,uint8(byte)代表ASCII的一个字符,rune代表一个utf-8字符。

    • 修改字符串,可先将其转换成 []rune 或 []byte,完成后再转换为 string.无论哪种转换,都会重新分配内存,并复制字节数组。有汉字等需要utf8支持的就用rune,没汉字随意。

      func main() {
          s := "abc汉字"
          for i := 0; i < len(s); i++ {
              // byte
              fmt.Printf("%c,", s[i])
          }
          fmt.Println()
          for _, r := range s {
              // rune
              fmt.Printf("%c,", r)
          }
      }
      
  • 指针

    • 默认值 nil,没有 NULL 常量。

    • 操作符 “&” 取变量地址,”*” 透过指针访问⺫目目标对象。

    • 不支持指针运算,不支持 “->” 运算符,直接用 “.” 访问目标成员。

      func main() {
          type data struct{ a int }
          var d = data{1234}
          var p *data
              
          p = &d
          
          fmt.Printf("%p, %v\n", p, p.a)    // 直接用指针访问目标对象成员,无须转换。
      }
      
    • 可以在 unsafe.Pointer 和任意类型指针间进行转换。

      func main() {
          x := 0x12345678
          p := unsafe.Pointer(&x) // *int -> Pointer
          n := (*[4]byte)(p) 		// Pointer -> *[4]byte
              
          for i := 0; i < len(n); i++ {
              fmt.Printf("%X ", n[i])
          }
      }
          
      // 78 56 34 12
      
    • 将 Pointer 转换成 uintptr,可变相实现指针运算。

      func main() {
          d := struct {
              s string
              x int
          }{"abc", 100}
              
          p := uintptr(unsafe.Pointer(&d)) // *struct -> Pointer -> uintptr
          p += unsafe.Offsetof(d.x) // uintptr + offset
              
          p2 := unsafe.Pointer(p) // uintptr -> Pointer
          px := (*int)(p2) // Pointer -> *int
          *px = 200 // d.x = 200
              
          fmt.Printf("%#v\n", d)
      }
          
      // struct { s string; x int }{s:"abc", x:200}
      
  • 自定义类型

    • 可用 type 在全局或函数内定义新类型。 type bigint int64
    • 显示转换。x := 1234 var b bigint = bigint(x)
  • 保留字

    break default func interface select
    case defer go map struct
    chan else goto package switch
    const fallthrough if range type
    continue for import return var
    
  • 位运算

  • 循环

    • for,支持初始化语句。

      s := "abcd"
      for i, n := 0, length(s); i < n; i++ {
          println(i, s[i])
      }
      
    • range,range 会复制对象。

      for k, v := range m {
      	println(k, v)
      }
      
    • switch,可省略break,表达式可以任意类型,不限于常量,需要继续下一支支,使用 fallthrough

    • 省略条件表达式,switch可当 if…else if…else 使用

      switch i := x[2]; {
          // 带初始化语句
          case i > 0:
          println("a")
          case i < 0:
          println("b")
          default:
          println("c")
      }
      
    • break 可用用于 for、switch、select,而 continue 仅能用于 for 循环。

    • 支持在函数内 goto 跳转。标签名区分大小写

  • 函数

    • 不支持 嵌套 (nested)、重载 (overload) 和 默认参数 (default parameter)。

    • 无需声明原型 支持不定长变参 支持多返回值 支持命名返回参数 支持匿名函数和闭包

    • 有返回值的函数,必须有明确的终止语句,否则会引发编译错误。

    • 变参,本质上就是 slice。只能有一个,且必须是最后一个。

    • 使用 slice 对象做变参时,必须展开。

      func main() {
          s := []int{1, 2, 3}
          println(test("sum: %d", s...))
      }
      
    • 多返回值可直接作为其他函数调用实参。

    • 使用用 slice 对象做变参时,必须展开。

    • 命名返回参数可被同名局部变量遮蔽,此时需要显式返回。

    • 匿名函数可赋值给变量,做为结构字段,或者在 channel 里传送。

    • 延迟执行defer

      • 在 defer 归属的函数即将返回时,将延迟处理的语句按 defer 的逆序进行执行

        func test() {
            defer func() {
                fmt.Println(recover())
            }()
                  
            defer func() {
                panic("defer panic")
            }()
                  
            panic("test panic")
        }
        func main() {
            test()
        }
        
      • 滥用 defer 可能会导致性能问题,尤其是在一个 “大循环” 里。

      • 如果需要保护代码片段,可将代码块重构成匿名函数,如此可确保后续代码被执行。

  • 数组

    • 数组是值类型,赋值和传参会复制整个数组,而不是指针。

    • 内置函数 len 和 cap 都返回数组长度 (元素数量)。

    • 指针数组 [n]*T,数组指针 *[n]T。

    • 值拷贝会造成性能问题,请使用 slice,或数组指针。

      a := [3]int{1, 2} // 未初始化元素值为 0。
      b := [...]int{1, 2, 3, 4} // 通过初始化值确定数组⻓长度。
      c := [5]int{2: 100, 4:200} // 使用用索引号初始化元素。
      
  • Slice

    • 初始化和数组很像,不用声明长度

      s1 := []int{0, 1, 2, 3, 8: 100} // 通过初始化表达式构造,可使用用索引号。
      fmt.Println(s1, len(s1), cap(s1))
          
      s2 := make([]int, 6, 8) // 使用用 make 创建,指定 len 和 cap 值。
      fmt.Println(s2, len(s2), cap(s2))
          
          
      s3 := make([]int, 6) // 省略 cap,相当于 cap = len。
      fmt.Println(s3, len(s3), cap(s3))
      
    • 可用指针直接访问底层数组,退化成普通数组操作。

      s := []int{0, 1, 2, 3}
      p := &s[2] // *int, 获取底层数组元素指针。
      *p += 100
      fmt.Println(s)  // [0 1 102 3]
      
    • 基于已有 slice 创建新 slice 对象,新对象依旧指向原底层数组。

      s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
      s1 := s[2:5]  // [2 3 4]
      s1[2] = 100
          
      s2 := s1[2:6]  // [100 5 6 7]
      s2[3] = 200
          
      fmt.Println(s) // [0 1 2 3 100 5 6 200 8 9]
      
    • append/copy

  • map 哈希表

    • 初始化

      m := make(map[string]int, 1000)
          
      m := map[string]int{
      "a": 1,
      }
          
      if v, ok := m["a"]; ok {
          // 判断 key 是否存在。
          println(v)
      }
          
      println(m["c"]) // 对于不存在的 key,直接返回 \0,不会出错。
      m["b"] = 2 // 新增或修改。
      delete(m, "c") // 删除。如果 key 不存在,不会出错。
      println(len(m)) // 获取键值对数量。cap 无无效。
          
      for k, v := range m { // 迭代,可仅返回 key。随机顺序返回,每次都不相同。
          println(k, v)
      }
      
    • 从 map 中取回的是一个 value 临时复制品,对其成员的修改是没有任何意义的。正确做法是完整替换 value 或使用用指针。

      type user struct{ name string }
      m := map[int]user{
          1: {"user1"},
          // 当 map 因扩张而而重新哈希时,各键值项存储位置都会发生生改变。 因此,map
          // 被设计成 not addressable。 类似 m[1].name 这种期望透过原 value
      } // 指针修改成员的行行为自自然会被禁止止。
          
      m[1].name = "Tom" // Error: cannot assign to m[1].name
          
      u := m[1]
      u.name = "Tom"
      m[1] = u      // 替换 value。
          
      m2 := map[int]*user{
      	1: &user{"user1"},
      }
      m2[1].name = "Jack" // 返回的是指针复制品。透过指针修改原对象是允许的。
      
  • struct

    • 支持指向自身类型的指针成员。

    • 顺序初始化必须包含全部字段,否则会出错。

      type Node struct {
          _ int
          id int
          data *byte
          next *Node
      }
      
    • 匿名字段

      可以像普通字段那样访问匿名字段成员, 编译器从外向内逐级查找所有层次的匿名字段,直到发现目标或出错。

      type User struct {
          name string
      }
      type Manager struct {
          User
          title string
      }
      

      外层同名字段会遮蔽嵌入入字段成员,解决方法是使用用显式字段名。

      不能同时嵌入入某一类型和其指针类型,因为它们名字相同。

  • 面向对象

    • 面向对象三大大特征里里, Go 仅支持封装(匿名字段的内存布局和行为类似继承)。没有class 关键字,没有继承、多态等等。
  • 方法

    • 方法总是绑定对象实例,并隐式将实例作为第一实参 (receiver)。

    • 只能为当前包内命名类型定义方法。 参数 receiver 可任意命名。如方法中未曾使用,可省略参数名。 参数 receiver 类型可以是 T 或 *T。基类型 T 不能是接口或指针。 不支持方法重载,receiver 只是参数签名的组成部分。 可用用实例 value 或 pointer 调用用全部方法,编译器自动转换。

    • 没有构造和析构方法,通常用简单工厂模式返回对象实例。

      type Queue struct {
          elements []interface{}
      }
          
      func NewQueue() *Queue {
          // 创建对象实例。
          return &Queue{make([]interface{}, 10)}
      }
          
      func (*Queue) Push(e interface{}) error {
          // 省略 receiver 参数名。
          panic("not implemented")
      }
          
      // func (Queue) Push(e int) error {
          // panic("not implemented")
          // Error: method redeclared: Queue.Push
              
      // }
      func (self *Queue) length() int {
          // receiver 参数名可以是 self、this 或其他。
          return len(self.elements)
      }
      
    • 不支持多级指针查找方法成员。

    • 通过匿名字段,可获得和继承类似的复用能力。依据编译器查找次序,只需在外层定义同名方法,就可以实现 “override”。

      type User struct {
          id
          int
          name string
      }
      type Manager struct {
          User
          title string
      }
          
      func (self *User) ToString() string {
          return fmt.Sprintf("User: %p, %v", self, self)
      }
      func (self *Manager) ToString() string {
          return fmt.Sprintf("Manager: %p, %v", self, self)
      }
      func main() {
          m := Manager{User{1, "Tom"}, "Administrator"}
          fmt.Println(m.ToString())
          fmt.Println(m.User.ToString())
      }
      
  • Interface

    • 接口命名习惯以 er 结尾,结构体。 接口只有方法签名,没有实现。 接口没有数据字段。 可在接口中嵌入其他接口。

    • 空接口 interface{} 没有任何方法签名,也就意味着任何类型都实现了空接口。其作用类似面向对象语言言中的根对象 object。

      type Stringer interface {
          String() string
      }
      type Printer interface {
          Stringer     // 接口口嵌入入。
          Print()
      }
      type User struct {
          id    int
          name string
      }
          
      func (self *User) String() string {
      	return fmt.Sprintf("user %d, %s", self.id, self.name)
      }
      func (self *User) Print() {
      	fmt.Println(self.String())
      }
          
      func main() {
          var t Printer = &User{1, "Tom"}     // *User 方方法集包含 String、Print。
          t.Print()
      }
      
    • 接口对象由接口表 (interface table) 指针和数据指针组成。 只有 tab 和 data 都为 nil 时,接口才等于 nil。

      var a interface{} = nil // tab = nil, data = nil
      var b interface{} = (*int)(nil) // tab 包含 *int 类型信息, data = nil
          
      type iface struct {
          itab, data uintptr
      }
          
      ia := *(*iface)(unsafe.Pointer(&a))
      ib := *(*iface)(unsafe.Pointer(&b))
          
      fmt.Println(a == nil, ia)
      fmt.Println(b == nil, ib, reflect.ValueOf(b).IsNil())
          
      //
      // true {0 0}
      // false {505728 0} true
      

      这个特性,官方有个关于error的有趣的描述:https://golang.org/doc/faq#nil_error,简单来说就是不要自己定义error,免得判断 nil 时候出问题。这里还有个类似的例子:

      image-20210825130630230

    • 数据指针持有的是目标对象的只读复制品,复制完整对象或指针。

      type User struct {
          id    int
          name string
      }
      func main() {
          u := User{1, "Tom"}
          var i interface{} = u
          u.id = 2
          u.name = "Jack"
          fmt.Printf("%v\n", u)
          fmt.Printf("%v\n", i.(User))
      }
          
      // {2 Jack}
      // {1 Tom}
      
    • 接口转型返回临时对象,只有使用指针才能修改其状态。

      type User struct {
          id    int
          name string
      }
      func main() {
          u := User{1, "Tom"}
          var vi, pi interface{} = u, &u
              
          // vi.(User).name = "Jack"    // Error: cannot assign to vi.(User).name
          pi.(*User).name = "Jack"
              
          fmt.Printf("%v\n", vi.(User))
          fmt.Printf("%v\n", pi.(*User))
      }
      
    • 接口类型判断

      type User struct {
          id int
          name string
      }
          
      func (self *User) String() string {
      	return fmt.Sprintf("%d, %s", self.id, self.name)
      }
      func main() {
          var o interface{} = &User{1, "Tom"}
          if i, ok := o.(fmt.Stringer); ok {       // ok-idiom
              fmt.Println(i)
          }
          u := o.(*User)
          // u := o.(User)     // panic: interface is *main.User, not main.User
          fmt.Println(u)
      }
      

      批量判断:

      func main() {
          var o interface{} = &User{1, "Tom"}
          switch v := o.(type) {
              case nil:
                  // o == nil
                  fmt.Println("nil")
              case fmt.Stringer:
                  // interface
                  fmt.Println(v)
              case func() string:
                  // func
                  fmt.Println(v())
              case *User:
                  // *struct
                  fmt.Printf("%d, %s\n", v.id, v.name)
              default:
                  fmt.Println("unknown")
          }
      }
      
    • 让编译器检查,以确保某个类型实现接口。

      var _ fmt.Stringer = (*Data)(nil)
      
  • 并发goroutine

    • 入口函数 main 就以 goroutine 运行。另有与之配套的 channel 类型,实现 “以通讯来共享内存” 的 CSP 模式。

      go func() {
      	println("Hello, World!")
      }()
      
    • 调度器不能保证多个 goroutine 执行行次序,且进程退出时不会等待它们结束。

    • 默认情况下,进程启动后仅允许一个系统线程服务于 goroutine。可使用环境变量或标准库函数 runtime.GOMAXPROCS 修改,让调度器用多个线程实现多核并行,而不仅仅是并发。

    • 调用 runtime.Goexit 将立即终止当前 goroutine 执行,调度器确保所有已注册 defer延迟调用被执行。

  • channel

    • 简单使用

      var chanName chan ElementType // 定义
      c := make(chan int, 1024) // 初始化,带缓冲区
          
      ch <- value // 将一个数据写入(发送)至channel的语法
      value := <-ch // 从channel中读取数据
          
      // 不带缓冲区时,向channel写入数据和读取数据会导致程序阻塞,直到有其他goroutine从这个channel中读取数据/写入数据为止。
          
      close(ch) //关闭channel
      
    • 默认为同步模式,需要发送和接收配对。否则会被阻塞。

    • 异步方式通过判断缓冲区来决定是否阻塞。如果缓冲区已满,发送被阻塞;缓冲区为空,接收被阻塞。

    • 异步 channel 可减少排队阻塞,具备更高的效率。

      func main() {
          data := make(chan int) // 数据交换队列
          exit := make(chan bool) // 退出通知
          go func() {
              for d := range data {            // 从队列迭代接收数据,直到 close 。
                  fmt.Println(d)
              }
                  
              fmt.Println("recv over.")
              exit <- true        // 发出退出通知。
          }()
              
          data <- 1    // 发送数据。
          data <- 2
          data <- 3
          close(data)    // 关闭队列。
              
              
          fmt.Println("send over.")
          <-exit     // 等待退出通知。
      }
      
    • channel 应该考虑使用指针规避大大对象拷贝,将多个元素打包,减小缓冲区大小等。

    • 除用 range 外,还可用 ok-idiom 模式判断 channel 是否关闭。

      for {
          if d, ok := <-data; ok {
              fmt.Println(d)
          } else {
              break
          }
      }
      
    • 单向 chan, 不能将单向 channel 转换为普通 channel。

      c := make(chan int, 3)
      var send chan<- int = c // send-only
      var recv <-chan int = c // receive-only
          
      send <- 1  发送数据
      // <-send  // Error: receive from send-only type chan<- int
          
      <-recv
      // recv <- 2 // Error: send to receive-only type <-chan int
          
          
      
    • 在循环中使用 select default case 需要小心,避免形成洪水。

  • 工具

    • go build

    • go install

    • go clean

    • go get

    • go tool objdump

    • 跨平台编译

    • 数据竞争 (data race)

    • go test

    • Benchmark

    • PProf

四、hello world

main.go

package main

import "fmt"

func main() {
  fmt.Printf("hello, world\n")
}

初始化go module

go mod init kelu.org/apptest
go mod tidy
go build 

这样会生成一个 apptest 的可执行文件。


kubernetes 版本更新记录(1.11-1.22)

可直接查看官方github: https://github.com/kubernetes/kubernetes/tree/master/CHANGELOG,包含1.2.md 至当前最新的 1.23.md,共22个版本。

这里简单记录 1.11至1.23的更新要点,方便查阅。更早的版本参考以前记录的这篇文章——《从 k8s changelog 了解 k8s》

发布通知可参考:https://groups.google.com/g/kubernetes-announce

周数 版本 时间 笔记
50 1.23 (12 月 7 日) KubeCon + CloudNativeCon NA
15 1.24 (4 月 12 日)  
    (4 月 26 日) KubeCon + CloudNativeCon EU
32 1.25 (8 月 9 日)  
    (8 月 22 日) KubeCon + CloudNativeCon NA
49 1.26 (12 月 6 日)  

1.23(未发布)

  • 使用 Golang 1.16.7 构建
  • Client-go 事件库允许自定义垃圾邮件过滤功能。
  • 允许节点扩展本地卷
  • 将 Cluster Autosaler 更新到 1.22.0 版
  • 更新基础镜像包
    • debian 到 v1.9.0
    • debian-iptables 到 v1.6.6
    • setcap 到 v2.0.4
  • apiserver 公开了 4 个 CIDR 分配状态的新指标
  • cri-tools 依赖更新到 v1.22.0

1.22 (2021-08-06)

53 项增强功能:13 项增强功能已升级到稳定版,24 项增强功能正在进入 beta 版,16 项增强功能正在进入 Alpha 版,还有 3 项功能已经被弃用。

  • 发布周期从每年 4 个版本更改为 3 个版本。
    • Kubernetes 发布周期的长度约为 15 周。
    • 第一个 Kubernetes 版本应该在 1 月的第二/三个星期开始。
    • 最后一个 Kubernetes 版本应该在 12 月中旬完成。
  • 使用 Golang 1.16.7 构建
  • 删除几个 beta Kubernetes API

  • 服务器端应用(Server-side Apply)GA。 Kubernetes API 服务器上运行的一个新的字段所有权和对象合并算法。
  • Kubernetes客户端凭证插件, Stable。1.11版本以来一直处于测试。
  • etcd 更新至3.5.0-beta.3。
  • cgroups v2 API 控制内存分配与隔离,内存QoS。alpha
  • node 节点支持 swap。
  • Windows 增强和功能。
  • kubelet seccomp 配置文件。 alpha
  • 非 root 用户的身份运行 kubeadm 控制平面组件,v1beta3 为首选api版本。alpha
  • client-go 凭证插件 GA

1.21 (2021-01-11~4-8)

51 项增强功能:13 项增强功能已进入稳定阶段,16 项增强功能已转为 beta 版,20 项增强功能已进入 alpha 版,弃用了 2 项功能。

  • CronJobs,GA
  • 不可变secret/configmap,stable
  • IPv4 / IPv6 双协议栈支持,beta。双栈支持可以将原生 IPv6 路由到 Pod 和 service,同时仍允许集群在需要的地方使用 IPv4。
  • kubelet 优雅节点关闭,alpha
  • pv 健康监控。 alpha
  • 简化构建kubernetes,标准化原生 Golang 构建工具
  • 弃用 PodSecurityPolicy,v1.25 中删除。
  • 弃用 topologyKeys,改由topology-aware hints(拓扑感知提示)替换,alpha。
  • EndpointSlice stable
  • sysctl 支持 stable

1.20 (2020.12.08)

42 项增强功能:11 项增强功能已升级到稳定版,15 项增强功能正在进入测试版,16 项增强功能正在进入 Alpha 版

  • Volume 快照,快照支持需要 Kubernetes 发行厂商捆绑 Snapshot 控制器、Snapshot CRD 和验证 Webhook。支持快照功能的 CSI 驱动程序也要部署在集群上。
  • Kubectl Debug beta
  • 默认启用 API 优先级和公平性 (APF)
  • IPV4/IPV6 alpha
  • 进程 PID 稳定性限制 GA
  • kubelet 优雅节点关闭,alpha
  • Dockershim弃用
  • Exec 探测超时处理

1.19 (2020.08.26)

33 个增强功能:其中 12 个增强功能已趋于稳定,18 个进入 beta,13 个进入 alpha。

  • 将 Kubernetes 的支持周期延长到一年
  • 储存容量追踪
  • CSI 健康状况监控 Alpha
  • Ingress API, GA
  • 结构化日志,Klog
  • Kubelet 客户端 TLS 证书轮换,请求证书签名成为 kubelet 启动过程的一部分,stable
  • Seccomp,stable
  • 限制节点对 API 的访问,stable
  • 重新设计 Event API,stable
  • Ingress V1,stable
  • CertificateSigningRequest API,stable
  • 在没有 Docker 的情况下构建 Kubelet,stable

1.18(2020-03-25)

38 个增强功能:其中 15 个增强功能已趋于稳定,11 个 beta,12 个进入 alpha

  • Kubernetes 拓扑管理器,beta
  • Serverside Apply,beta
  • 用 IngressClass 扩展 Ingress
  • 引入 kubectl debug,alpha
  • 引入 Windows CSI 支持,alpha
  • 基于污点的驱逐,stable
  • kubectl diff,stable
  • CSI 块存储支持,stable
  • API Server dry run ,stable
  • 在 CSI call 中传递 Pod 信息,stable
  • 支持 Out-of-Tree vSphere 云提供商,stable
  • 支持针对 Windows 工作负载的 GMSA,stable
  • 跳过 non-attachable CSI 的 attach,stable
  • PVC 克隆,stable
  • 将 kubectl 软件包代码移至暂存,stable
  • Windows 的 RunAsUserName,stable
  • 适用于服务和端点的 AppProtocol,stable
  • 扩展 Hugepage 功能,stable
  • 客户端签名重构,以标准化选项和上下文处理,stable
  • 节点本地 DNS 缓存,stable

1.17(2019-12-09)

22项改进:14项功能毕业升至GA版本,4项升至Beta版本,4项增强升至Alpha版本。

  • 云服务供应商标签功能,GA
  • 卷快照功能,beta
  • CSI迁移,Beta
  • 服务路由拓扑感知功能,Alpha
  • Windows功能增强
  • 添加IPv4/IPv6 双栈协议支持
  • 遵循条件的Taint节点,GA
  • 可配置的Pod进程命名空间共享,GA
  • 可使用kube-scheduler调度DaemonSet Pod,GA
  • 节点最大存储卷数量的动态和通用机制支持,GA
  • Kubernetes CSI 拓扑支持,GA
  • 在SubPath Mount中提供环境变量扩展,GA
  • 缺省自定义资源支持,GA
  • 将高频度Kubelet节点续约行为视为健康确认,GA
  • 拆分 Kubernetes的Test压缩包文件(从整个kubernetes-test.tar.gz文件拆分为按操作系统分的压缩包),GA
  • 添加Watch Bookmarks支持,GA
  • 行为驱动的一致性测试,GA
  • 为服务负载均衡器提供中止器保护,GA
  • 避免对各Watcher中的同一对象进行序列化,GA

1.16(2019-09-18)

31 个增强功能组成:8 个进入稳定,8 个进入 Beta,15 个进入 Alpha。

  • CRD ,GA
  • Admission webhook,GA
  • Overhauled metrics
  • Volume Extension,Beta
  • 拓扑管理器,一个新的 Kubelet 组件
  • IPv4/IPv6 双栈,引入
  • API Server Network Proxy ,Alpha
  • Cloud Controller Manager Migration 增强

1.15( 2019-06-19)

25 个增强功能组成:2 个进入稳定,13 个进入 Beta,10 个进入 Alpha。

正在 CRD 的 GA 版本和 admission webhooks GA 的道路上

  • CRD
    • 重新考虑了在 CRD 中使用基于 OpenAPI 的验证模式
    • 根据 structural schema 的限制检查资源定义(这基本上强制了 CustomResource 中每个字段使用非多态和完整类型)
    • CustomResourceDefinition Webhook 转换,Beta
    • CustomResourceDefinition OpenAPI 发布,Beta
    • CustomResourceDefinitions 自动删除发送到 Kubernetes API 对象中的未知字段
    • CustomResourceDefinition 默认值
  • Admission Webhook 重构和改进
  • 集群生命周期稳定性和可用性:kubeadm api v1beta2
  • CSI 持续改善
  • Kubernetes Core 支持 Go module
  • 继续筹备云提供商的提取和代码组织。云提供商代码现已移至 kubernetes/legacy-cloud-providers,以便以后更容易删除和方便外部使用;
  • Kubectl 的 get 和 describe 现在适用于扩展;
  • 节点现在支持第三方监控插件;
  • 一个用于调度插件的新调度框架已进入 Alpha;
  • 用于在容器中触发 hook 命令的 ExecutionHook API 现在已进入 Alpha;

1.14(2019-03-25)

31 项功能强化构成:10 个功能已经稳定,12 个功能进入 Beta,7 个全新功能。

  • 生产级 Windows 节点支持
  • 新的 kubectl 文档, kubectl 插件机制 GA,集成 kustomize
  • 持久化本地存储卷 GA
  • 应用就绪状态判断的改进:Pod Readiness Gates (Pod Ready++)
  • Pod 优先级与抢占式调度 Beta
  • PID 限制:beta
  • kubeadm:在 HA 设置中自动执行控制平面之间的证书复制

1.13 (2018-12-03)

史上发布时间最短的版本,只利用了 10 周的时间。

  • 使用 kubeadm 简化集群管理,GA
  • Container Storage Interface(CSI), GA
  • 以CoreDNS 作为默认 DNS。
  • SIG API Machinery beta
  • SIG Auth 改进

1.12 (2018-09-27)

  • Kubelet TLS Bootstrap GA
  • SIG API Machinery
  • SIG-autoscaling
  • Scheduling 提高调度器性能和可靠性方面开发:
    • 通过对算法优化,提高 Pod 亲和/反亲和特性,性能提升超过 100X;
    • DaemonSet pods由默认调度程序调度;
    • 调度程序吞吐量提高约 50%
  • Originating Identity 特性
  • Namespace 代理,GA

1.11 (2018-06-27)

增强网络方面的主要功能,为 SIG-API Machinery 和 SIG-Node 提供了两个主要功能用于 beta 测试,持续增强过去两个版本关注的存储功能。

  • SIG API Machinery 主要集中在 CustomResoures 方面
    • CustomResources 的子资源现在进入 beta 版本,并且默认开启
    • CustomResourceDefinitions 现在可以定义多个版本
  • 基于 IPVS 的集群内负载均衡
  • CoreDNS 进入 GA
  • 动态 Kubelet 配置, Beta
  • CSI 的增强

关于 ietf rfc 和 k8s kep

无论是 IETF RFCs, 还是 K8S KEPs,亦或是Pyton PEPs 、 Rust RFCs,都是为了解决一个问题:如何解决项目发展到很大规模时的功能协作问题。

名词释义:

  • IETF RFC - IETF Request For Comments: IETF 意见征集稿
  • K8S KEP - Kubernetes Enhancement Proposal: k8s增强特性提案
  • Pyton PEP - Python Enhancement Proposal:Python改进建议书
  • Rust RFC - Rust request for comments

这些形形色色的 RFC/Xep 在不同语境下意思也不完全一致,具体看社区达成的共识。无论是什么场景,初心都是有利于不同角色间的协作。以下是我整理的一些好处:

  • 对于项目管理人,能够跟踪重大功能从概念到实施整个路径。
  • 对于PM(项目经理、产品经理),以一个连贯的叙述,向外界阐述为什么这个特定的版本很重要。
  • 对于项目核心开发人员,需要一个前瞻性的路线图(roadmap)来规划什么时候落地那些特性。
  • 对于开发经理等,通过 rfc 快速掌握全局,安排开发工作。
  • 对于开发者,通过编写 rfc 理清自己的思路,避免过早的陷入实现细节。
  • 对于社区,可以加强沟通,扩大知识的范围,避免知识掌握在少数人手中。
  • 对于新人,通过 rfc 了解项目发展历程,更好融入社区
  • 对于一定经验的从业人员,通过 rfc 跟进社区动态,获知业内的最佳实践方案,调整学习方向,改进工作业务的内容
  • 对于资深的黑客,通过 rfc 了解本技术的特性,与其它同类社区的差异,为什么要设计这些特性,是怎么设计的,怎样更好地运用(攻破)它们

当然,一个东西当然不可能仅带来优点而没有缺点:

  • 写文档,不是所有人都愿意去做。
  • 额外的流程,会降低我们的开发速度。
  • 项目管理人员也需要花费额外的心思维护和处理rfc相关的工作。

至于有的人说什么开发圣经之类的,个人觉得也大可不必,rfc 就是一个记录设计思路的文档,掌握思路和背后的思考,才是我们开发者应该关注的。

rfc 经过长年累月会累积产生特别多的内容,我们并不需要对每个rfc都熟知,也没有必要,一般快速了解全貌,再针对性看个人感兴趣的。

ps: 写文档和写单元测试一样,虽然开发者可能不太喜欢,但还是要坚持才行啊。

参考资料