git如何查看已经commit文件改动内容
2021-09-13 tech git 1 mins 225 字
如果没有commit,使用如下命令:
git diff myfile.txt
如果commit了,使用如下命令:
git diff --cached myfile.txt
查看文件历史版本:
git show e0d599a9807b8ec1ce8271821feb2a90cc8a8e62:app/Console/Commands/TestCommand.php
如果没有commit,使用如下命令:
git diff myfile.txt
如果commit了,使用如下命令:
git diff --cached myfile.txt
查看文件历史版本:
git show e0d599a9807b8ec1ce8271821feb2a90cc8a8e62:app/Console/Commands/TestCommand.php
从1.22.1 升级日志 更新来看,使用了 Golang 1.16.7 的环境。
先确认系统路径PATH 有没有包含 goroot,没有的话加上。一般如果已经存在了都会有的:
可以看到我这里系统路径已经包含了goroot和gopath的路径。
主要思路是把 goroot / gopath 做备份,保留环境变量不变,将它们文件和文件夹重命名加个后缀版本号,再在用软链接 ln -s
指向它们。
我是 debian 9 x86 环境。
# 也可以直接访问 https://golang.org/dl/ 手动下载
wget https://golang.org/dl/go1.16.7.linux-amd64.tar.gz
tar zxvf go1.16.7.linux-amd64.tar.gz
mv go /var/local/go1.16.7 # 我习惯使用/var/local保存自己安装的软件
# 该整的软链接整一下
rm /var/local/go /home/kelu/Workspace/go
ln -s /var/local/go1.16.7 /var/local/go
ls -s /home/kelu/Workspace/gopath/go1.16.7 /home/kelu/Workspace/go
确认安装ok
go version
官方开发参考文档: https://github.com/kubernetes/community/blob/master/contributors/devel/development.md
直奔 https://github.com/kubernetes/kubernetes/releases/tag/v1.22.1
sudo apt update
sudo apt install build-essential
apt-get install jq
. 官方文档pip install pyyaml
官方文档./hack/install-etcd.sh
使用 kubernetes自带的 Makefile,使用make
即可编译。可以通过查看Makefile
文件代码,查看编译执行脚本。
也可以对不同的模块可以进行单独的编译。
在docker中执行跨平台编译出二进制文件。
./build/run.sh make
发现 docker pull 阶段没法拉镜像,需要添加代理:
systemctl cat docker
# /lib/systemd/system/docker.service
修改此文件: vi /lib/systemd/system/docker.service
[Service]
Environment="HTTP_PROXY=http://proxy.example.com:8080/"
Environment="HTTPS_PROXY=http://proxy.example.com:8080/"
Environment="NO_PROXY=localhost,127.0.0.1,.example.com"
如果是 sock5 代理,把 http://
改成 socks5://
即可.
重启docker:
docker pull k8s.gcr.io/v2/build-image/kube-cross/manifests/v1.16.7-1
下载有点久阿,要有亿点耐心。
继续上路:
./build/run.sh make
编译完成的二进制文件在/_output
目录下。
除了使用上边的 ./build/run.sh make
,还可以直接使用make命令进行编译,编译出来的目录稍有不同:
make WHAT=cmd/kubectl
make all # 编译所有
make cross # 跨平台编译
make cross KUBE_BUILD_PLATFORMS=windows/amd64 # 特定平台编译
make help # 编译帮助
# 如果需要使用dlv进行远程调试,make需要添加一些参数,使得我们可以dlv attach进来:
make WHAT=cmd/kube-apiserver GOGCFLAGS="-N -l" GOLDFLAGS=""
KUBE_BUILD_PLATFORMS=linux/amd64 KUBE_BUILD_CONFORMANCE=n KUBE_BUILD_HYPERKUBE=n make release-images GOFLAGS=-v GOGCFLAGS="-N -l"
UBE_BUILD_CONFORMANCE=n
和 KUBE_BUILD_HYPERKUBE=n
参数配置是否构建 hyperkube-amd64
和 conformance-amd64
镜像,默认是 y 构建,设置为 n 不需要构建。make release-images
表示执行编译并生成镜像 tar 包。编译的 kubernetes 组件 docker 镜像以 tar 包的形式发布在 kubernetes/_output/release-images/amd64
目录中。
https://github.com/kubernetes/community/blob/master/contributors/guide/github-workflow.md
我使用 vscode 作为 ide
# 注意本地环境要做好国外代理,有时候证书工具下载不下来。
./hack/install-etcd.sh
./hack/local-up-cluster.sh
# 打开新的terminal
cd $GOPATH/src/k8s.io/kubernetes
export KUBECONFIG=/var/run/kubernetes/admin.kubeconfig
# 使用kubectl
cluster/kubectl.sh get cs
cluster/kubectl.sh get ns
cluster/kubectl.sh get nodes
cluster/kubectl.sh run nginx --image=nginx
cluster/kubectl.sh get po -A
这样一个简易的本地环境就起来了。
一共运行了 7 个进程:
下面以调试 apiserver 作为例子:
# kill 掉 apiserver, 注意保存apiserver的启动命令。
ps aux | grep apiserver
kill -9 xxx
# 运行命令备忘:
/var/local/go/src/k8s.io/kubernetes/_output/local/bin/linux/amd64/kube-apiserver --authorization-mode=Node,RBAC --cloud-provider= --cloud-config= --v=3 --vmodule= --audit-policy-file=/tmp/kube-audit-policy-file --audit-log-path=/tmp/kube-apiserver-audit.log --authorization-webhook-config-file= --authentication-token-webhook-config-file= --cert-dir=/var/run/kubernetes --egress-selector-config-file=/tmp/kube_egress_selector_configuration.yaml --client-ca-file=/var/run/kubernetes/client-ca.crt --kubelet-client-certificate=/var/run/kubernetes/client-kube-apiserver.crt --kubelet-client-key=/var/run/kubernetes/client-kube-apiserver.key --service-account-key-file=/tmp/kube-serviceaccount.key --service-account-lookup=true --service-account-issuer=https://kubernetes.default.svc --service-account-jwks-uri=https://kubernetes.default.svc/openid/v1/jwks --service-account-signing-key-file=/tmp/kube-serviceaccount.key --enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,Priority,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota --disable-admission-plugins= --admission-control-config-file= --bind-address=0.0.0.0 --secure-port=6443 --tls-cert-file=/var/run/kubernetes/serving-kube-apiserver.crt --tls-private-key-file=/var/run/kubernetes/serving-kube-apiserver.key --storage-backend=etcd3 --storage-media-type=application/vnd.kubernetes.protobuf --etcd-servers=http://127.0.0.1:2379 --service-cluster-ip-range=10.0.0.0/24 --feature-gates=AllAlpha=false --external-hostname=localhost --requestheader-username-headers=X-Remote-User --requestheader-group-headers=X-Remote-Group --requestheader-extra-headers-prefix=X-Remote-Extra- --requestheader-client-ca-file=/var/run/kubernetes/request-header-ca.crt --requestheader-allowed-names=system:auth-proxy --proxy-client-cert-file=/var/run/kubernetes/client-auth-proxy.crt --proxy-client-key-file=/var/run/kubernetes/client-auth-proxy.key --cors-allowed-origins="/127.0.0.1(:[0-9]+)?$,/localhost(:[0-9]+)?$"
编译apiserver:
make WHAT=cmd/kube-apiserver GOGCFLAGS="-N -l" GOLDFLAGS=""
可以看到是刚刚编译的apiserver,时间稍微落后一点:
运行命令:
dlv exec --headless --listen=:2345 --api-version=2 /var/local/go/src/k8s.io/kubernetes/_output/local/bin/linux/amd64/kube-apiserver -- --authorization-mode=Node,RBAC --cloud-provider= --cloud-config= --v=3 --vmodule= --audit-policy-file=/tmp/kube-audit-policy-file --audit-log-path=/tmp/kube-apiserver-audit.log --authorization-webhook-config-file= --authentication-token-webhook-config-file= --cert-dir=/var/run/kubernetes --egress-selector-config-file=/tmp/kube_egress_selector_configuration.yaml --client-ca-file=/var/run/kubernetes/client-ca.crt --kubelet-client-certificate=/var/run/kubernetes/client-kube-apiserver.crt --kubelet-client-key=/var/run/kubernetes/client-kube-apiserver.key --service-account-key-file=/tmp/kube-serviceaccount.key --service-account-lookup=true --service-account-issuer=https://kubernetes.default.svc --service-account-jwks-uri=https://kubernetes.default.svc/openid/v1/jwks --service-account-signing-key-file=/tmp/kube-serviceaccount.key --enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,Priority,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota --disable-admission-plugins= --admission-control-config-file= --bind-address=0.0.0.0 --secure-port=6443 --tls-cert-file=/var/run/kubernetes/serving-kube-apiserver.crt --tls-private-key-file=/var/run/kubernetes/serving-kube-apiserver.key --storage-backend=etcd3 --storage-media-type=application/vnd.kubernetes.protobuf --etcd-servers=http://127.0.0.1:2379 --service-cluster-ip-range=10.0.0.0/24 --feature-gates=AllAlpha=false --external-hostname=localhost --requestheader-username-headers=X-Remote-User --requestheader-group-headers=X-Remote-Group --requestheader-extra-headers-prefix=X-Remote-Extra- --requestheader-client-ca-file=/var/run/kubernetes/request-header-ca.crt --requestheader-allowed-names=system:auth-proxy --proxy-client-cert-file=/var/run/kubernetes/client-auth-proxy.crt --proxy-client-key-file=/var/run/kubernetes/client-auth-proxy.key --cors-allowed-origins="/127.0.0.1(:[0-9]+)?$,/localhost(:[0-9]+)?$"
settings.json
配置如下:
{
"go.delveConfig": {
"debugAdapter": "legacy",
}
}
调试配置如下:
此时可以看到,dlv 开始刷日志了,我们连上了!
尝试暂停debug,看看左侧堆栈:
尝试打个断点,成功!
参考我这篇文章——《go 零散笔记》
verify 很烧 cpu,注意自己的机器性能,请酌情验证,我机器已经死机好几次了,16c16g的机器。
todo
todo
todo
https://github.com/kubernetes/community/blob/master/CLA.md#the-contributor-license-agreement
linux基金会个人账号信息:https://identity.linuxfoundation.org/user
在 Kubernetes issue 列表,用标签过滤问题列表,例如 “good first issue”,“help wanted”,这些标签表明这个问题对新手友好。
也可以搜索代码库里的TODO。
这里有两位大佬的first blood:
make verify
(可能需要 30-40 分钟)make test
make test-integration
参考资料:
- 给kubernetes项目贡献代码 - zeusro
- 全职工作者如何为 Kubernetes 做贡献
- 国内环境下 Kubernetes 源码编译及运行
- 为Kubernetes贡献的正确姿势 - xsky
- 骑上独角兽:Kubernetes新手贡献指南 译文 与 原文
- 为Kubernetes贡献的正确姿势
- Kubernetes 贡献指南
- K8s 系列(二) - K8s PR 怎样才能被 merge?
- Kubernetesで気になってるGitHub Issue・PR集
- kubernetes github issue good first issue
- 如何参与 Apache 项目社区 - PingCAP tison
- 基于Kubernetes1.20.1版本开发调试环境搭建
https://github.com/cncf/foundation/blob/master/code-of-conduct-languages/zh.md
Kubernetes 社区行为准则遵循 CNCF的行为准则,CNCF的行为准则也受 Linux 基金会行为准则约束。
CNCF 不可接受的参与者行为(v1.0):
Linux 基金会活动是旨在用于开源社区内的专业网络和协作的工作会议。它们的存在是为了鼓励思想和表达的开放交流,并需要一个承认每个人和团体的内在价值的环境。
不可接受的行为:
https://github.com/kubernetes/community/blob/master/contributors/guide/expectations.md
https://github.com/kubernetes/community/blob/master/community-membership.md
因为我vim是必装的,所以某些vscode的快捷键就不用了。
以下是我常用的快捷键:
Ctrl+Shift+P
打开命令面板
虽然要敲一些字符,因为我们不会记录特别多的快捷键,所以这个方式也是很常用的。
Ctrl+P
快速搜索(Mac:cmd+P),输入不同字符,进行不同操作:
?:列出当前可执行的动作;
!:显示Errors或Warnings;直接快捷键Ctrl+Shift+M;
::跳转到指定行;直接快捷键Ctrl+G;
@:查询本文件的 Symbol;
#:查询整个工程的Symbol;
Cmd+T,搜索Function,其实就是上一个快捷键+#
F5 debug
窗口管理(分割编辑窗口):
Ctrl+ ~
: 快速打开终端命令行Ctrl+\
:分割出新的窗口;Ctrl+'数字'
:切换窗口,如Ctrl+1
为第一个窗口;:q
:关闭当前窗口(标准模式下vim的快捷键);代码折叠:
Ctrl+Shift+[
:折叠当前区域(代码块);Ctrl+Shift+]
:展开当前区域(代码块);代码格式化
设置:
Ctrl+,
:快速打开 vscode 设置;
我从17年开始写go代码,到现在断断续续写了四年有余,其实比较惭愧,目前对go的认识非常浅薄。
究其原因,一个是我使用go的开发只是工作上粘合使用,每年写go代码的时间也不足1个月,基本上是在原有框架上做一些新功能的开发。得益于过去多年在laravel上的经验,看api文档和谷歌能力还是不错的,socket交互、orm、mongodb、k8s client-go等东西上手并不难,看半天基本上也就明白了如何使用。
二个是我工作中心更多是放在k8s这套系统以及偏网络方向上,虽然也是研发,更多是行业和架构层面的。
从今年下半年开始,我的工作重心转到了go的开发上来,而我个人也倾向于使用go作为我未来的主力开发语言。接下来这段时间我会记录更多关于 go 的基础知识。
这篇文章没什么重点,记录一些只言片语吧。
当遇到看不懂的内容时,有可能是作者的思考回路和我们的有差别。
不必纠结,跳过去,当看到同样内容不同作者的描述,你可能会豁然开朗。
入门时要注重理解go的设计理念和语言机制(Language Mechanics),
语言机制包括Go语言的句法、数据结构、解耦。
熟练时要理解软件设计,研究并发,Go协程(Goroutine)、数据竞赛、多个channel和不用模式和用模式下的操作
了解基本单元测试、表测试、自测试等发测试方法,以及常见的标准等,还有各种包(Packages)。
这一部分大多来自 《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 流程控制:
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
2. 6 类型系统:
2. 7 面向对象:
构造函数,以NewXXX 来命名,表示“构造函数”
func NewRect(x, y, width, height float64) *Rect {
return &Rect{x, y, width, height}
}
有继承,直接在struct引用父struct就ok了(匿名组合)。可以重写覆盖父方法。
方法/变量的可见性,用大小写表示public/private。
接口,隐式声明。
2. 8 并发编程:
使用场景:
实现方式:
协程:
goroutine:
go Add(1, 1)
并发通信
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:
高阶用法:
底层多核并行:还没支持?
出让时间片:Gosched()
同步锁:sync.Mutex 、 sync.RWMutex
多次只运行一次:once.Do()
代码示例:
2. 9 网络编程:
2. 10 安全编程:
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)
多语言
协程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 语言学习笔记》——雨痕
运行时runtime、编译时Combile-time。
变量
常量
必须是编译期可确定的数字、字符串、布尔值。
如不提供类型和初始化值,那么值与上一常量相同。
iota是常量计数器,在定义枚举时很有用。
type AudioOutput int
const (
OutMute AudioOutput = iota // 0
OutMono // 1
OutStereo // 2
_
_
OutSurround // 5
)
引用类型
字符串
字符串是不可变值类型,内部用指针指向 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 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
}
外层同名字段会遮蔽嵌入入字段成员,解决方法是使用用显式字段名。
不能同时嵌入入某一类型和其指针类型,因为它们名字相同。
面向对象
方法
方法总是绑定对象实例,并隐式将实例作为第一实参 (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 时候出问题。这里还有个类似的例子:
数据指针持有的是目标对象的只读复制品,复制完整对象或指针。
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
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 的可执行文件。
可直接查看官方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 日) |
53 项增强功能:13 项增强功能已升级到稳定版,24 项增强功能正在进入 beta 版,16 项增强功能正在进入 Alpha 版,还有 3 项功能已经被弃用。
删除几个 beta Kubernetes API
51 项增强功能:13 项增强功能已进入稳定阶段,16 项增强功能已转为 beta 版,20 项增强功能已进入 alpha 版,弃用了 2 项功能。
42 项增强功能:11 项增强功能已升级到稳定版,15 项增强功能正在进入测试版,16 项增强功能正在进入 Alpha 版
33 个增强功能:其中 12 个增强功能已趋于稳定,18 个进入 beta,13 个进入 alpha。
38 个增强功能:其中 15 个增强功能已趋于稳定,11 个 beta,12 个进入 alpha
22项改进:14项功能毕业升至GA版本,4项升至Beta版本,4项增强升至Alpha版本。
31 个增强功能组成:8 个进入稳定,8 个进入 Beta,15 个进入 Alpha。
25 个增强功能组成:2 个进入稳定,13 个进入 Beta,10 个进入 Alpha。
正在 CRD 的 GA 版本和 admission webhooks GA 的道路上
31 项功能强化构成:10 个功能已经稳定,12 个功能进入 Beta,7 个全新功能。
史上发布时间最短的版本,只利用了 10 周的时间。
增强网络方面的主要功能,为 SIG-API Machinery 和 SIG-Node 提供了两个主要功能用于 beta 测试,持续增强过去两个版本关注的存储功能。