├── principle ├── understand-channel.md ├── garbage-collection.md ├── memory-allocation.md └── go-scheduler.md ├── images ├── golang.jpg ├── favicon.ico ├── skill-tree.png └── golang_github_program.png ├── summary ├── skill-tree.md ├── language.md └── go-resource.md ├── .gitignore ├── oop ├── oop.md ├── interface │ ├── common-interface.md │ ├── interface.md │ └── client-go-interface.md ├── struct-method.md └── pointer.md ├── concurrency ├── parallelization.md ├── concurrency.md ├── channel.md └── goroutine.md ├── deploy.sh ├── web ├── beego │ ├── beego-introduction.md │ ├── bee.md │ ├── beego-log.md │ └── beego-project.md └── golang-http-execution-flow.md ├── introduction ├── install.md ├── package │ ├── dep-usage.md │ ├── glide-usage.md │ ├── govendor-usage.md │ └── go-modules.md └── golang.md ├── framework └── cobra │ ├── cobra-flags.md │ ├── cobra-usage.md │ └── cobra-command.md ├── basis ├── control-structures.md ├── var-const.md ├── errors.md ├── functions.md └── data-types.md ├── SUMMARY.md ├── test ├── test.md └── gdb.md ├── text ├── string.md ├── file.md ├── template.md └── json.md ├── README.md ├── book.json ├── LICENSE └── code └── confd-code-analysis.md /principle/understand-channel.md: -------------------------------------------------------------------------------- 1 | # Channel原理 -------------------------------------------------------------------------------- /principle/garbage-collection.md: -------------------------------------------------------------------------------- 1 | # 垃圾回收 2 | 3 | -------------------------------------------------------------------------------- /principle/memory-allocation.md: -------------------------------------------------------------------------------- 1 | # 内存分配 2 | 3 | -------------------------------------------------------------------------------- /images/golang.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huweihuang/golang-notes/HEAD/images/golang.jpg -------------------------------------------------------------------------------- /images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huweihuang/golang-notes/HEAD/images/favicon.ico -------------------------------------------------------------------------------- /images/skill-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huweihuang/golang-notes/HEAD/images/skill-tree.png -------------------------------------------------------------------------------- /images/golang_github_program.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huweihuang/golang-notes/HEAD/images/golang_github_program.png -------------------------------------------------------------------------------- /summary/skill-tree.md: -------------------------------------------------------------------------------- 1 | # 后端开发技能树 2 | 3 | ![技能树](../images/skill-tree.png) 4 | 5 | > 图片来源于网络 6 | 7 | 参考: 8 | 9 | - https://github.com/xingshaocheng/architect-awesome 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | *~ 14 | _book 15 | .DS_Store 16 | gh-pages -------------------------------------------------------------------------------- /oop/oop.md: -------------------------------------------------------------------------------- 1 | # 面向对象编程 2 | 3 | 把一组数据结构和处理它们的方法组成对象(object),把相同行为的对象归纳为类(class),通过类的封装(encapsulation)隐藏内部细节,通过继承(inheritance)实现类的特化(specialization)[方法的重写,子类不同于父类的特性]/泛化(generalization)[共性,子类都拥有父类的特性],通过多态(polymorphism)实现基于对象类型的动态分派(dynamic dispatch)。 4 | 5 | # 面对对象思想 6 | 7 | 面向对象思想是对现实世界事物的抽象,系统中一切事物皆为对象;对象是属性及其操作的封装体;对象可按其性质划分为类,对象成为类的实例;实例关系和继承关系是对象之间的静态关系;消息传递是对象之间动态联系的唯一形式,也是计算的唯一形式;方法是消息的序列。 8 | -------------------------------------------------------------------------------- /summary/language.md: -------------------------------------------------------------------------------- 1 | 2 | # 1. 计算机语言概述 3 | 4 | 学习一门计算机语言,将计算机语言分为以下几大部分: 5 | 6 | - 语言特点 7 | - 环境准备 8 | - 基本语法 9 | - 数据类型 10 | - 变量 11 | - 常量 12 | - 引用类型 13 | - 流程语句 14 | - 判断语句 15 | - 循环语句 16 | - 选择语句 17 | - 函数 18 | - 面向对象编程 19 | - 封装(类与方法) 20 | - 继承 21 | - 多态(接口) 22 | - 并发编程 23 | - 特殊属性 24 | - 包管理 25 | - 标准库 26 | 27 | 28 | # 2. 思维导图 29 | 30 | ![计算机语言](http://assets.processon.com/chart_image/5d7ce0d3e4b01080c73ee670.png) 31 | -------------------------------------------------------------------------------- /concurrency/parallelization.md: -------------------------------------------------------------------------------- 1 | # 多核并行化与同步锁 2 | 3 | ## 1. 多核并行化 4 | 5 | ```go 6 | //多核并行化 7 | runtime.GOMAXPROCS(16) //设置环境变量GOMAXPROCS的值来控制使用多少个CPU核心 8 | runtime.NumCPU() //来获取核心数 9 | //出让时间片 10 | runtime.Gosched() //在每个goroutine中控制何时出让时间片给其他goroutine 11 | ``` 12 | 13 | ## 2. 同步锁 14 | 15 | ```go 16 | //同步锁 17 | sync.Mutex //单读单写:占用Mutex后,其他goroutine只能等到其释放该Mutex 18 | sync.RWMutex //单写多读:会阻止写,不会阻止读 19 | RLock() //读锁 20 | Lock() //写锁 21 | RUnlock() //解锁(读锁) 22 | Unlock() //解锁(写锁) 23 | //全局唯一性操作 24 | //once的Do方法保证全局只调用指定函数(setup)一次,其他goroutine在调用到此函数是会阻塞,直到once调用结束才继续 25 | once.Do(setup) 26 | ``` 27 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # run in MASTER_DIR 3 | set -x 4 | set -e 5 | 6 | MESSAGE=$1 7 | 8 | # git clone -b gh-pages https://github.com/huweihuang/golang-notes.git gh-pages 9 | MASTER_DIR="$(pwd)" 10 | GHPAGE_DIR="${MASTER_DIR}/gh-pages" 11 | # CODING_DIR="${GITBOOK_DIR}/${PROGRAM}-coding-pages" 12 | 13 | # build 14 | gitbook build 15 | 16 | # clean GHPAGE_DIR 17 | rm -fr ${GHPAGE_DIR}/* 18 | # rm -fr ${CODING_DIR}/* 19 | 20 | # copy _book to GHPAGE_DIR 21 | cp -fr ${MASTER_DIR}/_book/* ${GHPAGE_DIR} 22 | cp -fr ${MASTER_DIR}/README.md ${GHPAGE_DIR} 23 | # cp -fr ${MASTER_DIR}/_book/* ${CODING_DIR} 24 | # cp -fr ${MASTER_DIR}/README.md ${CODING_DIR} 25 | 26 | # git commit 27 | cd ${GHPAGE_DIR} 28 | git add --all 29 | git commit -m "${MESSAGE}" 30 | git push origin gh-pages 31 | 32 | # cd ${CODING_DIR} 33 | # git add --all 34 | # git commit -m "${MESSAGE}" 35 | # git push origin coding-pages 36 | -------------------------------------------------------------------------------- /web/beego/beego-introduction.md: -------------------------------------------------------------------------------- 1 | # 1. beego的使用 2 | 3 | ## 1.1. beego的安装 4 | 5 | ```go 6 | go get github.com/astaxie/beego 7 | ``` 8 | 9 | ## 1.2. beego的升级 10 | 11 | 1、直接升级 12 | 13 | ```go 14 | go get -u github.com/astaxie/beego 15 | ``` 16 | 17 | 2、源码下载升级 18 | 19 | 用户访问 `https://github.com/astaxie/beego` ,下载源码,然后覆盖到 `$GOPATH/src/github.com/astaxie/beego` 目录,然后通过本地执行安装就可以升级了: 20 | 21 | ```go 22 | go install github.com/astaxie/beego 23 | ``` 24 | 25 | # 2. beego的架构 26 | 27 | beego 是一个快速开发 Go 应用的 HTTP 框架,他可以用来快速开发 API、Web 及后端服务等各种应用,是一个 RESTful 的框架。 28 | 29 | ## 2.1. beego架构图 30 | 31 | ![architecture](https://res.cloudinary.com/dqxtn0ick/image/upload/v1510578706/article/golang/beego/architecture.png) 32 | 33 | beego 是基于八大独立的模块构建的,是一个高度解耦的框架。 34 | 35 | 可以使用 cache 模块来做你的缓存逻辑;使用日志模块来记录你的操作信息;使用 config 模块来解析你各种格式的文件。 36 | 37 | ## 2.2. beego执行逻辑 38 | 39 | ![flow](https://res.cloudinary.com/dqxtn0ick/image/upload/v1510578706/article/golang/beego/flow.png) 40 | 41 | 42 | 43 | 参考: 44 | 45 | - https://beego.me/docs/intro/ 46 | - https://beego.me/docs/install/ 47 | -------------------------------------------------------------------------------- /concurrency/concurrency.md: -------------------------------------------------------------------------------- 1 | # 并发基础 2 | 3 | ## 1. 概念 4 | 5 | 并发意味着程序在运行时有多个执行上下文,对应多个调用栈。 6 | 7 | 并发与并行的区别: 8 | 9 | - 并发: 逻辑上具有处理多个同时性任务的能力。即看起来是多个任务同时执行,但并不一定同一个时刻执行,例如单核CPU,通过多线程共享CPU时间片串行执行(并发非并行)。 10 | 11 | - 并行: 物理上同一时刻执行多个并发任务。一般依赖多核CPU达到多个任务在同一个时刻执行(并发且并行)。 12 | 13 | 并发的主流实现模型: 14 | 15 | | 实现模型 | 说明 | 特点 | 16 | | ------------- | ------------------------ | ----------------------- | 17 | | 多进程 | 操作系统层面的并发模式 | 处理简单,互不影响,但开销大 | 18 | | 多线程 | 系统层面的并发模式 | 有效,开销较大,高并发时影响效率 | 19 | | 基于回调的非阻塞/异步IO | 多用于高并发服务器开发中 | 编程复杂,开销小 | 20 | | 协程 | 用户态线程,不需要操作系统抢占调度,寄存于线程中 | 编程简单,结构简单,开销极小,但需要语言的支持 | 21 | 22 | 共享内存系统:线程之间采用共享内存的方式通信,通过加锁来避免死锁或资源竞争。 23 | 24 | 消息传递系统:将线程间共享状态封装在消息中,通过发送消息来共享内存,而非通过共享内存来通信。 25 | 26 | ## 2. 协程 27 | 28 | 执行体是个抽象的概念,在操作系统中分为三个级别:进程(process),进程内的线程(thread),进程内的协程(coroutine,轻量级线程)。协程的数量级可达到上百万个,进程和线程的数量级最多不超过一万个。Go语言中的协程叫goroutine,Go标准库提供的调用操作,IO操作都会出让CPU给其他goroutine,让协程间的切换管理不依赖系统的线程和进程,不依赖CPU的核心数量。 29 | 30 | ## 3. 并发通信 31 | 32 | 并发编程的难度在于协调,协调需要通过通信,并发通信模型分为共享数据和消息。共享数据即多个并发单元保持对同一个数据的引用,数据可以是内存数据块,磁盘文件,网络数据等。数据共享通过加锁的方式来避免死锁和资源竞争。Go语言则采取消息机制来通信,每个并发单元是独立的个体,有独立的变量,不同并发单元间这些变量不共享,每个并发单元的输入输出只通过消息的方式。 33 | -------------------------------------------------------------------------------- /introduction/install.md: -------------------------------------------------------------------------------- 1 | # 1. install-go.sh 2 | 3 | ```bash 4 | #!/bin/bash 5 | set -x 6 | set -e 7 | 8 | # default version 9 | VERSION=$1 10 | VERSION=${VERSION:-1.14.6} 11 | 12 | PLATFORM=$2 13 | PLATFORM=${PLATFORM:-linux} 14 | 15 | GOROOT="/usr/local/go" 16 | GOPATH=$HOME/gopath 17 | GO_DOWNLOAD_URL="https://golang.org/dl" 18 | 19 | # download and install 20 | case ${PLATFORM} in 21 | "linux") 22 | wget ${GO_DOWNLOAD_URL}/go${VERSION}.${PLATFORM}-amd64.tar.gz 23 | tar -C /usr/local -xzf go${VERSION}.${PLATFORM}-amd64.tar.gz 24 | ;; 25 | "mac") 26 | PLATFORM="darwin" 27 | wget ${GO_DOWNLOAD_URL}/go${VERSION}.${PLATFORM}-amd64.tar.gz 28 | tar -C /usr/local -xzf go${VERSION}.${PLATFORM}-amd64.tar.gz 29 | ;; 30 | *) 31 | echo "platform not found" 32 | ;; 33 | esac 34 | 35 | # set golang env 36 | cat >> $HOME/.bashrc << EOF 37 | # Golang env 38 | export GOROOT=/usr/local/go 39 | export GOPATH=\$HOME/gopath 40 | export PATH=\$PATH:\$GOROOT/bin:\$GOPATH/bin 41 | EOF 42 | 43 | source $HOME/.bashrc 44 | 45 | # mkdir gopath 46 | mkdir -p $GOPATH/src $GOPATH/pkg $GOPATH/bin 47 | ``` 48 | 49 | # 2. 安装 50 | 51 | ```bash 52 | chmod +x install-go.sh 53 | ./install-go.sh 1.14.6 linux 54 | ``` 55 | 56 | > 更多版本号可参考:https://golang.org/dl/ 57 | 58 | 参考: 59 | - https://golang.org/doc/install 60 | -------------------------------------------------------------------------------- /summary/go-resource.md: -------------------------------------------------------------------------------- 1 | > 本文主要记录一些Golang相关的资源链接和书籍 2 | 3 | # 1. 官方文档 4 | 5 | ## 1.1. 官网 6 | 7 | - https://golang.org/ 8 | 9 | - https://golang.org/doc/ 10 | 11 | ## 1.2. 基础 12 | 13 | - [A Tour of Go](https://tour.golang.org/list) 14 | 15 | - [Effective Go](https://golang.org/doc/effective_go.html) 16 | 17 | - [Frequently Asked Questions (FAQ)](https://golang.org/doc/faq) 18 | 19 | - [CodeReviewComments](https://github.com/golang/go/wiki/CodeReviewComments) 20 | 21 | - [Uber Go Style Guide](https://github.com/uber-go/guide/blob/master/style.md) | [中文版](https://github.com/xxjwxc/uber_go_guide_cn) 22 | 23 | ## 1.3. 补充 24 | 25 | - [Diagnostics](https://golang.org/doc/diagnostics.html) 26 | 27 | - [The Go Wiki](https://golang.org/wiki) 28 | 29 | - [Language Specification](https://golang.org/ref/spec) 30 | 31 | - [The Go Memory Model](https://golang.org/ref/mem) 32 | 33 | - [Go Playground](https://golang.org/play) 34 | 35 | - [awesome-go.com](https://awesome-go.com/) 36 | 37 | ## 1.4. [The Go Blog](https://blog.golang.org/index) 38 | 39 | - [Share Memory by Communicating](https://golang.org/doc/codewalk/sharemem) 40 | - [Defer, Panic, and Recover](https://golang.org/blog/defer-panic-and-recover) 41 | - [Go Slices: usage and internals](https://golang.org/blog/go-slices-usage-and-internals) 42 | - [Profiling Go Programs](https://golang.org/blog/profiling-go-programs) 43 | 44 | # 2. 书籍 45 | 46 | - [《The Go Programming Language》]() 47 | 48 | - 《Mastering Go》 49 | 50 | - 《Go语言实战》 51 | 52 | # 3. GitHub上优秀的Go项目 53 | 54 | ![github_program](../images/golang_github_program.png) 55 | -------------------------------------------------------------------------------- /framework/cobra/cobra-flags.md: -------------------------------------------------------------------------------- 1 | # 添加Flags 2 | 3 | ## 1. Persistent Flags 4 | 5 | `Persistent Flags`表示该类参数可以被用于当前命令及其子命令。 6 | 7 | 例如,以下表示`verbose`参数可以被用于`rootCmd`及其子命令。 8 | 9 | ```go 10 | rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output") 11 | ``` 12 | 13 | ## 2. Local Flags 14 | 15 | `Local Flags`表示该类参数只能用于当前命令。 16 | 17 | 例如,以下表示`source`只能用于`localCmd`这个命令,不能用于其子命令。 18 | 19 | ```go 20 | localCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from") 21 | ``` 22 | 23 | ## 3. Local Flag on Parent Commands 24 | 25 | cobra默认只解析当前命令的local flags,通过开启`Command.TraverseChildren`参数,可以解析每个命令的local flags。 26 | 27 | ```bash 28 | command := cobra.Command{ 29 | Use: "print [OPTIONS] [COMMANDS]", 30 | TraverseChildren: true, 31 | } 32 | ``` 33 | 34 | ## 4. Bind Flags with Config 35 | 36 | 可以通过 [viper](https://github.com/spf13/viper)来绑定flags。 37 | 38 | ```bash 39 | var author string 40 | 41 | func init() { 42 | rootCmd.PersistentFlags().StringVar(&author, "author", "YOUR NAME", "Author name for copyright attribution") 43 | viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author")) 44 | } 45 | ``` 46 | 47 | 更多参考: [viper documentation](https://github.com/spf13/viper#working-with-flags)。 48 | 49 | ## 5. Required flags 50 | 51 | 默认添加的flags的`可选`参数,如果需要在二进制运行时添加`必要`参数,即当该参数没指定时会报错。可使用以下设置。 52 | 53 | ```go 54 | rootCmd.Flags().StringVarP(&Region, "region", "r", "", "AWS region (required)") 55 | rootCmd.MarkFlagRequired("region") 56 | ``` 57 | 58 | 59 | 60 | 参考: 61 | 62 | - https://github.com/spf13/cobra 63 | - https://github.com/spf13/cobra/blob/master/cobra/README.md 64 | -------------------------------------------------------------------------------- /basis/control-structures.md: -------------------------------------------------------------------------------- 1 | # 流程语句 2 | 3 | ## 1. 条件语句 4 | 5 | ```go 6 | //在if之后条件语句之前可以添加变量初始化语句,用;号隔离 7 | if <条件语句> { //条件语句不需要用括号括起来,花括号必须存在 8 | //语句体 9 | }else{ 10 | //语句体 11 | } 12 | 13 | //在有返回值的函数中,不允许将最后的return语句放在if...else...的结构中,否则会编译失败 14 | //例如以下为错误范例 15 | func example(x int) int{ 16 | if x==0{ 17 | return 5 18 | }else{ 19 | return x //最后的return语句放在if-else结构中,所以编译失败 20 | } 21 | } 22 | ``` 23 | 24 | ## 2. 选择语句 25 | 26 | ```go 27 | //1、根据条件不同,对应不同的执行体 28 | switch i{ 29 | case 0: 30 | fmt.Printf("0") 31 | case 1: //满足条件就会退出,只有添加fallthrough才会继续执行下一个case语句 32 | fmt.Prinntf("1") 33 | case 2,3,1: //单个case可以出现多个选项 34 | fmt.Printf("2,3,1") 35 | default: //当都不满足以上条件时,执行default语句 36 | fmt.Printf("Default") 37 | } 38 | 39 | //2、该模式等价于多个if-else的功能 40 | switch { 41 | case <条件表达式1>: 42 | 语句体1 43 | case <条件表达式2>: 44 | 语句体2 45 | } 46 | ``` 47 | 48 | ## 3. 循环语句 49 | 50 | ```go 51 | //1、Go只支持for关键字,不支持while,do-while结构 52 | for i,j:=0,1;i<10;i++{ //支持多个赋值 53 | //语句体 54 | } 55 | 56 | //2、无限循环 57 | sum:=1 58 | for{ //不接条件表达式表示无限循环 59 | sum++ 60 | if sum > 100{ 61 | break //满足条件跳出循环 62 | } 63 | } 64 | 65 | //3、支持continue和break,break可以指定中断哪个循环,break JLoop(标签) 66 | for j:=0;j<5;j++{ 67 | for i:=0;i<10;i++{ 68 | if i>5{ 69 | break JLoop //终止JLoop标签处的外层循环 70 | } 71 | fmt.Println(i) 72 | } 73 | JLoop: //标签处 74 | ... 75 | ``` 76 | 77 | ## 4. 跳转语句 78 | 79 | ```go 80 | //关键字goto支持跳转 81 | func myfunc(){ 82 | i:=0 83 | HERE: //定义标签处 84 | fmt.Println(i) 85 | i++ 86 | if i<10{ 87 | goto HERE //跳转到标签处 88 | } 89 | } 90 | ``` 91 | -------------------------------------------------------------------------------- /basis/var-const.md: -------------------------------------------------------------------------------- 1 | # 1.变量 2 | 3 | ## 1.1变量声明 4 | 5 | ```go 6 | //1、单变量声明,类型放在变量名之后,可以为任意类型 7 | var 变量名 类型 8 | var v1,v2,v3 string //多变量同类型声明 9 | //2、多变量声明 10 | var { 11 | v1 int 12 | v2 []int 13 | } 14 | ``` 15 | 16 | ## 1.2变量初始化 17 | 18 | ```go 19 | //1、使用关键字var,声明变量类型并赋值 20 | var v1 int=10 21 | //2、使用关键字var,直接对变量赋值,go可以自动推导出变量类型 22 | var v2=10 23 | //3、直接使用“:=”对变量赋值,不使用var,两者同时使用会语法冲突,推荐使用 24 | v3:=10 25 | ``` 26 | 27 | ## 1.3变量赋值 28 | 29 | ```go 30 | //1、声明后再变量赋值 31 | var v int 32 | v=10 33 | //2、多重赋值,经常使用在函数的多返回值中,err,v=func(arg) 34 | i,j=j,i //两者互换,并不需要引入中间变量 35 | ``` 36 | 37 | ## 1.4匿名变量 38 | 39 | ```go 40 | //Go中所有声明后的变量都需要调用到,当出现函数多返回值,并且部分返回值不需要使用时,可以使用匿名变量丢弃该返回值 41 | func GetName()(firstName,lastName,nickName string){ 42 | return "May","Chan","Make" 43 | } 44 | _,_,nickName:=GetName() //使用匿名变量丢弃部分返回值 45 | ``` 46 | 47 | # 2.常量 48 | 49 | ​ Go语言中,常量是编译时期就已知且不可变的值,常量可以是数值类型(整型、浮点型、复数类型)、布尔类型、字符串类型。 50 | 51 | ## 2.1字面常量 52 | 53 | ```go 54 | //字面常量(literal)指程序中硬编码的常量 55 | 3.14 56 | “foo” 57 | true 58 | ``` 59 | 60 | ## 2.2常量定义 61 | 62 | ```go 63 | //1、可以限定常量类型,但非必需 64 | const Pi float64 = 3.14 65 | //2、无类型常量和字面常量一样 66 | const zero=0.0 67 | //3、多常量赋值 68 | const( 69 | size int64=1024 70 | eof=-1 71 | ) 72 | //4、常量的多重赋值,类似变量的多重赋值 73 | const u,v float32=0,3 74 | const a,b,c=3,4,"foo" //无类型常量的多重赋值 75 | //5、常量赋值是编译期行为,可以赋值为一个编译期运算的常量表达式 76 | const mask=1<<3 77 | ``` 78 | 79 | ## 2.3预定义常量 80 | 81 | ```go 82 | //预定义常量:true、false、iota 83 | //iota:可修改常量,在每次const出现时被重置为0,在下一个const出现前,每出现一次iota,其代表的值自动增1。 84 | const( //iota重置为0 85 | c0=iota //c0==0 86 | c1=iota //c1==1 87 | c2=iota //c2==2 88 | ) 89 | //两个const赋值语句一样可以省略后一个 90 | const( //iota重置为0 91 | c0=iota //c0==0 92 | c1 //c1==1 93 | c2 //c2==2 94 | ) 95 | ``` 96 | 97 | ## 2.4枚举 98 | 99 | 枚举指一系列相关常量。 100 | 101 | ```go 102 | const( 103 | Sunday=iota //Sunday==0,以此类推 104 | Monday 105 | Tuesday 106 | Wednesday 107 | Thursday 108 | Friday 109 | Saturday //大写字母开头表示包外可见 110 | numberOfDays //小写字母开头表示包内私有 111 | ) 112 | ``` 113 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | ## 概述 4 | 5 | * [目录索引](README.md) 6 | * [Golang资源](summary/go-resource.md) 7 | * [计算机语言概述](summary/language.md) 8 | * [后端开发技能树](summary/skill-tree.md) 9 | 10 | ## 安装与配置 11 | 12 | * [Golang介绍](introduction/golang.md) 13 | * [Golang安装](introduction/install.md) 14 | * [包管理工具]() 15 | * [go modules的使用](introduction/package/go-modules.md) 16 | * [dep的使用](introduction/package/dep-usage.md) 17 | * [govendor的使用](introduction/package/govendor-usage.md) 18 | * [glide的使用](introduction/package/glide-usage.md) 19 | 20 | ## 顺序编程 21 | 22 | * [变量与常量](basis/var-const.md) 23 | * [数据类型](basis/data-types.md) 24 | * [流程语句](basis/control-structures.md) 25 | * [函数与闭包](basis/functions.md) 26 | * [错误处理](basis/errors.md) 27 | 28 | ## 面向对象编程 29 | 30 | * [概述](oop/oop.md) 31 | * [类与方法](oop/struct-method.md) 32 | * [接口]() 33 | * [接口概述](oop/interface/interface.md) 34 | * [client-go中接口](oop/interface/client-go-interface.md) 35 | * [通用接口设计](oop/interface/common-interface.md) 36 | * [指针](oop/pointer.md) 37 | 38 | ## 并发编程 39 | 40 | * [并发基础](concurrency/concurrency.md) 41 | * [Goroutine](concurrency/goroutine.md) 42 | * [Channel](concurrency/channel.md) 43 | * [并行化](concurrency/parallelization.md) 44 | 45 | ## 文本处理 46 | 47 | * [Json处理](text/json.md) 48 | * [文件操作](text/file.md) 49 | * [字符串处理](text/string.md) 50 | * [模板语法](text/template.md) 51 | 52 | ## 测试与调试 53 | 54 | * [单元测试](test/test.md) 55 | * [GDB调试](test/gdb.md) 56 | 57 | --- 58 | 59 | ## 原理篇 60 | 61 | * [Goroutine调度](principle/go-scheduler.md) 62 | * [Go内存分配](principle/memory-allocation.md) 63 | * [Go垃圾回收](principle/garbage-collection.md) 64 | * [深入理解Channel](principle/understand-channel.md) 65 | 66 | ## 框架与工具 67 | 68 | * [Cobra]() 69 | * [cobra 介绍](framework/cobra/cobra-usage.md) 70 | * [cobra command](framework/cobra/cobra-command.md) 71 | * [cobra flags](framework/cobra/cobra-flags.md) 72 | 73 | ## Web 编程 74 | 75 | * [Beego]() 76 | * [Beego 介绍](web/beego/beego-introduction.md) 77 | * [Bee 工具使用](web/beego/bee.md) 78 | * [Beego 项目逻辑](web/beego/beego-project.md) 79 | * [Beego 日志处理](web/beego/beego-log.md) 80 | * [Http包源码分析](web/golang-http-execution-flow.md) 81 | 82 | ## 源码分析 83 | 84 | * [confd源码分析](code/confd-code-analysis.md) 85 | -------------------------------------------------------------------------------- /web/beego/bee.md: -------------------------------------------------------------------------------- 1 | # 1. bee工具 2 | 3 | bee工具用来进行beego项目的创建、热编译、开发、测试、和部署。 4 | 5 | 安装: 6 | 7 | ```go 8 | go get github.com/beego/bee 9 | ``` 10 | 11 | 配置: 12 | 13 | 安装完之后,`bee`可执行文件默认存放在`$GOPATH/bin`里面,所以要把`$GOPATH/bin`添加到环境变量中。 14 | 15 | # 2. bee命令 16 | 17 | ```bash 18 | Bee is a tool for managing beego framework. 19 | 20 | Usage: 21 | 22 | bee command [arguments] 23 | 24 | The commands are: 25 | 26 | new create an application base on beego framework 27 | run run the app which can hot compile 28 | pack compress an beego project 29 | api create an api application base on beego framework 30 | bale packs non-Go files to Go source files 31 | version show the bee & beego version 32 | generate source code generator 33 | migrate run database migrations 34 | ``` 35 | 36 | 说明: 37 | 38 | ## 2.1. new 39 | 40 | 在 `$GOPATH/src`的目录下执行`bee new `,会在当前目录下生成以下文件: 41 | 42 | ```go 43 | myproject 44 | ├── conf 45 | │ └── app.conf 46 | ├── controllers 47 | │ └── default.go 48 | ├── main.go 49 | ├── models 50 | ├── routers 51 | │ └── router.go 52 | ├── static 53 | │ ├── css 54 | │ ├── img 55 | │ └── js 56 | ├── tests 57 | │ └── default_test.go 58 | └── views 59 | └── index.tpl 60 | ``` 61 | 62 | ## 2.2. run 63 | 64 | 必须在`$GOPATH/src/appname`下执行bee run,默认监听8080端口:`http://localhost:8080/。` 65 | 66 | ## 2.3. api 67 | 68 | `api` 命令就是用来创建 API 应用,生成以下文件:和 Web 项目相比,少了 static 和 views 目录,多了一个 test 模块,用来做单元测试。 69 | 70 | ``` 71 | apiproject 72 | ├── conf 73 | │ └── app.conf 74 | ├── controllers 75 | │ └── object.go 76 | │ └── user.go 77 | ├── docs 78 | │ └── doc.go 79 | ├── main.go 80 | ├── models 81 | │ └── object.go 82 | │ └── user.go 83 | ├── routers 84 | │ └── router.go 85 | └── tests 86 | └── default_test.go 87 | ``` 88 | 89 | ## 2.4. pack 90 | 91 | `pack` 目录用来发布应用的时候打包,会把项目打包成 zip 包(`apiproject.tar.gz`),这样我们部署的时候直接把打包之后的项目上传,解压就可以部署了: 92 | 93 | ## 2.5. generate 94 | 95 | 用来自动化的生成代码的,包含了从数据库一键生成model,还包含了scaffold。 96 | 97 | ## 2.6. migrate 98 | 99 | 这个命令是应用的数据库迁移命令,主要是用来每次应用升级,降级的SQL管理。 100 | 101 | 102 | 参考: 103 | 104 | - https://beego.me/docs/install/bee.md 105 | -------------------------------------------------------------------------------- /introduction/package/dep-usage.md: -------------------------------------------------------------------------------- 1 | # 1. dep简介 2 | 3 | `dep`是一个golang项目的包管理工具,一般只需要2-3个命令就可以将go依赖包自动下载并归档到`vendor`的目录中。dep官网参考:https://github.com/golang/dep 4 | 5 | # 2. dep安装 6 | 7 | ```bash 8 | go get -u github.com/golang/dep/cmd/dep 9 | ``` 10 | 11 | # 3. dep使用 12 | 13 | ```bash 14 | #进入到项目目录 15 | cd /home/gopath/src/demo 16 | #dep初始化,初始化配置文件Gopkg.toml 17 | dep init 18 | #dep加载依赖包,自动归档到vendor目录 19 | dep ensure 20 | # 最终会生成vendor目录,Gopkg.toml和Gopkg.lock的文件 21 | ``` 22 | 23 | # 4. dep的配置文件 24 | 25 | `Gopkg.toml`记录依赖包列表。 26 | 27 | ```bash 28 | # Gopkg.toml example 29 | # 30 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html 31 | # for detailed Gopkg.toml documentation. 32 | # 33 | # required = ["github.com/user/thing/cmd/thing"] 34 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 35 | # 36 | # [[constraint]] 37 | # name = "github.com/user/project" 38 | # version = "1.0.0" 39 | # 40 | # [[constraint]] 41 | # name = "github.com/user/project2" 42 | # branch = "dev" 43 | # source = "github.com/myfork/project2" 44 | # 45 | # [[override]] 46 | # name = "github.com/x/y" 47 | # version = "2.4.0" 48 | # 49 | # [prune] 50 | # non-go = false 51 | # go-tests = true 52 | # unused-packages = true 53 | 54 | ignored = ["demo"] 55 | 56 | [[constraint]] 57 | name = "github.com/BurntSushi/toml" 58 | version = "0.3.0" 59 | 60 | [prune] 61 | go-tests = true 62 | unused-packages = true 63 | ``` 64 | 65 | # 5. dep-help 66 | 67 | 更多dep的命令帮助参考`dep`。 68 | 69 | ```bash 70 | $ dep 71 | Dep is a tool for managing dependencies for Go projects 72 | 73 | Usage: "dep [command]" 74 | 75 | Commands: 76 | 77 | init Set up a new Go project, or migrate an existing one 78 | status Report the status of the project's dependencies 79 | ensure Ensure a dependency is safely vendored in the project 80 | prune Pruning is now performed automatically by dep ensure. 81 | version Show the dep version information 82 | 83 | Examples: 84 | dep init set up a new project 85 | dep ensure install the project's dependencies 86 | dep ensure -update update the locked versions of all dependencies 87 | dep ensure -add github.com/pkg/errors add a dependency to the project 88 | 89 | Use "dep help [command]" for more information about a command. 90 | ``` 91 | 92 | -------------------------------------------------------------------------------- /oop/interface/common-interface.md: -------------------------------------------------------------------------------- 1 | # 3. 通用接口设计 2 | 3 | ## 3.1 接口定义 4 | 5 | ```go 6 | // ProjectManager manage life cycle of Deployment and Resources 7 | type PodInterface interface { 8 | Create(*v1.Pod) (*v1.Pod, error) 9 | Update(*v1.Pod) (*v1.Pod, error) 10 | UpdateStatus(*v1.Pod) (*v1.Pod, error) 11 | Delete(name string, options *meta_v1.DeleteOptions) error 12 | DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error 13 | Get(name string, options meta_v1.GetOptions) (*v1.Pod, error) 14 | List(opts meta_v1.ListOptions) (*v1.PodList, error) 15 | Watch(opts meta_v1.ListOptions) (watch.Interface, error) 16 | Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.Pod, err error) 17 | PodExpansion 18 | } 19 | ``` 20 | 21 | ## 3.2 结构体定义 22 | 23 | ```go 24 | // pods implements PodInterface 25 | type pods struct { 26 | client rest.Interface 27 | ns string 28 | } 29 | ``` 30 | 31 | ## 3.3 构造函数 32 | 33 | ```go 34 | // newPods returns a Pods 35 | func newPods(c *CoreV1Client, namespace string) *pods { 36 | return &pods{ 37 | client: c.RESTClient(), 38 | ns: namespace, 39 | } 40 | } 41 | ``` 42 | 43 | ## 3.4 结构体实现 44 | 45 | **List()** 46 | 47 | ```go 48 | // List takes label and field selectors, and returns the list of Pods that match those selectors. 49 | func (c *pods) List(opts meta_v1.ListOptions) (result *v1.PodList, err error) { 50 | result = &v1.PodList{} 51 | err = c.client.Get(). 52 | Namespace(c.ns). 53 | Resource("pods"). 54 | VersionedParams(&opts, scheme.ParameterCodec). 55 | Do(). 56 | Into(result) 57 | return 58 | } 59 | ``` 60 | 61 | ## 3.5 接口调用 62 | 63 | ```go 64 | pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{}) 65 | ``` 66 | 67 | ## 3.6 其他接口设计示例 68 | 69 | ```go 70 | type XxxManager interface { 71 | Create(args argsType) (*XxxStruct, error) 72 | Get(args argsType) (**XxxStruct, error) 73 | Update(args argsType) (*XxxStruct, error) 74 | Delete(name string, options *DeleleOptions) error 75 | } 76 | 77 | type XxxManagerImpl struct { 78 | Name string 79 | Namespace string 80 | kubeCli *kubernetes.Clientset 81 | } 82 | 83 | func NewXxxManagerImpl (namespace, name string, kubeCli *kubernetes.Clientset) XxxManager { 84 | return &XxxManagerImpl{ 85 | Name name, 86 | Namespace namespace, 87 | kubeCli: kubeCli, 88 | } 89 | } 90 | 91 | func (xm *XxxManagerImpl) Create(args argsType) (*XxxStruct, error) { 92 | //具体的方法实现 93 | } 94 | ``` 95 | 96 | -------------------------------------------------------------------------------- /basis/errors.md: -------------------------------------------------------------------------------- 1 | # 错误处理 2 | 3 | ## 1. error接口 4 | ```go 5 | //定义error接口 6 | type error interface{ 7 | Error() string 8 | } 9 | //调用error接口 10 | func Foo(param int) (n int,err error){ 11 | //... 12 | } 13 | n,err:=Foo(0) 14 | if err!=nil{ 15 | //错误处理 16 | }else{ 17 | //使用返回值 18 | } 19 | ``` 20 | 21 | ## 2. defer[延迟函数] 22 | 23 | 语法: 24 | 25 | ```go 26 | defer function_name() 27 | ``` 28 | 29 | 1)defer在声明时不会执行,而是推迟执行,在return执行前,倒序执行defer[先进后出],一般用于释放资源,清理数据,记录日志,异常处理等。 30 | 31 | 2)defer有一个特性:即使函数抛出异常,defer仍会被执行,这样不会出现程序错误导致资源不被释放,或者因为第三方包的异常导致程序崩溃。 32 | 33 | 3)一般用于打开文件后释放资源的操作,比如打开一个文件,最后总是要关闭的。而在打开和关闭之间,会有诸多的处理,可能会有诸多的if-else、根据不同的情况需要提前返回 34 | 35 | ```go 36 | f, = os.open(filename) 37 | defer f.close() 38 | do_something() 39 | if (condition_a) {return} 40 | do_something_again() 41 | if (condition_b) {return} 42 | do_further_things() 43 | ``` 44 | 45 | 4)defer示例 46 | 47 | ```go 48 | package main 49 | import "fmt" 50 | 51 | func deferTest(number int) int { 52 | defer func() { 53 | number++ 54 | fmt.Println("three:", number) 55 | }() 56 | 57 | defer func() { 58 | number++ 59 | fmt.Println("two:", number) 60 | }() 61 | 62 | defer func() { 63 | number++ 64 | fmt.Println("one:", number) 65 | }() 66 | 67 | return number 68 | } 69 | 70 | func main() { 71 | fmt.Println("函数返回值:", deferTest(0)) 72 | } 73 | 74 | /* 75 | one: 1 76 | two: 2 77 | three: 3 78 | 函数返回值: 0 79 | */ 80 | ``` 81 | 82 | ## 3. panic()和recover() 83 | 84 | Go中使用内置函数`panic()`和`recover()`来处理程序中的错误。 85 | 86 | ```go 87 | func panic(interface{}) 88 | func recover() interface{} 89 | ``` 90 | 91 | ### 3.1. panic() 92 | 93 | 当函数执行触发了panic()函数时,如果没有使用到defer关键字,函数执行流程会被立即终止;如果使用了defer关键字,则会逐层执行defer语句,直到所有的函数被终止。 94 | 95 | 错误信息,包括panic函数传入的参数,将会被报告出来。 96 | 97 | 示例如下: 98 | 99 | ```go 100 | panic(404) 101 | panic("network broken") 102 | panic(Error("file not exists")) 103 | ``` 104 | 105 | ### 3.2. recover() 106 | 107 | recover()函数用于终止错误处理流程。一般使用在defer关键字后以有效截取错误处理流程。如果没有在发生异常的goroutine中明确调用恢复过程(使用recover关键字) ,会导致该goroutine所属的进程打印异常信息后直接退出。 108 | 109 | 示例如下: 110 | 111 | ```go 112 | defer func() { 113 | if r := recover(); r != nil { 114 | log.Printf("Runtime error caught: %v", r) 115 | } 116 | }() 117 | foo() 118 | ``` 119 | 120 | 无论foo()中是否触发了错误处理流程,该匿名defer函数都将在函数退出时得到执行。假如foo()中触发了错误处理流程, recover()函数执行将使得该错误处理过程终止。如果错误处理流程被触发时,程序传给panic函数的参数不为nil,则该函数还会打印详细的错误信息。 121 | 122 | 参考: 123 | 124 | - 《Go语言编程》 125 | -------------------------------------------------------------------------------- /test/test.md: -------------------------------------------------------------------------------- 1 | # 1. Go中的测试框架 2 | 3 | Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test`命令来实现单元测试和性能测试,`testing`框架和其他语言中的测试框架类似,你可以基于这个框架写针对相应函数的测试用例,也可以基于该框架写相应的压力测试用例。 4 | 5 | # 2. 单元测试原则 6 | 7 | - 文件名必须是`_test.go`结尾的,这样在执行`go test`的时候才会执行到相应的代码 8 | - 你必须import `testing`这个包 9 | - 所有的测试用例函数必须是`Test`开头 10 | - 测试用例会按照源代码中写的顺序依次执行 11 | - 测试函数`TestXxx()`的参数是`testing.T`,我们可以使用该类型来记录错误或者是测试状态 12 | - 测试格式:`func TestXxx (t *testing.T)`,`Xxx`部分可以为任意的字母数字的组合,但是首字母不能是小写字母[a-z],例如`Testintdiv`是错误的函数名。 13 | - 函数中通过调用`testing.T`的`Error`, `Errorf`, `FailNow`, `Fatal`, `FatalIf`方法,说明测试不通过,调用`Log`方法用来记录测试的信息。 14 | 15 | # 3 测试常用命令 16 | 17 | ``` 18 | # 测试整个目录 19 | go test -v ./pkg/... ./cmd/... -coverprofile cover.out 20 | 21 | # 测试某个文件 22 | go test -v file_test.go file.go 23 | 24 | # 测试某个函数 25 | go test -v -test.run TestFunction 26 | ``` 27 | 28 | 29 | # 4. 示例 30 | 31 | ## 4.1. 源文件getest.go 32 | 33 | ```go 34 | package gotest 35 | import ( 36 | "errors" 37 | ) 38 | func Division(a, b float64) (float64, error) { 39 | if b == 0 { 40 | return 0, errors.New("除数不能为0") 41 | } 42 | return a / b, nil 43 | } 44 | ``` 45 | 46 | ## 4.2. 测试文件gotest_test.go 47 | 48 | ```go 49 | func Test_Division_2(t *testing.T) { 50 | if _, e := Division(6, 0); e == nil { //try a unit test on function 51 | t.Error("Division did not work as expected.") // 如果不是如预期的那么就报错 52 | } else { 53 | t.Log("one test passed.", e) //记录一些你期望记录的信息 54 | } 55 | } 56 | ``` 57 | 58 | # 5. 压力测试 59 | 60 | 压力测试用来检测函数(方法)的性能,和编写单元功能测试的方法类似。 61 | 62 | - 压力测试用例必须遵循如下格式,其中XXX可以是任意字母数字的组合,但是首字母不能是小写字母 63 | 64 | ``` 65 | func BenchmarkXXX(b *testing.B) { ... } 66 | ``` 67 | 68 | - `go test`不会默认执行压力测试的函数,如果要执行压力测试需要带上参数`-test.bench`,语法:`-test.bench="test_name_regex"`,例如`go test -test.bench=".*"`表示测试全部的压力测试函数 69 | - 在压力测试用例中,请记得在循环体内使用`testing.B.N`,以使测试可以正常的运行 70 | - 文件名也必须以`_test.go`结尾 71 | 72 | ## 5.1. 示例 73 | 74 | ```go 75 | package gotest 76 | 77 | import ( 78 | "testing" 79 | ) 80 | func Benchmark_Division(b *testing.B) { 81 | for i := 0; i < b.N; i++ { //use b.N for looping 82 | Division(4, 5) 83 | } 84 | } 85 | func Benchmark_TimeConsumingFunction(b *testing.B) { 86 | b.StopTimer() //调用该函数停止压力测试的时间计数 87 | //做一些初始化的工作,例如读取文件数据,数据库连接之类的, 88 | //这样这些时间不影响我们测试函数本身的性能 89 | b.StartTimer() //重新开始时间 90 | for i := 0; i < b.N; i++ { 91 | Division(4, 5) 92 | } 93 | } 94 | ``` 95 | 96 | 执行测试命令 97 | 98 | ```go 99 | go test -file webbench_test.go -test.bench=".*" 100 | ``` 101 | -------------------------------------------------------------------------------- /text/string.md: -------------------------------------------------------------------------------- 1 | # 字符串处理 2 | 3 | 字符串操作涉及的标准库有**strings**和**strconv**两个包 4 | 5 | ## 1. 字符串操作 6 | 7 | | 函数 | 说明 | 8 | | ---------------------------------------- | ---------------------------------------- | 9 | | func Contains(s, substr string) bool | 字符串 s 中是否包含 substr,返回 bool 值 | 10 | | func Join(a []string, sep string) string | 字符串链接,把 slice a 通过 sep 链接起来 | 11 | | func Index(s, sep string) int | 在字符串 s 中查找 sep 所在的位置,返回位置值,找不到返回-1 | 12 | | func Repeat(s string, count int) string | 重复 s 字符串 count 次,最后返回重复的字符串 | 13 | | func Replace(s, old, new string, n int) string | 在 s 字符串中,把 old 字符串替换为 new 字符串,n 表示替换的次数,小于 0 表示全部替换 | 14 | | func Split(s, sep string) []string | 把 s 字符串按照 sep 分割,返回 slice | 15 | | func Trim(s string, cutset string) string | 在 s 字符串中去除 cutset 指定的字符串 | 16 | | func Fields(s string) []string | 去除 s 字符串的空格符,并且按照空格分割返回 slice | 17 | 18 | ## 2. 字符串转换 19 | 20 | 1、Append 系列函数将整数等转换为字符串后,添加到现有的字节数组中 21 | 22 | ```go 23 | package main 24 | import ( 25 | "fmt" 26 | "strconv" 27 | ) 28 | func main() { 29 | str := make([]byte, 0, 100) 30 | str = strconv.AppendInt(str, 4567, 10) 31 | str = strconv.AppendBool(str, false) 32 | str = strconv.AppendQuote(str, "abcdefg") 33 | str = strconv.AppendQuoteRune(str, '单') 34 | fmt.Println(string(str)) 35 | } 36 | ``` 37 | 38 | 2、Format 系列函数把其他类型的转换为字符串 39 | 40 | ```go 41 | package main 42 | import ( 43 | "fmt" 44 | "strconv" 45 | ) 46 | func main() { 47 | a := strconv.FormatBool(false) 48 | b := strconv.FormatFloat(123.23, 'g', 12, 64) 49 | c := strconv.FormatInt(1234, 10) 50 | d := strconv.FormatUint(12345, 10) 51 | e := strconv.Itoa(1023) 52 | fmt.Println(a, b, c, d, e) 53 | } 54 | ``` 55 | 56 | 3、Parse 系列函数把字符串转换为其他类型 57 | 58 | ```go 59 | package main 60 | import ( 61 | "fmt" 62 | "strconv" 63 | ) 64 | func main() { 65 | 66 | a, err := strconv.ParseBool("false") 67 | if err != nil { 68 | fmt.Println(err) 69 | } 70 | b, err := strconv.ParseFloat("123.23", 64) 71 | if err != nil { 72 | fmt.Println(err) 73 | } 74 | c, err := strconv.ParseInt("1234", 10, 64) 75 | if err != nil { 76 | fmt.Println(err) 77 | } 78 | d, err := strconv.ParseUint("12345", 10, 64) 79 | if err != nil { 80 | fmt.Println(err) 81 | } 82 | e, err := strconv.Itoa("1023") 83 | if err != nil { 84 | fmt.Println(err) 85 | } 86 | fmt.Println(a, b, c, d, e) 87 | } 88 | ``` 89 | -------------------------------------------------------------------------------- /introduction/golang.md: -------------------------------------------------------------------------------- 1 | # 初识Go语言 2 | 3 | ## 1. 概述 4 | 5 | 一个在语言层面实现了并发机制的类C通用型编程语言。 6 | 7 | ## 2. Go关键字(25个) 8 | 9 | | 类别 | 关键字 | 说明 | 10 | | ------ | --------------------------------------- | -------------------------- | 11 | | 程序声明 | package,import | 包的声明和导入 | 12 | | 声明与定义 | var,const | 变量和常量的声明 | 13 | | | type | 用于定义类型 | 14 | | 复合数据类型 | struct | 定义结构体,类似java中的class | 15 | | | interface | 定义接口 | 16 | | | map | 定义键值对 | 17 | | | func | 定义函数和方法 | 18 | | | chan | 定义管道,并发中channel通信 | 19 | | 并发编程 | go | 并发编程 | 20 | | | select | 用于选择不同类型通信 | 21 | | 流程语句 | for;if,else;switch,case | 循环语句;条件语句;选择语句 | 22 | | | break,continue,fallthrough,default,goto | 跳转语句等 | 23 | | | return | 函数返回值 | 24 | | | defer | 延迟函数,用于return前释放资源 | 25 | | | range | 用于读取slice,map,channel容器类数据 | 26 | 27 | ## 3. Go语言命令 28 | 29 | Usage:go command [arguments] 30 | 31 | | 分类 | 命令 | 说明 | 32 | | ---- | -------- | ---------------------------------------- | 33 | | | build | compile packages and dependencies | 34 | | | clean | remove object files | 35 | | | doc | show documentation for package or symbol | 36 | | | env | print Go environment information | 37 | | | fix | run go tool fix on packages | 38 | | | fmt | run gofmt on package sources | 39 | | | generate | generate Go files by processing source | 40 | | | get | download and install packages and dependencies | 41 | | | install | compile and install packages and dependencies | 42 | | | list | list packages | 43 | | | run | compile and run Go program | 44 | | | test | test packages | 45 | | | tool | run specified go tool | 46 | | | version | print Go version | 47 | | | vet | run go tool vet on packages | 48 | -------------------------------------------------------------------------------- /text/file.md: -------------------------------------------------------------------------------- 1 | # 文件操作 2 | 3 | 更多文件操作见Go的os包。 4 | 5 | ## 1. 目录操作 6 | 7 | - **func Mkdir(name string, perm FileMode) error** 8 | 创建名称为 name 的目录,权限设置是 perm,例如 0777 9 | 10 | 11 | - **func MkdirAll(path string, perm FileMode) error** 12 | 根据 path 创建多级子目录,例如 astaxie/test1/test2。 13 | - **func Remove(name string) error** 14 | 删除名称为 name 的目录,当目录下有文件或者其他目录是会出错 15 | - **func RemoveAll(path string) error** 16 | 根据 path 删除多级子目录,如果 path 是单个名称,那么该目录不删除 17 | 18 | 19 | 20 | ## 2. 文件操作 21 | 22 | ## 2.1. 建立与打开文件 23 | 24 | **新建文件:** 25 | 26 | - **func Create(name string) (file \*File, err Error)** 27 | 根据提供的文件名创建新的文件,返回一个文件对象,默认权限是 0666 的文件,返回的文件对象是可读写的。 28 | - ** func NewFile(fd uintptr, name string) \*File** 29 | 根据文件描述符创建相应的文件,返回一个文件对象 30 | 31 | **打开文件:** 32 | 33 | - **func Open(name string) (file \*File, err Error)** 34 | 该方法打开一个名称为 name 的文件,但是是只读方式,内部实现其实调用了 OpenFile。 35 | - **func OpenFile(name string, flag int, perm uint32) (file \*File, err Error)** 36 | 打开名称为 name 的文件,flag 是打开的方式,只读、读写等,perm 是权限 37 | 38 | ## 2.2. 写文件 39 | 40 | **写文件函数:** 41 | 42 | - **func (file \*File) Write(b []byte) (n int, err Error)** 43 | 写入 byte 类型的信息到文件 44 | - **func (file \*File) WriteAt(b []byte, off int64) (n int, err Error)** 45 | 在指定位置开始写入 byte 类型的信息 46 | - **func (file \*File) WriteString(s string) (ret int, err Error)** 47 | 写入 string 信息到文件 48 | 49 | ```go 50 | package main 51 | import ( 52 | "fmt" 53 | "os" 54 | ) 55 | func main() { 56 | userFile := "test.txt" 57 | fout, err := os.Create(userFile) 58 | defer fout.Close() 59 | if err != nil { 60 | fmt.Println(userFile, err) 61 | return 62 | } 63 | for i := 0; i < 10; i++ { 64 | fout.WriteString("Just a test!\r\n") 65 | fout.Write([]byte("Just a test!\r\n")) 66 | } 67 | } 68 | ``` 69 | 70 | ## 2.3. 读文件 71 | 72 | **读文件函数:** 73 | 74 | - func (file *File) Read(b []byte) (n int, err Error) 75 | 读取数据到 b 中 76 | 77 | 78 | - func (file *File) ReadAt(b []byte, off int64) (n int, err Error) 79 | 从 off 开始读取数据到 b 中 80 | 81 | ```go 82 | package main 83 | import ( 84 | "fmt" 85 | "os" 86 | ) 87 | func main() { 88 | userFile := "text.txt" 89 | fl, err := os.Open(userFile) 90 | defer fl.Close() 91 | if err != nil { 92 | fmt.Println(userFile, err) 93 | return 94 | } 95 | buf := make([]byte, 1024) 96 | for { 97 | n, _ := fl.Read(buf) 98 | if 0 == n { 99 | break 100 | } 101 | os.Stdout.Write(buf[:n]) 102 | } 103 | } 104 | ``` 105 | 106 | ## 2.4. 删除文件 107 | 108 | Go 语言里面删除文件和删除文件夹是同一个函数 109 | 110 | - func Remove(name string) Error 111 | 调用该函数就可以删除文件名为 name 的文件 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Golang 学习笔记 2 | 3 | > 本系列是 [Golang 学习笔记](https://www.huweihuang.com/golang-notes/) 4 | > 5 | > 更多的学习笔记请参考: 6 | > - [Kubernetes 学习笔记](https://www.huweihuang.com/kubernetes-notes/) 7 | > - [Golang 学习笔记](https://www.huweihuang.com/golang-notes/) 8 | > - [Linux 学习笔记](https://www.huweihuang.com/linux-notes/) 9 | > - [数据结构学习笔记](https://www.huweihuang.com/data-structure-notes/) 10 | > 11 | > 个人博客:[www.huweihuang.com](https://www.huweihuang.com/) 12 | 13 | 14 | # 目录 15 | 16 | ## 概述 17 | 18 | * [Golang资源](summary/go-resource.md) 19 | * [计算机语言概述](summary/language.md) 20 | * [后端开发技能树](summary/skill-tree.md) 21 | 22 | ## 安装与配置 23 | 24 | * [Golang介绍](introduction/golang.md) 25 | * [Golang安装](introduction/install.md) 26 | * [包管理工具]() 27 | * [go modules的使用](introduction/package/go-modules.md) 28 | * [dep的使用](introduction/package/dep-usage.md) 29 | * [govendor的使用](introduction/package/govendor-usage.md) 30 | * [glide的使用](introduction/package/glide-usage.md) 31 | 32 | ## 顺序编程 33 | 34 | * [变量与常量](basis/var-const.md) 35 | * [数据类型](basis/data-types.md) 36 | * [流程语句](basis/control-structures.md) 37 | * [函数与闭包](basis/functions.md) 38 | * [错误处理](basis/errors.md) 39 | 40 | ## 面向对象编程 41 | 42 | * [概述](oop/oop.md) 43 | * [类与方法](oop/struct-method.md) 44 | * [接口]() 45 | * [接口概述](oop/interface/interface.md) 46 | * [client-go中接口](oop/interface/client-go-interface.md) 47 | * [通用接口设计](oop/interface/common-interface.md) 48 | * [指针](oop/pointer.md) 49 | 50 | ## 并发编程 51 | 52 | * [并发基础](concurrency/concurrency.md) 53 | * [Goroutine](concurrency/goroutine.md) 54 | * [Channel](concurrency/channel.md) 55 | * [并行化](concurrency/parallelization.md) 56 | 57 | ## 文本处理 58 | 59 | * [Json处理](text/json.md) 60 | * [文件操作](text/file.md) 61 | * [字符串处理](text/string.md) 62 | * [模板语法](text/template.md) 63 | 64 | ## 测试与调试 65 | 66 | * [单元测试](test/test.md) 67 | * [GDB调试](test/gdb.md) 68 | 69 | --- 70 | 71 | ## 原理篇 72 | 73 | * [Goroutine调度](principle/go-scheduler.md) 74 | * [Go内存分配](principle/memory-allocation.md) 75 | * [Go垃圾回收](principle/garbage-collection.md) 76 | * [深入理解Channel](principle/understand-channel.md) 77 | 78 | ## 框架与工具 79 | 80 | * [Cobra]() 81 | * [cobra 介绍](framework/cobra/cobra-usage.md) 82 | * [cobra command](framework/cobra/cobra-command.md) 83 | * [cobra flags](framework/cobra/cobra-flags.md) 84 | 85 | ## Web 编程 86 | 87 | * [Beego]() 88 | * [Beego 介绍](web/beego/beego-introduction.md) 89 | * [Bee 工具使用](web/beego/bee.md) 90 | * [Beego 项目逻辑](web/beego/beego-project.md) 91 | * [Beego 日志处理](web/beego/beego-log.md) 92 | * [Http包源码分析](web/golang-http-execution-flow.md) 93 | 94 | ## 源码分析 95 | 96 | * [confd源码分析](code/confd-code-analysis.md) 97 | 98 | 99 | # 赞赏 100 | 101 | > 如果觉得文章有帮助的话,可以打赏一下,谢谢! 102 | 103 | 104 | -------------------------------------------------------------------------------- /basis/functions.md: -------------------------------------------------------------------------------- 1 | # 函数 2 | 3 | ## 1. 函数定义与调用 4 | 5 | ```go 6 | //1、函数组成:关键字func ,函数名,参数列表,返回值,函数体,返回语句 7 | //先名称后类型 8 | func 函数名(参数列表)(返回值列表){ //参数列表和返回值列表以变量声明的形式,如果单返回值可以直接加类型 9 | 函数体 10 | return //返回语句 11 | } 12 | //例子 13 | func Add(a,b int)(ret int,err error){ 14 | //函数体 15 | return //return语句 16 | } 17 | 18 | //2、函数调用 19 | //先导入函数所在的包,直接调用函数 20 | import "mymath" 21 | sum,err:=mymath.Add(1,2) //多返回值和错误处理机制 22 | //可见性,包括函数、类型、变量 23 | //本包内可见(private):小写字母开头 24 | //包外可见(public):大写字母开头 25 | ``` 26 | 27 | ## 2. 不定参数 28 | 29 | ```go 30 | //1、不定参数的类型 31 | func myfunc(args ...int){ //...type不定参数的类型,必须是最后一个参数,本质是切片 32 | for _,arg:=range args{ 33 | fmt.Println(arg) 34 | } 35 | } 36 | //函数调用,传参可以选择多个,个数不定 37 | myfunc(1,2,3) 38 | myfunc(1,2,3,4,5) 39 | 40 | //2、不定参数的传递,假如有个变参函数myfunc2(args ...int) 41 | func myfunc1(args ...int){ 42 | //按原样传递 43 | myfunc2(args...) 44 | //传递切片 45 | myfunc2(args[1:]...) 46 | } 47 | 48 | //3、任意类型的不定参数,使用interface{}作为指定类型 49 | func Printf(format string,args ...interface{}){ //此为标准库中fmt.Printf()函数的原型 50 | //函数体 51 | } 52 | ``` 53 | 54 | ## 3. 多返回值 55 | 56 | ```go 57 | //多返回值 58 | func (file *File) Read(b []byte) (n int,err error) 59 | //使用下划线"_"来丢弃返回值 60 | n,_:=f.Read(buf) 61 | ``` 62 | 63 | ## 4. 匿名函数 64 | 65 | 匿名函数:不带函数名的函数,可以像变量一样被传递。 66 | 67 | ```go 68 | func(a,b int,z float32) bool{ //没有函数名 69 | return a*b closure.go 117 | 118 | ```go 119 | package main 120 | 121 | import ( 122 | "fmt" 123 | ) 124 | 125 | func main() { 126 | var j int = 5 127 | a := func() func() { 128 | var i int = 10 129 | return func() { 130 | fmt.Printf("i, j: %d, %d\n", i, j) 131 | } 132 | }() 133 | a() 134 | j *= 2 135 | a() 136 | } 137 | ``` 138 | 139 | ### 5.6 闭包的参考链接 140 | 141 | - https://tour.golang.org/moretypes/25 142 | - https://golang.org/doc/faq#closures_and_goroutines 143 | - https://github.com/golang/go/wiki/CommonMistakes 144 | 145 | 参考: 146 | 147 | - 《Go语言编程》 148 | -------------------------------------------------------------------------------- /text/template.md: -------------------------------------------------------------------------------- 1 | # 基本语法 2 | 3 | go 统一使用了 `{{` 和 `}}` 作为左右标签,没有其他的标签符号。 4 | 5 | 使用 `.` 来访问当前位置的上下文 6 | 7 | 使用 `$` 来引用当前模板根级的上下文 8 | 9 | 使用 `$var` 来访问创建的变量 10 | 11 | **模板中支持的 go 语言符号** 12 | 13 | ```go 14 | {{"string"}} // 一般 string 15 | {{`raw string`}} // 原始 string 16 | {{'c'}} // byte 17 | {{print nil}} // nil 也被支持 18 | ``` 19 | 20 | **模板中的 pipeline** 21 | 22 | 可以是上下文的变量输出,也可以是函数通过管道传递的返回值 23 | 24 | ```go 25 | {{. | FuncA | FuncB | FuncC}} 26 | ``` 27 | 28 | 当 pipeline 的值等于: 29 | 30 | - false 或 0 31 | - nil 的指针或 interface 32 | - 长度为 0 的 array, slice, map, string 33 | 34 | 那么这个 pipeline 被认为是空 35 | 36 | ## 1. if … else … end 37 | 38 | ```go 39 | {{if pipeline}}{{end}} 40 | ``` 41 | 42 | if 判断时,pipeline 为空时,相当于判断为 False 43 | 44 | 支持嵌套的循环 45 | 46 | ```go 47 | {{if .IsHome}} 48 | {{else}} 49 | {{if .IsAbout}}{{end}} 50 | {{end}} 51 | ``` 52 | 53 | 也可以使用 else if 进行 54 | 55 | ```go 56 | {{if .IsHome}} 57 | {{else if .IsAbout}} 58 | {{else}} 59 | {{end}} 60 | ``` 61 | 62 | ## 2. range … end 63 | 64 | ```go 65 | {{range pipeline}}{{.}}{{end}} 66 | ``` 67 | 68 | pipeline 支持的类型为 array, slice, map, channel 69 | 70 | range 循环内部的 `.` 改变为以上类型的子元素 71 | 72 | 对应的值长度为 0 时,range 不会执行,`.` 不会改变。 73 | 74 | ```go 75 | pages := []struct { 76 | Num int 77 | }{{10}, {20}, {30}} 78 | 79 | this.Data["Total"] = 100 80 | this.Data["Pages"] = pages 81 | ``` 82 | 83 | 使用 `.Num` 输出子元素的 Num 属性,使用 `$.` 引用模板中的根级上下文 84 | 85 | ```go 86 | {{range .Pages}} 87 | {{.Num}} of {{$.Total}} 88 | {{end}} 89 | ``` 90 | 91 | 使用创建的变量,在这里和 go 中的 range 用法是相同的。 92 | 93 | ```go 94 | {{range $index, $elem := .Pages}} 95 | {{$index}} - {{$elem.Num}} - {{.Num}} of {{$.Total}} 96 | {{end}} 97 | ``` 98 | 99 | range 也支持 else 100 | 101 | ```go 102 | {{range .Pages}} 103 | {{else}} 104 | {{/* 当 .Pages 为空 或者 长度为 0 时会执行这里 */}} 105 | {{end}} 106 | ``` 107 | 108 | ## 3. with … end 109 | 110 | ```go 111 | {{with pipeline}}{{end}} 112 | ``` 113 | 114 | with 用于重定向 pipeline 115 | 116 | ```go 117 | {{with .Field.NestField.SubField}} 118 | {{.Var}} 119 | {{end}} 120 | ``` 121 | 122 | 也可以对变量赋值操作 123 | 124 | ```go 125 | {{with $value := "My name is %s"}} 126 | {{printf . "slene"}} 127 | {{end}} 128 | ``` 129 | 130 | with 也支持 else 131 | 132 | ```go 133 | {{with pipeline}} 134 | {{else}} 135 | {{/* 当 pipeline 为空时会执行这里 */}} 136 | {{end}} 137 | ``` 138 | 139 | ## 4. define 140 | 141 | define 可以用来定义自模板,可用于模块定义和模板嵌套 142 | 143 | ```go 144 | {{define "loop"}} 145 |
  • {{.Name}}
  • 146 | {{end}} 147 | ``` 148 | 149 | 使用 template 调用模板 150 | 151 | ```go 152 |
      153 | {{range .Items}} 154 | {{template "loop" .}} 155 | {{end}} 156 |
    157 | ``` 158 | 159 | ## 5. template 160 | 161 | ```go 162 | {{template "模板名" pipeline}} 163 | ``` 164 | 165 | 将对应的上下文 pipeline 传给模板,才可以在模板中调用 166 | 167 | **Beego 中支持直接载入文件模板** 168 | 169 | ```go 170 | {{template "path/to/head.html" .}} 171 | ``` 172 | 173 | Beego 会依据你设置的模板路径读取 head.html 174 | 175 | 在模板中可以接着载入其他模板,对于模板的分模块处理很有用处 176 | 177 | ## 6. 注释 178 | 179 | 允许多行文本注释,不允许嵌套 180 | 181 | ```go 182 | {{/* comment content 183 | support new line */ 184 | ``` 185 | -------------------------------------------------------------------------------- /introduction/package/glide-usage.md: -------------------------------------------------------------------------------- 1 | # 1. glide简介 2 | 3 | glide是一个golang项目的包管理工具,非常方便快捷,一般只需要2-3个命令就可以将go依赖包自动下载并归档到vendor的目录中。 4 | 5 | # 2. glide安装 6 | 7 | ```bash 8 | go get github.com/Masterminds/glide 9 | ``` 10 | 11 | # 3. glide使用 12 | 13 | ```bash 14 | #进入到项目目录 15 | cd /home/gopath/src/demo 16 | #glide初始化,初始化配置文件glide.yaml 17 | glide init 18 | #glide加载依赖包,自动归档到vendor目录 19 | glide up -v 20 | ``` 21 | 22 | # 4. glide的配置文件 23 | 24 | `glide.yaml`记录依赖包列表。 25 | 26 | ```bash 27 | package: demo 28 | import: 29 | - package: github.com/astaxie/beego 30 | version: v1.9.2 31 | testImport: 32 | - package: github.com/smartystreets/goconvey 33 | version: 1.6.3 34 | subpackages: 35 | - convey 36 | ``` 37 | 38 | # 5. glide-help 39 | 40 | 更多glide的命令帮助参考`glide —help`。 41 | 42 | ```bash 43 | ➜ demo glide --help 44 | NAME: 45 | glide - Vendor Package Management for your Go projects. 46 | 47 | Each project should have a 'glide.yaml' file in the project directory. Files 48 | look something like this: 49 | 50 | package: github.com/Masterminds/glide 51 | imports: 52 | - package: github.com/Masterminds/cookoo 53 | version: 1.1.0 54 | - package: github.com/kylelemons/go-gypsy 55 | subpackages: 56 | - yaml 57 | 58 | For more details on the 'glide.yaml' files see the documentation at 59 | https://glide.sh/docs/glide.yaml 60 | 61 | 62 | USAGE: 63 | glide [global options] command [command options] [arguments...] 64 | 65 | VERSION: 66 | 0.13.2-dev 67 | 68 | COMMANDS: 69 | create, init Initialize a new project, creating a glide.yaml file 70 | config-wizard, cw Wizard that makes optional suggestions to improve config in a glide.yaml file. 71 | get Install one or more packages into `vendor/` and add dependency to glide.yaml. 72 | remove, rm Remove a package from the glide.yaml file, and regenerate the lock file. 73 | import Import files from other dependency management systems. 74 | name Print the name of this project. 75 | novendor, nv List all non-vendor paths in a directory. 76 | rebuild Rebuild ('go build') the dependencies 77 | install, i Install a project's dependencies 78 | update, up Update a project's dependencies 79 | tree (Deprecated) Tree prints the dependencies of this project as a tree. 80 | list List prints all dependencies that the present code references. 81 | info Info prints information about this project 82 | cache-clear, cc Clears the Glide cache. 83 | about Learn about Glide 84 | mirror Manage mirrors 85 | help, h Shows a list of commands or help for one command 86 | 87 | GLOBAL OPTIONS: 88 | --yaml value, -y value Set a YAML configuration file. (default: "glide.yaml") 89 | --quiet, -q Quiet (no info or debug messages) 90 | --debug Print debug verbose informational messages 91 | --home value The location of Glide files (default: "/Users/meitu/.glide") [$GLIDE_HOME] 92 | --tmp value The temp directory to use. Defaults to systems temp [$GLIDE_TMP] 93 | --no-color Turn off colored output for log messages 94 | --help, -h show help 95 | --version, -v print the version 96 | ``` 97 | 98 | -------------------------------------------------------------------------------- /concurrency/channel.md: -------------------------------------------------------------------------------- 1 | # Channel 2 | 3 | channel就像管道的形式,是goroutine之间的通信方式,是进程内的通信方式,跨进程通信建议用分布式系统的方法来解决,例如Socket或http等通信协议。channel是类型相关,即一个channel只能传递一种类型的值,在声明时指定。 4 | 5 | # 1. 基本语法 6 | 7 | ## 1.1. channel的声明 8 | 9 | ```go 10 | //1、channel声明,声明一个管道chanName,该管道可以传递的类型是ElementType 11 | //管道是一种复合类型,[chan ElementType],表示可以传递ElementType类型的管道[类似定语从句的修饰方法] 12 | var chanName chan ElementType 13 | var ch chan int //声明一个可以传递int类型的管道 14 | var m map[string] chan bool //声明一个map,值的类型为可以传递bool类型的管道 15 | ``` 16 | 17 | ## 1.2. 初始化 18 | 19 | ```go 20 | //2、初始化 21 | ch:=make(chan int)   //make一般用来声明一个复合类型,参数为复合类型的属性 22 | ``` 23 | 24 | ## 1.3. 管道读写 25 | 26 | ```go 27 | //3、管道写入,把值想象成一个球,"<-"的方向,表示球的流向,ch即为管道 28 | //写入时,当管道已满(管道有缓冲长度)则会导致程序堵塞,直到有goroutine从中读取出值 29 | ch <- value 30 | //管道读取,"<-"表示从管道把球倒出来赋值给一个变量 31 | //当管道为空,读取数据会导致程序阻塞,直到有goroutine写入值 32 | value:= <-ch 33 | ``` 34 | 35 | ## 1.4. select 36 | 37 | ```go 38 | //4、每个case必须是一个IO操作,面向channel的操作,只执行其中的一个case操作,一旦满足则结束select过程 39 | //面向channel的操作无非三种情况:成功读出;成功写入;即没有读出也没有写入 40 | select{ 41 | case <-chan1: 42 | //如果chan1读到数据,则进行该case处理语句 43 | case chan2<-1: 44 | //如果成功向chan2写入数据,则进入该case处理语句 45 | default: 46 | //如果上面都没有成功,则进入default处理流程 47 | } 48 | ``` 49 | 50 | # 2. 缓冲和超时机制 51 | 52 | ## 2.1. 缓冲机制 53 | 54 | ```go 55 | //1、缓冲机制:为管道指定空间长度,达到类似消息队列的效果 56 | c:=make(chan int,1024) //第二个参数为缓冲区大小,与切片的空间大小类似 57 | //通过range关键字来实现依次读取管道的数据,与数组或切片的range使用方法类似 58 | for i :=range c{ 59 | fmt.Println("Received:",i) 60 | } 61 | ``` 62 | 63 | ## 2.2. 超时机制 64 | 65 | ```go 66 | //2、超时机制:利用select只要一个case满足,程序就继续执行而不考虑其他case的情况的特性实现超时机制 67 | timeout:=make(chan bool,1) //设置一个超时管道 68 | go func(){ 69 | time.Sleep(1e9) //设置超时时间,等待一分钟 70 | timeout<-true //一分钟后往管道放一个true的值 71 | }() 72 | // 73 | select { 74 | case <-ch: //如果读到数据,则会结束select过程 75 | //从ch中读取数据 76 | case <-timeout: //如果前面的case没有调用到,必定会读到true值,结束select,避免永久等待 77 | //一直没有从ch中读取到数据,但从timeout中读取到了数据 78 | } 79 | ``` 80 | 81 | ## 2.3. channel的传递 82 | 83 | ```go 84 | //1、channel的传递,来实现Linux系统中管道的功能,以插件的方式增加数据处理的流程 85 | type PipeData struct{ 86 | value int 87 | handler func(int) int //handler是属性? 88 | next chan int //可以把[chan int]看成一个整体,表示放int类型的管道 89 | } 90 | func handler(queue chan *PipeData){ //queue是一个存放*PipeDate类型的管道,可改变管道里的数据块内容 91 | for data:=range queue{ //data的类型就是管道存放定义的类型,即PipeData 92 | data.next <- data.handler(data.value) //该方法实现将PipeData的value值存放到next的管道中 93 | } 94 | } 95 | ``` 96 | 97 | ## 2.4. 单向channel 98 | 99 | ```go 100 | //2、单向channel:只能用于接收或发送数据,是对channel的一种使用限制 101 | //单向channel的声明 102 | var ch1 chan int //正常channel,可读写 103 | var ch2 chan<- int //单向只写channel [chan<- int]看成一个整体,表示流入管道 104 | var ch3 <-chan int //单向只读channel [<-chan int]看成一个整体,表示流出管道 105 | //管道类型强制转换 106 | ch4:=make(chan int) //ch4为双向管道 107 | ch5:=<-chan int(ch4) //把[<-chan int]看成单向只读管道类型,对ch4进行强制类型转换 108 | ch6:=chan<- int(ch4) //把[chan<- int]看成单向只写管道类型,对ch4进行强制类型转换 109 | func Parse(ch <-chan int){ //最小权限原则 110 | for value:=range ch{ 111 | fmt.Println("Parsing value",value) 112 | } 113 | } 114 | ``` 115 | 116 | ## 2.5. 关闭channel 117 | 118 | ```go 119 | //3、关闭channel,使用内置函数close()函数即可 120 | close(ch) 121 | //判断channel是否关闭 122 | x,ok:=<-ch //ok==false表示channel已经关闭 123 | if !ok { //如果channel关闭,ok==false,!ok==true 124 | //执行体 125 | } 126 | ``` 127 | -------------------------------------------------------------------------------- /principle/go-scheduler.md: -------------------------------------------------------------------------------- 1 | > 本文主要介绍Go的调度模型。 2 | 3 | # 1. 线程实现模型 4 | 5 | 线程模型有三类:[内核级线程模型、用户级线程模型、混合型线程模型](https://en.wikipedia.org/wiki/Thread_%28computing%29#Models)。三者的区别主要在于线程与内核调度实体KSE(Kernel Scheduling Entity)之间的对应关系上。 6 | 7 | > 内核调度实体KSE指操作系统内核调度器调度的对象实体,是内核调度的最小单元。 8 | 9 | ## 1.1. 线程模型对比 10 | 11 | | 线程模型 | 用户线程与KSE之间的对应关系 | 特点 | 优点 | 缺点 | 12 | | -------------- | --------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | 13 | | 内核级线程模型 | 1:1 | 1条用户线程对应一条内核进程/线程来调度,即以核心态线程实现。 | 具有和内核线程一致的优点,不同用户线程之间不会互相影响。可以利用多核系统的优势。 | 在大量线程的情况下,线程的创建、删除、切换的代价更昂贵,影响性能。 | 14 | | 用户级线程模型 | M:1 | N条用户线程只由一条内核进程/线程调度,即以用户态线程实现。 | 线程的创建、删除和环境切换都很高效。 | 一旦一个线程发生阻塞,整个进程下的其他线程也会被阻塞。不能利用多核系统的优势。 | 15 | | 混合型线程模型 | M:N | M条用户线程由N条内核线程动态关联。又称两级线程模型 | 可以快速地执行上下文切换,而且可以利用多核的优势。当某个线程发生阻塞可以调度出CPU关联到可以执行的线程上。目前Go就是采用这种线程模型。 | 动态关联机制实现复杂,需要用户或runtime自己去实现。 | 16 | 17 | ## 1.2. 线程模型示意图 18 | 19 | 20 | 21 | # 2. G-P-M调度模型 22 | 23 | **调度模型:** 24 | 25 | 26 | 27 | **G-P-M对应关系:** 28 | 29 | 30 | 31 | ## 2.1. 基本概念 32 | 33 | - M:machine,代表系统内核进程,用来执行G。(工人) 34 | - P:processor,代表调度执行的上下文(context),维护了一个本地的goroutine的队列。(小推车) 35 | - G:goroutine,代表goroutine,即执行的goroutine的数据结构及栈等。(砖头) 36 | 37 | ## 2.2. 基本流程 38 | 39 | 调度的本质是将G尽量均匀合理地安排给M来执行,其中P的作用就是来实现合理安排逻辑。 40 | 41 | 42 | 43 | - P的数量通过 `GOMAXPROCS()` 来设置,一般等于CPU的核数,对于一次代码执行设置好一般不会变。 44 | - P维护了一个本地的G队列(runqueue),包括正在执行和待执行的G,尽量保证所有的P都匹配一个M同时在执行G。 45 | - 当P本地goroutine队列消费完,会从全局的goroutine队列(global runqueue)中拿goroutine到本地队列。P也会定期检查全局的goroutine队列,避免存在全局的goroutine没有被执行而"饿死"的现象。 46 | - P和M是动态形式的一对一的关系,P和G是动态形式的一对多的关系。 47 | 48 | ## 2.3. 抢占式调度(阻塞) 49 | 50 | 51 | 52 | 当goroutine发生阻塞的时候,可以通过P将剩余的G切换给新的M来执行,而不会导致剩余的G无法执行,如果没有M则创建M来匹配P。 53 | 54 | 当阻塞的goroutine返回后,进程会尝试获取一个上下文(Context)来执行这个goroutine。一般是先从其他进程中"偷取"一个Context,如果"偷取"不成功,则将goroutine放入全局的goroutine中。 55 | 56 | ## 2.4. 偷任务 57 | 58 | 59 | 60 | P可以偷任务即goroutine,当某个P的本地G执行完,且全局没有G需要执行的时候,P可以去偷别的P还没有执行完的一半的G来给M执行,提高了G的执行效率。 61 | 62 | 63 | 64 | 参考: 65 | 66 | - http://morsmachine.dk/go-scheduler 67 | - [Scalable Go Scheduler Design Doc]() 68 | - [Go Preemptive Scheduler Design Doc]() 69 | - [k2huang/blogpost/Go并发机制]() 70 | -------------------------------------------------------------------------------- /framework/cobra/cobra-usage.md: -------------------------------------------------------------------------------- 1 | # 1. Cobra简介 2 | 3 | `Cobra`是一个cli接口模式的应用程序框架,同时也是生成该框架的命令行工具。用户可以通过`help`方式快速查看该二进制的使用方式。 4 | 5 | `Cobra`主要包括以下部分 6 | 7 | - `Command`:一般表示action,即运行的二进制命令服务。同时可以拥有子命令(children commands)。 8 | - `Args`:命令执行相关参数。 9 | - `Flags`:二进制命令的配置参数,可对应配置文件。参数可分为全局参数和子命令参数。参考:[pflag library](https://github.com/spf13/pflag)。 10 | 11 | # 2. 安装 12 | 13 | 通过以下操作可以在`$GOPATH/bin`安装cobra的二进制命令。 14 | 15 | ```bash 16 | go get -u github.com/spf13/cobra/cobra 17 | ``` 18 | 19 | # 3. 使用 20 | 21 | `cobra`命令行帮助信息如下: 22 | 23 | ```bash 24 | # cobra 25 | Cobra is a CLI library for Go that empowers applications. 26 | This application is a tool to generate the needed files 27 | to quickly create a Cobra application. 28 | 29 | Usage: 30 | cobra [command] 31 | 32 | Available Commands: 33 | add Add a command to a Cobra Application 34 | help Help about any command 35 | init Initialize a Cobra Application 36 | 37 | Flags: 38 | -a, --author string author name for copyright attribution (default "YOUR NAME") 39 | --config string config file (default is $HOME/.cobra.yaml) 40 | -h, --help help for cobra 41 | -l, --license string name of license for the project 42 | --viper use Viper for configuration (default true) 43 | 44 | Use "cobra [command] --help" for more information about a command. 45 | ``` 46 | 47 | ## 3.1. cobra init 48 | 49 | ```bash 50 | # cobra init --help 51 | Initialize (cobra init) will create a new application, with a license 52 | and the appropriate structure for a Cobra-based CLI application. 53 | 54 | * If a name is provided, it will be created in the current directory; 55 | * If no name is provided, the current directory will be assumed; 56 | * If a relative path is provided, it will be created inside $GOPATH 57 | (e.g. github.com/spf13/hugo); 58 | * If an absolute path is provided, it will be created; 59 | * If the directory already exists but is empty, it will be used. 60 | 61 | Init will not use an existing directory with contents. 62 | 63 | Usage: 64 | cobra init [name] [flags] 65 | 66 | Aliases: 67 | init, initialize, initialise, create 68 | 69 | Flags: 70 | -h, --help help for init 71 | --pkg-name string fully qualified pkg name 72 | 73 | Global Flags: 74 | -a, --author string author name for copyright attribution (default "YOUR NAME") 75 | --config string config file (default is $HOME/.cobra.yaml) 76 | -l, --license string name of license for the project 77 | --viper use Viper for configuration (default true) 78 | ``` 79 | 80 | 81 | ## 3.2. cobra add 82 | 83 | ```bash 84 | # cobra add --help 85 | Add (cobra add) will create a new command, with a license and 86 | the appropriate structure for a Cobra-based CLI application, 87 | and register it to its parent (default rootCmd). 88 | 89 | If you want your command to be public, pass in the command name 90 | with an initial uppercase letter. 91 | 92 | Example: cobra add server -> resulting in a new cmd/server.go 93 | 94 | Usage: 95 | cobra add [command name] [flags] 96 | 97 | Aliases: 98 | add, command 99 | 100 | Flags: 101 | -h, --help help for add 102 | -p, --parent string variable name of parent command for this command (default "rootCmd") 103 | 104 | Global Flags: 105 | -a, --author string author name for copyright attribution (default "YOUR NAME") 106 | --config string config file (default is $HOME/.cobra.yaml) 107 | -l, --license string name of license for the project 108 | --viper use Viper for configuration (default true) 109 | ``` 110 | 111 | 112 | 113 | 参考: 114 | 115 | - https://github.com/spf13/cobra 116 | - https://github.com/spf13/cobra/blob/master/cobra/README.md 117 | -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "title": "Golang 学习笔记", 4 | "description": "Golang 学习笔记", 5 | "language": "zh-hans", 6 | "author": "胡伟煌", 7 | "links": { 8 | "sidebar": { 9 | "胡伟煌的博客": "https://www.huweihuang.com", 10 | "Kubernetes 学习笔记": "https://www.huweihuang.com/kubernetes-notes/", 11 | "Golang 学习笔记": "https://www.huweihuang.com/golang-notes/", 12 | "Linux 学习笔记": "https://www.huweihuang.com/linux-notes/", 13 | "数据结构学习笔记": "https://www.huweihuang.com/data-structure-notes/", 14 | "Python 学习笔记": "https://www.huweihuang.com/python-notes/", 15 | "Blockchain 学习笔记": "https://www.huweihuang.com/blockchain-notes/" 16 | } 17 | }, 18 | "plugins": [ 19 | "github", 20 | "codesnippet", 21 | "splitter", 22 | "page-toc-button", 23 | "image-captions", 24 | "editlink", 25 | "back-to-top-button", 26 | "-lunr", "-search", "search-plus", 27 | "github-buttons@2.1.0", 28 | "favicon@^0.0.2", 29 | "tbfed-pagefooter@^0.0.1", 30 | "3-ba", 31 | "theme-default", 32 | "-highlight", "prism", "prism-themes", 33 | "sitemap-general", 34 | "ga", 35 | "disqus", 36 | "donate", 37 | "readmore", 38 | "google-ads" 39 | ], 40 | "pluginsConfig": { 41 | "theme-default": { 42 | "showLevel": true 43 | }, 44 | "github": { 45 | "url": "https://github.com/huweihuang" 46 | }, 47 | "editlink": { 48 | "base": "https://github.com/huweihuang/golang-notes/blob/master/", 49 | "label": "编辑本页" 50 | }, 51 | "image-captions": { 52 | "caption": "图片 - _CAPTION_" 53 | }, 54 | "github-buttons": { 55 | "repo": "huweihuang/golang-notes", 56 | "types": ["star"], 57 | "size": "small" 58 | }, 59 | "tbfed-pagefooter": { 60 | "copyright": "Copyright © www.huweihuang.com", 61 | "modify_label": "Updated at ", 62 | "modify_format": "YYYY-MM-DD HH:mm:ss" 63 | }, 64 | "favicon": { 65 | "shortcut": "images/favicon.ico", 66 | "bookmark": "images/favicon.ico", 67 | "appleTouch": "images/favicon.ico", 68 | "appleTouchMore": { 69 | "120x120": "images/favicon.ico", 70 | "180x180": "images/favicon.ico" 71 | } 72 | }, 73 | "3-ba": { 74 | "token": "e146d71b77957235bba1e709d930f62e" 75 | }, 76 | "prism": { 77 | "css": [ 78 | "prismjs/themes/prism-okaidia.css" 79 | ] 80 | }, 81 | "sitemap-general": { 82 | "prefix": "https://www.huweihuang.com/golang-notes/" 83 | }, 84 | "ga": { 85 | "token": "UA-114718458-2" 86 | }, 87 | "disqus": { 88 | "shortName": "huweihuang" 89 | }, 90 | "donate": { 91 | "wechat": "https://res.cloudinary.com/dqxtn0ick/image/upload/v1551599472/blog/donate/wechatpay.jpg", 92 | "alipay": "https://res.cloudinary.com/dqxtn0ick/image/upload/v1551599640/blog/donate/alipay.jpg", 93 | "title": "", 94 | "button": "赏", 95 | "alipayText": "支付宝打赏", 96 | "wechatText": "微信打赏" 97 | }, 98 | "readmore":{ 99 | "allowDomain":["www.huweihuang.com","localhost"], 100 | "blogId": "30679-1666624342180-175", 101 | "name": "容器云架构", 102 | "qrcode": "https://res.cloudinary.com/dqxtn0ick/image/upload/v1551600382/blog/wechat-public-acconut.jpg", 103 | "keyword": "golang" 104 | }, 105 | "google-ads": { 106 | "ads": [ 107 | { 108 | "client": "ca-pub-8205636531078391" 109 | } 110 | ] 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /text/json.md: -------------------------------------------------------------------------------- 1 | # JSON处理 2 | 3 | JSON是一种轻量级的数据交换语言。 4 | 5 | ## 1. 解析JSON[Unmarshal(data []byte, v interface{})] 6 | 7 | ## 1.1. Unmarshal源码 8 | 9 | **/src/encoding/json/decode.go** 10 | 11 | ```go 12 | func Unmarshal(data []byte, v interface{}) error { 13 | // Check for well-formedness. 14 | // Avoids filling out half a data structure 15 | // before discovering a JSON syntax error. 16 | var d decodeState 17 | err := checkValid(data, &d.scan) 18 | if err != nil { 19 | return err 20 | } 21 | d.init(data) 22 | return d.unmarshal(v) 23 | } 24 | ... 25 | func (d *decodeState) unmarshal(v interface{}) (err error) { 26 | defer func() { 27 | if r := recover(); r != nil { 28 | if _, ok := r.(runtime.Error); ok { 29 | panic(r) 30 | } 31 | err = r.(error) 32 | } 33 | }() 34 | rv := reflect.ValueOf(v) 35 | if rv.Kind() != reflect.Ptr || rv.IsNil() { 36 | return &InvalidUnmarshalError{reflect.TypeOf(v)} 37 | } 38 | d.scan.reset() 39 | // We decode rv not rv.Elem because the Unmarshaler interface 40 | // test must be applied at the top level of the value. 41 | d.value(rv) 42 | return d.savedError 43 | } 44 | ``` 45 | 46 | ## 1.2. 解析到结构体 47 | 48 | ```go 49 | package main 50 | import ( 51 | "encoding/json" 52 | "fmt" 53 | ) 54 | type Server struct { 55 | ServerName string 56 | ServerIP string 57 | } 58 | type Serverslice struct { 59 | Servers []Server 60 | } 61 | func main() { 62 | var s Serverslice 63 | str := `{"servers": 64 | [{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"}, 65 | {"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}` 66 | err:=json.Unmarshal([]byte(str), &s) 67 | if err!=nil{ 68 | fmt.Println(err) 69 | } 70 | fmt.Println(s) 71 | } 72 | ``` 73 | 74 | **说明** 75 | 76 | JSON格式与结构体一一对应,Unmarshal方法即将JSON文本转换成结构体。只会匹配结构体中的可导出字段,即首字母大写字段(类似java的public),匹配规则如下:json的key为Foo为例 77 | 78 | 1. 先查找struct tag中含有Foo的可导出的struct字段(首字母大写) 79 | 2. 其次查找字段名为Foo的可导出字段。 80 | 3. 最后查找类似FOO或者FoO这类除首字母外,其他大小写不敏感的可导出字段。 81 | 82 | ## 1.3. 解析到interface 83 | 84 | 85 | 86 | ## 2. 生成JSON[Marshal(v interface{})] 87 | 88 | ## 2.1. Marshal源码 89 | 90 | **/src/encoding/json/encode.go** 91 | 92 | ```go 93 | func Marshal(v interface{}) ([]byte, error) { 94 | e := &encodeState{} 95 | err := e.marshal(v) 96 | if err != nil { 97 | return nil, err 98 | } 99 | return e.Bytes(), nil 100 | } 101 | ... 102 | func (e *encodeState) marshal(v interface{}) (err error) { 103 | defer func() { 104 | if r := recover(); r != nil { 105 | if _, ok := r.(runtime.Error); ok { 106 | panic(r) 107 | } 108 | if s, ok := r.(string); ok { 109 | panic(s) 110 | } 111 | err = r.(error) 112 | } 113 | }() 114 | e.reflectValue(reflect.ValueOf(v)) 115 | return nil 116 | } 117 | ``` 118 | 119 | ## 2.2. 使用方法 120 | 121 | ```go 122 | package main 123 | import ( 124 | "encoding/json" 125 | "fmt" 126 | ) 127 | type Server struct { 128 | ServerName string `json:"serverName,string"` 129 | ServerIP string `json:"serverIP,omitempty"` 130 | } 131 | type Serverslice struct { 132 | Servers []Server `json:"servers"` 133 | } 134 | func main() { 135 | var s Serverslice 136 | s.Servers = append(s.Servers, Server{ServerName: "Shanghai_VPN", ServerIP: "127.0.0.1"}) 137 | s.Servers = append(s.Servers, Server{ServerName: "Beijing_VPN", ServerIP: "127.0.02"}) 138 | b, err := json.Marshal(s) 139 | if err != nil { 140 | fmt.Println("JSON ERR:", err) 141 | } 142 | fmt.Println(string(b)) 143 | } 144 | ``` 145 | 146 | ## 2.3. 说明 147 | 148 | Marshal方法将结构体转换成json文本,匹配规则如下: 149 | 150 | 1. 如果字段的tag是“-”,那么该字段不会输出到JSON。 151 | 2. 如果tag中带有自定义名称,那么该自定义名称会出现在JSON字段名中。例如例子中的“serverName” 152 | 3. 如果tag中带有“omitempty”选项,那么如果该字段值为空,就不会输出到JSON中。 153 | 4. 如果字段类型是bool,string,int,int64等,而tag中带有“,string”选项,那么这个字段在输出到JSON的时候会把该字段对应的值转换成JSON字符串。 154 | 155 | 注意事项: 156 | 157 | 1. Marshal只有在转换成功的时候才会返回数据,JSON对象只支持string作为key,如果要编码一个map,那么必须是map[string]T这种类型。(T为任意类型) 158 | 2. Channel,complex和function不能被编码成JSON。 159 | 3. 嵌套的数据不能编码,会进入死循环。 160 | 4. 指针在编码时会输出指针指向的内容,而空指针会输出null。 161 | -------------------------------------------------------------------------------- /introduction/package/govendor-usage.md: -------------------------------------------------------------------------------- 1 | # 1. govendor简介 2 | 3 | golang工程的依赖包经常使用go get 的方式来获取,例如:go get github.com/kardianos/govendor ,会将依赖包下载到GOPATH的路径下。 4 | 5 | 常用的依赖包管理工具有godep,govendor等,在Golang1.5之后,Go提供了 `GO15VENDOREXPERIMENT` 环境变量,用于将go build时的应用路径搜索调整成为 `当前项目目录/vendor` 目录方式。通过这种形式,我们可以实现类似于 `godep` 方式的项目依赖管理。 6 | 7 | # 2. 安装与使用 8 | 9 | ## 2.1. 安装 10 | 11 | ```go 12 | go get -u -v github.com/kardianos/govendor 13 | ``` 14 | 15 | ## 2.2. 使用 16 | 17 | ```bash 18 | #进入到项目目录 19 | cd /home/gopath/src/mytool 20 | #初始化vendor目录 21 | govendor init 22 | #查看vendor目录 23 | [root@CC54425A mytool]# ls 24 | commands main.go vendor mytool_test.sh 25 | #进入vendor目录 26 | cd vendor 27 | #将GOPATH中本工程使用到的依赖包自动移动到vendor目录中 28 | #说明:如果本地GOPATH没有依赖包,先go get相应的依赖包 29 | govendor add +external 30 | #通过设置环境变量 GO15VENDOREXPERIMENT=1 使用vendor文件夹构建文件。 31 | #可以选择 export GO15VENDOREXPERIMENT=1 或 GO15VENDOREXPERIMENT=1 go build 执行编译 32 | export GO15VENDOREXPERIMENT=1 33 | ``` 34 | 35 | ## 2.3. 说明 36 | 37 | govendor只是用来管理项目的依赖包,如果GOPATH中本身没有项目的依赖包,则需要通过go get先下载到GOPATH中,再通过govendor add +external 拷贝到vendor目录中。 38 | 39 | # 3. govendor的配置文件 40 | 41 | `vendor.json`记录依赖包列表。 42 | 43 | ```json 44 | { 45 | "comment": "", 46 | "ignore": "test", 47 | "package": [ 48 | { 49 | "checksumSHA1": "uGalSICR4r7354vvKuNnh7Y/R/4=", 50 | "path": "github.com/urfave/cli", 51 | "revision": "b99aa811b4c1dd84cc6bccb8499c82c72098085a", 52 | "revisionTime": "2017-08-04T09:34:15Z" 53 | } 54 | ], 55 | "rootPath": "mytool" 56 | } 57 | ``` 58 | 59 | # 4. govendor使用命令 60 | 61 | ```bash 62 | [root@CC54425A mytool]# govendor 63 | govendor (v1.0.8): record dependencies and copy into vendor folder 64 | -govendor-licenses Show govendor's licenses. 65 | -version Show govendor version 66 | -cpuprofile 'file' Writes a CPU profile to 'file' for debugging. 67 | -memprofile 'file' Writes a heap profile to 'file' for debugging. 68 | Sub-Commands 69 | init Create the "vendor" folder and the "vendor.json" file. 70 | list List and filter existing dependencies and packages. 71 | add Add packages from $GOPATH. 72 | update Update packages from $GOPATH. 73 | remove Remove packages from the vendor folder. 74 | status Lists any packages missing, out-of-date, or modified locally. 75 | fetch Add new or update vendor folder packages from remote repository. 76 | sync Pull packages into vendor folder from remote repository with revisions 77 | from vendor.json file. 78 | migrate Move packages from a legacy tool to the vendor folder with metadata. 79 | get Like "go get" but copies dependencies into a "vendor" folder. 80 | license List discovered licenses for the given status or import paths. 81 | shell Run a "shell" to make multiple sub-commands more efficient for large 82 | projects. 83 | go tool commands that are wrapped: 84 | "+status" package selection may be used with them 85 | fmt, build, install, clean, test, vet, generate, tool 86 | Status Types 87 | +local (l) packages in your project 88 | +external (e) referenced packages in GOPATH but not in current project 89 | +vendor (v) packages in the vendor folder 90 | +std (s) packages in the standard library 91 | +excluded (x) external packages explicitly excluded from vendoring 92 | +unused (u) packages in the vendor folder, but unused 93 | +missing (m) referenced packages but not found 94 | +program (p) package is a main package 95 | +outside +external +missing 96 | +all +all packages 97 | Status can be referenced by their initial letters. 98 | Package specifier 99 | [::][{/...|/^}][@[]] 100 | Ignoring files with build tags, or excluding packages from being vendored: 101 | The "vendor.json" file contains a string field named "ignore". 102 | It may contain a space separated list of build tags to ignore when 103 | listing and copying files. 104 | This list may also contain package prefixes (containing a "/", possibly 105 | as last character) to exclude when copying files in the vendor folder. 106 | If "foo/" appears in this field, then package "foo" and all its sub-packages 107 | ("foo/bar", …) will be excluded (but package "bar/foo" will not). 108 | By default the init command adds the "test" tag to the ignore list. 109 | If using go1.5, ensure GO15VENDOREXPERIMENT=1 is set. 110 | ``` 111 | -------------------------------------------------------------------------------- /oop/struct-method.md: -------------------------------------------------------------------------------- 1 | # 1. 类型系统[类的声明] 2 | 3 | 类型系统: 4 | 5 | - 一组基本类型构成的“基本类型集合”; 6 | - “基本类型集合”上定义的一系列组合、运算、转换方法。 7 | 8 | 类型系统包括基础类型(byte、int、bool、float等);复合类型(数组、结构体、指针等);可以指向任何对象的类型(Any类型,类似Java的Object类型);值语义和引用语义;面向对象类型;接口。Go大多数类型为值语义,可以给任何类型添加方法(包括内置类型,不包括指针类型)。Any类型是空接口即interface{}。 9 | 10 | # 2. 结构体 11 | 12 | **结构体[类属性的声明]** 13 | 14 | struct的功能类似Java的class,可实现嵌套组合(类似继承的功能) 15 | struct实际上就是一种复合类型,只是对类中的属性进行定义赋值,并没有对方法进行定义,方法可以随时定义绑定到该类的对象上,更具灵活性。可利用嵌套组合来实现类似继承的功能避免代码重复。 16 | 17 | ```go 18 | type Rect struct{ //定义矩形类 19 | x,y float64 //类型只包含属性,并没有方法 20 | width,height float64 21 | } 22 | func (r *Rect) Area() float64{ //为Rect类型绑定Area的方法,*Rect为指针引用可以修改传入参数的值 23 | return r.width*r.height //方法归属于类型,不归属于具体的对象,声明该类型的对象即可调用该类型的方法 24 | } 25 | ``` 26 | 27 | # 3. 方法 28 | 29 | 1、为类型添加方法[类方法声明],方法即为有接收者的函数 30 | func (对象名 对象类型) 函数名(参数列表) (返回值列表) 31 | 可随时为某个对象添加方法即为某个方法添加归属对象(receiver),以方法为中心 32 | 在Go语言中没有隐藏的this指针,即显示传递,形参即为this,例如以下的形参为a。 33 | 34 | ```go 35 | type Integer int 36 | func (a Integer) Less(b Integer) bool{ //表示a这个对象定义了Less这个方法,a可以为任意类型 37 | return a,==,>=,<=,!=” 31 | //不同类型不能进行比较例如int和int8,但可以与字面常量(literal)进行比较 32 | var i int32 33 | var j int64 34 | i,j=1,2 35 | if i==j //编译错误,不同类型不能进行比较 36 | if i==1 || j==2 //编译通过,可以与字面常量(literal)进行比较 37 | 38 | //4、位运算 39 | //Go(^x)取反与C语言(~x)不同,其他类似,具体见下表 40 | ``` 41 | 42 | ![这里写图片描述](https://res.cloudinary.com/dqxtn0ick/image/upload/v1510578663/article/golang/basics/bit-operation.png) 43 | 44 | ## 1.3. 浮点型 45 | 46 | ```go 47 | //1、浮点型分为float32(类似C中的float),float64(类似C中的double) 48 | var f1 float32 49 | f1=12 //不加小数点,被推导为整型 50 | f2:=12.0 //加小数点,被推导为float64 51 | f1=float32(f2) //需要执行强制转换 52 | //2、浮点数的比较 53 | //浮点数不是精确的表达方式,不能直接使用“==”来判断是否相等,可以借用math的包math.Fdim 54 | ``` 55 | 56 | ## 1.4. 复数类型 57 | 58 | ```go 59 | //1、复数的表示 60 | var v1 complex64 61 | v1=3.2+12i 62 | //v1 v2 v3 表示为同一个数 63 | v2:=3.2+12i 64 | v3:=complex(3.2,12) 65 | //2、实部与虚部 66 | //z=complex(x,y),通过内置函数实部x=real(z),虚部y=imag(z) 67 | ``` 68 | 69 | ## 1.5. 字符串 70 | 71 | ```go 72 | //声明与赋值 73 | var str string 74 | str="hello world" 75 | ``` 76 | 77 | ![这里写图片描述](https://res.cloudinary.com/dqxtn0ick/image/upload/v1510578664/article/golang/basics/string.png) 78 | 79 | ## 1.6. 字符类型 80 | 81 | ```go 82 | //1、byte,即uint8的别名 83 | //2、rune,即Unicode 84 | ``` 85 | 86 | ## 1.7. 错误类型(error) 87 | 88 | # 2. 复合类型 89 | 90 | ## 2.1. 数组(array) 91 | 92 | 数组表示同一类型数据,数组长度定义后就不可更改,长度是数组内的一个内置常量,可通过len()来获取。 93 | 94 | ```go 95 | //1、创建数组 96 | var array1 [5]int //声明:var 变量名 类型 97 | var array2 [5]int=[5]int{1,2,3,4,5} //初始化 98 | array3:=[5]int{1,2,3,4,5} //直接用“:=”赋值 99 | [3][5]int //二维数组 100 | [3]*float //指针数组 101 | 102 | //2、元素访问 103 | for i,v:=range array{ 104 | //第一个返回值为数组下标,第二个为元素的值 105 | } 106 | 107 | //3、值类型 108 | //数组在Go中作为一个值类型,值类型在赋值和函数参数传递时,只复制副本,因此在函数体中并不能改变数组的内容,需用指针来改变数组的值。 109 | ``` 110 | 111 | ## 2.2. 切片(slice) 112 | 113 | ​ 数组在定义了长度后无法改变,且作为值类型在传递时产生副本,并不能改变数组元素的值。因此切片的功能弥补了这个不足,切片类似指向数组的一个指针。可以抽象为三个变量:指向数组的指针;切片中元素的个数(len函数);已分配的存储空间(cap函数)。 114 | 115 | ```go 116 | //1、创建切片 117 | //a)基于数组创建 118 | var myArray [5]int=[5]{1,2,3,4,5} 119 | var mySlice []int=myArray[first:last] 120 | slice1=myArray[:] //基于数组所有元素创建 121 | slice2=myArray[:3] //基于前三个元素创建 122 | slice3=myArray[3:] //基于第3个元素开始后的所有元素创建 123 | //b)直接创建 124 | slice1:=make([]int,5) //元素初始值为0,初始个数为5 125 | slice2:=make([]int,5,10) //元素初始值为0,初始个数为5,预留个数为10 126 | slice3:=[]int{1,2,3,4,5} //初始化赋值 127 | //c)基于切片创建 128 | oldSlice:=[]int{1,2,3,4,5} 129 | newSlice:=oldSlice[:3] //基于切片创建,不能超过原切片的存储空间(cap函数的值) 130 | 131 | //2、元素遍历 132 | for i,v:=range slice{ 133 | //与数组的方式一致,使用range来遍历 134 | //第一个返回值(i)为索引,第二个为元素的值(v) 135 | } 136 | 137 | //3、动态增减元素 138 | //切片分存储空间(cap)和元素个数(len),当存储空间小于实际的元素个数,会重新分配一块原空间2倍的内存块,并将原数据复制到该内存块中,合理的分配存储空间可以以空间换时间,降低系统开销。 139 | //添加元素 140 | newSlice:=append(oldSlice,1,2,3) //直接将元素加进去,若存储空间不够会按上述方式扩容。 141 | newSlice1:=append(oldSlice1,oldSlice2...) //将oldSlice2的元素打散后加到oldSlice1中,三个点不可省略。 142 | 143 | //4、内容复制 144 | //copy()函数可以复制切片,如果切片大小不一样,按较小的切片元素个数进行复制 145 | slice1:=[]int{1,2,3,4,5} 146 | slice2:=[]int{6,7,8} 147 | copy(slice2,slice1) //只会复制slice1的前三个元素到slice2中 148 | copy(slice1,slice1) //只会复制slice2的三个元素到slice1中的前三个位置 149 | ``` 150 | 151 | ## 2.3. 键值对(map) 152 | 153 | map是一堆键值对的未排序集合。 154 | 155 | ```go 156 | //1、先声明后创建再赋值 157 | var map1 map[键类型] 值类型 158 | //创建 159 | map1=make(map[键类型] 值类型) 160 | map1=make(map[键类型] 值类型 存储空间) 161 | //赋值 162 | map1[key]=value 163 | 164 | // 直接创建 165 | m2 := make(map[string]string) 166 | // 然后赋值 167 | m2["a"] = "aa" 168 | m2["b"] = "bb" 169 | 170 | // 初始化 + 赋值一体化 171 | m3 := map[string]string{ 172 | "a": "aa", 173 | "b": "bb", 174 | } 175 | 176 | //2、元素删除 177 | //delete()函数删除对应key的键值对,如果key不存在,不会报错;如果value为nil,则会抛出异常(panic)。 178 | delete(map1,key) 179 | 180 | //3、元素查找 181 | value,ok:=myMap[key] 182 | if ok{//如果找到 183 | //处理找到的value值 184 | } 185 | 186 | //遍历 187 | for key,value:=range myMap{ 188 | //处理key或value 189 | } 190 | ``` 191 | map可以用来判断一个值是否在切片或数组中。 192 | 193 | ```go 194 | // 判断某个类型(假如为myType)的值是否在切片或数组(假如为myList)中 195 | // 构造一个map,key的类型为myType,value为bool型 196 | myMap := make(map[myType]bool) 197 | myList := []myType{value1, value2} 198 | // 将切片中的值存为map中的key(因为key不能重复),map的value都为true 199 | for _, value := range myList { 200 | myMap[value] = true 201 | } 202 | // 判断valueX是否在myList中,即判断其是否在myMap的key中 203 | if _, ok := myMap[valueX]; ok { 204 | // 如果valueX 在myList中,执行后续操作 205 | } 206 | ``` 207 | 208 | ## 2.4. 指针(pointer) 209 | 具体参考[Go语言指针详解](https://www.huweihuang.com/golang-notes/oop/pointer.html) 210 | 211 | ## 2.5. 结构体(struct) 212 | 具体参考[Go面向对象编程之结构体](https://www.huweihuang.com/golang-notes/oop/struct-method.html) 213 | 214 | ## 2.6. 接口(interface) 215 | 具体参考[Go面向对象编程之接口](https://www.huweihuang.com/golang-notes/oop/interface/interface.html) 216 | 217 | ## 2.7. 通道(chan) 218 | 具体参考[Go并发编程之channel](https://www.huweihuang.com/golang-notes/concurrency/channel.html) 219 | -------------------------------------------------------------------------------- /introduction/package/go-modules.md: -------------------------------------------------------------------------------- 1 | # 1. Go modules简介 2 | 3 | `Go 1.11`版本开始支持`Go modules`方式的依赖包管理功能,官网参考:https://github.com/golang/go/wiki/Modules 。 4 | 5 | 6 | # 2. [go mod的使用](https://github.com/golang/go/wiki/Modules#how-to-use-modules) 7 | 8 | 项目文件如下: 9 | 10 | hello.go 11 | 12 | ```go 13 | package main 14 | 15 | import ( 16 | "fmt" 17 | "rsc.io/quote" 18 | ) 19 | 20 | func main() { 21 | fmt.Println(quote.Hello()) 22 | } 23 | ``` 24 | 25 | 操作记录: 26 | 27 | ```bash 28 | # 安装GO 1.11及以上版本 29 | go version 30 | go version go1.12.5 darwin/amd64 31 | 32 | # 开启module功能 33 | export GO111MODULE=on 34 | 35 | # 进入到项目目录 36 | cd /home/gopath/src/hello 37 | 38 | # 初始化 39 | go mod init 40 | go: creating new go.mod: module hello 41 | 42 | # 编译 43 | go build 44 | go: finding rsc.io/quote v1.5.2 45 | go: downloading rsc.io/quote v1.5.2 46 | go: extracting rsc.io/quote v1.5.2 47 | go: finding rsc.io/sampler v1.3.0 48 | go: finding golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c 49 | go: downloading rsc.io/sampler v1.3.0 50 | go: extracting rsc.io/sampler v1.3.0 51 | go: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c 52 | go: extracting golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c 53 | 54 | # 不添加vendor目录 55 | go mod tidy -v 56 | 57 | # 如果添加vendor目录,则执行vendor参数 58 | go mod vendor -v 59 | 60 | # 命令输出如下: 61 | # golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c 62 | golang.org/x/text/language 63 | golang.org/x/text/internal/tag 64 | # rsc.io/quote v1.5.2 65 | rsc.io/quote 66 | # rsc.io/sampler v1.3.0 67 | rsc.io/sampler 68 | 69 | # 文件目录结构 70 | ./ 71 | ├── go.mod 72 | ├── go.sum 73 | ├── hello # 二进制文件 74 | ├── hello.go 75 | └── vendor 76 | ├── golang.org 77 | ├── modules.txt 78 | └── rsc.io 79 | ``` 80 | 81 | # 3. go mod的相关文件 82 | 83 | ## 3.1. go.mod 84 | 85 | 文件路径:项目根目录下 86 | 87 | ```bash 88 | module hello 89 | 90 | go 1.12 91 | 92 | require rsc.io/quote v1.5.2 93 | ``` 94 | 95 | ## 3.2. go.sum 96 | 97 | 文件路径:项目根目录下 98 | 99 | ```bash 100 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8= 101 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 102 | rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y= 103 | rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0= 104 | rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4= 105 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 106 | ``` 107 | 108 | ## 3.3. modules.txt 109 | 110 | 文件路径:/{project}/vendor/modules.txt 111 | 112 | ```bash 113 | # golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c 114 | golang.org/x/text/language 115 | golang.org/x/text/internal/tag 116 | # rsc.io/quote v1.5.2 117 | rsc.io/quote 118 | # rsc.io/sampler v1.3.0 119 | rsc.io/sampler 120 | ``` 121 | 122 | # 4. go mod的帮助信息 123 | 124 | ```bash 125 | go help mod 126 | Go mod provides access to operations on modules. 127 | 128 | Note that support for modules is built into all the go commands, 129 | not just 'go mod'. For example, day-to-day adding, removing, upgrading, 130 | and downgrading of dependencies should be done using 'go get'. 131 | See 'go help modules' for an overview of module functionality. 132 | 133 | Usage: 134 | 135 | go mod [arguments] 136 | 137 | The commands are: 138 | 139 | download download modules to local cache 140 | edit edit go.mod from tools or scripts 141 | graph print module requirement graph 142 | init initialize new module in current directory 143 | tidy add missing and remove unused modules 144 | vendor make vendored copy of dependencies 145 | verify verify dependencies have expected content 146 | why explain why packages or modules are needed 147 | 148 | Use "go help mod " for more information about a command. 149 | ``` 150 | 151 | ## 4.1. go mod init 152 | 153 | ```bash 154 | go help mod init 155 | usage: go mod init [module] 156 | 157 | Init initializes and writes a new go.mod to the current directory, 158 | in effect creating a new module rooted at the current directory. 159 | The file go.mod must not already exist. 160 | If possible, init will guess the module path from import comments 161 | (see 'go help importpath') or from version control configuration. 162 | To override this guess, supply the module path as an argument. 163 | ``` 164 | 165 | ## 4.2. go mod tidy 166 | 167 | ``` 168 | usage: go mod tidy [-v] 169 | 170 | Tidy makes sure go.mod matches the source code in the module. 171 | It adds any missing modules necessary to build the current module's 172 | packages and dependencies, and it removes unused modules that 173 | don't provide any relevant packages. It also adds any missing entries 174 | to go.sum and removes any unnecessary ones. 175 | 176 | The -v flag causes tidy to print information about removed modules 177 | to standard error. 178 | ``` 179 | 180 | ## 4.3. go mod vendor 181 | 182 | ```bash 183 | go help mod vendor 184 | usage: go mod vendor [-v] 185 | 186 | Vendor resets the main module's vendor directory to include all packages 187 | needed to build and test all the main module's packages. 188 | It does not include test code for vendored packages. 189 | 190 | The -v flag causes vendor to print the names of vendored 191 | modules and packages to standard error. 192 | ``` 193 | 194 | 195 | 参考: 196 | 197 | - https://github.com/golang/go/wiki/Modules 198 | - https://blog.golang.org/modules2019 199 | - https://blog.golang.org/using-go-modules 200 | -------------------------------------------------------------------------------- /web/beego/beego-log.md: -------------------------------------------------------------------------------- 1 | # 1. 使用入门 2 | 3 | beego 的日志处理是基于 logs 模块搭建的,内置了一个变量 `BeeLogger`,默认已经是 `logs.BeeLogger` 类型,初始化了 console,也就是默认输出到 `console。` 4 | 5 | ```go 6 | beego.Emergency("this is emergency") 7 | beego.Alert("this is alert") 8 | beego.Critical("this is critical") 9 | beego.Error("this is error") 10 | beego.Warning("this is warning") 11 | beego.Notice("this is notice") 12 | beego.Informational("this is informational") 13 | beego.Debug("this is debug") 14 | ``` 15 | 16 | # 2. 设置输出 17 | 18 | 我们的程序往往期望把信息输出到 log 中,现在设置输出到文件很方便,如下所示: 19 | 20 | ```go 21 | beego.SetLogger("file", `{"filename":"logs/test.log"}`) 22 | ``` 23 | 24 | 这个默认情况就会同时输出到两个地方,一个 console,一个 file,如果只想输出到文件,就需要调用删除操作: 25 | 26 | ```go 27 | beego.BeeLogger.DelLogger("console") 28 | ``` 29 | 30 | # 3. 设置级别 31 | 32 | ```go 33 | LevelEmergency 34 | LevelAlert 35 | LevelCritical 36 | LevelError 37 | LevelWarning 38 | LevelNotice 39 | LevelInformational 40 | LevelDebug 41 | ``` 42 | 43 | 级别依次降低,默认全部打印,但是一般我们在部署环境,可以通过设置级别设置日志级别: 44 | 45 | ```go 46 | beego.SetLevel(beego.LevelInformational) 47 | ``` 48 | 49 | # 4. 输出文件名和行号 50 | 51 | 日志默认不输出调用的文件名和文件行号,如果你期望输出调用的文件名和文件行号,可以如下设置 52 | 53 | ```go 54 | beego.SetLogFuncCall(true) 55 | ``` 56 | 57 | 开启传入参数 true, 关闭传入参数 false, 默认是关闭的。 58 | 59 | # 5. beego/logs模块的使用 60 | 61 | 是一个用来处理日志的库,目前支持的引擎有 file、console、net、smtp,可以通过如下方式进行安装: 62 | 63 | ```go 64 | go get github.com/astaxie/beego/logs 65 | ``` 66 | 67 | ## 5.1. 通用方式 68 | 69 | 首先引入包: 70 | 71 | ```go 72 | import ( "github.com/astaxie/beego/logs" ) 73 | ``` 74 | 75 | 然后添加输出引擎(log 支持同时输出到多个引擎),这里我们以 console 为例,第一个参数是引擎名(包括:console、file、conn、smtp、es、multifile) 76 | 77 | ```go 78 | logs.SetLogger("console") 79 | ``` 80 | 81 | 添加输出引擎也支持第二个参数,用来表示配置信息,详细的配置请看下面介绍: 82 | 83 | ```go 84 | logs.SetLogger(logs.AdapterFile,`{"filename":"project.log","level":7,"maxlines":0,"maxsize":0,"daily":true,"maxdays":10}`) 85 | ``` 86 | 87 | 示例: 88 | 89 | ```go 90 | package main 91 | 92 | import ( 93 | "github.com/astaxie/beego/logs" 94 | ) 95 | 96 | func main() { 97 | //an official log.Logger 98 | l := logs.GetLogger() 99 | l.Println("this is a message of http") 100 | //an official log.Logger with prefix ORM 101 | logs.GetLogger("ORM").Println("this is a message of orm") 102 | 103 | logs.Debug("my book is bought in the year of ", 2016) 104 | logs.Info("this %s cat is %v years old", "yellow", 3) 105 | logs.Warn("json is a type of kv like", map[string]int{"key": 2016}) 106 | logs.Error(1024, "is a very", "good game") 107 | logs.Critical("oh,crash") 108 | } 109 | ``` 110 | 111 | ## 5.2. 输出文件名和行号 112 | 113 | 日志默认不输出调用的文件名和文件行号,如果你期望输出调用的文件名和文件行号,可以如下设置 114 | 115 | ```go 116 | logs.EnableFuncCallDepth(true) 117 | ``` 118 | 119 | 开启传入参数 true,关闭传入参数 false,默认是关闭的. 120 | 121 | 如果你的应用自己封装了调用 log 包,那么需要设置 SetLogFuncCallDepth,默认是 2,也就是直接调用的层级,如果你封装了多层,那么需要根据自己的需求进行调整. 122 | 123 | ```go 124 | logs.SetLogFuncCallDepth(3) 125 | ``` 126 | 127 | ## 5.3. 异步输出日志 128 | 129 | 为了提升性能, 可以设置异步输出: 130 | 131 | ```go 132 | logs.Async() 133 | ``` 134 | 135 | 异步输出允许设置缓冲 chan 的大小 136 | 137 | ```go 138 | logs.Async(1e3) 139 | ``` 140 | 141 | ## 5.4. 引擎配置 142 | 143 | - console 144 | 145 | 可以设置输出的级别,或者不设置保持默认,默认输出到 `os.Stdout`: 146 | 147 | ```go 148 | logs.SetLogger(logs.AdapterConsole, `{"level":1}`) 149 | ``` 150 | 151 | - file 152 | 153 | 设置的例子如下所示: 154 | 155 | ```go 156 | logs.SetLogger(logs.AdapterFile, `{"filename":"test.log"}`) 157 | ``` 158 | 159 | 主要的参数如下说明: 160 | 161 | - filename 保存的文件名 162 | - maxlines 每个文件保存的最大行数,默认值 1000000 163 | - maxsize 每个文件保存的最大尺寸,默认值是 1 << 28, //256 MB 164 | - daily 是否按照每天 logrotate,默认是 true 165 | - maxdays 文件最多保存多少天,默认保存 7 天 166 | - rotate 是否开启 logrotate,默认是 true 167 | - level 日志保存的时候的级别,默认是 Trace 级别 168 | - perm 日志文件权限 169 | 170 | - multifile 171 | 172 | 设置的例子如下所示: 173 | 174 | ```go 175 | logs.SetLogger(logs.AdapterMultiFile, ``{"filename":"test.log","separate":["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"]}``) 176 | ``` 177 | 178 | 主要的参数如下说明(除 separate 外,均与file相同): 179 | 180 | - filename 保存的文件名 181 | - maxlines 每个文件保存的最大行数,默认值 1000000 182 | - maxsize 每个文件保存的最大尺寸,默认值是 1 << 28, //256 MB 183 | - daily 是否按照每天 logrotate,默认是 true 184 | - maxdays 文件最多保存多少天,默认保存 7 天 185 | - rotate 是否开启 logrotate,默认是 true 186 | - level 日志保存的时候的级别,默认是 Trace 级别 187 | - perm 日志文件权限 188 | - separate 需要单独写入文件的日志级别,设置后命名类似 test.error.log 189 | 190 | - conn 191 | 192 | 网络输出,设置的例子如下所示: 193 | 194 | ```go 195 | logs.SetLogger(logs.AdapterConn, `{"net":"tcp","addr":":7020"}`) 196 | ``` 197 | 198 | 主要的参数说明如下: 199 | 200 | - reconnectOnMsg 是否每次链接都重新打开链接,默认是 false 201 | - reconnect 是否自动重新链接地址,默认是 false 202 | - net 发开网络链接的方式,可以使用 tcp、unix、udp 等 203 | - addr 网络链接的地址 204 | - level 日志保存的时候的级别,默认是 Trace 级别 205 | 206 | - smtp 207 | 208 | 邮件发送,设置的例子如下所示: 209 | 210 | ```go 211 | logs.SetLogger(logs.AdapterMail, `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`) 212 | ``` 213 | 214 | 主要的参数说明如下: 215 | 216 | - username smtp 验证的用户名 217 | - password smtp 验证密码 218 | - host 发送的邮箱地址 219 | - sendTos 邮件需要发送的人,支持多个 220 | - subject 发送邮件的标题,默认是 `Diagnostic message from server` 221 | - level 日志发送的级别,默认是 Trace 级别 222 | 223 | - ElasticSearch 224 | 225 | 输出到 ElasticSearch: 226 | 227 | ```go 228 | logs.SetLogger(logs.AdapterEs, `{"dsn":"http://localhost:9200/","level":1}` 229 | ``` 230 | 231 | 参考文章: 232 | 233 | - https://beego.me/docs/mvc/controller/logs.md 234 | - https://beego.me/docs/module/logs.md 235 | -------------------------------------------------------------------------------- /oop/interface/interface.md: -------------------------------------------------------------------------------- 1 | # 1. 接口[多态] 2 | 3 | ​多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。 4 | 5 | 简而言之,就是允许将子类类型的指针赋值给父类类型的指针。 6 | 7 | 即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。多态分为编译时多态(静态多态)和运行时多态(动态多态),编译时多态一般通过方法重载实现,运行时多态一般通过方法重写实现。 8 | 9 | ## 1.1 接口概念 10 | 11 | >接口类型可以看作是类型系统中一种特殊的类型,而实例就是实现了该接口的具体结构体类型。 12 | > 13 | >接口类型与实现了该接口的结构体对象之间的关系好比变量类型与变量之间的关系。 14 | 15 | ​ **接口即一组方法定义的集合,定义了对象的一组行为**,由具体的类型实例实现具体的方法。换句话说,**一个接口就是定义(规范或约束),而方法就是实现**,接口的作用应该是**将定义与实现分离**,降低耦合度。习惯用“er”结尾来命名,例如“Reader”。**接口与对象的关系是多对多**,即一个对象可以实现多个接口,一个接口也可以被多个对象实现。 16 | 17 | ​ 接口是Go语言整个类型系统的基石,其他语言的接口是不同组件之间的契约的存在,对契约的实现是强制性的,必须显式声明实现了该接口,这类接口称之为“侵入式接口”。而Go语言的接口是隐式存在,**只要实现了该接口的所有函数则代表已经实现了该接口,并不需要显式的接口声明**。 18 | 19 | - **接口的比喻** 20 | 21 | ​ 你的电脑上只有一个USB接口。这个USB接口可以接MP3,数码相机,摄像头,鼠标,键盘等。。。所有的上述硬件都可以公用这个接口,有很好的扩展性,**该USB接口定义了一种规范,只要实现了该规范,就可以将不同的设备接入电脑,而设备的改变并不会对电脑本身有什么影响(低耦合)**。 22 | 23 | - **面向接口编程** 24 | 25 | ​ **接口表示调用者和设计者的一种约定**,在多人合作开发同一个项目时,事先定义好相互调用的接口可以大大提高开发的效率。接口是用类来实现的,实现接口的类必须严格按照接口的声明来实现接口提供的所有功能。有了接口,就可以在不影响现有接口声明的情况下,修改接口的内部实现,从而使兼容性问题最小化。 26 | 27 | ​ **面向接口编程可以分为三方面:制定者(或者叫协调者),实现者(或者叫生产者),调用者(或者叫消费者)**。 28 | 29 | ​ 当其他设计者调用了接口后,就不能再随意更改接口的定义,否则项目开发者事先的约定就失去了意义。但是可以在类中修改相应的代码,完成需要改动的内容。 30 | 31 | ## 1.2 非侵入式接口 32 | 33 | 非侵入式接口:一个类只需要实现了接口要求的所有函数就表示实现了该接口,并不需要显式声明 34 | 35 | ```go 36 | type File struct{ 37 | //类的属性 38 | } 39 | //File类的方法 40 | func (f *File) Read(buf []byte) (n int,err error) 41 | func (f *File) Write(buf []byte) (n int,err error) 42 | func (f *File) Seek(off int64,whence int) (pos int64,err error) 43 | func (f *File) Close() error 44 | //接口1:IFile 45 | type IFile interface{ 46 | Read(buf []byte) (n int,err error) 47 | Write(buf []byte) (n int,err error) 48 | Seek(off int64,whence int) (pos int64,err error) 49 | Close() error 50 | } 51 | //接口2:IReader 52 | type IReader interface{ 53 | Read(buf []byte) (n int,err error) 54 | } 55 | //接口赋值,File类实现了IFile和IReader接口,即接口所包含的所有方法 56 | var file1 IFile = new(File) 57 | var file2 IReader = new(File) 58 | ``` 59 | 60 | ## 1.3 接口赋值 61 | 62 | 只要类实现了该接口的所有方法,即可将该类赋值给这个接口,接口主要用于多态化方法。即对接口定义的方法,不同的实现方式。 63 | 64 | 接口赋值: 65 | **1)将对象实例赋值给接口** 66 | 67 | ```go 68 | type IUSB interface{ 69 | //定义IUSB的接口方法 70 | } 71 | //方法定义在类外,绑定该类,以下为方便,备注写在类中 72 | type MP3 struct{ 73 | //实现IUSB的接口,具体实现方式是MP3的方法 74 | } 75 | type Mouse struct{ 76 | //实现IUSB的接口,具体实现方式是Mouse的方法 77 | } 78 | //接口赋值给具体的对象实例MP3 79 | var usb IUSB =new(MP3) 80 | usb.Connect() 81 | usb.Close() 82 | //接口赋值给具体的对象实例Mouse 83 | var usb IUSB =new(Mouse) 84 | usb.Connect() 85 | usb.Close() 86 | ``` 87 | 88 | **2)将接口赋值给另一个接口** 89 | 90 | 1. 只要两个接口拥有相同的方法列表(与次序无关),即是两个相同的接口,可以相互赋值 91 | 2. 接口赋值只需要接口A的方法列表是接口B的子集(即假设接口A中定义的所有方法,都在接口B中有定义),那么B接口的实例可以赋值给A的对象。反之不成立,即子接口B包含了父接口A,因此可以将子接口的实例赋值给父接口。 92 | 3. 即子接口实例实现了子接口的所有方法,而父接口的方法列表是子接口的子集,则子接口实例自然实现了父接口的所有方法,因此可以将子接口实例赋值给父接口。 93 | 94 | ```go 95 | type Writer interface{ //父接口 96 | Write(buf []byte) (n int,err error) 97 | } 98 | type ReadWriter interface{ //子接口 99 | Read(buf []byte) (n int,err error) 100 | Write(buf []byte) (n int,err error) 101 | } 102 | var file1 ReadWriter=new(File) //子接口实例 103 | var file2 Writer=file1 //子接口实例赋值给父接口 104 | ``` 105 | 106 | ## 1.4 接口查询 107 | 108 | 若要在 switch 外判断一个接口类型是否实现了某个接口,可以使用“逗号 ok ”。 109 | 110 | value, ok := Interfacevariable.(implementType) 111 | 112 | 其中 Interfacevariable 是接口变量(接口值),implementType 为实现此接口的类型,value 返回接口变量实际类型变量的值,如果该类型实现了此接口返回 true。 113 | 114 | ```go 115 | //判断file1接口指向的对象实例是否是File类型 116 | var file1 Writer=... 117 | if file5,ok:=file1.(File);ok{ 118 | ... 119 | } 120 | ``` 121 | 122 | ## 1.5 接口类型查询 123 | 124 | 在 Go 中,要判断传递给接口值的变量类型,可以在使用 type switch 得到。(type)只能在 switch 中使用。 125 | 126 | ```go 127 | // 另一个实现了 I 接口的 R 类型 128 | type R struct { i int } 129 | func (p *R) Get() int { return p.i } 130 | func (p *R) Put(v int) { p.i = v } 131 | 132 | func f(p I) { 133 | switch t := p.(type) { // 判断传递给 p 的实际类型 134 | case *S: // 指向 S 的指针类型 135 | case *R: // 指向 R 的指针类型 136 | case S: // S 类型 137 | case R: // R 类型 138 | default: //实现了 I 接口的其他类型 139 | } 140 | } 141 | ``` 142 | 143 | ## 1.6 接口组合 144 | 145 | ```go 146 | //接口组合类似类型组合,只不过只包含方法,不包含成员变量 147 | type ReadWriter interface{ //接口组合,避免代码重复 148 | Reader //接口Reader 149 | Writer //接口Writer 150 | } 151 | ``` 152 | 153 | ## 1.7 Any类型[空接口] 154 | 155 | 每种类型都能匹配到空接口:interface{}。空接口类型对方法没有任何约束(因为没有方法),它能包含任意类型,也可以实现到其他接口类型的转换。如果传递给该接口的类型变量实现了转换后的接口则可以正常运行,否则出现运行时错误。 156 | 157 | ```go 158 | //interface{}即为可以指向任何对象的Any类型,类似Java中的Object类 159 | var v1 interface{}=struct{X int}{1} 160 | var v2 interface{}="abc" 161 | 162 | func DoSomething(v interface{}) { //该函数可以接收任何类型的参数,因为任何类型都实现了空接口 163 | // ... 164 | } 165 | ``` 166 | 167 | ## 1.8 接口的代码示例 168 | 169 | ```go 170 | //接口animal 171 | type Animal interface { 172 | Speak() string 173 | } 174 | //Dog类实现animal接口 175 | type Dog struct { 176 | } 177 | 178 | func (d Dog) Speak() string { 179 | return "Woof!" 180 | } 181 | //Cat类实现animal接口 182 | type Cat struct { 183 | } 184 | 185 | func (c Cat) Speak() string { 186 | return "Meow!" 187 | } 188 | //Llama实现animal接口 189 | type Llama struct { 190 | } 191 | 192 | func (l Llama) Speak() string { 193 | return "?????" 194 | } 195 | //JavaProgrammer实现animal接口 196 | type JavaProgrammer struct { 197 | } 198 | 199 | func (j JavaProgrammer) Speak() string { 200 | return "Design patterns!" 201 | } 202 | //主函数 203 | func main() { 204 | animals := []Animal{Dog{}, Cat{}, Llama{}, JavaProgrammer{}} //利用接口实现多态 205 | for _, animal := range animals { 206 | fmt.Println(animal.Speak()) //打印不同实现该接口的类的方法返回值 207 | } 208 | } 209 | ``` 210 | 211 | -------------------------------------------------------------------------------- /web/beego/beego-project.md: -------------------------------------------------------------------------------- 1 | # beego项目逻辑 2 | 3 | # 1. 路由设置 4 | 5 | ## 1.1. beego.Router 6 | 7 | 入口文件main.go 8 | 9 | ```go 10 | package main 11 | 12 | import ( 13 | _ "quickstart/routers" 14 | "github.com/astaxie/beego" 15 | ) 16 | 17 | func main() { 18 | beego.Run() 19 | } 20 | ``` 21 | 22 | go中导入包中init函数的执行逻辑 23 | 24 | ![init](https://res.cloudinary.com/dqxtn0ick/image/upload/v1510578706/article/golang/beego/init.png) 25 | 26 | `_ "quickstart/routers"`,包只引入执行了里面的init函数 27 | 28 | ```go 29 | package routers 30 | 31 | import ( 32 | "quickstart/controllers" 33 | "github.com/astaxie/beego" 34 | ) 35 | 36 | func init() { 37 | beego.Router("/", &controllers.MainController{}) 38 | } 39 | ``` 40 | 41 | 路由包里执行了路由注册`beego.Router`, 这个函数的功能是映射URL到controller,第一个参数是URL(用户请求的地址),这里是 `/`,也就是访问的不带任何参数的URL,第二个参数是对应的 Controller,就是把请求分发到那个控制器来执行相应的逻辑。 42 | 43 | ## 1.2. beego.Run 44 | 45 | - 解析配置文件 46 | 47 | beego 会自动解析在 conf 目录下面的配置文件 `app.conf`,通过修改配置文件相关的属性,我们可以定义:开启的端口,是否开启 session,应用名称等信息。 48 | 49 | - 执行用户的hookfunc 50 | 51 | beego会执行用户注册的hookfunc,默认的已经存在了注册mime,用户可以通过函数`AddAPPStartHook`注册自己的启动函数。 52 | 53 | - 是否开启 session 54 | 55 | 会根据上面配置文件的分析之后判断是否开启 session,如果开启的话就初始化全局的 session。 56 | 57 | - 是否编译模板 58 | 59 | beego 会在启动的时候根据配置把 views 目录下的所有模板进行预编译,然后存在 map 里面,这样可以有效的提高模板运行的效率,无需进行多次编译。 60 | 61 | - 是否开启文档功能 62 | 63 | 根据EnableDocs配置判断是否开启内置的文档路由功能 64 | 65 | - 是否启动管理模块 66 | 67 | beego 目前做了一个很酷的模块,应用内监控模块,会在 8088 端口做一个内部监听,我们可以通过这个端口查询到 QPS、CPU、内存、GC、goroutine、thread 等统计信息。 68 | 69 | - 监听服务端口 70 | 71 | 这是最后一步也就是我们看到的访问 8080 看到的网页端口,内部其实调用了 `ListenAndServe`,充分利用了 goroutine 的优势,一旦 run 起来之后,我们的服务就监听在两个端口了,一个服务端口 8080 作为对外服务,另一个 8088 端口实行对内监控。 72 | 73 | # 2. controller 逻辑 74 | 75 | ```go 76 | package controllers 77 | 78 | import ( 79 | "github.com/astaxie/beego" 80 | ) 81 | 82 | type MainController struct { 83 | beego.Controller 84 | } 85 | 86 | func (this *MainController) Get() { 87 | this.Data["Website"] = "beego.me" 88 | this.Data["Email"] = "astaxie@gmail.com" 89 | this.TplName = "index.tpl" 90 | } 91 | ``` 92 | 93 | 1、声明了一个控制器 `MainController`,这个控制器里面内嵌了 `beego.Controller`,即Go 的嵌入方式,也就是 `MainController` 自动拥有了所有 `beego.Controller` 的方法。而 `beego.Controller` 拥有很多方法,其中包括 `Init`、`Prepare`、`Post`、`Get`、`Delete`、`Head`等方法。可以通过重写的方式来实现这些方法,以上例子重写了 `Get` 方法。 94 | 95 | 2、beego 是一个 RESTful 的框架,请求默认是执行对应 `req.Method` 的方法。例如浏览器的是 `GET` 请求,那么默认就会执行 `MainController` 下的 `Get` 方法。(用户可以改变这个行为,通过注册自定义的函数名)。 96 | 97 | 3、获取数据,赋值到 `this.Data` 中,这是一个用来存储输出数据的 map。 98 | 99 | 4、渲染模板,`this.TplName` 就是需要渲染的模板,这里指定了 `index.tpl`,如果用户不设置该参数,那么默认会去到模板目录的 `Controller/<方法名>.tpl` 查找,例如上面的方法会去 `maincontroller/get.tpl`(文件、文件夹必须小写)。用户设置了模板之后系统会自动的调用 `Render` 函数(这个函数是在 beego.Controller 中实现的),所以无需用户自己来调用渲染。 100 | 101 | 5、如果不使用模板可以直接输出: 102 | 103 | ```go 104 | func (this *MainController) Get() { 105 | this.Ctx.WriteString("hello") 106 | } 107 | ``` 108 | 109 | # 3. model逻辑 110 | 111 | model一般用来处理数据库操作,如果逻辑中存在可以复用的部分就可以抽象成一个model。 112 | 113 | ```go 114 | package models 115 | 116 | import ( 117 | "loggo/utils" 118 | "path/filepath" 119 | "strconv" 120 | "strings" 121 | ) 122 | 123 | var ( 124 | NotPV []string = []string{"css", "js", "class", "gif", "jpg", "jpeg", "png", "bmp", "ico", "rss", "xml", "swf"} 125 | ) 126 | 127 | const big = 0xFFFFFF 128 | 129 | func LogPV(urls string) bool { 130 | ext := filepath.Ext(urls) 131 | if ext == "" { 132 | return true 133 | } 134 | for _, v := range NotPV { 135 | if v == strings.ToLower(ext) { 136 | return false 137 | } 138 | } 139 | return true 140 | } 141 | ``` 142 | 143 | # 4. view逻辑 144 | 145 | `Controller中的this.TplName = "index.tpl"`,设置显示的模板文件,默认支持 `tpl` 和 `html` 的后缀名,如果想设置其他后缀你可以调用 `beego.AddTemplateExt` 接口设置。beego 采用了 Go 语言默认的模板引擎,和 Go 的模板语法一样。 146 | 147 | ```html 148 | 149 | 150 | 151 | 152 | Beego 153 | 154 | 155 | 156 | 157 |
    158 |
    159 |
    160 |
    161 |

    Welcome to Beego!

    162 |

    163 | Beego is a simple & powerful Go web framework which is inspired by tornado and sinatra. 164 |
    165 | Official website: {{.Website}} 166 |
    167 | Contact me: {{.Email}} 168 |

    169 |
    170 |
    171 |
    172 |
    173 | 174 | 175 | ``` 176 | 177 | Controller 里面把数据赋值给了 data(map 类型),然后在模板中就直接通过 key 访问 `.Website` 和 `.Email` 。这样就做到了数据的输出。 178 | 179 | # 5. 静态文件 180 | 181 | 网页往往包含了很多的静态文件,包括图片、JS、CSS 等 182 | 183 | ```bash 184 | ├── static 185 | │ ├── css 186 | │ ├── img 187 | │ └── js 188 | ``` 189 | 190 | beego 默认注册了 static 目录为静态处理的目录,注册样式:URL 前缀和映射的目录(在/main.go文件中beego.Run()之前加入): 191 | 192 | ```go 193 | StaticDir["/static"] = "static" 194 | ``` 195 | 196 | 用户可以设置多个静态文件处理目录,例如你有多个文件下载目录 download1、download2,你可以这样映射(在/main.go文件中beego.Run()之前加入): 197 | 198 | ```go 199 | beego.SetStaticPath("/down1", "download1") beego.SetStaticPath("/down2", "download2") 200 | ``` 201 | 202 | 这样用户访问 URL `http://localhost:8080/down1/123.txt` 则会请求 download1 目录下的 123.txt 文件。 203 | 204 | 205 | 参考: 206 | 207 | - https://beego.me/docs/quickstart/router.md 208 | - https://beego.me/docs/quickstart/controller.md 209 | - https://beego.me/docs/quickstart/model.md 210 | - https://beego.me/docs/quickstart/view.md 211 | - https://beego.me/docs/quickstart/static.md 212 | -------------------------------------------------------------------------------- /oop/interface/client-go-interface.md: -------------------------------------------------------------------------------- 1 | # 2. client-go中接口的使用分析 2 | 3 | 以下以`k8s.io/client-go/kubernetes/typed/core/v1/pod.go`的pod对象做分析。 4 | 5 | ## 2.1 接口设计与定义 6 | 7 | ## 2.1.1 接口组合 8 | 9 | ```go 10 | // PodsGetter has a method to return a PodInterface. 11 | // A group's client should implement this interface. 12 | type PodsGetter interface { 13 | Pods(namespace string) PodInterface 14 | } 15 | ``` 16 | 17 | ## 2.1.2 接口定义 {#index1} 18 | 19 | ```go 20 | // PodInterface has methods to work with Pod resources. 21 | type PodInterface interface { 22 | Create(*v1.Pod) (*v1.Pod, error) 23 | Update(*v1.Pod) (*v1.Pod, error) 24 | UpdateStatus(*v1.Pod) (*v1.Pod, error) 25 | Delete(name string, options *meta_v1.DeleteOptions) error 26 | DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error 27 | Get(name string, options meta_v1.GetOptions) (*v1.Pod, error) 28 | List(opts meta_v1.ListOptions) (*v1.PodList, error) 29 | Watch(opts meta_v1.ListOptions) (watch.Interface, error) 30 | Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.Pod, err error) 31 | PodExpansion 32 | } 33 | ``` 34 | 35 | `PodInterface`接口定义了pod对象所使用的方法,一般为增删改查等。其他kubernetes资源对象的接口定义类似,区别在于入参和出参与对象相关。例如`Create(*v1.Pod) (*v1.Pod, error)`方法定义的入参出参为`*v1.Pod`。如果要实现该接口,即实现该接口的所有方法。 36 | 37 | ## 2.2 接口的实现 {#index2} 38 | 39 | ## 2.2.1 结构体的定义 40 | 41 | ```go 42 | // pods implements PodInterface 43 | type pods struct { 44 | client rest.Interface 45 | ns string 46 | } 47 | ``` 48 | 49 | ## 2.2.2 new函数[构造函数] 50 | 51 | ```go 52 | // newPods returns a Pods 53 | func newPods(c *CoreV1Client, namespace string) *pods { 54 | return &pods{ 55 | client: c.RESTClient(), 56 | ns: namespace, 57 | } 58 | } 59 | ``` 60 | 61 | ## 2.2.3 方法的实现 62 | 63 | **Get** 64 | 65 | ```go 66 | // Get takes name of the pod, and returns the corresponding pod object, and an error if there is any. 67 | func (c *pods) Get(name string, options meta_v1.GetOptions) (result *v1.Pod, err error) { 68 | result = &v1.Pod{} 69 | err = c.client.Get(). 70 | Namespace(c.ns). 71 | Resource("pods"). 72 | Name(name). 73 | VersionedParams(&options, scheme.ParameterCodec). 74 | Do(). 75 | Into(result) 76 | return 77 | } 78 | ``` 79 | 80 | **List** 81 | 82 | ```go 83 | // List takes label and field selectors, and returns the list of Pods that match those selectors. 84 | func (c *pods) List(opts meta_v1.ListOptions) (result *v1.PodList, err error) { 85 | result = &v1.PodList{} 86 | err = c.client.Get(). 87 | Namespace(c.ns). 88 | Resource("pods"). 89 | VersionedParams(&opts, scheme.ParameterCodec). 90 | Do(). 91 | Into(result) 92 | return 93 | } 94 | ``` 95 | 96 | **Create** 97 | 98 | ```go 99 | // Create takes the representation of a pod and creates it. Returns the server's representation of the pod, and an error, if there is any. 100 | func (c *pods) Create(pod *v1.Pod) (result *v1.Pod, err error) { 101 | result = &v1.Pod{} 102 | err = c.client.Post(). 103 | Namespace(c.ns). 104 | Resource("pods"). 105 | Body(pod). 106 | Do(). 107 | Into(result) 108 | return 109 | } 110 | ``` 111 | 112 | **Update** 113 | 114 | ```go 115 | // Update takes the representation of a pod and updates it. Returns the server's representation of the pod, and an error, if there is any. 116 | func (c *pods) Update(pod *v1.Pod) (result *v1.Pod, err error) { 117 | result = &v1.Pod{} 118 | err = c.client.Put(). 119 | Namespace(c.ns). 120 | Resource("pods"). 121 | Name(pod.Name). 122 | Body(pod). 123 | Do(). 124 | Into(result) 125 | return 126 | } 127 | ``` 128 | 129 | **Delete** 130 | 131 | ```go 132 | // Delete takes name of the pod and deletes it. Returns an error if one occurs. 133 | func (c *pods) Delete(name string, options *meta_v1.DeleteOptions) error { 134 | return c.client.Delete(). 135 | Namespace(c.ns). 136 | Resource("pods"). 137 | Name(name). 138 | Body(options). 139 | Do(). 140 | Error() 141 | } 142 | ``` 143 | 144 | ## 2.3 接口的调用 145 | 146 | 示例: 147 | 148 | ```go 149 | // 创建clientset实例 150 | clientset, err := kubernetes.NewForConfig(config) 151 | // 具体的调用 152 | pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{}) 153 | ``` 154 | 155 | clientset实现了接口`Interface`,`Interface`是个接口组合,包含各个client的接口类型。例如`CoreV1()`方法对应的接口类型是`CoreV1Interface`。 156 | 157 | 以下是clientset的`CoreV1()`方法实现: 158 | 159 | ```go 160 | // CoreV1 retrieves the CoreV1Client 161 | func (c *Clientset) CoreV1() corev1.CoreV1Interface { 162 | return c.coreV1 163 | } 164 | ``` 165 | 166 | 该方法可以理解为是一个`构造函数`。构造函数的返回值类型是一个接口类型`CoreV1Interface`,而return的返回值是实现了该接口类型的结构体对象`c.coreV1`。 167 | 168 | **接口类型是一种特殊的类型,接口类型与结构体对象之间的关系好比变量类型与变量之间的关系**。其中的结构体对象必须实现了该接口类型的所有方法。 169 | 170 | 所以clientset的`CoreV1()`方法实现是返回一个`CoreV1Client`结构体对象。该结构体对象实现了`CoreV1Interface`接口,该接口也是一个接口组合。 171 | 172 | ```go 173 | type CoreV1Interface interface { 174 | RESTClient() rest.Interface 175 | ComponentStatusesGetter 176 | ConfigMapsGetter 177 | EndpointsGetter 178 | EventsGetter 179 | LimitRangesGetter 180 | NamespacesGetter 181 | NodesGetter 182 | PersistentVolumesGetter 183 | PersistentVolumeClaimsGetter 184 | PodsGetter 185 | PodTemplatesGetter 186 | ReplicationControllersGetter 187 | ResourceQuotasGetter 188 | SecretsGetter 189 | ServicesGetter 190 | ServiceAccountsGetter 191 | } 192 | ``` 193 | 194 | 而实现的`Pods()`方法是其中的`PodsGetter`接口。 195 | 196 | `Pods()`同`CoreV1()`一样是个构造函数,构造函数的返回值类型是`PodInterface`接口,返回值是实现了`PodInterface`接口的`pods`结构体对象。 197 | 198 | ```go 199 | func (c *CoreV1Client) Pods(namespace string) PodInterface { 200 | return newPods(c, namespace) 201 | } 202 | ``` 203 | 204 | 而`PodInterface`接口定义参考[接口定义](#index1),`pods`对象实现了`PodInterface`接口的方法,具体参考[接口的实现](#index2)。 205 | 206 | 最终调用了`pods`对象的`List()`方法。 207 | 208 | ```go 209 | pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{}) 210 | ``` 211 | 212 | 即以上代码就是不断调用实现了某接口的结构体对象的构造函数,生成具体的结构体对象,再调用结构体对象的某个具体方法。 213 | -------------------------------------------------------------------------------- /oop/pointer.md: -------------------------------------------------------------------------------- 1 | # 1. 指针的概念 2 | 3 | | 概念 | 说明 | 4 | | ---- | --------------------------------- | 5 | | 变量 | 是一种占位符,用于引用计算机的内存地址。可理解为内存地址的标签 | 6 | | 指针 | 表示内存地址,表示地址的指向。指针是一个指向另一个变量内存地址的值 | 7 | | & | 取地址符,例如:{指针}:=&{变量} | 8 | | * | 取值符,例如:{变量}:=*{指针} | 9 | 10 | # 2. 内存地址说明 11 | 12 | ## 2.1. 内存定义 13 | 计算机的内存 RAM 可以把它想象成一些有序的盒子,一个接一个的排成一排,每一个盒子或者单元格都被一个唯一的数字标记依次递增,这个数字就是该单元格的地址,也就是内存的地址。 14 | ![什么是内存](https://res.cloudinary.com/dqxtn0ick/image/upload/v1510578751/article/golang/pointer/memory.png) 15 | 16 | **硬件角度**:内存是CPU沟通的桥梁,程序运行在内存中。 17 | 18 | **逻辑角度**:内存是一块具备随机访问能力,支持读写操作,用来存放程序及程序运行中产生的数据的区域。 19 | 20 | | 概念 | 比喻 | 21 | | ---- | ----------------------------- | 22 | | 内存 | 一层楼层 | 23 | | 内存块 | 楼层中的一个房间 | 24 | | 变量名 | 房间的标签,例如:总经理室 | 25 | | 指针 | 房间的具体地址(门牌号),例如:总经理室地址是2楼201室 | 26 | | 变量值 | 房间里的具体存储物 | 27 | | 指针地址 | 指针的地址:存储指针内存块的地址 | 28 | 29 | ## 2.2. 内存单位和编址 30 | 31 | ## 2.2.1. 内存单位 32 | 33 | | 单位 | 说明 | 34 | | -------- | --------------------------------- | 35 | | 位(bit) | 计算机中最小的数据单位,每一位的状态只能是0或1 | 36 | | 字节(Byte) | 1Byte=8bit,是内存基本的计量单位 | 37 | | 字 | “字”由若干个字节构成,字的位数叫字长,不同档次的机器有不同的字长 | 38 | | KB | 1KB=1024Byte,即1024个字节 | 39 | | MB | 1MB=1024KB | 40 | | GB | 1GB=1024MB | 41 | 42 | ## 2.2.2. 内存编址 43 | 44 | 计算机中的内存按字节编址,每个地址的存储单元可以存放一个字节的数据,CPU通过内存地址获取指令和数据,并不关心这个地址所代表的空间在什么位置,内存地址和地址指向的空间共同构成了一个内存单元。 45 | 46 | ## 2.2.3. 内存地址 47 | 48 | 内存地址通常用16进制的数据表示,例如0x0ffc1。 49 | # 3.变量与指针运算理解 50 | 编写一段程序,检索出值并存储在地址为 200 的一个块内存中,将其乘以 3,并将结果存储在地址为 201 的另一块内存中 51 | ## 3.1.本质 52 | 1. 检索出内存地址为 200 的值,并将其存储在 CPU 中 53 | 2. 将存储在 CPU 中的值乘以 3 54 | 3. 将 CPU 中存储的结果,写入地址为 201 的内存块中 55 | 56 | ![什么是变量](https://res.cloudinary.com/dqxtn0ick/image/upload/v1510578752/article/golang/pointer/var1.png) 57 | 58 | ## 3.2.基于变量的理解 59 | 1. 获取变量 a 中存储的值,并将其存储在 CPU 中 60 | 2. 将其乘以 3 61 | 3. 将结果保存在变量 b 中 62 | 63 | ![什么是变量2](https://res.cloudinary.com/dqxtn0ick/image/upload/v1510578752/article/golang/pointer/var2.png) 64 | 65 | ``` 66 | var a = 6 67 | var b = a * 3 68 | ``` 69 | ## 3.3.基于指针的理解 70 | ```go 71 | func main() { 72 | a := 200 73 | b := &a 74 | *b++ 75 | fmt.Println(a) 76 | } 77 | ``` 78 | 以上函数对a进行+1操作,具体理解如下: 79 | 80 | **1.a:=200** 81 | 82 | ![什么是指针](https://res.cloudinary.com/dqxtn0ick/image/upload/v1510578751/article/golang/pointer/pointer1.png) 83 | 84 | **2. b := &a** 85 | 86 | ![什么是指针2](https://res.cloudinary.com/dqxtn0ick/image/upload/v1510578751/article/golang/pointer/pointer2.png) 87 | 88 | **3. *b++** 89 | 90 | ![什么是指针3](https://res.cloudinary.com/dqxtn0ick/image/upload/v1510578752/article/golang/pointer/pointer3.png) 91 | 92 | ![什么是指针4](https://res.cloudinary.com/dqxtn0ick/image/upload/v1510578752/article/golang/pointer/pointer4.png) 93 | 94 | # 4. 指针的使用 95 | 96 | ## 4.1. 方法中的指针 97 | 98 | 方法即为有接受者的函数,接受者可以是类型的实例变量或者是类型的实例指针变量。但两种效果不同。 99 | 100 | **1、类型的实例变量** 101 | 102 | ```go 103 | func main(){ 104 | person := Person{"vanyar", 21} 105 | fmt.Printf("person<%s:%d>\n", person.name, person.age) 106 | person.sayHi() 107 | person.ModifyAge(210) 108 | person.sayHi() 109 | } 110 | type Person struct { 111 | name string 112 | age int 113 | } 114 | func (p Person) sayHi() { 115 | fmt.Printf("SayHi -- This is %s, my age is %d\n",p.name, p.age) 116 | } 117 | func (p Person) ModifyAge(age int) { 118 | fmt.Printf("ModifyAge") 119 | p.age = age 120 | } 121 | 122 | 123 | //输出结果 124 | person 125 | SayHi -- This is vanyar, my age is 21 126 | ModifyAgeSayHi -- This is vanyar, my age is 21 127 | ``` 128 | 129 | 尽管 ModifyAge 方法修改了其age字段,可是方法里的p是person变量的一个副本,修改的只是副本的值。下一次调用sayHi方法的时候,还是person的副本,因此修改方法并不会生效。 130 | 131 | 即实例变量的方式并不会改变接受者本身的值。 132 | 133 | **2、类型的实例指针变量** 134 | 135 | ```go 136 | func (p *Person) ChangeAge(age int) { 137 | fmt.Printf("ModifyAge") 138 | p.age = age 139 | } 140 | ``` 141 | 142 | Go会根据Person的示例类型,转换成指针类型再拷贝,即 person.ChangeAge会变成 (&person).ChangeAge。 143 | 144 | 指针类型的接受者,如果实例对象是值,那么go会转换成指针,然后再拷贝,如果本身就是指针对象,那么就直接拷贝指针实例。因为指针都指向一处值,就能修改对象了。 145 | 146 | # 5. 零值与nil(空指针) 147 | 148 | 变量声明而没有赋值,默认为零值,不同类型零值不同,例如字符串零值为空字符串; 149 | 150 | 指针声明而没有赋值,默认为nil,即该指针没有任何指向。当指针没有指向的时候,不能对(*point)进行操作包括读取,否则会报空指针异常。 151 | 152 | ```go 153 | func main(){ 154 | // 声明一个指针变量 aPot 其类型也是 string 155 | var aPot *string 156 | fmt.Printf("aPot: %p %#v\n", &aPot, aPot) // 输出 aPot: 0xc42000c030 (*string)(nil) 157 | *aPot = "This is a Pointer" // 报错: panic: runtime error: invalid memory address or nil pointer dereference 158 | } 159 | ``` 160 | 161 | 解决方法即给该指针分配一个指向,即初始化一个内存,并把该内存地址赋予指针变量,例如: 162 | 163 | ```go 164 | // 声明一个指针变量 aPot 其类型也是 string 165 | var aPot *string 166 | fmt.Printf("aPot: %p %#v\n", &aPot, aPot) // 输出 aPot: 0xc42000c030 (*string)(nil) 167 | 168 | aPot = &aVar 169 | *aPot = "This is a Pointer" 170 | fmt.Printf("aVar: %p %#v \n", &aVar, aVar) // 输出 aVar: 0xc42000e240 "This is a Pointer" 171 | fmt.Printf("aPot: %p %#v %#v \n", &aPot, aPot, *aPot) // 输出 aPot: 0xc42000c030 (*string)(0xc42000e240) "This is a Pointer" 172 | ``` 173 | 174 | 或者通过new开辟一个内存,并返回这个内存的地址。 175 | 176 | ```go 177 | var aNewPot *int 178 | 179 | aNewPot = new(int) 180 | *aNewPot = 217 181 | fmt.Printf("aNewPot: %p %#v %#v \n", &aNewPot, aNewPot, *aNewPot) // 输出 aNewPot: 0xc42007a028 (*int)(0xc42006e1f0) 217 182 | ``` 183 | 184 | # 6. 总结 185 | 186 | - Golang提供了指针用于操作数据内存,并通过引用来修改变量。 187 | - 只声明未赋值的变量,golang都会自动为其初始化为零值,基础数据类型的零值比较简单,引用类型和指针的零值都为nil,nil类型不能直接赋值,因此需要通过new开辟一个内存,或者通过make初始化数据类型,或者两者配合,然后才能赋值。 188 | - 指针也是一种类型,不同于一般类型,指针的值是地址,这个地址指向其他的内存,通过指针可以读取其所指向的地址所存储的值。 189 | - 函数方法的接受者,也可以是指针变量。无论普通接受者还是指针接受者都会被拷贝传入方法中,不同在于拷贝的指针,其指向的地方都一样,只是其自身的地址不一样。 190 | 191 | 192 | 193 | 参考: 194 | 195 | - [http://www.jianshu.com/p/d23f78a3922b](http://www.jianshu.com/p/d23f78a3922b) 196 | 197 | - [http://www.jianshu.com/p/44b9429d7bef](http://www.jianshu.com/p/44b9429d7bef) 198 | -------------------------------------------------------------------------------- /test/gdb.md: -------------------------------------------------------------------------------- 1 | # 1. GDB简介 2 | 3 | GDB是FSF(自由软件基金会)发布的一个强大的类UNIX系统下的程序调试工具。使用GDB可以做如下事情: 4 | 5 | 1. 启动程序,可以按照开发者的自定义要求运行程序。 6 | 2. 可让被调试的程序在开发者设定的调置的断点处停住。(断点可以是条件表达式) 7 | 3. 当程序被停住时,可以检查此时程序中所发生的事。 8 | 4. 动态的改变当前程序的执行环境。 9 | 10 | 目前支持调试Go程序的GDB版本必须大于7.1。 11 | 12 | 编译Go程序的时候需要注意以下几点 13 | 14 | 1. 传递参数-ldflags "-s",忽略debug的打印信息 15 | 2. 传递-gcflags "-N -l" 参数,这样可以忽略Go内部做的一些优化,聚合变量和函数等优化,这样对于GDB调试来说非常困难,所以在编译的时候加入这两个参数避免这些优化。 16 | 17 | # 2. 常用命令 18 | 19 | ## 2.1. list 20 | 21 | 简写命令`l`,用来显示源代码,默认显示十行代码,后面可以带上参数显示的具体行,例如:`list 15`,显示十行代码,其中第15行在显示的十行里面的中间,如下所示。 22 | 23 | ```go 24 | 10 time.Sleep(2 * time.Second) 25 | 11 c <- i 26 | 12 } 27 | 13 close(c) 28 | 14 } 29 | 15 30 | 16 func main() { 31 | 17 msg := "Starting main" 32 | 18 fmt.Println(msg) 33 | 19 bus := make(chan int) 34 | ``` 35 | 36 | ## 2.2. break 37 | 38 | 简写命令 `b`,用来设置断点,后面跟上参数设置断点的行数,例如`b 10`在第十行设置断点。 39 | 40 | ## 2.3. delete 41 | 42 | 简写命令 `d`,用来删除断点,后面跟上断点设置的序号,这个序号可以通过`info breakpoints`获取相应的设置的断点序号,如下是显示的设置断点序号。 43 | 44 | ```bash 45 | Num Type Disp Enb Address What 46 | 2 breakpoint keep y 0x0000000000400dc3 in main.main at /home/xiemengjun/gdb.go:23 47 | breakpoint already hit 1 time 48 | ``` 49 | 50 | ## 2.4. backtrace 51 | 52 | 简写命令 `bt`,用来打印执行的代码过程,如下所示: 53 | 54 | ```bash 55 | #0 main.main () at /home/xiemengjun/gdb.go:23 56 | #1 0x000000000040d61e in runtime.main () at /home/xiemengjun/go/src/pkg/runtime/proc.c:244 57 | #2 0x000000000040d6c1 in schedunlock () at /home/xiemengjun/go/src/pkg/runtime/proc.c:267 58 | #3 0x0000000000000000 in ?? () 59 | ``` 60 | 61 | ## 2.5. info 62 | 63 | info命令用来显示信息,后面有几种参数,我们常用的有如下几种: 64 | 65 | 1、 `info locals` 66 | 67 | 显示当前执行的程序中的变量值 68 | 69 | 2、 `info breakpoints` 70 | 71 | 显示当前设置的断点列表 72 | 73 | 3、 `info goroutines` 74 | 75 | 显示当前执行的goroutine列表,如下代码所示,带*的表示当前执行的 76 | 77 | ```bash 78 | * 1 running runtime.gosched 79 | * 2 syscall runtime.entersyscall 80 | 3 waiting runtime.gosched 81 | 4 runnable runtime.gosched 82 | ``` 83 | 84 | ## 2.6. print 85 | 86 | 简写命令`p`,用来打印变量或者其他信息,后面跟上需要打印的变量名,当然还有一些很有用的函数$len()和$cap(),用来返回当前string、slices或者maps的长度和容量。 87 | 88 | ## 2.7. whatis 89 | 90 | 用来显示当前变量的类型,后面跟上变量名,例如`whatis msg`,显示如下: 91 | 92 | ```bash 93 | type = struct string 94 | ``` 95 | 96 | ## 2.8. next 97 | 98 | 简写命令 `n`,用来单步调试,跳到下一步,当有断点之后,可以输入`n`跳转到下一步继续执行 99 | 100 | ## 2.9. coutinue 101 | 102 | 简称命令 `c`,用来跳出当前断点处,后面可以跟参数N,跳过多少次断点 103 | 104 | ## 2.10. set variable 105 | 106 | 该命令用来改变运行过程中的变量值,格式如:`set variable =` 107 | 108 | # 3. 调试过程 109 | 110 | ## 3.1. 示例代码 111 | 112 | ```go 113 | package main 114 | import ( 115 | "fmt" 116 | "time" 117 | ) 118 | func counting(c chan<- int) { 119 | for i := 0; i < 10; i++ { 120 | time.Sleep(2 * time.Second) 121 | c <- i 122 | } 123 | close(c) 124 | } 125 | func main() { 126 | msg := "Starting main" 127 | fmt.Println(msg) 128 | bus := make(chan int) 129 | msg = "starting a gofunc" 130 | go counting(bus) 131 | for count := range bus { 132 | fmt.Println("count:", count) 133 | } 134 | } 135 | ``` 136 | 137 | ## 3.2. 调试步骤 138 | 139 | 编译文件,生成可执行文件gdbfile: 140 | 141 | ```bash 142 | go build -gcflags "-N -l" gdbfile.go 143 | ``` 144 | 145 | 通过gdb命令启动调试: 146 | 147 | ```bash 148 | gdb gdbfile 149 | ``` 150 | 151 | 启动之后首先看看这个程序是不是可以运行起来,只要输入`run`命令回车后程序就开始运行,程序正常的话可以看到程序输出如下,和我们在命令行直接执行程序输出是一样的: 152 | 153 | ```bash 154 | (gdb) run 155 | Starting program: /home/xiemengjun/gdbfile 156 | Starting main 157 | count: 0 158 | count: 1 159 | count: 2 160 | count: 3 161 | count: 4 162 | count: 5 163 | count: 6 164 | count: 7 165 | count: 8 166 | count: 9 167 | [LWP 2771 exited] 168 | [Inferior 1 (process 2771) exited normally] 169 | ``` 170 | 171 | 好了,现在我们已经知道怎么让程序跑起来了,接下来开始给代码设置断点: 172 | 173 | ```bash 174 | (gdb) b 23 175 | Breakpoint 1 at 0x400d8d: file /home/xiemengjun/gdbfile.go, line 23. 176 | (gdb) run 177 | Starting program: /home/xiemengjun/gdbfile 178 | Starting main 179 | [New LWP 3284] 180 | [Switching to LWP 3284] 181 | 182 | Breakpoint 1, main.main () at /home/xiemengjun/gdbfile.go:23 183 | 23 fmt.Println("count:", count) 184 | ``` 185 | 186 | 上面例子`b 23`表示在第23行设置了断点,之后输入`run`开始运行程序。现在程序在前面设置断点的地方停住了,我们需要查看断点相应上下文的源码,输入`list`就可以看到源码显示从当前停止行的前五行开始: 187 | 188 | ```bash 189 | (gdb) list 190 | 18 fmt.Println(msg) 191 | 19 bus := make(chan int) 192 | 20 msg = "starting a gofunc" 193 | 21 go counting(bus) 194 | 22 for count := range bus { 195 | 23 fmt.Println("count:", count) 196 | 24 } 197 | 25 } 198 | ``` 199 | 200 | 现在GDB在运行当前的程序的环境中已经保留了一些有用的调试信息,我们只需打印出相应的变量,查看相应变量的类型及值: 201 | 202 | ```bash 203 | (gdb) info locals 204 | count = 0 205 | bus = 0xf840001a50 206 | (gdb) p count 207 | $1 = 0 208 | (gdb) p bus 209 | $2 = (chan int) 0xf840001a50 210 | (gdb) whatis bus 211 | type = chan int 212 | ``` 213 | 214 | 接下来该让程序继续往下执行,请继续看下面的命令 215 | 216 | ```bash 217 | (gdb) c 218 | Continuing. 219 | count: 0 220 | [New LWP 3303] 221 | [Switching to LWP 3303] 222 | 223 | Breakpoint 1, main.main () at /home/xiemengjun/gdbfile.go:23 224 | 23 fmt.Println("count:", count) 225 | (gdb) c 226 | Continuing. 227 | count: 1 228 | [Switching to LWP 3302] 229 | 230 | Breakpoint 1, main.main () at /home/xiemengjun/gdbfile.go:23 231 | 23 fmt.Println("count:", count) 232 | ``` 233 | 234 | 每次输入`c`之后都会执行一次代码,又跳到下一次for循环,继续打印出来相应的信息。设想目前需要改变上下文相关变量的信息,跳过一些过程,并继续执行下一步,得出修改后想要的结果: 235 | 236 | ```bash 237 | (gdb) info locals 238 | count = 2 239 | bus = 0xf840001a50 240 | (gdb) set variable count=9 241 | (gdb) info locals 242 | count = 9 243 | bus = 0xf840001a50 244 | (gdb) c 245 | Continuing. 246 | count: 9 247 | [Switching to LWP 3302] 248 | 249 | Breakpoint 1, main.main () at /home/xiemengjun/gdbfile.go:23 250 | 23 fmt.Println("count:", count) 251 | ``` 252 | 253 | 最后稍微思考一下,前面整个程序运行的过程中到底创建了多少个goroutine,每个goroutine都在做什么: 254 | 255 | ```bash 256 | (gdb) info goroutines 257 | * 1 running runtime.gosched 258 | * 2 syscall runtime.entersyscall 259 | 3 waiting runtime.gosched 260 | 4 runnable runtime.gosched 261 | (gdb) goroutine 1 bt 262 | #0 0x000000000040e33b in runtime.gosched () at /home/xiemengjun/go/src/pkg/runtime/proc.c:927 263 | #1 0x0000000000403091 in runtime.chanrecv (c=void, ep=void, selected=void, received=void) 264 | at /home/xiemengjun/go/src/pkg/runtime/chan.c:327 265 | #2 0x000000000040316f in runtime.chanrecv2 (t=void, c=void) 266 | at /home/xiemengjun/go/src/pkg/runtime/chan.c:420 267 | #3 0x0000000000400d6f in main.main () at /home/xiemengjun/gdbfile.go:22 268 | #4 0x000000000040d0c7 in runtime.main () at /home/xiemengjun/go/src/pkg/runtime/proc.c:244 269 | #5 0x000000000040d16a in schedunlock () at /home/xiemengjun/go/src/pkg/runtime/proc.c:267 270 | #6 0x0000000000000000 in ?? () 271 | ``` 272 | 273 | 通过查看goroutines的命令我们可以清楚地了解goruntine内部是怎么执行的,每个函数的调用顺序已经明明白白地显示出来了。 274 | 275 | 参考《Go Web编程》 276 | -------------------------------------------------------------------------------- /framework/cobra/cobra-command.md: -------------------------------------------------------------------------------- 1 | > 本文以cobra-demo为例介绍cobra添加命令的具体使用操作。 2 | 3 | # 0. cobra-demo 4 | 5 | `cobra-demo`编译二进制执行的结果如下。具体代码参考:https://github.com/huweihuang/cobra-demo 6 | 7 | ```bash 8 | $ ./cobra-demo 9 | A longer description that spans multiple lines and likely contains 10 | examples and usage of using your application. For example: 11 | 12 | Cobra is a CLI library for Go that empowers applications. 13 | This application is a tool to generate the needed files 14 | to quickly create a Cobra application. 15 | 16 | Usage: 17 | cobra-demo [command] 18 | 19 | Available Commands: 20 | config A brief description of your command 21 | help Help about any command 22 | server A brief description of your command 23 | 24 | Flags: 25 | --config string config file (default is $HOME/.cobra-demo.yaml) 26 | -h, --help help for cobra-demo 27 | -t, --toggle Help message for toggle 28 | 29 | Use "cobra-demo [command] --help" for more information about a command. 30 | ``` 31 | 32 | # 1. cobra init 33 | 34 | > cobra init 的具体使用参考 [init](http://www.huweihuang.com/framework/cobra/cobra-usage.html#31-cobra-init) 35 | 36 | ## 1.1. cobra init --pkg-name 37 | 38 | ```bash 39 | cobra init --pkg-name github.com/huweihuang/cobra-demo -a 'author name ' 40 | ``` 41 | 42 | 执行以上命令,创建的文件目录结构如下: 43 | 44 | ```bash 45 | ./ 46 | ├── LICENSE 47 | ├── cmd 48 | │   ├── root.go 49 | └── main.go 50 | ``` 51 | 52 | ## 1.2. main.go 53 | 54 | ```go 55 | package main 56 | 57 | import "github.com/huweihuang/cobra-demo/cmd" 58 | 59 | func main() { 60 | cmd.Execute() 61 | } 62 | ``` 63 | 64 | ## 1.3. cmd/root.go 65 | 66 | ```go 67 | package cmd 68 | 69 | import ( 70 | "fmt" 71 | "os" 72 | "github.com/spf13/cobra" 73 | 74 | homedir "github.com/mitchellh/go-homedir" 75 | "github.com/spf13/viper" 76 | 77 | ) 78 | 79 | 80 | var cfgFile string 81 | 82 | 83 | // rootCmd represents the base command when called without any subcommands 84 | var rootCmd = &cobra.Command{ 85 | Use: "cobra-demo", 86 | Short: "A brief description of your application", 87 | Long: `A longer description that spans multiple lines and likely contains 88 | examples and usage of using your application. For example: 89 | 90 | Cobra is a CLI library for Go that empowers applications. 91 | This application is a tool to generate the needed files 92 | to quickly create a Cobra application.`, 93 | // Uncomment the following line if your bare application 94 | // has an action associated with it: 95 | // Run: func(cmd *cobra.Command, args []string) { }, 96 | } 97 | 98 | // Execute adds all child commands to the root command and sets flags appropriately. 99 | // This is called by main.main(). It only needs to happen once to the rootCmd. 100 | func Execute() { 101 | if err := rootCmd.Execute(); err != nil { 102 | fmt.Println(err) 103 | os.Exit(1) 104 | } 105 | } 106 | 107 | func init() { 108 | cobra.OnInitialize(initConfig) 109 | 110 | // Here you will define your flags and configuration settings. 111 | // Cobra supports persistent flags, which, if defined here, 112 | // will be global for your application. 113 | 114 | rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra-demo.yaml)") 115 | 116 | 117 | // Cobra also supports local flags, which will only run 118 | // when this action is called directly. 119 | rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 120 | } 121 | 122 | 123 | // initConfig reads in config file and ENV variables if set. 124 | func initConfig() { 125 | if cfgFile != "" { 126 | // Use config file from the flag. 127 | viper.SetConfigFile(cfgFile) 128 | } else { 129 | // Find home directory. 130 | home, err := homedir.Dir() 131 | if err != nil { 132 | fmt.Println(err) 133 | os.Exit(1) 134 | } 135 | 136 | // Search config in home directory with name ".cobra-demo" (without extension). 137 | viper.AddConfigPath(home) 138 | viper.SetConfigName(".cobra-demo") 139 | } 140 | 141 | viper.AutomaticEnv() // read in environment variables that match 142 | 143 | // If a config file is found, read it in. 144 | if err := viper.ReadInConfig(); err == nil { 145 | fmt.Println("Using config file:", viper.ConfigFileUsed()) 146 | } 147 | } 148 | ``` 149 | 150 | 151 | # 2. cobra add 152 | 153 | > cobra add 的具体使用参考 [add](http://www.huweihuang.com/framework/cobra/cobra-usage.html#32-cobra-add) 154 | 155 | ## 2.1. cobra add command 156 | 157 | ```bash 158 | cobra add serve -a 'author name ' 159 | cobra add config -a 'author name ' 160 | cobra add create -p 'configCmd' -a 'author name '# 在父命令config命令下创建子命令create,若没有指定-p,默认的父命令为rootCmd。 161 | ``` 162 | 163 | 执行以上命令,创建的文件目录结构如下: 164 | 165 | ```bash 166 | ./ 167 | ├── LICENSE 168 | ├── cmd 169 | │   ├── config.go # rootCmd的子命令 170 | │   ├── create.go # config的子命令 171 | │   ├── root.go # 默认父命令 172 | │   └── server.go # rootCmd的子命令 173 | └── main.go 174 | ``` 175 | 176 | ## 2.2. cmd/config.go 177 | 178 | ```go 179 | package cmd 180 | 181 | import ( 182 | "fmt" 183 | 184 | "github.com/spf13/cobra" 185 | ) 186 | 187 | // configCmd represents the config command 188 | var configCmd = &cobra.Command{ 189 | Use: "config", 190 | Short: "A brief description of your command", 191 | Long: `A longer description that spans multiple lines and likely contains examples 192 | and usage of using your command. For example: 193 | 194 | Cobra is a CLI library for Go that empowers applications. 195 | This application is a tool to generate the needed files 196 | to quickly create a Cobra application.`, 197 | Run: func(cmd *cobra.Command, args []string) { 198 | fmt.Println("config called") 199 | }, 200 | } 201 | 202 | func init() { 203 | rootCmd.AddCommand(configCmd) 204 | 205 | // Here you will define your flags and configuration settings. 206 | 207 | // Cobra supports Persistent Flags which will work for this command 208 | // and all subcommands, e.g.: 209 | // configCmd.PersistentFlags().String("foo", "", "A help for foo") 210 | 211 | // Cobra supports local flags which will only run when this command 212 | // is called directly, e.g.: 213 | // configCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 214 | } 215 | ``` 216 | 217 | ## 2.3. cmd/create.go 218 | 219 | create为config的子命令。 220 | 221 | ```go 222 | package cmd 223 | 224 | import ( 225 | "fmt" 226 | 227 | "github.com/spf13/cobra" 228 | ) 229 | 230 | // createCmd represents the create command 231 | var createCmd = &cobra.Command{ 232 | Use: "create", 233 | Short: "A brief description of your command", 234 | Long: `A longer description that spans multiple lines and likely contains examples 235 | and usage of using your command. For example: 236 | 237 | Cobra is a CLI library for Go that empowers applications. 238 | This application is a tool to generate the needed files 239 | to quickly create a Cobra application.`, 240 | Run: func(cmd *cobra.Command, args []string) { 241 | fmt.Println("create called") 242 | }, 243 | } 244 | 245 | func init() { 246 | configCmd.AddCommand(createCmd) 247 | 248 | // Here you will define your flags and configuration settings. 249 | 250 | // Cobra supports Persistent Flags which will work for this command 251 | // and all subcommands, e.g.: 252 | // createCmd.PersistentFlags().String("foo", "", "A help for foo") 253 | 254 | // Cobra supports local flags which will only run when this command 255 | // is called directly, e.g.: 256 | // createCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 257 | } 258 | ``` 259 | 260 | 参考: 261 | 262 | - https://github.com/spf13/cobra 263 | - https://github.com/spf13/cobra/blob/master/cobra/README.md 264 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /web/golang-http-execution-flow.md: -------------------------------------------------------------------------------- 1 | # 1. http包建立web服务器 2 | 3 | ```go 4 | package main 5 | import ( 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "strings" 10 | ) 11 | func sayhelloName(w http.ResponseWriter, r *http.Request) { 12 | r.ParseForm() 13 | fmt.Println(r.Form) 14 | fmt.Println("path", r.URL.Path) 15 | fmt.Println("scheme", r.URL.Scheme) 16 | fmt.Println(r.Form["url_long"]) 17 | for k, v := range r.Form { 18 | fmt.Println("key:", k) 19 | fmt.Println("val:", strings.Join((v), "")) 20 | } 21 | fmt.Println(w, "hello world") 22 | } 23 | func main() { 24 | http.HandleFunc("/", sayhelloName) 25 | err := http.ListenAndServe(":9090", nil) 26 | if err != nil { 27 | log.Fatal("ListenAndServe:", err) 28 | } 29 | } 30 | ``` 31 | 32 | # 2. http包的运行机制 33 | 34 | 相关源码位于:/src/net/http/server.go 35 | 36 | **服务端的几个概念** 37 | 38 | - Request:用户请求的信息,用来解析用户的请求信息,包括post,get,Cookie,url等信息。 39 | - Response:服务器需要反馈给客户端的信息。 40 | - Conn:用户的每次请求链接。 41 | - Handle:处理请求和生成返回信息的处理逻辑。 42 | 43 | **Go实现web服务的流程** 44 | 45 | 1. 创建Listen Socket,监听指定的端口,等待客户端请求到来。 46 | 2. Listen Socket接受客户端的请求,得到Client Socket,接下来通过Client Socket与客户端通信。 47 | 3. 处理客户端请求,首先从Client Socket读取HTTP请求的协议头,如果是POST方法,还可能要读取客户端提交的数据,然后交给相应的handler处理请求,handler处理完,将数据通过Client Socket返回给客户端。 48 | 49 | ## 2.1. http包执行流程图 50 | 51 | ![image2017-3-5 22-46-35](https://res.cloudinary.com/dqxtn0ick/image/upload/v1510578729/article/golang/http/http-flow.png) 52 | 53 | ## 2.2. 注册路由[HandleFunc] 54 | 55 | http.HandlerFunc类型默认实现了ServeHTTP的接口。 56 | 57 | ```go 58 | // The HandlerFunc type is an adapter to allow the use of 59 | // ordinary functions as HTTP handlers. If f is a function 60 | // with the appropriate signature, HandlerFunc(f) is a 61 | // Handler that calls f. 62 | type HandlerFunc func(ResponseWriter, *Request) 63 | // ServeHTTP calls f(w, r). 64 | func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { 65 | f(w, r) 66 | } 67 | ``` 68 | 69 | ```go 70 | // HandleFunc registers the handler function for the given pattern 71 | // in the DefaultServeMux. 72 | // The documentation for ServeMux explains how patterns are matched. 73 | func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { 74 | DefaultServeMux.HandleFunc(pattern, handler) 75 | } 76 | ... 77 | // HandleFunc registers the handler function for the given pattern. 78 | func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { 79 | mux.Handle(pattern, HandlerFunc(handler)) 80 | } 81 | ``` 82 | 83 | 84 | 85 | **Handle** 86 | 87 | ```go 88 | // Handle registers the handler for the given pattern. 89 | // If a handler already exists for pattern, Handle panics. 90 | func (mux *ServeMux) Handle(pattern string, handler Handler) { 91 | mux.mu.Lock() 92 | defer mux.mu.Unlock() 93 | 94 | if pattern == "" { 95 | panic("http: invalid pattern " + pattern) 96 | } 97 | if handler == nil { 98 | panic("http: nil handler") 99 | } 100 | if mux.m[pattern].explicit { 101 | panic("http: multiple registrations for " + pattern) 102 | } 103 | 104 | mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern} 105 | 106 | if pattern[0] != '/' { 107 | mux.hosts = true 108 | } 109 | 110 | // Helpful behavior: 111 | // If pattern is /tree/, insert an implicit permanent redirect for /tree. 112 | // It can be overridden by an explicit registration. 113 | n := len(pattern) 114 | if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit { 115 | // If pattern contains a host name, strip it and use remaining 116 | // path for redirect. 117 | path := pattern 118 | if pattern[0] != '/' { 119 | // In pattern, at least the last character is a '/', so 120 | // strings.Index can't be -1. 121 | path = pattern[strings.Index(pattern, "/"):] 122 | } 123 | url := &url.URL{Path: path} 124 | mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(url.String(), StatusMovedPermanently), pattern: pattern} 125 | } 126 | } 127 | ``` 128 | 129 | ## 2.3. 如何监听端口 130 | 131 | 通过ListenAndServe来监听,底层实现:初始化一个server对象,调用net.Listen("tcp",addr),也就是底层用TCP协议搭建了一个服务,监听设置的端口。然后调用srv.Serve(net.Listener)函数,这个函数处理接收客户端的请求信息。这个函数里起了一个for循环,通过Listener接收请求,创建conn,开一个goroutine,把请求的数据当作参数给conn去服务:go c.serve(),即每次请求都是在新的goroutine中去服务,利于高并发。 132 | 133 | **src/net/http/server.go** 134 | 135 | ```go 136 | // ListenAndServe always returns a non-nil error. 137 | func ListenAndServe(addr string, handler Handler) error { 138 | server := &Server{Addr: addr, Handler: handler} 139 | return server.ListenAndServe() 140 | } 141 | ... 142 | // ListenAndServe listens on the TCP network address srv.Addr and then 143 | // calls Serve to handle requests on incoming connections. 144 | // Accepted connections are configured to enable TCP keep-alives. 145 | // If srv.Addr is blank, ":http" is used. 146 | // ListenAndServe always returns a non-nil error. 147 | func (srv *Server) ListenAndServe() error { 148 | addr := srv.Addr 149 | if addr == "" { 150 | addr = ":http" 151 | } 152 | ln, err := net.Listen("tcp", addr) 153 | if err != nil { 154 | return err 155 | } 156 | return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}) 157 | } 158 | ``` 159 | 160 | ## 2.4. 如何接收客户端的请求 161 | 162 | srv.Serve 163 | 164 | ```go 165 | // Serve accepts incoming connections on the Listener l, creating a 166 | // new service goroutine for each. The service goroutines read requests and 167 | // then call srv.Handler to reply to them. 168 | // Serve always returns a non-nil error. 169 | func (srv *Server) Serve(l net.Listener) error { 170 | defer l.Close() 171 | if fn := testHookServerServe; fn != nil { 172 | fn(srv, l) 173 | } 174 | var tempDelay time.Duration // how long to sleep on accept failure 175 | if err := srv.setupHTTP2(); err != nil { 176 | return err 177 | } 178 | for { 179 | rw, e := l.Accept() 180 | if e != nil { 181 | if ne, ok := e.(net.Error); ok && ne.Temporary() { 182 | if tempDelay == 0 { 183 | tempDelay = 5 * time.Millisecond 184 | } else { 185 | tempDelay *= 2 186 | } 187 | if max := 1 * time.Second; tempDelay > max { 188 | tempDelay = max 189 | } 190 | srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay) 191 | time.Sleep(tempDelay) 192 | continue 193 | } 194 | return e 195 | } 196 | tempDelay = 0 197 | c := srv.newConn(rw) 198 | c.setState(c.rwc, StateNew) // before Serve can return 199 | go c.serve() 200 | } 201 | } 202 | ``` 203 | 204 | 关键代码: 205 | 206 | ```go 207 | c := srv.newConn(rw) 208 | c.setState(c.rwc, StateNew) // before Serve can return 209 | go c.serve() 210 | ``` 211 | 212 | **newConn** 213 | 214 | ```go 215 | // Create new connection from rwc. 216 | func (srv *Server) newConn(rwc net.Conn) *conn { 217 | c := &conn{ 218 | server: srv, 219 | rwc: rwc, 220 | } 221 | if debugServerConnections { 222 | c.rwc = newLoggingConn("server", c.rwc) 223 | } 224 | return c 225 | } 226 | ``` 227 | 228 | ## 2.5. 如何分配handler 229 | 230 | conn先解析request:c.readRequest(),获取相应的handler:handler:=c.server.Handler,即ListenAndServe的第二个参数,因为值为nil,所以默认handler=DefaultServeMux。该变量是一个路由器,用来匹配url跳转到其相应的handle函数。其中http.HandleFunc("/",sayhelloName)即注册了请求“/”的路由规则,当uri为“/”时,路由跳转到函数sayhelloName。DefaultServeMux会调用ServeHTTP方法,这个方法内部调用sayhelloName本身,最后写入response的信息反馈给客户端。 231 | 232 | ## 2.5.1. c.serve() 233 | 234 | ```go 235 | // Serve a new connection. 236 | func (c *conn) serve() { 237 | ... 238 | for { 239 | w, err := c.readRequest() 240 | ... 241 | serverHandler{c.server}.ServeHTTP(w, w.req) 242 | .. 243 | } 244 | } 245 | ``` 246 | 247 | ## 2.5.2. c.readRequest() 248 | 249 | ```go 250 | // Read next request from connection. 251 | func (c *conn) readRequest() (w *response, err error) { 252 | if c.hijacked() { 253 | return nil, ErrHijacked 254 | } 255 | 256 | if d := c.server.ReadTimeout; d != 0 { 257 | c.rwc.SetReadDeadline(time.Now().Add(d)) 258 | } 259 | if d := c.server.WriteTimeout; d != 0 { 260 | defer func() { 261 | c.rwc.SetWriteDeadline(time.Now().Add(d)) 262 | }() 263 | } 264 | 265 | c.r.setReadLimit(c.server.initialReadLimitSize()) 266 | c.mu.Lock() // while using bufr 267 | if c.lastMethod == "POST" { 268 | // RFC 2616 section 4.1 tolerance for old buggy clients. 269 | peek, _ := c.bufr.Peek(4) // ReadRequest will get err below 270 | c.bufr.Discard(numLeadingCRorLF(peek)) 271 | } 272 | req, err := readRequest(c.bufr, keepHostHeader) 273 | c.mu.Unlock() 274 | if err != nil { 275 | if c.r.hitReadLimit() { 276 | return nil, errTooLarge 277 | } 278 | return nil, err 279 | } 280 | c.lastMethod = req.Method 281 | c.r.setInfiniteReadLimit() 282 | 283 | hosts, haveHost := req.Header["Host"] 284 | if req.ProtoAtLeast(1, 1) && (!haveHost || len(hosts) == 0) { 285 | return nil, badRequestError("missing required Host header") 286 | } 287 | if len(hosts) > 1 { 288 | return nil, badRequestError("too many Host headers") 289 | } 290 | if len(hosts) == 1 && !validHostHeader(hosts[0]) { 291 | return nil, badRequestError("malformed Host header") 292 | } 293 | for k, vv := range req.Header { 294 | if !validHeaderName(k) { 295 | return nil, badRequestError("invalid header name") 296 | } 297 | for _, v := range vv { 298 | if !validHeaderValue(v) { 299 | return nil, badRequestError("invalid header value") 300 | } 301 | } 302 | } 303 | delete(req.Header, "Host") 304 | 305 | req.RemoteAddr = c.remoteAddr 306 | req.TLS = c.tlsState 307 | if body, ok := req.Body.(*body); ok { 308 | body.doEarlyClose = true 309 | } 310 | 311 | w = &response{ 312 | conn: c, 313 | req: req, 314 | reqBody: req.Body, 315 | handlerHeader: make(Header), 316 | contentLength: -1, 317 | } 318 | w.cw.res = w 319 | w.w = newBufioWriterSize(&w.cw, bufferBeforeChunkingSize) 320 | return w, nil 321 | } 322 | ``` 323 | 324 | ## 2.5.3. ServeHTTP(w, w.req) 325 | 326 | ```go 327 | func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) { 328 | handler := sh.srv.Handler 329 | if handler == nil { 330 | handler = DefaultServeMux 331 | } 332 | if req.RequestURI == "*" && req.Method == "OPTIONS" { 333 | handler = globalOptionsHandler{} 334 | } 335 | handler.ServeHTTP(rw, req) 336 | } 337 | ``` 338 | 339 | ## 2.5.4. DefaultServeMux 340 | 341 | ```go 342 | type ServeMux struct { 343 | mu sync.RWMutex 344 | m map[string]muxEntry 345 | hosts bool // whether any patterns contain hostnames 346 | } 347 | type muxEntry struct { 348 | explicit bool 349 | h Handler 350 | pattern string 351 | } 352 | // NewServeMux allocates and returns a new ServeMux. 353 | func NewServeMux() *ServeMux { return &ServeMux{m: make(map[string]muxEntry)} } 354 | // DefaultServeMux is the default ServeMux used by Serve. 355 | var DefaultServeMux = NewServeMux() 356 | ``` 357 | 358 | **handler接口的定义** 359 | 360 | ```go 361 | type Handler interface { 362 | ServeHTTP(ResponseWriter, *Request) 363 | } 364 | ``` 365 | 366 | ## 2.5.5. ServeMux.ServeHTTP 367 | 368 | ```go 369 | // ServeHTTP dispatches the request to the handler whose 370 | // pattern most closely matches the request URL. 371 | func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { 372 | if r.RequestURI == "*" { 373 | if r.ProtoAtLeast(1, 1) { 374 | w.Header().Set("Connection", "close") 375 | } 376 | w.WriteHeader(StatusBadRequest) 377 | return 378 | } 379 | h, _ := mux.Handler(r) 380 | h.ServeHTTP(w, r) 381 | } 382 | ``` 383 | 384 | **mux.Handler(r)** 385 | 386 | ```go 387 | // Handler returns the handler to use for the given request, 388 | // consulting r.Method, r.Host, and r.URL.Path. It always returns 389 | // a non-nil handler. If the path is not in its canonical form, the 390 | // handler will be an internally-generated handler that redirects 391 | // to the canonical path. 392 | // 393 | // Handler also returns the registered pattern that matches the 394 | // request or, in the case of internally-generated redirects, 395 | // the pattern that will match after following the redirect. 396 | // 397 | // If there is no registered handler that applies to the request, 398 | // Handler returns a ``page not found'' handler and an empty pattern. 399 | func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) { 400 | if r.Method != "CONNECT" { 401 | if p := cleanPath(r.URL.Path); p != r.URL.Path { 402 | _, pattern = mux.handler(r.Host, p) 403 | url := *r.URL 404 | url.Path = p 405 | return RedirectHandler(url.String(), StatusMovedPermanently), pattern 406 | } 407 | } 408 | 409 | return mux.handler(r.Host, r.URL.Path) 410 | } 411 | 412 | // handler is the main implementation of Handler. 413 | // The path is known to be in canonical form, except for CONNECT methods. 414 | func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) { 415 | mux.mu.RLock() 416 | defer mux.mu.RUnlock() 417 | 418 | // Host-specific pattern takes precedence over generic ones 419 | if mux.hosts { 420 | h, pattern = mux.match(host + path) 421 | } 422 | if h == nil { 423 | h, pattern = mux.match(path) 424 | } 425 | if h == nil { 426 | h, pattern = NotFoundHandler(), "" 427 | } 428 | return 429 | } 430 | ``` 431 | 432 | ## 2.6. http连接处理流程图 433 | 434 | ![image2017-3-5 23-50-6](https://res.cloudinary.com/dqxtn0ick/image/upload/v1510578730/article/golang/http/http-connect-flow.png) 435 | 436 | # 3. http的执行流程总结 437 | 438 | 1、首先调用Http.HandleFunc,按如下顺序执行: 439 | 440 | 1. 调用了DefaultServerMux的HandleFunc。 441 | 2. 调用了DefaultServerMux的Handle。 442 | 3. 往DefaultServerMux的map[string] muxEntry中增加对应的handler和路由规则。 443 | 444 | 2、调用http.ListenAndServe(":9090",nil),按如下顺序执行: 445 | 446 | 1. 实例化Server。 447 | 2. 调用Server的ListenAndServe()。 448 | 3. 调用net.Listen("tcp",addr)监听端口。 449 | 4. 启动一个for循环,在循环体中Accept请求。 450 | 5. 对每个请求实例化一个Conn,并且开启一个goroutine为这个请求进行服务go c.serve()。 451 | 6. 读取每个请求的内容w,err:=c.readRequest()。 452 | 7. 判断handler是否为空,如果没有设置handler,handler默认设置为DefaultServeMux。 453 | 8. 调用handler的ServeHttp。 454 | 9. 根据request选择handler,并且进入到这个handler的ServeHTTP, 455 | mux.handler(r).ServeHTTP(w,r) 456 | 10. 选择handler 457 | 458 | - 判断是否有路由能满足这个request(循环遍历ServeMux的muxEntry)。 459 | - 如果有路由满足,调用这个路由handler的ServeHttp。 460 | - 如果没有路由满足,调用NotFoundHandler的ServeHttp。 461 | 462 | # 4. 自定义路由 463 | 464 | Go支持外部实现路由器,ListenAndServe的第二个参数就是配置外部路由器,它是一个Handler接口。即外部路由器实现Hanlder接口。 465 | 466 | Handler接口: 467 | 468 | ```go 469 | type Handler interface { 470 | ServeHTTP(ResponseWriter, *Request) 471 | } 472 | ``` 473 | 474 | 自定义路由 475 | 476 | ```go 477 | package main 478 | import ( 479 | "fmt" 480 | "net/http" 481 | ) 482 | type MyMux struct{ 483 | } 484 | func (p *MyMux) ServeHTTP(w http.ResponseWriter,r *http.Request){ 485 | if r.URL.Path=="/"{ 486 | sayhelloName(w,r) 487 | return 488 | } 489 | http.NotFound(w,r) 490 | return 491 | } 492 | func sayhelloName(w http.ResponseWriter,r *http.Request){ 493 | fmt.Fprintln(w,"Hello myroute") 494 | } 495 | func main() { 496 | mux:=&MyMux{} 497 | http.ListenAndServe(":9090",mux) 498 | 499 | } 500 | ``` 501 | 502 | 文章参考: 503 | 504 | 《Go web编程》 505 | -------------------------------------------------------------------------------- /code/confd-code-analysis.md: -------------------------------------------------------------------------------- 1 | > confd的源码参考:https://github.com/kelseyhightower/confd 2 | 3 | 本文分析的`confd`的版本是`v0.16.0`,代码参考:https://github.com/kelseyhightower/confd/tree/v0.16.0。 4 | 5 | # 1. [Main](https://github.com/kelseyhightower/confd/blob/v0.16.0/confd.go#L16) 6 | 7 | confd的入口函数 Main 函数,先解析参数,如果是打印版本信息的参数,则执行打印版本的命令。 8 | 9 | ```go 10 | func main() { 11 | flag.Parse() 12 | if config.PrintVersion { 13 | fmt.Printf("confd %s (Git SHA: %s, Go Version: %s)\n", Version, GitSHA, runtime.Version()) 14 | os.Exit(0) 15 | } 16 | ... 17 | } 18 | ``` 19 | 20 | 其中版本信息记录在https://github.com/kelseyhightower/confd/blob/v0.16.0/version.go#L3 21 | 22 | ```go 23 | const Version = "0.16.0" 24 | ``` 25 | 26 | ## 1.1. [initConfig](https://github.com/kelseyhightower/confd/blob/v0.16.0/config.go#L82) 27 | 28 | 初始化配置文件。 29 | 30 | ```go 31 | if err := initConfig(); err != nil { 32 | log.Fatal(err.Error()) 33 | } 34 | ``` 35 | 36 | `initConfig`函数对基本的配置内容做初始化,当没有指定后端存储的时候,设置默认存储。 37 | 38 | ```go 39 | // initConfig initializes the confd configuration by first setting defaults, 40 | // then overriding settings from the confd config file, then overriding 41 | // settings from environment variables, and finally overriding 42 | // settings from flags set on the command line. 43 | // It returns an error if any. 44 | func initConfig() error { 45 | _, err := os.Stat(config.ConfigFile) 46 | if os.IsNotExist(err) { 47 | log.Debug("Skipping confd config file.") 48 | } else { 49 | log.Debug("Loading " + config.ConfigFile) 50 | configBytes, err := ioutil.ReadFile(config.ConfigFile) 51 | if err != nil { 52 | return err 53 | } 54 | 55 | _, err = toml.Decode(string(configBytes), &config) 56 | if err != nil { 57 | return err 58 | } 59 | } 60 | 61 | // Update config from environment variables. 62 | processEnv() 63 | 64 | if config.SecretKeyring != "" { 65 | kr, err := os.Open(config.SecretKeyring) 66 | if err != nil { 67 | log.Fatal(err.Error()) 68 | } 69 | defer kr.Close() 70 | config.PGPPrivateKey, err = ioutil.ReadAll(kr) 71 | if err != nil { 72 | log.Fatal(err.Error()) 73 | } 74 | } 75 | 76 | if config.LogLevel != "" { 77 | log.SetLevel(config.LogLevel) 78 | } 79 | 80 | if config.SRVDomain != "" && config.SRVRecord == "" { 81 | config.SRVRecord = fmt.Sprintf("_%s._tcp.%s.", config.Backend, config.SRVDomain) 82 | } 83 | 84 | // Update BackendNodes from SRV records. 85 | if config.Backend != "env" && config.SRVRecord != "" { 86 | log.Info("SRV record set to " + config.SRVRecord) 87 | srvNodes, err := getBackendNodesFromSRV(config.SRVRecord) 88 | if err != nil { 89 | return errors.New("Cannot get nodes from SRV records " + err.Error()) 90 | } 91 | 92 | switch config.Backend { 93 | case "etcd": 94 | vsm := make([]string, len(srvNodes)) 95 | for i, v := range srvNodes { 96 | vsm[i] = config.Scheme + "://" + v 97 | } 98 | srvNodes = vsm 99 | } 100 | 101 | config.BackendNodes = srvNodes 102 | } 103 | if len(config.BackendNodes) == 0 { 104 | switch config.Backend { 105 | case "consul": 106 | config.BackendNodes = []string{"127.0.0.1:8500"} 107 | case "etcd": 108 | peerstr := os.Getenv("ETCDCTL_PEERS") 109 | if len(peerstr) > 0 { 110 | config.BackendNodes = strings.Split(peerstr, ",") 111 | } else { 112 | config.BackendNodes = []string{"http://127.0.0.1:4001"} 113 | } 114 | case "etcdv3": 115 | config.BackendNodes = []string{"127.0.0.1:2379"} 116 | case "redis": 117 | config.BackendNodes = []string{"127.0.0.1:6379"} 118 | case "vault": 119 | config.BackendNodes = []string{"http://127.0.0.1:8200"} 120 | case "zookeeper": 121 | config.BackendNodes = []string{"127.0.0.1:2181"} 122 | } 123 | } 124 | // Initialize the storage client 125 | log.Info("Backend set to " + config.Backend) 126 | 127 | if config.Watch { 128 | unsupportedBackends := map[string]bool{ 129 | "dynamodb": true, 130 | "ssm": true, 131 | } 132 | 133 | if unsupportedBackends[config.Backend] { 134 | log.Info(fmt.Sprintf("Watch is not supported for backend %s. Exiting...", config.Backend)) 135 | os.Exit(1) 136 | } 137 | } 138 | 139 | if config.Backend == "dynamodb" && config.Table == "" { 140 | return errors.New("No DynamoDB table configured") 141 | } 142 | config.ConfigDir = filepath.Join(config.ConfDir, "conf.d") 143 | config.TemplateDir = filepath.Join(config.ConfDir, "templates") 144 | return nil 145 | } 146 | ``` 147 | 148 | ## 1.2. storeClient 149 | 150 | ```go 151 | log.Info("Starting confd") 152 | 153 | storeClient, err := backends.New(config.BackendsConfig) 154 | if err != nil { 155 | log.Fatal(err.Error()) 156 | } 157 | ``` 158 | 159 | 根据配置文件中的存储后端类型构造一个存储后端的client,其中主要调用的函数为[backends.New(config.BackendsConfig)](https://github.com/kelseyhightower/confd/blob/v0.16.0/backends/client.go#L29)。 160 | 161 | 当没有设置存储后端时,默认为`etcd`。 162 | 163 | ```go 164 | if config.Backend == "" { 165 | config.Backend = "etcd" 166 | } 167 | backendNodes := config.BackendNodes 168 | ``` 169 | 170 | 当存储后端为`file`类型的处理。 171 | 172 | ```go 173 | if config.Backend == "file" { 174 | log.Info("Backend source(s) set to " + strings.Join(config.YAMLFile, ", ")) 175 | } else { 176 | log.Info("Backend source(s) set to " + strings.Join(backendNodes, ", ")) 177 | } 178 | ``` 179 | 180 | 最后再根据不同类型的存储后端,调用不同的存储后端构建函数,本文只分析`redis`类型的存储后端。 181 | 182 | ```go 183 | switch config.Backend { 184 | case "consul": 185 | return consul.New(config.BackendNodes, config.Scheme, 186 | config.ClientCert, config.ClientKey, 187 | config.ClientCaKeys, 188 | config.BasicAuth, 189 | config.Username, 190 | config.Password, 191 | ) 192 | case "etcd": 193 | // Create the etcd client upfront and use it for the life of the process. 194 | // The etcdClient is an http.Client and designed to be reused. 195 | return etcd.NewEtcdClient(backendNodes, config.ClientCert, config.ClientKey, config.ClientCaKeys, config.BasicAuth, config.Username, config.Password) 196 | case "etcdv3": 197 | return etcdv3.NewEtcdClient(backendNodes, config.ClientCert, config.ClientKey, config.ClientCaKeys, config.BasicAuth, config.Username, config.Password) 198 | case "zookeeper": 199 | return zookeeper.NewZookeeperClient(backendNodes) 200 | case "rancher": 201 | return rancher.NewRancherClient(backendNodes) 202 | case "redis": 203 | return redis.NewRedisClient(backendNodes, config.ClientKey, config.Separator) 204 | case "env": 205 | return env.NewEnvClient() 206 | case "file": 207 | return file.NewFileClient(config.YAMLFile, config.Filter) 208 | case "vault": 209 | vaultConfig := map[string]string{ 210 | "app-id": config.AppID, 211 | "user-id": config.UserID, 212 | "role-id": config.RoleID, 213 | "secret-id": config.SecretID, 214 | "username": config.Username, 215 | "password": config.Password, 216 | "token": config.AuthToken, 217 | "cert": config.ClientCert, 218 | "key": config.ClientKey, 219 | "caCert": config.ClientCaKeys, 220 | "path": config.Path, 221 | } 222 | return vault.New(backendNodes[0], config.AuthType, vaultConfig) 223 | case "dynamodb": 224 | table := config.Table 225 | log.Info("DynamoDB table set to " + table) 226 | return dynamodb.NewDynamoDBClient(table) 227 | case "ssm": 228 | return ssm.New() 229 | } 230 | return nil, errors.New("Invalid backend") 231 | ``` 232 | 233 | 其中redis类型的存储后端调用了`NewRedisClient`方法来构造redis的client。 234 | 235 | ```go 236 | case "redis": 237 | return redis.NewRedisClient(backendNodes, config.ClientKey, config.Separator) 238 | ``` 239 | 240 | 其中涉及三个参数: 241 | 242 | - `backendNodes`:redis的节点地址。 243 | - `ClientKey`:redis的密码。 244 | - `Separator`:查找redis键的分隔符,该参数只用在redis类型。 245 | 246 | `NewRedisClient`函数方法如下: 247 | 248 | ```go 249 | // NewRedisClient returns an *redis.Client with a connection to named machines. 250 | // It returns an error if a connection to the cluster cannot be made. 251 | func NewRedisClient(machines []string, password string, separator string) (*Client, error) { 252 | if separator == "" { 253 | separator = "/" 254 | } 255 | log.Debug(fmt.Sprintf("Redis Separator: %#v", separator)) 256 | var err error 257 | clientWrapper := &Client{machines: machines, password: password, separator: separator, client: nil, pscChan: make(chan watchResponse), psc: redis.PubSubConn{Conn: nil} } 258 | clientWrapper.client, _, err = tryConnect(machines, password, true) 259 | return clientWrapper, err 260 | } 261 | ``` 262 | 263 | ## 1.3. processor 264 | 265 | ```go 266 | stopChan := make(chan bool) 267 | doneChan := make(chan bool) 268 | errChan := make(chan error, 10) 269 | 270 | var processor template.Processor 271 | switch { 272 | case config.Watch: 273 | processor = template.WatchProcessor(config.TemplateConfig, stopChan, doneChan, errChan) 274 | default: 275 | processor = template.IntervalProcessor(config.TemplateConfig, stopChan, doneChan, errChan, config.Interval) 276 | } 277 | 278 | go processor.Process() 279 | ``` 280 | 281 | 当开启`watch`参数的时候,则构造`WatchProcessor`,否则构造`IntervalProcessor`,最后起一个goroutine。 282 | 283 | ```go 284 | go processor.Process() 285 | ``` 286 | 287 | 这块的逻辑在本文第二部分析。 288 | 289 | ## 1.4. signalChan 290 | 291 | ```go 292 | signalChan := make(chan os.Signal, 1) 293 | signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) 294 | for { 295 | select { 296 | case err := <-errChan: 297 | log.Error(err.Error()) 298 | case s := <-signalChan: 299 | log.Info(fmt.Sprintf("Captured %v. Exiting...", s)) 300 | close(doneChan) 301 | case <-doneChan: 302 | os.Exit(0) 303 | } 304 | } 305 | ``` 306 | 307 | # 2. [Process](https://github.com/kelseyhightower/confd/blob/v0.16.0/resource/template/processor.go#L12) 308 | 309 | ```go 310 | type Processor interface { 311 | Process() 312 | } 313 | ``` 314 | 315 | `Processor`是一个接口类型,主要的实现体有: 316 | 317 | - `intervalProcessor`:默认的实现体,即没有添加watch参数。 318 | - `watchProcessor`:添加watch参数的实现体。 319 | 320 | ## 2.1. intervalProcessor 321 | 322 | ```go 323 | type intervalProcessor struct { 324 | config Config 325 | stopChan chan bool 326 | doneChan chan bool 327 | errChan chan error 328 | interval int 329 | } 330 | ``` 331 | 332 | intervalProcessor根据config内容和几个channel构造一个intervalProcessor。 333 | 334 | ```go 335 | func IntervalProcessor(config Config, stopChan, doneChan chan bool, errChan chan error, interval int) Processor { 336 | return &intervalProcessor{config, stopChan, doneChan, errChan, interval} 337 | } 338 | ``` 339 | 340 | ### 2.1.1. intervalProcessor.Process 341 | 342 | ```go 343 | func (p *intervalProcessor) Process() { 344 | defer close(p.doneChan) 345 | for { 346 | ts, err := getTemplateResources(p.config) 347 | if err != nil { 348 | log.Fatal(err.Error()) 349 | break 350 | } 351 | process(ts) 352 | select { 353 | case <-p.stopChan: 354 | break 355 | case <-time.After(time.Duration(p.interval) * time.Second): 356 | continue 357 | } 358 | } 359 | } 360 | ``` 361 | 362 | 通过解析config内容获取`TemplateResources`,其中核心函数为`process(ts)`,然后执行`t.process()`,该函数中会调用`t.sync()`。`t.process()`的具体逻辑后文分析。 363 | 364 | ```go 365 | func process(ts []*TemplateResource) error { 366 | var lastErr error 367 | for _, t := range ts { 368 | if err := t.process(); err != nil { 369 | log.Error(err.Error()) 370 | lastErr = err 371 | } 372 | } 373 | return lastErr 374 | } 375 | ``` 376 | 377 | ## 2.2. watchProcessor 378 | 379 | ``` 380 | type watchProcessor struct { 381 | config Config 382 | stopChan chan bool 383 | doneChan chan bool 384 | errChan chan error 385 | wg sync.WaitGroup 386 | } 387 | ``` 388 | 389 | watchProcessor根据config内容和几个channel构造一个watchProcessor。 390 | 391 | ```go 392 | func WatchProcessor(config Config, stopChan, doneChan chan bool, errChan chan error) Processor { 393 | var wg sync.WaitGroup 394 | return &watchProcessor{config, stopChan, doneChan, errChan, wg} 395 | } 396 | ``` 397 | 398 | ### 2.2.1. watchProcessor.Process 399 | 400 | ```go 401 | func (p *watchProcessor) Process() { 402 | defer close(p.doneChan) 403 | ts, err := getTemplateResources(p.config) 404 | if err != nil { 405 | log.Fatal(err.Error()) 406 | return 407 | } 408 | for _, t := range ts { 409 | t := t 410 | p.wg.Add(1) 411 | go p.monitorPrefix(t) 412 | } 413 | p.wg.Wait() 414 | } 415 | ``` 416 | 417 | `watchProcessor.Process`方法实现了`Processor`接口中定义的方法,通过解析config内容获取`TemplateResources`,再遍历`TemplateResources`执行`monitorPrefix`,有多少个`TemplateResources`就运行多少个`monitorPrefix`的goroutine。 418 | 419 | ### 2.2.2. monitorPrefix 420 | 421 | ```go 422 | func (p *watchProcessor) monitorPrefix(t *TemplateResource) { 423 | defer p.wg.Done() 424 | keys := util.AppendPrefix(t.Prefix, t.Keys) 425 | for { 426 | index, err := t.storeClient.WatchPrefix(t.Prefix, keys, t.lastIndex, p.stopChan) 427 | if err != nil { 428 | p.errChan <- err 429 | // Prevent backend errors from consuming all resources. 430 | time.Sleep(time.Second * 2) 431 | continue 432 | } 433 | t.lastIndex = index 434 | if err := t.process(); err != nil { 435 | p.errChan <- err 436 | } 437 | } 438 | } 439 | ``` 440 | 441 | 先对配置文件中的`prefix`和`keys`参数进行拼接。 442 | 443 | ```go 444 | keys := util.AppendPrefix(t.Prefix, t.Keys) 445 | ``` 446 | 447 | `AppendPrefix`函数如下: 448 | 449 | ```go 450 | func AppendPrefix(prefix string, keys []string) []string { 451 | s := make([]string, len(keys)) 452 | for i, k := range keys { 453 | s[i] = path.Join(prefix, k) 454 | } 455 | return s 456 | } 457 | ``` 458 | 459 | 接着再执行`storeClient`的`WatchPrefix`方法,因为`storeClient`是一个接口,对应不同类型的存储后端,`WatchPrefix`的实现逻辑也不同,本文分析的存储类型为`redis`。 460 | 461 | ```go 462 | index, err := t.storeClient.WatchPrefix(t.Prefix, keys, t.lastIndex, p.stopChan) 463 | if err != nil { 464 | p.errChan <- err 465 | // Prevent backend errors from consuming all resources. 466 | time.Sleep(time.Second * 2) 467 | continue 468 | } 469 | ``` 470 | 471 | `storeClient.WatchPrefix`主要是获取`lastIndex`的值,这个值在`t.process()`中使用。 472 | 473 | ```go 474 | t.lastIndex = index 475 | if err := t.process(); err != nil { 476 | p.errChan <- err 477 | } 478 | ``` 479 | 480 | ## 2.3. TemplateResource.process 481 | 482 | 无论是否加`watch`参数,即`intervalProcessor`和`watchProcessor`最终都会调用到`TemplateResource.process`这个函数,而这个函数中的核心函数为`t.sync()`。 483 | 484 | ```go 485 | // process is a convenience function that wraps calls to the three main tasks 486 | // required to keep local configuration files in sync. First we gather vars 487 | // from the store, then we stage a candidate configuration file, and finally sync 488 | // things up. 489 | // It returns an error if any. 490 | func (t *TemplateResource) process() error { 491 | if err := t.setFileMode(); err != nil { 492 | return err 493 | } 494 | if err := t.setVars(); err != nil { 495 | return err 496 | } 497 | if err := t.createStageFile(); err != nil { 498 | return err 499 | } 500 | if err := t.sync(); err != nil { 501 | return err 502 | } 503 | return nil 504 | } 505 | ``` 506 | 507 | ### 2.3.1. setFileMode 508 | 509 | `setFileMode`设置文件的权限,如果没有在配置文件指定`mode`参数则默认为`0644`,否则根据配置文件中指定的`mode`来设置文件权限。 510 | 511 | ```go 512 | // setFileMode sets the FileMode. 513 | func (t *TemplateResource) setFileMode() error { 514 | if t.Mode == "" { 515 | if !util.IsFileExist(t.Dest) { 516 | t.FileMode = 0644 517 | } else { 518 | fi, err := os.Stat(t.Dest) 519 | if err != nil { 520 | return err 521 | } 522 | t.FileMode = fi.Mode() 523 | } 524 | } else { 525 | mode, err := strconv.ParseUint(t.Mode, 0, 32) 526 | if err != nil { 527 | return err 528 | } 529 | t.FileMode = os.FileMode(mode) 530 | } 531 | return nil 532 | } 533 | ``` 534 | 535 | ### 2.3.2. setVars 536 | 537 | `setVars`将后端存储中最新的值拿出来暂存到内存中供后续进程使用。其中根据不同的后端,`storeClient.GetValues`的逻辑可能不同,但通过接口的方式可以让不同的存储后端实现不同的获取值的方法。 538 | 539 | ```go 540 | // setVars sets the Vars for template resource. 541 | func (t *TemplateResource) setVars() error { 542 | var err error 543 | log.Debug("Retrieving keys from store") 544 | log.Debug("Key prefix set to " + t.Prefix) 545 | 546 | result, err := t.storeClient.GetValues(util.AppendPrefix(t.Prefix, t.Keys)) 547 | if err != nil { 548 | return err 549 | } 550 | log.Debug("Got the following map from store: %v", result) 551 | 552 | t.store.Purge() 553 | 554 | for k, v := range result { 555 | t.store.Set(path.Join("/", strings.TrimPrefix(k, t.Prefix)), v) 556 | } 557 | return nil 558 | } 559 | ``` 560 | 561 | ### 2.3.3. createStageFile 562 | 563 | `createStageFile`通过`src`的`template`文件和最新内存中的变量数据生成`StageFile`,该文件在sync中和目标文件进行比较,看是否有修改。即`StageFile`实际上是根据后端存储生成的最新的配置文件,如果这份配置文件跟当前的配置文件不同,表明后端存储的数据被更新了需要重新生成一份新的配置文件。 564 | 565 | ```go 566 | // createStageFile stages the src configuration file by processing the src 567 | // template and setting the desired owner, group, and mode. It also sets the 568 | // StageFile for the template resource. 569 | // It returns an error if any. 570 | func (t *TemplateResource) createStageFile() error { 571 | log.Debug("Using source template " + t.Src) 572 | 573 | if !util.IsFileExist(t.Src) { 574 | return errors.New("Missing template: " + t.Src) 575 | } 576 | 577 | log.Debug("Compiling source template " + t.Src) 578 | 579 | tmpl, err := template.New(filepath.Base(t.Src)).Funcs(t.funcMap).ParseFiles(t.Src) 580 | if err != nil { 581 | return fmt.Errorf("Unable to process template %s, %s", t.Src, err) 582 | } 583 | 584 | // create TempFile in Dest directory to avoid cross-filesystem issues 585 | temp, err := ioutil.TempFile(filepath.Dir(t.Dest), "."+filepath.Base(t.Dest)) 586 | if err != nil { 587 | return err 588 | } 589 | 590 | if err = tmpl.Execute(temp, nil); err != nil { 591 | temp.Close() 592 | os.Remove(temp.Name()) 593 | return err 594 | } 595 | defer temp.Close() 596 | 597 | // Set the owner, group, and mode on the stage file now to make it easier to 598 | // compare against the destination configuration file later. 599 | os.Chmod(temp.Name(), t.FileMode) 600 | os.Chown(temp.Name(), t.Uid, t.Gid) 601 | t.StageFile = temp 602 | return nil 603 | } 604 | ``` 605 | 606 | ### 2.3.4. sync 607 | 608 | ```go 609 | if err := t.sync(); err != nil { 610 | return err 611 | } 612 | ``` 613 | 614 | `t.sync()`是执行confd核心功能的函数,将配置文件通过模板的方式自动生成,并执行检查命令和reload命令。该部分逻辑在本文第三部分分析。 615 | 616 | # 3. [sync](https://github.com/kelseyhightower/confd/blob/v0.16.0/resource/template/resource.go#L238) 617 | 618 | `sync`通过比较源文件和目标文件的差别,如果不同则重新生成新的配置,当设置了`check_cmd`和`reload_cmd`的时候,会执行`check_cmd`指定的检查命令,如果都没有问题则执行`reload_cmd`中指定的reload命令。 619 | 620 | ## 3.1. IsConfigChanged 621 | 622 | `IsConfigChanged`比较源文件和目标文件是否相等,其中比较内容包括:`Uid`、`Gid`、`Mode`、`Md5`。只要其中任意值不同则认为两个文件不同。 623 | 624 | ```go 625 | // IsConfigChanged reports whether src and dest config files are equal. 626 | // Two config files are equal when they have the same file contents and 627 | // Unix permissions. The owner, group, and mode must match. 628 | // It return false in other cases. 629 | func IsConfigChanged(src, dest string) (bool, error) { 630 | if !IsFileExist(dest) { 631 | return true, nil 632 | } 633 | d, err := FileStat(dest) 634 | if err != nil { 635 | return true, err 636 | } 637 | s, err := FileStat(src) 638 | if err != nil { 639 | return true, err 640 | } 641 | if d.Uid != s.Uid { 642 | log.Info(fmt.Sprintf("%s has UID %d should be %d", dest, d.Uid, s.Uid)) 643 | } 644 | if d.Gid != s.Gid { 645 | log.Info(fmt.Sprintf("%s has GID %d should be %d", dest, d.Gid, s.Gid)) 646 | } 647 | if d.Mode != s.Mode { 648 | log.Info(fmt.Sprintf("%s has mode %s should be %s", dest, os.FileMode(d.Mode), os.FileMode(s.Mode))) 649 | } 650 | if d.Md5 != s.Md5 { 651 | log.Info(fmt.Sprintf("%s has md5sum %s should be %s", dest, d.Md5, s.Md5)) 652 | } 653 | if d.Uid != s.Uid || d.Gid != s.Gid || d.Mode != s.Mode || d.Md5 != s.Md5 { 654 | return true, nil 655 | } 656 | return false, nil 657 | } 658 | ``` 659 | 660 | 如果文件发生改变则执行`check_cmd`命令(有配置的情况下),重新生成配置文件,并执行`reload_cmd`命令(有配置的情况下)。 661 | 662 | ```go 663 | if ok { 664 | log.Info("Target config " + t.Dest + " out of sync") 665 | if !t.syncOnly && t.CheckCmd != "" { 666 | if err := t.check(); err != nil { 667 | return errors.New("Config check failed: " + err.Error()) 668 | } 669 | } 670 | log.Debug("Overwriting target config " + t.Dest) 671 | err := os.Rename(staged, t.Dest) 672 | if err != nil { 673 | if strings.Contains(err.Error(), "device or resource busy") { 674 | log.Debug("Rename failed - target is likely a mount. Trying to write instead") 675 | // try to open the file and write to it 676 | var contents []byte 677 | var rerr error 678 | contents, rerr = ioutil.ReadFile(staged) 679 | if rerr != nil { 680 | return rerr 681 | } 682 | err := ioutil.WriteFile(t.Dest, contents, t.FileMode) 683 | // make sure owner and group match the temp file, in case the file was created with WriteFile 684 | os.Chown(t.Dest, t.Uid, t.Gid) 685 | if err != nil { 686 | return err 687 | } 688 | } else { 689 | return err 690 | } 691 | } 692 | if !t.syncOnly && t.ReloadCmd != "" { 693 | if err := t.reload(); err != nil { 694 | return err 695 | } 696 | } 697 | log.Info("Target config " + t.Dest + " has been updated") 698 | } else { 699 | log.Debug("Target config " + t.Dest + " in sync") 700 | } 701 | ``` 702 | 703 | ## 3.2. check 704 | 705 | `check`检查暂存的配置文件即`stageFile`,该文件是由最新的后端存储中的数据生成的。 706 | 707 | ```go 708 | if !t.syncOnly && t.CheckCmd != "" { 709 | if err := t.check(); err != nil { 710 | return errors.New("Config check failed: " + err.Error()) 711 | } 712 | } 713 | ``` 714 | 715 | `t.check()`只是执行配置文件中`checkcmd`参数指定的命令而已,根据是否执行成功来返回报错。当`check`命令产生错误的是,则直接return报错,不再执行重新生成配置文件和``reload`的操作了。 716 | 717 | ```go 718 | // check executes the check command to validate the staged config file. The 719 | // command is modified so that any references to src template are substituted 720 | // with a string representing the full path of the staged file. This allows the 721 | // check to be run on the staged file before overwriting the destination config 722 | // file. 723 | // It returns nil if the check command returns 0 and there are no other errors. 724 | func (t *TemplateResource) check() error { 725 | var cmdBuffer bytes.Buffer 726 | data := make(map[string]string) 727 | data["src"] = t.StageFile.Name() 728 | tmpl, err := template.New("checkcmd").Parse(t.CheckCmd) 729 | if err != nil { 730 | return err 731 | } 732 | if err := tmpl.Execute(&cmdBuffer, data); err != nil { 733 | return err 734 | } 735 | return runCommand(cmdBuffer.String()) 736 | } 737 | ``` 738 | 739 | `check`会通过模板解析的方式解析出`checkcmd`中的`{{.src}}`部分,并用`stageFile`来替代。即check的命令是拉取最新后端存储的数据形成临时配置文件(stageFile),并通过指定的`checkcmd`来检查最新的临时配置文件是否合法,如果合法则替换会新的配置文件,否则返回错误。 740 | 741 | ## 3.3. Overwriting 742 | 743 | 将`staged`文件命名为`Dest`文件的名字,读取`staged`文件中的内容并将它写入到`Dest`文件中,该过程实际上就是重新生成一份新的配置文件。`staged`文件的生成逻辑在函数`createStageFile`中。 744 | 745 | ```go 746 | log.Debug("Overwriting target config " + t.Dest) 747 | err := os.Rename(staged, t.Dest) 748 | if err != nil { 749 | if strings.Contains(err.Error(), "device or resource busy") { 750 | log.Debug("Rename failed - target is likely a mount. Trying to write instead") 751 | // try to open the file and write to it 752 | var contents []byte 753 | var rerr error 754 | contents, rerr = ioutil.ReadFile(staged) 755 | if rerr != nil { 756 | return rerr 757 | } 758 | err := ioutil.WriteFile(t.Dest, contents, t.FileMode) 759 | // make sure owner and group match the temp file, in case the file was created with WriteFile 760 | os.Chown(t.Dest, t.Uid, t.Gid) 761 | if err != nil { 762 | return err 763 | } 764 | } else { 765 | return err 766 | } 767 | } 768 | ``` 769 | 770 | ## 3.4. reload 771 | 772 | 如果没有指定syncOnly参数并且指定了`ReloadCmd`则执行`reload`操作。 773 | 774 | ```go 775 | if !t.syncOnly && t.ReloadCmd != "" { 776 | if err := t.reload(); err != nil { 777 | return err 778 | } 779 | } 780 | ``` 781 | 782 | 其中`t.reload()`实现如下: 783 | 784 | ```go 785 | // reload executes the reload command. 786 | // It returns nil if the reload command returns 0. 787 | func (t *TemplateResource) reload() error { 788 | return runCommand(t.ReloadCmd) 789 | } 790 | ``` 791 | 792 | `t.reload()`和`t.check()`都调用了`runCommand`函数: 793 | 794 | ```go 795 | // runCommand is a shared function used by check and reload 796 | // to run the given command and log its output. 797 | // It returns nil if the given cmd returns 0. 798 | // The command can be run on unix and windows. 799 | func runCommand(cmd string) error { 800 | log.Debug("Running " + cmd) 801 | var c *exec.Cmd 802 | if runtime.GOOS == "windows" { 803 | c = exec.Command("cmd", "/C", cmd) 804 | } else { 805 | c = exec.Command("/bin/sh", "-c", cmd) 806 | } 807 | 808 | output, err := c.CombinedOutput() 809 | if err != nil { 810 | log.Error(fmt.Sprintf("%q", string(output))) 811 | return err 812 | } 813 | log.Debug(fmt.Sprintf("%q", string(output))) 814 | return nil 815 | } 816 | ``` 817 | 818 | # 4. [redisClient.WatchPrefix](https://github.com/kelseyhightower/confd/blob/v0.16.0/backends/redis/client.go#L228) 819 | 820 | `redisClient.WatchPrefix`是当用户设置了`watch`参数的时候,并且存储后端为`redis`,则会调用到redis的watch机制。其中`redisClient.WatchPrefix`是redis存储类型的时候实现了`StoreClient`接口的`WatchPrefix`方法。 821 | 822 | ```go 823 | // The StoreClient interface is implemented by objects that can retrieve 824 | // key/value pairs from a backend store. 825 | type StoreClient interface { 826 | GetValues(keys []string) (map[string]string, error) 827 | WatchPrefix(prefix string, keys []string, waitIndex uint64, stopChan chan bool) (uint64, error) 828 | } 829 | ``` 830 | 831 | `StoreClient`是对后端存储类型的抽象,常用的后端存储类型有`Etcd`和`Redis`等,不同的后端存储类型`GetValues`和`WatchPrefix`的具体实现不同,本文主要分析Redis类型的`watch`机制。 832 | 833 | ## 4.1. WatchPrefix 834 | 835 | WatchPrefix的调用函数在[monitorPrefix](#222-monitorPrefix)的部分,具体参考: 836 | 837 | ```go 838 | func (p *watchProcessor) monitorPrefix(t *TemplateResource) { 839 | defer p.wg.Done() 840 | keys := util.AppendPrefix(t.Prefix, t.Keys) 841 | for { 842 | index, err := t.storeClient.WatchPrefix(t.Prefix, keys, t.lastIndex, p.stopChan) 843 | if err != nil { 844 | p.errChan <- err 845 | // Prevent backend errors from consuming all resources. 846 | time.Sleep(time.Second * 2) 847 | continue 848 | } 849 | t.lastIndex = index 850 | if err := t.process(); err != nil { 851 | p.errChan <- err 852 | } 853 | } 854 | } 855 | ``` 856 | 857 | redis的`watch`主要通过`pub-sub`的机制,即WatchPrefix会根据传入的`prefix`起一个sub的监听机制,而在写入redis的数据的同时需要执行redis的publish操作,channel为符合prefix的值,value为给定命令之一,实际上是给定命令之一,具体是什么命令并没有关系,则会触发watch机制,从而自动更新配置,给定的命令列表如下: 858 | 859 | ```go 860 | "del", "append", "rename_from", "rename_to", "expire", "set", "incrby", "incrbyfloat", "hset", "hincrby", "hincrbyfloat", "hdel" 861 | ``` 862 | 863 | sub监听的key的格式如下: 864 | 865 | ```go 866 | __keyspace@0__:{prefix}/* 867 | ``` 868 | 869 | 如果只是写入redis数据而没有自动执行publish的操作,并不会触发redis的watch机制来自动更新配置。但是如果使用etcd,则etcd的watch机制,只需要用户写入或更新数据就可以自动触发更新配置。 870 | 871 | `WatchPrefix`源码如下: 872 | 873 | ```go 874 | func (c *Client) WatchPrefix(prefix string, keys []string, waitIndex uint64, stopChan chan bool) (uint64, error) { 875 | 876 | if waitIndex == 0 { 877 | return 1, nil 878 | } 879 | 880 | if len(c.pscChan) > 0 { 881 | var respChan watchResponse 882 | for len(c.pscChan) > 0 { 883 | respChan = <-c.pscChan 884 | } 885 | return respChan.waitIndex, respChan.err 886 | } 887 | 888 | go func() { 889 | if c.psc.Conn == nil { 890 | rClient, db, err := tryConnect(c.machines, c.password, false); 891 | 892 | if err != nil { 893 | c.psc = redis.PubSubConn{Conn: nil} 894 | c.pscChan <- watchResponse{0, err} 895 | return 896 | } 897 | 898 | c.psc = redis.PubSubConn{Conn: rClient} 899 | 900 | go func() { 901 | defer func() { 902 | c.psc.Close() 903 | c.psc = redis.PubSubConn{Conn: nil} 904 | }() 905 | for { 906 | switch n := c.psc.Receive().(type) { 907 | case redis.PMessage: 908 | log.Debug(fmt.Sprintf("Redis Message: %s %s\n", n.Channel, n.Data)) 909 | data := string(n.Data) 910 | commands := [12]string{"del", "append", "rename_from", "rename_to", "expire", "set", "incrby", "incrbyfloat", "hset", "hincrby", "hincrbyfloat", "hdel"} 911 | for _, command := range commands { 912 | if command == data { 913 | c.pscChan <- watchResponse{1, nil} 914 | break 915 | } 916 | } 917 | case redis.Subscription: 918 | log.Debug(fmt.Sprintf("Redis Subscription: %s %s %d\n", n.Kind, n.Channel, n.Count)) 919 | if n.Count == 0 { 920 | c.pscChan <- watchResponse{0, nil} 921 | return 922 | } 923 | case error: 924 | log.Debug(fmt.Sprintf("Redis error: %v\n", n)) 925 | c.pscChan <- watchResponse{0, n} 926 | return 927 | } 928 | } 929 | }() 930 | 931 | c.psc.PSubscribe("__keyspace@" + strconv.Itoa(db) + "__:" + c.transform(prefix) + "*") 932 | } 933 | }() 934 | 935 | select { 936 | case <-stopChan: 937 | c.psc.PUnsubscribe() 938 | return waitIndex, nil 939 | case r := <- c.pscChan: 940 | return r.waitIndex, r.err 941 | } 942 | } 943 | ``` 944 | 945 | # 5. 总结 946 | 947 | 1. confd的作用是通过将配置存放到存储后端,来自动触发更新配置的功能,其中常用的后端有`Etcd`和`Redis`等。 948 | 2. 不同的存储后端,watch机制不同,例如Etcd只需要更新key便可以触发自动更新配置的操作,而redis除了更新key还需要执行`publish`的操作。 949 | 3. 可以通过配置`check_cmd`来校验配置文件是否正确,如果配置文件非法则不会执行自动更新配置和`reload`的操作,但是当存储后端存入的非法数据,会导致每次校验都是失败的,即使后面新增的配置部分是合法的,所以需要有机制来控制存入存储后端的数据始终是合法的。 950 | 951 | 952 | 953 | 参考: 954 | 955 | - https://github.com/kelseyhightower/confd/tree/v0.16.0 956 | 957 | --------------------------------------------------------------------------------