Try   HackMD
tags Golang

GOLANG 學習紀錄

一、Go 指令

Golang 主要提供以下五個指令:


#取得遠端 Golang 原始碼之後編譯成執行檔(假如有定義 main function 的話)
go get

#執行
go run

#執行*_test.go 測試
go test

#將原始碼編譯成執行檔放到 bin 資料夾底下
go install

#將原始碼編譯到目前資料夾下
go build

二、專案目錄結構

GOROOT:指向 Golang 原始碼路徑,透過 Homebrew 安裝的話都自動設定好了。

GOPATH:Golang 專案存放路徑,Golang 專案需要有個專門存放的資料夾位置,假如沒設定就會存放到 GOROOT 底下,這樣在管理自己在開發的專案較不好,而 Golang 專案目錄結構如下:

  1. /cmd:本專案的主要應用程式。每個應用程式的目錄名應該與你的執行檔案名稱一致 (例如:/cmd/myapp)。

  2. /internal:私有應用程式和函式庫的程式碼,是你不希望其他人在其應用程式或函式庫中匯入的程式碼。

  3. /pkg:函式庫的程式碼當然可以讓外部應用程式來使用 (例如:/pkg/mypubliclib),其他專案會匯入這些函式庫,並且期待它們能正常運作

  4. /vendor:應用程式的相依套件可透過手動管理,或使用你喜歡的相依性套件管理工具,例如內建的 Go Modules 特性。

  5. /api:OpenAPI/Swagger 規格、JSON schema 檔案、各種協定定義檔。

  6. /web:Web 應用程式相關的元件:靜態 Web 檔案、伺服器端範本與 SPAs 相關檔案。

  7. /configs:組態設定的檔案範本或預設設定。將你的 confd 或 consul-template 範本檔案放在這裡。

  8. /init:放置 System init (systemd, upstart, sysv) 與 Process manager/supervisor (runit, supervisor) 相關設定。

  9. /scripts:放置要執行各種建置、安裝、分析等操作的命令腳本!

  10. /build:封裝套件與持續整合(CI)。將你的雲端 (AMI)、容器 (Docker)、OS (deb, rpm, pkg) 套件的組態設定與腳本放在 /build/package 目錄下。

  11. /deployments:IaaS、PaaS、系統和容器編配部署的組態設定與範本 (docker-compose、kubernetes/helm、mesos、terraform、bosh)。注意:在某些儲存庫中(特別是那些部署在 kubernetes 的應用程式),這個目錄會被命名為 /deploy。

  12. /test:額外的外部測試應用程式和測試資料。你可以自在的調整你在 /test 目錄中的結構。對於較大的專案來說,通常會有一個 data 資料夾也是蠻正常的。例如:如果你需要 Go 忽略這些目錄下的檔案,你可以使用 /test/data 或 /test/testdata 當作你的目錄名稱。請注意:Go 還會忽略以 . 或 _ 開頭的目錄或檔案,所以你在測試資料的目錄命名上,將擁有更大的彈性。

  13. /docs:設計和使用者文件 (除了 godoc 自動產生的文件之外)。

  14. /tools:這個專案的支援工具。注意:這些工具可以從 /pkg 和 /internal 目錄匯入程式碼。

  15. /examples:放置關於你的應用程式或公用函式庫的使用範例。

  16. /third_party:外部輔助工具、Forked 程式碼,以及其他第三方工具 (例如:Swagger UI)。

web
├── api/v1 -- 主要API
|   ├── sys_initdb.go -- ico
|   └── sys_user.go --  
├── cmd -- 可执行文件
├── config -- 配置文件 设定操作的结构体
├── global -- global
├── initialize -- 初始化global包的工具
├── middleware -- 中间件
├── model -- global
│   ├── request  -- 所有请求model结构体
|   |   ├── common.go 
|   |   ├── ...
|   ├── response  -- 返回数据
|   |   ├── common.go 
|   |   ├── ...
├── router -- 路由
├── service -- service层
├── utils
├── config.yaml  -- 
├── go.mod    -- mod 配置
├── go.sum -- sum
├── goxorm.yaml -- 数据库表生成struct配置
└── main.go  -- main函数

三、套件管理

GO Modules實用連結:

https://mileslin.github.io/2020/01/Golang/Go-Module-快速上手筆記/

https://myapollo.com.tw/zh-tw/golang-go-module-tutorial/

私有套件、公有套件設定管理: https://blog.csdn.net/weixin_43700106/article/details/118279983

四、傳值傳址

//宣告
var x int = 4

//傳值接值
y int = x

//傳址接址
y *int = &x

//反解得值
fmt.print(*y)

//不反解得址
fmt.print(y)

五、Receiver Function 介紹

介紹:https://calvertyang.github.io/2019/11/19/golang-methods-tutorial-with-examples/

呼叫 receiver function時,呼叫者必須使用接收器去呼叫。
否則會導致這篇的錯誤:https://stackoverflow.com/questions/65291901/local-method-call-results-in-undeclared-name-name

六、Panic(new Exception), defer (final), recover(跳出當前func 繼續執行)

https://www.jianshu.com/p/0cbc97bd33fb
https://studygolang.com/articles/20691

七、wildcard

https://studygolang.com/articles/14429

八、Log

最廣泛使用 - Logrus: https://mojotv.cn/2018/12/27/golang-logrus-tutorial

性能敏感但是難用 - Zap: https://tonybai.com/2021/07/14/uber-zap-advanced-usage/

九、web framework

最廣泛使用 - gin

輕量化的web框架、實現http api,以及middleware(消息中間件,包含filter)
http://soiiy.com/go/15688.html

中間件驗證:
https://blog.csdn.net/impressionw/article/details/84194783

中間件 c.next c.abort c.set c.get c.use : https://blog.csdn.net/qq_37767455/article/details/104712028

特殊路由參數(星號、冒號)
https://www.flysnow.org/2019/12/13/golang-gin-parameters-in-path.html
程式碼範例:https://github.com/ronnielin8862/go-practice/blob/master/cmd/ginApiGateway/main.go

web framwork - gin vs echo :

先说结论:

如果你代表企业,最好选择gin,无痛开发。
如果是个人,开发个轻量服务,哪怕echo有点小问题,你也觉得没啥,那么,就用echo。
https://blog.csdn.net/ds1130071727/article/details/90216713

十、goroutine, channel

1.執行緒

  1. 主執行緒結束時會關閉程序,不會等待子執行緒的完成
  2. 只要主執行序還在,子執行緒結束,孫執行緒依然不會被結束。 這特點與主執行緒結束的狀況不同
  3. 三種方式讓執行緒等待,分別是time.Sleep, sync.WaitGroup, channel,實作請參考(https://github.com/ronnielin8862/go-api/tree/main/cmd/goRountine/waitForMainThread)

控制管理執行序好工具 - context

自1.7以後加入go自帶包

cxt := context.Backgroud() 會生成context的根
ctx2 ,_ := context.WithDeadline(ctx, time.second * 10) 生成的的ctx2會在指定時間後關閉所有攜帶著他的執行緒
ctx3 , cancel := context.WithCancel(cxt) 生成的ctx3 會在調用其 cancel()後關閉攜帶有ctx3的執行緒
cxt4 := context.WithValue(cxt, "keyA", "valueA") 生成的cxt4,可在攜帶著cxt4的任何執行緒中透過cxt4.Value("keyA"),取得 "valueA"

參考: https://github.com/ronnielin8862/go-practice/blob/master/cmd/basic/contex/main.go

2.執行緒安全

確保執行緒安全的兩種方式:mutex的互斥鎖,及channel(主流方式)
https://peterhpchen.github.io/2020/03/08/goroutine-and-channel.html

mutex(互斥鎖), rwmutex(讀寫鎖)
https://blog.csdn.net/K346K346/article/details/90476721

3.Buffered Channel and Unbuffered Channel

  1. Unbuffered Channel
    只要推入一個資料會造成推入方的等待
    拉出時沒有資料會造成拉出方的等待
    golang主要利用此種channel等待特性,實現讓主執行緒等待子執行緒

  2. Buffered Channel
    ch: make(chan int, 100)
    Buffered Channel 的宣告會在第二個參數中定義 buffer 的長度,它只會在 Buffered 中資料填滿以後才會阻塞造成等待,以上例來說:第101個資料推入的時候,推入方的 Goroutine 才會等待。
    https://peterhpchen.github.io/2020/03/08/goroutine-and-channel.html

4.select

如果有同时多个case去处理,比如同时有多个channel可以接收数据,那么Go会伪随机的选择一个case处理(pseudo-random)。如果没有case需要处理,则会选择default去处理,如果default case存在的情况下。如果没有default case,则select语句会阻塞,直到某个case需要处理。
code: https://github.com/ronnielin8862/go-api/tree/develop/cmd/goRountine/select
說明: https://colobu.com/2016/04/14/Golang-Channels/

5.其他

除非是兩個 goruoutine 之間需要交換訊息,否則還是使用一般的 waitGroup + Lock 效能會比較好。不要因為使用 channel 比較潮,而強制在專案內使用。在很多狀況底下一般的 Slice 或 callback 效能會比較好。(https://blog.wu-boy.com/2020/01/when-to-use-go-channel-and-goroutine/)

6.goroutine and panic

在goroutine 中遇到panic會怎樣? 怎樣準確的捕獲panic並recover
https://developer.51cto.com/article/701947.html

十一、interface

在 Golang 裏有兩種 Interface,一種是型態,一種是任意值。

1.Interface{} 是任意值

func Hello(value interface{}) {  
}

在這種時候 interface{} 意味著任何型態的值,意思是你可以傳入 int、string 或者是建構體都行,這令你在設計程式時擁有足夠的彈性來接收任意型態的值。

接著需要使用型態斷言(Type Assertion)來宣告這個 interface{} 的內容真正是什麼型態,如此一來才能夠在其他函式中用上。

func Hello(value interface{}) {  
    // 透過型態斷言揭露 interface{} 真正的型態。
    switch v := value.(type) {
        // 如果 value 是字串型態。
        case string:
            fmt.Println("value 是字串,內容是 " + v)
        // 如果 value 是 int 型態。
        case int:
            fmt.Printf("value 是數值,加上二就是 %d", v + 2)
    }
}

如果你很確越切地知道這個 interface{} 是什麼型態,可以直接透過 value.(型態) 來進行型態宣告(Type Casting)並將 interface{} 變成指定型態。

倘若該型態不正確,則會出現 panic 警告。

func Hello(value interface{}) {  
    fmt.Println("value 是字串,內容是 " + value.(string))
}

2.Interface 是介面定義

這裡的介面指的不是 GUI 那種可見的畫面,而是指 Interface,中國稱其為「接口」,顧名思義,你會透過 Interface 定義一個接口並讓別人以其實作,聽起來霧矇矇嗎?這裡有個實際例子。

假設你有個資料庫驅動函式,他支援寫入和讀取資料庫,但你不想把它寫死成僅支援 MySQL,所以你會透過 Interface 定義一個接口。

實作 Interface 的時候有件事情要注意,那就是欲實作的建構體必須要有 Interface 所定義的所有函式、接收參數、回傳值,否則 Golang 會表明無法實作該 Interface 因為缺少某某條件。

程式碼參考:https://github.com/ronnielin8862/go-api/tree/develop/cmd/interface/test2

後記

以前會以為 Interface 是多此一舉的型態,但後來改用 Golang 之後,就發現根本是開發套件的神器,因為你能透過 Interface 定義你的程式讓別人也可以接上自己的東西,又不會導致把自己的套件寫死只支援少許的功能。

十二、Unit-Test

寫法參考:https://github.com/ronnielin8862/go-api/blob/main/test/mathForTest/mathForTest_test.go
執行時:在終端機執行 go test -v ,就會執行所有測試函式:( -v 顯示詳細資訊 ); 也可以用 -run 參數指定執行的函式:$ go test -v -run TestHello

十三、Auto Migrate????

https://blog.51cto.com/u_15301988/3079977

十四、Http Client

程式碼:
https://github.com/ronnielin8862/solidity-practice/tree/master/cmd/trc/getBalance

參考文章:
https://www.cnblogs.com/zhaof/p/11346412.html

十五、效能分析-1 BenchMark

參考文章:
1.https://sc0vu.medium.com/高效能-golang-程式-效能比較-f84bb4fc390a
2.https://segmentfault.com/a/1190000040868502

執行語法:go test -bench=.

指定執行的benchmark:go test -bench=hello.go

go test 會在執行 benchmark 前執行所有的測試,如果有些測試執行時間比較久,可以加入 -run 規定只執行某些"測試",例如 -run=^$ 不會執行任何"測試"

ns(nanosecond):纳秒,时间单位。 一秒的十亿分之一

執行語法:go test -bench=. -benchmem

會再多呈現內存使用情況: 0.0004376 ns/op 313 B/op

執行語法:go test -bench=. -benchmem run=none

在執行benchmark以前不執行testing

十六、反射

reflect.valueOf 下kind與type的不同:

package main
import (
    "fmt"
    "reflect"
)
func main() {
    var x uint8 = 'x'
    v := reflect.ValueOf(x)
    fmt.Println("type:", v.Type())                            // uint8.
    fmt.Println("kind is uint8: ", v.Kind() == reflect.Uint8) // true.
    x = uint8(v.Uint())                                       // v.Uint returns a uint64.
}

运行结果如下:
type: uint8
kind is uint8: true
其次,反射对象的 Kind 方法描述的是基础类型,而不是静态类型。如果一个反射对象包含了用户定义类型的值,如下所示:

type MyInt int
var x MyInt = 7
v := reflect.ValueOf(x)

上面的代码中,虽然变量 v 的静态类型是 MyInt,而不是 int,但 Kind 方法仍然会返回 reflect.Int。换句话说 Kind 方法不会像 Type 方法一样区分 MyInt 和 int。

动态调用函数(无参数)

type T struct {}

func main() {
    name := "Do"
    t := &T{}
    reflect.ValueOf(t).MethodByName(name).Call(nil)
}

func (t *T) Do() {
    fmt.Println("hello")
}

动态调用函数(有参数)

type T struct{}

func main() {
    name := "Do"
    t := &T{}
    a := reflect.ValueOf(1111)
    b := reflect.ValueOf("world")
    in := []reflect.Value{a, b}
    reflect.ValueOf(t).MethodByName(name).Call(in)
}

func (t *T) Do(a int, b string) {
    fmt.Println("hello" + b, a)
}

处理返回值中的错误

返回值也是 Value 类型,对于错误,可以转为 interface 之后断言

type T struct{}

func main() {
    name := "Do"
    t := &T{}
    ret := reflect.ValueOf(t).MethodByName(name).Call(nil)
    fmt.Printf("strValue: %[1]v\nerrValue: %[2]v\nstrType: %[1]T\nerrType: %[2]T", ret[0], ret[1].Interface().(error))
}

func (t *T) Do() (string, error) {
    return "hello", errors.New("new error")
}

對指針內容的反射處理,要加上elem(), ex:

	for i := 0; i < reflectMathStruct.Elem().NumField(); i++ {
		fmt.Println(reflectMathStruct.Elem().Field(i))
	}

git: https://github.com/ronnielin8862/go-practice/blob/master/cmd/basic/reflectPractice/main.go

參考文章:

  1. http://c.biancheng.net/golang/reflect/
  2. https://segmentfault.com/a/1190000016230264
  3. 官方文檔: https://golang.org/pkg/reflect/

十七、高精度計算

  1. 使用big.rat

常用函式:

rat := new(*big.rat)

//string to rat
rat.setString("0.123456789")

//rat to string
rat.ratString()

轉換前 = 0.123456789012345678901234567890
轉換後 = 12345678901234567890123456789/100000000000000000000000000000

參考文章: https://doc.yonyoucloud.com/doc/wiki/project/the-way-to-go/09.4.html

  1. shopspring/decimal
    轉換前 = 0.12345678901234567890123456789
    轉換後 = 0.12345678901234567890123456789

參考文章: https://www.cnblogs.com/yangxinpython/p/12831084.html

程式碼: https://github.com/ronnielin8862/go-practice/tree/master/cmd/highPrecision

十八、專案環境設置

學習規劃:透過下面兩步驟實現專案環境切換

  1. 實現於程式中透過yaml檔帶入參數,並預設為本機環境
  2. 實現在cicd過程中指定運行的yaml檔,類似於spring profile的功能

十九、gomock

環境安裝參考: https://blog.csdn.net/u010918487/article/details/89949064
使用參考: https://geektutu.com/post/quick-gomock.html

程式碼: https://github.com/ronnielin8862/go-practice/blob/master/cmd/mock/practice2/user/db_mock.go

二十、Generic

1.18後新特性。
官方說明及範例:
https://go.dev/doc/tutorial/generics
https://learnku.com/articles/56076

程式碼:https://github.com/ronnielin8862/go-practice/blob/master/cmd/generic/officialExample/main.go

二十一、connection pool

二十二、go generate

go generate命令是在Go语言 1.4 版本里面新添加的一个命令,当运行该命令时,它将扫描与当前包相关的源代码文件,找出所有包含 "//go:generate"的特殊注释,提取并执行该特殊注释后面的命令

-run 正则表达式匹配命令行,仅执行匹配的命令;
-v 输出被处理的包名和源文件名;
-n 显示不执行命令;
-x 显示并执行命令;
command 可以是在环境变量 PATH 中的任何命令

相當適合在需要在執行代碼前先編譯過某些檔案、執行某些命令時使用。 例如先編譯出 pb.go, mock.go
將落落長的執行命令直接寫在註釋後,以後只需go generate -x即可

甚至將go run 之類運行命令也放在後面,一行即可完整執行

參考文章: http://c.biancheng.net/view/4442.html

二十三、garbage collection

Go 的 GC 被设计为极致简洁,与较为成熟的 Java GC 的数十个可控参数相比,严格意义上来讲,Go 可供用户调整的参数只有 GOGC 环境变量。当我们谈论 GC 调优时,通常是指减少用户代码对 GC 产生的压力,这一方面包含了减少用户代码分配内存的数量(即对程序的代码行为进行调优),另一方面包含了最小化 Go 的 GC 对 CPU 的使用率(即调整 GOGC)。

总的来说,我们可以在现在的开发中处理的有以下几种情况:

对停顿敏感:GC 过程中产生的长时间停顿、或由于需要执行 GC 而没有执行用户代码,导致需要立即执行的用户代码执行滞后(进而导致 application 的 throughput 下降)。
对资源消耗敏感:对于频繁分配内存的应用而言,频繁分配内存增加 GC 的工作量,原本可以充分利用 CPU 的应用不得不频繁地执行垃圾回收,影响用户代码对 CPU 的利用率,进而影响用户代码的执行效率(同样也导致了 application 的 throughput 下降)。
从这两点来看,所谓 GC 调优的核心思想也就是充分的围绕上面的两点来展开:优化内存的申请速度,尽可能的少申请内存,复用已申请的内存。或者简单来说,不外乎这三个关键字:控制、减少、复用。

GC 触发场景

GC 触发的场景主要分为两大类,分别是:

  1. 系统触发:运行时自行根据内置的条件,检查、发现到,则进行 GC 处理,维护整个应用程序的可用性。

    ​​​​const (
    ​​​​ gcTriggerHeap gcTriggerKind = iota
    ​​​​ gcTriggerTime
    ​​​​ gcTriggerCycle
    ​​​​)
    
    • gcTriggerHeap:当所分配的堆大小达到阈值(由控制器计算的触发堆的大小)时,将会触发。 (上次gc後內存的兩倍)
    • gcTriggerTime:当距离上一个 GC 周期的时间超过一定时间时,将会触发。-时间周期以 runtime.forcegcperiod 变量为准,默认 2 分钟。
    • gcTriggerCycle:如果没有开启 GC,则启动 GC。
      在手动触发的 runtime.GC 方法中涉及。
  2. 手动触发:开发者在业务代码中自行调用 runtime.GC 方法来触发 GC 行为。

    在手动触发的场景下,Go 语言中仅有 runtime.GC 方法可以触发,也就没什么额外的分类的。

    但我们要思考的是,一般我们在什么业务场景中,要涉及到手动干涉 GC,强制触发他呢?

    需要手动强制触发的场景极其少见,可能会是在某些业务方法执行完后,因其占用了过多的内存,需要人为释放。又或是 debug 程序所需。

參考文章:
https://cloud.tencent.com/developer/article/1900650
https://zhuanlan.zhihu.com/p/501808700
go 1.15以後gc 處理建議 - https://icode.best/i/22960044377955
https://juejin.cn/post/7111515970669117447

二十四、效能分析-2 pprof

benchmark是針對function分析進而優化。 在針對系統的效能分析時,使用pprof

  1. 在main方法增加兩行:
    go func() {
    log.Println(http.ListenAndServe("localhost:8211", nil))
    }()
  2. import 增加一行: _ "net/http/pprof"
  3. 1 - 網頁可以開就開 http://localhost:8211/debug/pprof/,
  4. 2 - 網頁不能開就cmd 輸入: go tool pprof http://localhost:8211/debug/pprof/ + 查看項目 ,例如heap,goroutine等,可以在網頁版本看到所有選項,然後輸入top 5 之類的參數,可以看到前五大資源佔用項目,包括代碼行數

參考文章: https://xusenqi.site/2018/12/11/一次go内存泄漏的排查过程/
個人實務: https://hackmd.io/xIsa4wtzRi6SuMtZ2fXvUQ

二十五、cron job

表達式:
https://segmentfault.com/a/1190000039647260
https://studygolang.com/articles/17910?fr=sidebar

二十六、Go work

使用 Go 多模块工作区的功能,可以让我们轻松在多个模块之间切换工作,更能适应现代微服务架构开发

version require
go 1.18
J.B. golang 2022.2

好理解的範例文:https://www.jianshu.com/p/6e338468ad97

二十七、redis

好用範例文: https://www.cnblogs.com/itbsl/p/14198111.html#expireexpireat设置有效期

二十八、效能分析-3 計算函式運行時間

有時當我們對某個接口、功能運行時間不滿意要進行優化時,可能遇到一種情況: 該方法是等你調用的時候才單次執行,並且流經多個函式。 這種情形下使用benchmark單函式分析並不適用。pprof這類以時間片斷累積觀察正在運行哪些協程的方式也不適用,因為系統大多時間並沒有在執行真正要測試的函式。 這時候還是會需要透過log觀測每個函式的執行時長,以找到要優化的目標,再深入輔以其他工具。
但四處插入log既醜陋又不便,這時候可以使用以下的時長計算函式,並且只要在功能流經的函式 defer內加入他即可。

時長計算式:

func timeCost() func() {
	startTime := time.Now()
	pc := make([]uintptr, 1)
	runtime.Callers(2, pc)
	f := runtime.FuncForPC(pc[0])
	return func() {
		t := time.Since(startTime)
		fmt.Println(f.Name(), " time cost : ", t.Nanoseconds())
	}
}

調用:

func sum(n int) int {
	defer timeCost()()
	var s int = 0
	for i := 0; i < n; i++ {
		s += i
	}
	return s
}

可以得到:

main.sum  time cost :  36627

對效能調校相當友善且美觀

二十九、bloom filter

介紹文章: https://juejin.cn/post/7058445741097746462
Go推薦包: https://github.com/RedisBloom/redisbloom-go
系統安裝: https://hackmd.io/So7yJIlXTi2nDfMJqY2lhQ
code example: https://github.com/ronnielin8862/go-practice/blob/master/cmd/db/redis/redisBloom/main.go

三十、Dependency Inversion

fx套件 - uber

介紹短文: https://ken00535.medium.com/用-fx-來替-go-依賴注入吧-d82adcd4d56b
介紹短文2: https://speakerdeck.com/ken00535/20220928-golang-meetup-di-fx-release?slide=2
講解影片: https://www.youtube.com/watch?v=jzEM865k1-4

wire套件 - google

講解影片: https://youtu.be/iYOYOAEVsRw

參考資料

  1. https://github.com/golang-standards/project-layout/blob/master/README_zh-TW.md