第一部分:文章简介 及 云原生思维概览 第二部分:Go从入门到精通语言 第三部分:Docker从入门到精通 第四部分:Kubernetes从入门到精通
文章目录
- 2、Go语言
-
- Go 语言的原则
- 为什么需要 Go 语言
- Go 不支持语言的特点
- Go语言特征衍生源
- Go语言安装
- 配置Go国内代理模块
- Go的目录介绍
- Go 常用命令
-
- 基本命令
- Go build
- Go test
- Go vet
- IDE
-
- File Watcher
- 快捷键
- Go语言依赖管理
-
- 依靠管理的概念
- 三个阶段依赖管理 GOPATH、GOVENDOR、go mod
-
- GOPATH方式
- GOVENDOR方式
- go mod方式
-
- 旧项目迁移go mod
- main 函数
-
- 获取输入参数和标志值
- Init 函数
- 变量
-
- 标准声明
- 批量声明
- 定义和初始化
-
- 标准
- 多个
- 类型推导
- 短变量声明
- 匿名变量_
- 内建变量类型
-
- 字符串遍历
- 常用的字符串方法
- 强制类型转换
- 常量与枚举
-
- 常量
- 枚举
- iota
- fmt.Printf格式化输出
- 条件语句
-
- if else
- switch
- 循环语句
-
- for
- for range
- 函数 func
-
- 可变参数列表
- 内置函数
- 指针(变量取地址&、 地址取变量值*)
-
- 取变量的 指针地址
- 根据指针地址 修改变量值
- `b := &a`的剖析:
- 实现引用传输
- 数组Array
-
- 声明
- 遍历
- 数组是值类型
- 实现引用传输
- 切片Slice
-
- 定义 及 在数组中创建切片
- 证明Slice是引用类型
- Slice的扩展
- Slice底层实现
- 向Slice添加元素
- Slice创建、复制、删除
- Slice的遍历
- 将字符串切片
- 集合Map 映射关系
-
- 创建
- 遍历
- 获取v、判断key是否存在
- 删除元素
- 获取元素的数量
- 结构体struct
-
- 定义
- 实例化
- 实现构造函数
- 结构定义方法(接收者)
- 定制内置类型
- 封装
- 三种扩展现有类型的方式
-
- 定义别名
- 使用组合
- 使用内嵌(Embedding)来扩展 (类似继承、重载)
- defer
-
- defer执行时机
- defer经典案例
- 接口interface
-
- 概念
- 声明
- duck typing概念
- 接口的组合
- 任何类型的空接口都可以用作
- 函数与闭包
-
- 斐波那契数列的闭包实现
- 斐波那契数列为函数实现接口
- 多态
-
- 概念
- 为什么要用多态?
- 多态有什么好处?
- Go语言多态
- 反射
- 回调函数(Callback)
- Json编解码
-
- Unmarshal:从string转换至struct
- Marshal:从 struct 转换至 string
- json 包使用 map[string]interface{} 和 []interface{} 类型保存任何对象
- 错误处理
- Panic 和 recover
- 多线程
-
- 并发和并行
- 协程
- Go语言多线程模型:通信顺序处理Communicating Sequential Process
- 线程与协程的差异
- 协程示例
- channel - 多线程通信
- 通道缓冲
- 使用遍历通道缓冲区for rang方式
- 单向通道
- 关闭通道
- select
- 定时器 Timer 上下文 Context
- 如何停止一个子协程
- 基于 Context 停止子协程
- 线程加锁(Sync包)
-
- 理解线程安全
- 锁
-
- Mutex示例
-
- Kubernetes 中的 informer factory
- RWMutex示例
- WaitGroup 示例
-
- Kubernetes 中的 端对端测试的源码
- Once示例
- Cond示例
-
- Kubernetes 中的队列,标准的生产者消费者模式
- 线程调度
-
- Linux内核原理
- Linux 进程的内存使用
- CPU 对内存的访问
- 进程切换开销
- 线程切换开销
- 用户线程
- Goroutine
- MPG的对应关系
- GMP 模型细节
- P的状态
- G的状态
- G的状态转换图
- G 所处的位置
- Goroutine 创建过程
- 将 Goroutine 放到运行队列上
- 调度器行为
- 内存管理
-
- 关于内存管理的争论
- 堆内存管理
- 堆内存管理的挑战
- ThreadCacheMalloc 概览(TCMalloc)
- Go 语言内存分配
- 内存回收
- mspan
- GC 工作流程
- 三色标记
- 垃圾回收触发机制
- 网络
-
- 理解网络协议层
- 理解Socket
- 理解 net.http 包
- 阻塞 IO 模型
- 非阻塞 IO 模型
- IO 多路复用
- 异步IO
- Linux epoll
- Go 语言高性能 httpserver 的实现细节
- 调试
-
- debug
- dlv 的配置
- 更多 debug 方法
- Glog 使用方法示例
- 性能分析(Performance Profiling)
-
- 分析 CPU 瓶颈
- 其他可用 profiling 工具分析的问题
- 针对 http 服务的 pprof
-
- 分析 go profiling 结果
- 结果分析示例
2、Go语言
Go 语言的原则
- “Less is exponentially more“ – Rob Pike, Go Designer
- “Do Less, Enable More” – Russ Cox, Go Tech Lead
为什么需要 Go 语言
-
其他编程语言的弊端。
-
硬件发展速度远远超过软件。
-
C 语言等原生语言缺乏好的依赖管理 (依赖头文件)。
-
Java 和 C++ 等语言过于笨重。
-
系统语言对垃圾回收和并行计算等基础功能缺乏支持。
-
对多核计算机缺乏支持。
-
-
Go 语言是一个可以编译高效,支持高并发的,面向垃圾回收的全新语言。
-
秒级完成大型程序的单节点编译。
-
依赖管理清晰。
-
不支持继承,程序员无需花费精力定义不同类型之间的关系。
-
支持垃圾回收,支持并发执行,支持多线程通讯。
-
对多核计算机支持友好。
-
Go 语言不支持的特性
-
不支持函数重载和操作符重载
-
为了避免在 C/C++ 开发中的一些 Bug 和混乱,不支持隐式转换
-
支持接口抽象,不支持继承
-
不支持动态加载代码
-
不支持动态链接库
-
通过 recover 和 panic 来替代异常机制
-
不支持断言
-
不支持静态变量
Go语言特性衍生来源
Go语言安装
- 官网:golang.org
- 国内下载:studygolang.com
- macOS:
brew install go
- 更新Go:
brew upgrade go
- 更新Go:
配置Go模块国内代理
goproxy.cn
直接执行下面命令配置Go国内镜像:
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
检查配置是否成功:
go env
Go的目录介绍
-
GOROOT
- go的安装目录
-
GOPATH:
默认目录在unix、linux:
~/go
;windows:%USERPROFILE%\go
- src:存放源代码,建议不管用哪种依赖管理模式,都建议把源代码放在此。
- pkg:存放依赖包
- bin:存放可执行文件
Go 常用命令
# 设置全局go的环境变量
go env -w GO111MODULE=on
# 设置命令端临时go的环境变量
export GO111MODULE=on
# 查看go的环境变量
go env
# 查看go的版本
go version
# go mod模式初始化:创建go.mod文件
go mod init <mygomodname>
# 安装go的依赖库(Latest release版本)
go get -u go.uber.org/zap
# 安装go的旧版本依赖库
go get -u go.uber.org/zap@v1.21
# 进行清洁(去掉go.mod文件中项目不需要的依赖,清理go.sum文件中同名依赖库的不同版本为代码使用版本)
go mod tidy
# build当前目录所有的go文件,会把可执行文件放在当前目录下。
go build
# build当前目录及所有子目录下所有的go文件,不会产生可执行文件,只是检查编译是否正确。
go build ./...
# build当前目录及所有子目录下所有的go文件,产生可执行文件,在$GOPATH下的bin目录
go install ./...
# 指定输出目录
go build –o bin/mybinary .
# 编译成linux可执行的二进制文件
GOOS=linux go build
# 代码标准化entry.go文件
go fmt entry.go
基本命令
-
bug:start a bug report
-
build:compile packages and dependencies
-
clean:remove object files and cached files
-
doc:show documentation for package or symbol
-
env:print Go environment information
-
fix:update packages to use new APIs
-
fmt:gofmt (reformat) package sources
-
generate:generate Go files by processing source
-
get:add dependencies to current module and install them
-
install:compile and install packages and dependencies
-
list:list packages or modules
-
mod:module maintenance
-
run:compile and run Go program
-
test:test packages
-
tool:run specified go tool
-
version:print Go version
-
vet:report likely mistakes in packages
Go build
-
Go 语言不支持动态链接,因此编译时会将所有依赖编译进同一个二进制文件。
-
指定输出目录。
- go build –o bin/mybinary .
-
常用环境变量设置编译操作系统和 CPU 架构。是用来交叉编译的。
- 编译成linux可执行的二进制文件:
GOOS=linux go build
GOOS=linux GOARCH=amd64 go build
- 编译成linux可执行的二进制文件:
-
全支持列表。
GOOS
表示操作系统,比如windows、linux等;GOARCH
表示平台比如amd64、386等。关于支持的操作系统和平台,在
$GOROOT/src/go/build/syslist.go
文件中有详细定义。
Go test
Go 语言原生自带测试
import "testing"
func TestIncrease(t *testing.T) {
t.Log("Start testing")
increase(1, 2)
}
go test ./… -v 运行测试
go test 命令扫描所有*_test.go为结尾的文件,惯例是将测试代码与正式代码放在同目录, 如 foo.go 的测试代码一般写在 foo_test.go
Go vet
IDE
GoLand
File Watcher
可以安装goimports
快捷键
调用func时,自动生成返回值
option+command+v
Go语言的依赖管理
依赖管理的概念
这里的依赖指的是我们引用别人写好的库,比如uber-go/zap,主要用来在 Go 中快速、结构化、分级日志记录。
所以,我们需要把别人的库拉到本地,和我们自己的代码一起build,一起工作。
依赖管理的三个阶段 GOPATH、GOVENDOR、go mod
GOPATH
:最早的管理方法,不好用。GOVENDOR
:比GOPATH稍好一点,但是还是不好用。go mod
:在2018年底,也就是Go 1.11版本开始,把对Go的包管理做到了Go的命令里去。使用go mod逐步淘汰GOPATH、GOVENDOR。
这里说明一下:jetbrains的GoLand中创建新项目选择"Go"就是GOPATH方式了。
GOPATH方式
-
所有的依赖都需要到GOPATH目录下去找,会造成所有的项目的依赖都放在GOPATH目录下,GOPATH会越来越大、甚至整个github都镜像到GOPATH下了。
-
GOPATH方式对结构目录有要求,名字是固定的。一定要创建src,否则依赖的库将找不到。
-
GOPATH模式会去以下目录中找依赖:
-
$GOROOT
-
$GOPATH/src
-
-
GOPATH只能去git clone github的tag为Latest release版本的zap库,无法做到多个项目用不同版本的zap库。(为了解决此问题,诞生了GOVENDOR)
mkdir /tmp/gopathtest
cd /tmp/gopathtest
go env
# 设置全局go的环境变量(仅限测试使用,这样会把整个系统下的GOPATH都设置为此目录。)
go env -w GOPATH=/tmp/gopathtest
# 临时设置go的GOPATH(推荐测试使用)
export GOPATH=/tmp/gopathtest
mkdir src
# 打开jetbrains的GoLand创建新项目,选择“Go”而不是“Go Modules”。Project Location设置为/tmp/gopathtest/src/project1
# 因为我们是用export方式,所以需要再配置GoLand的GOPATH为/tmp/gopathtest
# 另外,我们还要设置GO111MODULE=off
go get -u go.uber.org/zap # 会clone github的tag为Latest release版本
cd /tmp/gopathtest/src
ls # go.uber.org project1
// 先配置一下jetbrains的GoLand的Run/Debug Configurations的Environment:GO111MODULE=off
package main
import "go.uber.org/zap"
func main(){
log, _ := zap.NewProduction()
log.Warn("warning test")
}
GOVENDOR方式
-
和GOPATH差不多,只是多了一个在项目文件夹内新建一个文件夹,新建完后IDE会认识这个文件夹的。
-
会先去项目内的vendor文件夹去找依赖库,如果找不到在到$GOPATH/src下去找。
-
为了解决我们每次go get命令下载完依赖库后手工拷贝到vendor文件夹内,大量第三方依赖管理工具针对GOVENDOR而诞生:glide、dep、go dep、…
-
一般开发的时候我们不会手动的去动vendor目录,动的都是第三方管理工具的配置文件。例如:glide.yml
mkdir /tmp/govendortest
cd /tmp/govendortest
go env
export GOPATH=/tmp/govendortest
mkdir src
mkdir src/project1/vendor
mkdir src/project2/vendor
# 然后我们只需要把zap的所有文件夹及文件全部放在各自项目文件夹下的vendor文件夹下即可。
# 别忘了设置GO111MODULE=off
// 测试代码与GOPATH相同
go mod方式
- 我们的项目可以建在任何地方,用户只需要专注项目即可,不必关心目录结构。
- 初始化:
go mod init
会创建go.mod文件 - 添加依赖:
go get
- 更新依赖:
go get [@v...] , go mod tidy
- 旧项目迁移到go mod:
go mod init , go build ./...
- 在jetbrains的GoLand中选择创建go modules会自动帮我们创建一个go.mod和go.sum文件
- go.sum是记录库的版本和hash,确保我们拉下来的是正确的,没有被篡改的。
- go.mod文件会被go自动编辑,当我们的代码没有用到某库时,后面会写// indirect ;如果我们使用了该库,则没有// indirect 标记。
- 由go命令统一的管理,依赖库会自动保存在$GOPATH/pkg/mod下
- 依赖的检索逻辑是:
- 先去go.mod文件去查用到的库及库的版本是什么
- 然后去$GOPATH/pkg/mod/go.uber.org/zap@1.21.0去找
- 如果我们要变更使用库的版本,不要手动改go.mod文件,要使用命令的方式:
go get -u go.uber.org/zap@v1.11
- 我们查看go.sum文件发现1.21和1.11都同时存在,我们使用:
go mod tidy
进行清洁(去掉go.mod文件中项目不需要的依赖。),清洁后发现,1.21被删除了。
- 我们查看go.sum文件发现1.21和1.11都同时存在,我们使用:
旧的项目迁移到go mod
# 把当前目录以及所有子目录下所有的go文件全部build一遍,这样在build的时候碰到代码中的import就会触发go mod去拉取库放在本地。
go mod init # 创建go.mod文件
go build ./...
main 函数
Go语言入口就是package main
func main
是固定搭配。
-
每个 Go 语言程序都应该有个 main package
-
Main package 里的 main 函数是 Go 语言程序入口
所以,一个项目只允许一个入口,如果是测试用,可以修改IDE的Run kind为file。
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
获取输入参数和标志值
package main
import (
"flag"
"fmt"
"os"
)
func main() {
/* flag.String()定义了一个带有指定名称、默认值和使用字符串的字符串标志。 返回值是存储标志值的字符串变量的地址。 os.Args保存命令行参数,并以程序名称开头。 */
name := flag.String("name", "jenrey", "specify the name you want to say hi")
flag.Parse()
fmt.Println("os args is:", os.Args)
fmt.Println("input parameter is:", *name)
fullString := fmt.Sprintf("Hello %s from Go\n", *name)
fmt.Println(fullString)
}
Init 函数
-
Init 函数:会在包初始化时运行
-
谨慎使用 init 函数
-
当多个依赖项目引用统一项目,且被引用项目的初始化在 init 中完成,并且不可重复运行时,会导致启动错误
# k8s解析参数的逻辑
kubernetes -> glog -> init() -> flag parse parameter
# k8s引用了项目a,项目a在自己的vendor目录引用了自己的glog
kubernetes -> a -> vendor -> glog -> init() -> flag parse parameter
# 综上,整个项目里glog存在于多个地方,这个时候Go在编译的时候就不认为这两个glog是一个包,所以,就会在第4行中重新做init(),然后重新的flag parse parameter
# 所以,就会发现第4行的flag在之前第2行中被定义过了,重复定义,所以最终整个项目跑不起来了
# 现在的k8s废弃了glog,用的是自己开发的klog提供了自己的log机制
-
一般都是做一些初始化的操作时需要用init函数
-
如果main包引用了a包,而a包又引用了b包,则在main方法执行的时候会先执行b包的init方法,然后再执行a包的init方法,最后执行main包的init方法
package main import ( "fmt" _ "jenrey.com/learn-go/a" _ "jenrey.com/learn-go/b" ) func init() { fmt.Println("main init") } func main() { fmt.Println("main") }
package a import ( "fmt" _ "jenrey.com/learn-go/b" ) func init() { fmt.Println("init from a") }
package b import "fmt" func init() { fmt.Println("init from b") }
# 结果展示 init from b init from a main init main
说明:当导入一个包时,该包下的文件里所有init()函数都会被执行,然而,有些时候我们并不需要把整个包都导入进来,仅仅是希望它执行init()函数而已。这个时候就可以使用 import _ 引用该包,当然也就无法通过包名来调用包中的其他函数。 b没有被打印两次是因为init方法只执行一次。
变量
函数外的每个语句都必须以关键字开始(var、const、func等)
标准声明
var a int
var s string
var isOK bool
var d float32
批量声明
var (
a string = "qwe"
b int
c bool = true
d float32
)
定义及初始化
标准
var name string = "Q1mi"
var age int = 18
多个
// 一次初始化多个变量
var name, age = "Q1mi", 20
类型推导
var name = "Q1mi"
var age = 18
短变量声明
:=
不能使用在函数外。
// 短变量声明并初始化只能在函数内部
package main
import fmt
// 包内部变量m(Go语言没有全局变量一说)
var m = 100
func main() {
n := 10
m := 200 // 此处声明局部变量m
fmt.Println(m, n)
}
匿名变量_
匿名变量不占用命名空间,不会分配内存,所以匿名变量之间不存在重复声明。
// 匿名变量(作用:想要忽略某个值)
func foo() (int, string) {
return 10, "Q1mi"
}
func main() {
x, _ := foo()
_, y := foo()
fmt.Println("x=", x)
fmt.Println("y=", y)
}
内建变量类型
- bool,string
- (u)int,(u)int8,(u)int16,(u)int32,(u)int64,uintptr
- byte,rune
- float32,float64,complex64,complex128
- 特殊类型:error
- 指针类型:*int、*int64、*string、*[10]int等
uint8:无符号8位整型(0到255)或者叫 byte 型,代表了ASCII码的一个字符。
int8:有符号8位整型(-128到127)
int:32位操作系统上就是int32,64位操作系统上就是int64
ptr:就是指针,指针的长度和操作系统相关,64位操作系统就是64位,32位操作系统就是32位。
rune:类似于其他语言的char,就是字符型。当需要处理中文、日文或者其他复合字符时,则需要用到rune类型。rune类型实际是一个int32。rune 类型来处理 Unicode。
complex64、complex128:复数,复数有实部和虚部,complex64的实部和虚部为32位,complex128的实部和虚部为64位。
package main
import (
"fmt"
"math/cmplx"
)
func main() {
var c1 complex128
c1 = 3 + 4i
var c2 complex128
c2 = 2 + 3i
fmt.Println(cmplx.Abs(c1)) // 5
fmt.Println(cmplx.Abs(c2)) //3.6055512754639896
}
字符串遍历
var s string = "Yes你好啊!"
for i, ch := range []rune(s) {
fmt.Println(i,string(ch))
fmt.Printf("%d %c\n", i, ch)
}
字符串常用方法
import (
"fmt"
"strings"
)
strings.Join()
strings.Split()
strings.Fields()
strings.Contains()
strings.Index()
strings.ToLower()
strings.ToUpper()
strings.Trim()
strings.TrimRight()
strings.TrimLeft()
强制类型转换
Go语言只有强制类型转换,没有隐式类型转换。
int() // 强制转int
float64() // 强制转float64
常量与枚举
常量
变量使用var
定义,常量使用const
定义,常量在定义的时候必须赋值且后期不能被修改。
const filename string = "abc.txt"
const a, b = 3, 4 // 不确定类型,既可以是int也可以是float
枚举
const (
cpp = 0
java = 1
python = 2
golang = 3
)
iota
iota是go语言的常量计数器,只能在常量的表达式中使用。
使用iota能简化定义,在定义枚举时很有用。
举个例子:
const (
n1 = iota //0
n2 //1
n3 //2
n4 //3
)
使用_
跳过某些值
const (
n1 = iota //0
n2 //1
_
n4 //3
)
iota
声明中间插队
const (
n1 = iota //0
n2 = 100 //100
n3 = iota //2
n4 //3
)
const n5 = iota //0
const (
b = 1 << (10 * iota)
kb
mb
gb
tb
pb
)
func main() {
// 1 1024 1048576 1073741824 1099511627776 1125899906842624
fmt.Println(b, kb, mb, gb, tb, pb)
}
fmt.Printf格式化输出
占位符 | 说明 |
---|---|
%s | 直接输出字符串或[]byte |
%q | 该值用双引号括起来 |
%T | 打印值的类型 |
%v | 相应值的默认格式 |
%d | 表示位十进制 |
条件语句
if else
func bounded(v int) int {
if v > 100 {
return 100
} else if v < 100 {
return 0
} else {
return v
}
}
if条件判断还有一种特殊的写法,可以在 if 表达式之前添加一个执行语句,再根据变量值进行判断
package main
import (
"fmt"
"io/ioutil"
)
func main() {
const filename = "abc.txt"
if contents, err := ioutil.ReadFile(filename); err != nil {
fmt.Println(err)
} else {
fmt.Printf("%s\n", contents)
}
// 如果文件不存在则提示:open abc.txt: no such file or directory
// 如果文件存在则打印文件内容
}
switch
func eval(a, b int, op string) int {
var result int
switch op {
case "+":
result = a + b
case "-":
result = a - b
case "*":
result = a * b
case "/":
result = a / b
default:
panic("unsupported operator:" + op)
}
return result
}
// switch后不带表达式 func grade(score int) string { g := "" switch { case score < 0 || score > 100: panic(fmt.Sprintf("Wrong score:%d", score)) case score < 60: g = "D" case score < 80: g = "C" case score < 90: g = "B" case score <= 100: g = "A" } return g } func main() { fmt.Println