![一名 Node.js 工程師的 Go 學習之旅](http://p2.ttnews.xyz/loading.gif)
近幾年, Kubernetes 一直是容器編排和管理平臺的首選。對於我來說,也很想搞清楚它的底層是如何工作的,所以我決定來學一學 Go 這門語言。
在這邊文章中,我會基於我的經驗,並從一個 Node.js 工程師的視角出發。因此我會特別關注:
- 依賴管理
- 如何處理異步操作
- 那就讓我們開始吧 :smile:
什麼是 Go ?
Go 是一個開源的編程語言,它能讓構造簡單、可靠且高效的軟件變得容易. - golang.org
Go 由 Google 的 Robert Griesemer, Rob Pike, 和 Ken Thompson 於 2009 年發佈。它是一款靜態類型的編譯語言,擁有垃圾收集機制,基於 CSP 併發模型來處理異步操作。 Go 還有類 C 的語法:
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
安裝 Go: 官方指導鏈接: https://golang.org/doc/install.
Go 的依賴管理
如果打算寫大量的 JavaScript 代碼,首要問題就是依賴管理問題,Go 是怎麼處理的呢?有兩個辦法:
- go get
- dep
- 用 npm 的術語來說,你可以把他們看作是:在你需要使用 npm install -g 的時候,使用 go get,然後使用 dep 來管理不同項目的依賴。
要安裝 dep,可以通過 go get 來安裝,使用如下命令:
go get -u github.com/golang/dep/cmd/dep
然而,使用 go get 有一個缺點 —— go get 並不處理版本,它僅僅是獲取 Github 倉庫的最新版本。這就是為什麼推薦大家安裝並使用 dep。如果是 Mac 系統,安裝 dep 也可以通過如下命令:
brew install dep
brew upgrade dep
(如果是其他操作系統,安裝請參見: https://golang.org/doc/install)
一旦安裝了 dep,你就可以使用 dep init 來初始化項目,就好像使用 npm init 初始化 nodejs 項目一樣。
開發Go項目之前,你需要花點時間設置好GOPATH環境變量。—— 官方指導鏈接
dep 會像 npm 一樣,創建一個 Node.js 項目中類似 package.json 的文件來描述工程 —— Gopkg.toml。類似 package-lock.json,也會有一個 Gopkg.lock 文件。不同於 nodejs 項目將依賴放入 node modules 文件夾中,dep 將依賴放入一個叫作 vendor 的文件夾中。
要添加依賴,你只需要運行 dep ensure -add github.com/pkg/errors 命令。運行結束後,這個依賴就會出現在 lock 和 toml 文件中:
[[constraint]]
name = "github.com/pkg/errors"
version = "0.8.0"
Go處理異步操作
當用 JavaScript 寫異步代碼時,我們會用到一些庫或者語言特性,比如:
- async 庫
- Promises
- 或者異步函數
- 有了它們,我們可以很輕易的從文件系統中讀取文件:
const fs = require('fs')
const async = require('async')
const filesToRead = [
'file1',
'file2'
]
async.map(filesToRead, (filePath, callback) => {
fs.readFile(filePath, 'utf-8', callback)
}, (err, results) => {
if (err) {
return console.log(err)
}
console.log(results)
})
再讓我們來看看 Go 是怎麼實現的:
package main
import (
"fmt"
"io/ioutil"
)
func main() {
datFile1, errFile1 := ioutil.ReadFile("file1")
if errFile1 != nil {
panic(errFile1)
}
datFile2, errFile2 := ioutil.ReadFile("file2")
if errFile2 != nil {
panic(errFile2)
}
fmt.Print(string(datFile1), string(datFile2))
}
我們來一行行看看上面代碼是怎麼工作的:
- import —— 有了 import 關鍵字,你可以引入項目依賴的包文件,就像 Node.js 中的 require
- func main —— 應用程序的入口
- ioutil.ReadFile —— 該函數嘗試去讀取文件,並由兩個返回值:
- datFile1 如果讀操作成功,
- errFile1 如果讀取過程中有錯
- 你可以在這裡處理錯誤,或者直接讓程序崩潰
- fmt.Print 僅僅是打印結果到標準輸出
- 上面的例子是可以正常運行的,但是讀取文件是一個接一個讀取。—— 讓我們來稍加改進,將它異步化吧!
Go有一個叫作 goroutines 的概念來處理多線程。一個 goroutine 是一個輕量級的線程,它由 Go runtime 來管理。goroutine 使得你可以併發地跑Go的函數。
我最終使用 errgroup 包來管理或者說同步 goroutines。這個包提供同步機制,錯誤傳播,以及對同一個由一組 goroutines 子任務組成的公共任務提供上下文取消機制。
有了 errgroup,我們可以重寫讀文件的代碼片段,併發地執行:
package main
import (
"context"
"fmt"
"io/ioutil"
"os"
"golang.org/x/sync/errgroup"
)
func readFiles(ctx context.Context, files []string) ([]string, error) {
g, ctx := errgroup.WithContext(ctx)
results := make([]string, len(files))
for i, file := range files {
i, file := i, file
g.Go(func() error {
data, err := ioutil.ReadFile(file)
if err == nil {
results[i] = string(data)
}
return err
})
}
if err := g.Wait(); err != nil {
return nil, err
}
return results, nil
}
func main() {
var files = []string{
"file1",
"file2",
}
results, err := readFiles(context.Background(), files)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
for _, result := range results {
fmt.Println(result)
}
}
用 Go 來構建一條 REST API
在 Node.js 中,當談及到選擇一套框架來寫 HTTP 服務的時候,我們有一堆的選擇。Go 在這方面也不例外。Google 一下後,我選擇 Gin 來開始。
Gin 的接口類似於 Express 或者 Koa,包含中間件支持,JSON 校驗以及渲染:
package main
import "github.com/gin-gonic/gin"
func main() {
// 創建默認不帶任何中間件的路由
r := gin.New()
// 默認gin的輸出為標準輸出
r.Use(gin.Logger())
// Recovery中間件從異常中恢復,並回復500
r.Use(gin.Recovery())
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
// 監聽 0.0.0.0:8080
r.Run(":8080")
}
這就是我目前掌握的 —— 還沒有生產經驗。如果你這篇博客對你有幫助,並且你想學到更多關於 Go 的東西,就跟我聯繫吧,我會持續分享我的 Go 學習之旅。
閱讀更多 互聯網技能圖譜 的文章
關鍵字: 工程師 JSON JavaScript