go入门实践
0、和java一样,go也是跨平台,天生支持Unicode。但是go直接生成可执行文件,性能更高,内存占用少。但是又和java一样,go打出来的二进制包能够扫描到依赖的库,如果库有漏洞,安全扫描会被扫出来。
1、安装、环境配置及术语
从https://golang.google.cn/下载对应的版本。
使用命令行go env可以查看,如下:
[lightdb@lightdb-dev ~]$ go env GO111MODULE="" #设置是否打开go modules,auto/on/off三个取值,1.13开始默认,1.14开始推荐 GOARCH="amd64" GOBIN="" # go install最终拷贝到的目录,一般go程序打成tar.gz分发,所以关系不大,配置的话指向$GOPATH/bin GOCACHE="/home/lightdb/.cache/go-build" GOENV="/home/lightdb/.config/go/env" GOEXE="" GOEXPERIMENT="" GOFLAGS="" GOHOSTARCH="amd64" GOHOSTOS="linux" GOINSECURE="" GOMODCACHE="/home/lightdb/go/pkg/mod" GONOPROXY="" GONOSUMDB="" GOOS="linux" GOPATH="/home/lightdb/go" # 项目的根目录,最重要的环境变量 GOPRIVATE="" GOPROXY="https://goproxy.cn,direct" # GOPROXY是用来设置go mod的代理,以英文逗号“,”分割,使Go在后续拉取模块版本时能够脱离传统的VCS方式从镜像站点快速拉取。它拥有一个默认:https://proxy.golang.org,direct,但proxy.golang.org在中国无法访问,所以建议使用goproxy.cn替代 GOROOT="/usr/lib/golang" # golang的安装目录,跟JAVA_HOME一样 GOSUMDB="sum.golang.org" GOTMPDIR="" GOTOOLDIR="/usr/lib/golang/pkg/tool/linux_amd64" GOVCS="" GOVERSION="go1.20.6" GCCGO="gccgo" GOAMD64="v1" AR="ar" CC="gcc" CXX="g++" CGO_ENABLED="1" GOMOD="/dev/null" GOWORK="" CGO_CFLAGS="-O2 -g" CGO_CPPFLAGS="" CGO_CXXFLAGS="-O2 -g" CGO_FFLAGS="-O2 -g" CGO_LDFLAGS="-O2 -g" PKG_CONFIG="pkg-config" GOGCCFLAGS="-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build261630818=/tmp/go-build -gno-record-gcc-switches"
hello world验证环境及熟悉目录
[lightdb@lightdb-dev go]$ cd src/example/
[lightdb@lightdb-dev example]$ go mod init
[lightdb@lightdb-dev go]$ go run src/example/first.go 123 hello world 65 12.345 [lightdb@lightdb-dev go]$ pwd /home/lightdb/go [lightdb@lightdb-dev go]$
[lightdb@lightdb-dev bin]$ export | grep GO declare -x GOBIN="//home/lightdb/go/bin" declare -x GOPATH="/home/lightdb/go" declare -x GOROOT="//home/lightdb/go/go"
这样脚手架就可以运行了。
[lightdb@lightdb-dev src]$ cd go-web/
[lightdb@lightdb-dev go-web]$ go build ginweb.go # go build也是个很大的范畴,可以参考https://blog.csdn.net/wan212000/article/details/124288970
相关的报错及原因和解决办法如下:
[lightdb@lightdb-dev go]$ go build go: go.mod file not found in current directory or any parent directory; see 'go help modules' [lightdb@lightdb-dev go]$ go mod init # 理论上go mod init即可 go: cannot determine module path for source directory /home/lightdb/go (outside GOPATH, module path must be specified) Example usage: 'go mod init example.com/m' to initialize a v0 or v1 module 'go mod init example.com/m/v2' to initialize a v2 module Run 'go help mod init' for more information. [lightdb@lightdb-dev go]$ go mod init example.com/m # 可以提前预建目录,这样cd dir; go mod init即可,不用指定名称 go: creating new go.mod: module example.com/m go: to add module requirements and sums: go mod tidy [lightdb@lightdb-dev go]$ go build $GOPATH/go.mod exists but should not # 使用go module模块化机制后,go.mod需要放在具体的模块中而非在GOPATH目录下,如$GOPATH/src/example/go.mod,再运行即可 [lightdb@lightdb-dev go]$ go install $GOPATH/go.mod exists but should not
[lightdb@lightdb-dev go]$ go run src/go-web/ginweb.go src/go-web/ginweb.go:7:2: no required module provides package github.com/gin-gonic/gin: go.mod file not found in current directory or any parent directory; see 'go help modules' src/go-web/ginweb.go:5:2: package tool/controller is not in GOROOT (/usr/lib/golang/src/tool/controller) # 需要在模块的根目录下打包和运行
package first is not in GOROOT (/usr/lib/golang/src/first) # 在$GOPATH目录下执行 ln -s /usr/lib/golang go
1.1 主要文件
go.mod Go Modules 的核心文件,包含module、require、replace 和 exclude 4部分。
go.sum 供 Go 命令在构建时判断依赖包是否合法
详见https://zhuanlan.zhihu.com/p/635696935,讲得很清楚了。
1.2 依赖关系
和java一样,go的依赖管理也经过了多次构造,最新为2019年的go mode。
模块是go管理依赖的基本单元,模块由多个package组成。go.mod 文件定义了模块的名称及其依赖包,通过导入路径和版本描述一个依赖。
[lightdb@lightdb-dev go]$ go help modules Modules are how Go manages dependencies. A module is a collection of packages that are released, versioned, and distributed together. Modules may be downloaded directly from version control repositories or from module proxy servers. For a series of tutorials on modules, see https://golang.org/doc/tutorial/create-module. For a detailed reference on modules, see https://golang.org/ref/mod. By default, the go command may download modules from https://proxy.golang.org. It may authenticate modules using the checksum database at https://sum.golang.org. Both services are operated by the Go team at Google. The privacy policies for these services are available at https://proxy.golang.org/privacy and https://sum.golang.org/privacy, respectively. The go command's download behavior may be configured using GOPROXY, GOSUMDB, GOPRIVATE, and other environment variables. See 'go help environment' and https://golang.org/ref/mod#private-module-privacy for more information.
2、IDE配置(vscode为例)及调试
vscode支持远程开发和本地开发,attach/launch都一样,都支持本地和远程。所以除非要访问linux资源,否则直接windows开发也可以,这和c/c++语言需要依赖很多posix兼容库如pthread.h/uinstd.h/sys/select.h等具有很大的差别。
2.1 工程结构推荐
一般来说,一个公司多个模块,下面的结构就行。
GOPATH #环境变量配置 bin pkg src com.xxx 顶级域名作为区分,便于和三方引入包保持一致 lightdb main.go base service1 service2 unisql main.go base service1 service2 infra module1 module2
go以包为组织单位,虽然分目录,但包名和目录名不一定要相同(只是习惯上最后一层相同),而且包是单层如packageName而非com.xxx.packageName。一个文件夹下的所有文件必须具有相同的包名声明。
import的是路径名而非包名(因为一个目录下的所有文件需要声明为一个包,所以import目录就相当于import包),引用的是GOPATH(跟CLASSPATH很像)开始的相对路径。需要注意的是如果目录里面有子目录,则不是同一个包,这个和java是一样的。
如果多个目录下存在同名的package(这可是必不可免的),需要在import这些目录时为每个目录指定一个package的别名。
2.2、代码辅助、跳转及格式化配置
2.3、debug和attach配置
3、基本概念
每个 Go 程序都是由包构成的。程序从 main
包开始运行。
一个程序中可以有很多main包,这个main包之间无法相互调用,运行时通过go run xxx.go运行。
"math/rand"
包中的源码均以 package rand
语句开始。
go里面只有包和函数级别的变量,不存在对象级别。 也就是没有对象。
go里面,类型名在变量名之后,函数返回值在最后(这和java/c++相反),如下:
package main import "fmt" func add(x int, y int) int { // 有点类似scala,和java/c都不一样 return x + y } func main() { fmt.Println(add(42, 13)) }
函数可以返回多值,这个和主流编程语言(如java/c/c++)存在明显的差异,虽然c++在<utility>中支持返回pair带两个值。
支持string、bool和无符号类型(和java/c的差别,java不支持无符号,c在17之前不支持布尔,一般模式typedef定义1和0来模拟),类型转换需要强制写,比如int到float,跟postgresql的强类型一样。而且转换是函数调用式的语法target_type(src_val)而不是java/c里面的(target_type) src_val。如下:
package main import ( "fmt" "math" ) func main() { var x, y int = 3, 4 var f float64 = math.Sqrt(float64(x*x + y*y)) // float64是类型转换 var z uint = uint(f) fmt.Println(x, y, z) }
for/if/else/switch更像是pl/sql的用法加上另外一半的c/c++/java,必须大括号、条件不必强制括号,switch不用带break(因为内置)。支持将某个语句延迟到函数调用(defer)返回后在执行(有点aop after的概念哈)
package main import "fmt" func main() { defer fmt.Println("world") fmt.Println("hello") }
函数内外语句使用差异明显,var和:=
package main import "fmt" func main() { var i, j int = 1, 2 k := 3 // 函数外的每个语句都必须以关键字开始(var, func 等等),因此 := 结构不能在函数外使用。 c, python, java := true, false, "no!" fmt.Println(i, j, k, c, python, java) }
包成员可见性,大写字母开头为默认导出,小写则不导出,因为大部分开发喜欢默认小写标识符,所以其实对于控制可访问性设计比java/c++可更妥些。
init()函数
和c/c++里面dlopen()加载so文件后自动执行_init()函数一样,init()函数在每次import一个包后自动执行。如果import相互依赖,也不会重复加载,每个包只会被初始化一次。
基本类型以外的高级数据类型
指针(虽然支持,但基本不推荐使用,除非为了效率),go中非简单类型也是传值、并且没有对象java的概念,没有c++的传引用,所以指针不可避免还是会用的多。
结构体(go不支持类,结构体上支持定义方法),结构体上的方法(按照go官方说法:方法只是个带接收者参数的函数)语法略微怪异,如下:
package main import ( "fmt" "math" ) type Vertex struct { X, Y float64 } func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func main() { v := Vertex{3, 4} fmt.Println(v.Abs()) }
支持接口类型(也就是interface的概念)
type Abser interface { Abs() float64 }
可变长度数组(切片实现,也支持数组的数组)
map(原生支持)
支持函数作为参数和返回值(像函数指针或c++里面的function对象),此时函数支持闭包的概念(其实就是中间变量)。
上面两点更像c with object
协程(goroutine和channel):并发的核心是:不用使用共享内存来通信,而是用通信(channel)来共享内存。
5、go自动化回归单元测试
默认情况下,go的单元测试文件放在和源代码一样的目录下,运行go test会对所有的xxx_test.go进行测试,如下:
[lightdb@lightdb-dev go-web]$ go test
? tool [no test files]
这是一个比较大的主题,可以参考有赞paas的做法,https://cloud.tencent.com/developer/article/1684515,其中还结合了和sonar报告的集成
https://before80.github.io/go_docs_with_hugo/goBlog/2023/CodeCoverageForGoIntegrationTests/
https://go.dev/blog/integration-test-coverage go 1.20在go build上增加了-cover选项,支持集成测试统计
对于集成测试,建议搭建一个专门的平台维护用例输出和输入,自动化回归和对比,开发、测试一体化,跟postgresql的make check/make checkworld一样。
java jacoco是一个可以单元测试和集成测试均覆盖的覆盖率工具,支持java agent模式统计。输出也很完整。
https://blog.csdn.net/d_chunyu/article/details/117136293
6、makefile不仅可用于c/c++,还可以用于go。也就是makefile可用于任何linux下的开发,跟语言无关。尤其是go涉及到部分代码使用c/c++开发时更是如此。
7、命令行程序设计
go提供了一个设计命令行程序的框架cobra cli(Kubernetes, Docker, Hugo,pgcenter均基于它)
8、go web开发之gin框架
9、go访问数据库之postgresql
10、模块分发
go build可以用来打包整个模块
go install将其安装到$GOBIN目录下
https://stackoverflow.com/questions/59741795/how-to-distribute-a-go-module-with-c-dependencies
11、三方库
日志:logrus
kafka:kafka-go ,https://blog.csdn.net/qq_30614345/article/details/131056586,https://zhuanlan.zhihu.com/p/431434480
json:jsoniter
orm:https://github.com/go-gorm/gorm
sql解析:https://github.com/pingcap/parser,当然也可以用postgresql的c语言sql解析器(性能更强),pgpool-ii就是剥离的psotgresql解析器
12、docker中go get无法访问代理,如下:
[root@lightdb-dev pkg]# docker-compose up [+] Building 31.4s (9/9) FINISHED docker:default => [helloweb internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [helloweb internal] load build definition from helloweb.build 0.0s => => transferring dockerfile: 439B 0.0s => [helloweb internal] load metadata for docker.io/library/golang:1.20 10.4s => [helloweb 1/5] FROM docker.io/library/golang:1.20@sha256:bfc60723228b88180b1e15872eb435cf7e6d8199eae9be77c8dfd8f8079343df 0.0s => [helloweb internal] load build context 0.0s => => transferring context: 33B 0.0s => CACHED [helloweb 2/5] WORKDIR /app 0.0s => CACHED [helloweb 3/5] COPY helloweb.go /app 0.0s => CACHED [helloweb 4/5] RUN go mod init helloweb 0.0s => ERROR [helloweb 5/5] RUN go get github.com/go-redis/redis 21.0s ------ > [helloweb 5/5] RUN go get github.com/go-redis/redis: 20.99 go: module github.com/go-redis/redis: Get "https://proxy.golang.org/github.com/go-redis/redis/@v/list": dial tcp 142.251.43.17:443: connect: connection refused ------ failed to solve: process "/bin/sh -c go get github.com/go-redis/redis" did not complete successfully: exit code: 1
解决方法:docker.build中,使用RUN指定GOPROXY,如下:
# 导入依赖的Redis go module RUN go mod init helloweb RUN go env -w GO111MODULE=on RUN go env -w GOPROXY=https://goproxy.cn,direct RUN go get github.com/go-redis/redis
再运行docker compose up即可。
查看某个模块依赖的包清单
https://blog.csdn.net/aaqq123456654321/article/details/127113393
go性能分析
pgo,pprof
go与c/c++交互 cgo及环境变量
内存管理
gc
打印堆栈及性能
https://www.51cto.com/article/748837.html