go 雜談一

目錄

? 1.go 各種代碼運行

? 2.go 在線編輯代碼運行

? 3.通過 Gob 包序列化二進(jìn)制數(shù)據(jù)

? 4.使用 encoding/csv 包讀寫 CSV 文件

? 5.實現(xiàn) HTTP 斷點續(xù)傳多線程下載

5.1. HTTP斷點續(xù)傳多線程下載

5.2. Range & Content-Range

5.3. Golang代碼實現(xiàn)HTTP斷點續(xù)傳多線程下載

? 6.logrus 日志使用教程

? 6.1 基本用法

? 6.2 簡單定義格式

? 6.3 輸入json格式和設(shè)置行號

? 6.4 自定義Logger

? 6.5 Hook 接口用法 logrus每次在執(zhí)行輸入之前都會調(diào)用這個hook方法

? 6.6 原生方法實現(xiàn)日志管理

? 6.7 日志分割悔橄,把日志變小

? 6.8 hooh 只調(diào)用一次

? 6.9 使用hook實現(xiàn)郵件警醒耙替,郵件警報hook

7.始構(gòu)建全文搜索引擎(譯)

8.go trace 剖析? 1.4占式調(diào)度

8.1 Scheduler latency profile

8.2 Goroutine analysis

8.3 View trace

9.Go語言自定義自己的SSH-Server

10.cavaliercoder/grab 支持?jǐn)帱c續(xù)傳

10.1 簡單例子

10.2 第二次運行該示例時鹦倚,它將自動恢復(fù)以前的下載并更快地退出

10.3 支持多個文件同時下載

11. 獲取目錄所有文件


------------

### 1.go 各種代碼運行

1.go 1.15 版本使用defer 速度提升,但是使用panic速度減低

go 2020年8月份穴翩,go各種版本的更新情況,地址:https://mp.weixin.qq.com/s/Tzqn5kzdfzcQPUD5NtN2mg

1. Go 1.0[1] — 2012 年 3 月:

2. Go 的第一個版本,帶著一份兼容性說明文檔[2]來保證與未來發(fā)布版本的兼容性牍陌,進(jìn)而不會破壞已有的程序。

3. 第一個版本已經(jīng)有 go tool pprof 命令和 go vet 命令员咽。go tool pprof 與 Google 的 pprof C++ profiler[3] 稍微有些差異毒涧。go vet(前身是 go tool vet)命令可以檢查包中潛在的錯誤。

4. Go 1.1[4] — 2013 年 5 月:

5. 這個 Go 版本專注于優(yōu)化語言(編譯器贝室,gc契讲,map仿吞,go 調(diào)度器)和提升它的性能。下面是一些提升的例子

6. Go 1.2[10] — 2013 年 12 月:

7. 本版本中 test 命令支持測試代碼覆蓋范圍并提供了一個新命令 go tool cover 捡偏,此命令能測試代碼覆蓋率:

8. Go 1.3[11] — 2014 年 6 月:

9. 這個版本對棧管理做了重要的改進(jìn)唤冈。棧可以申請連續(xù)的內(nèi)存片段[12]银伟,提高了分配的效率你虹,使下一個版本的棧空間降到 2KB彤避。

10. 棧頻繁申請/釋放棧片段會導(dǎo)致某些元素變慢傅物,本版本也改進(jìn)了一些由于上述場景糟糕的分配導(dǎo)致變慢的元素。下面是一個 json 包的例子琉预,展示了它對椂危空間的敏感程度:

11. Go 1.4[16] — 2014 年 12 月:

12. 此版本帶來了官方對 Android 的支持,[golang.org/x/mobile](Go 1.4[17] ) 讓我們可以只用 Go 代碼就能寫出簡單的 Android 程序圆米。

13. 歸功于更高效的 gc卒暂,之前用 C 和匯編寫的運行時代碼被翻譯成 Go 后,堆的大小降低了 10% 到 30%榨咐。

14. 與版本無關(guān)的一個巧合是介却,Go 項目管理從 Mercurial 移植到了 Git,代碼從 Google Code 移到了 Github块茁。

15. Go 也提供了 go generate 命令通過掃描用 //go:generate 指示的代碼來簡化代碼生成過程齿坷。

16. 在 Go 博客[18] 和文章生成代碼[19]中可以查看更多信息。

17. Go 1.5[20] — 2015 年 8 月:

18. 這個新版本数焊,發(fā)布時間推遲[21]了兩個月永淌,目的是在以后每年八月和二月發(fā)布新版本:

19. Go 1.6[24] — 2016 年 2 月:

20. 這個版本最重大的變化是使用 HTTPS 時默認(rèn)支持 HTTP/2。

21. 在這個版本中 gc 等待時間也降低了:

22. Go 1.7[25] — 2016 年 8 月:

23. 這個版本發(fā)布了 context 包[26]佩耳,為用戶提供了處理超時和任務(wù)取消的方法遂蛀。

24. 閱讀我的文章 傳遞上下文和取消[27]來獲取更多關(guān)于 context 的信息。

25. 對編譯工具鏈也作了優(yōu)化干厚,編譯速度更快李滴,生成的二進(jìn)制文件更小,有時甚至可以減小 20% 到 30%蛮瞄。

26. Go 1.8[28] — 2017 年 2 月:

27. 把 gc 的停頓時間減少到了 1 毫秒以下:

28. Go 1.9[30] — 2017 年 8 月:

29. 這個版本支持下面的別名聲明:

30. type byte = uint8

31. 這里 byte 是 uint8 的一個別名所坯。

32. sync 包新增了一個 Map[31] 類型,是并發(fā)寫安全的挂捅。

33. 我的文章 Map 與并發(fā)寫[32] 中有更多信息

34. Go 1.10[33] — 2018 年 2 月:

35. test 包引進(jìn)了一個新的智能 cache芹助,運行會測試后會緩存測試結(jié)果。如果運行完一次后沒有做任何修改,那么開發(fā)者就不需要重復(fù)運行測試状土,節(jié)省時間无蜂。

36. Go 1.11[34] — 2018 年 8 月:

37. Go 1.11 帶來了一個重要的新功能:Go modules[35]。去年的調(diào)查顯示蒙谓,Go modules 是 Go 社區(qū)遭遇重大挑戰(zhàn)后的產(chǎn)物:

38. Go 1.12[37] — 2019 年 2 月:

39. 基于 analysis 包重寫了 go vet 命令斥季,為開發(fā)者寫自己的檢查器提供了更大的靈活性。

40. 我的文章構(gòu)建自己的分析器[38]中有更多信息彼乌。

41. Go 1.13[39] — 2019 年 9 月:

42. 改進(jìn)了 sync 包中的 Pool泻肯,在 gc 運行時不會清除 pool。它引進(jìn)了一個緩存來清理兩次 gc 運行時都沒有被引用的 pool 中的實例慰照。

43. 重寫了逃逸分析,減少了 Go 程序中堆上的內(nèi)存申請的空間琉朽。下面是對這個新的逃逸分析運行基準(zhǔn)的結(jié)果:

44. Go1.14[40] - 2020 年 2 月:

45. 現(xiàn)在 Go Module 已經(jīng)可以用于生產(chǎn)環(huán)境毒租,鼓勵所有用戶遷移到 Module。該版本支持嵌入具有重疊方法集的接口箱叁。性能方面做了較大的改進(jìn)墅垮,包括:進(jìn)一步提升 defer 性能、頁分配器更高效耕漱,同時 timer 也更高效算色。

46. 現(xiàn)在,Goroutine 支持異步搶占螟够。

47. Go1.15[41]? - 2020 年 8 月:

48. 受疫情影響灾梦,這次版本變化的內(nèi)容不太多,但如期發(fā)布了妓笙。

49. 它的大部分更改在工具鏈若河、運行時和庫的實現(xiàn)。與往常一樣寞宫,該版本保留了 Go 1 兼容性的承諾萧福。這幾乎保證所有的 Go 程序都能像以前一樣正常編譯和運行。

50. Go 1.15 包括對鏈接器的重大改進(jìn)辈赋,改進(jìn)了對具有大量內(nèi)核的小對象的分配鲫忍,并棄用了 X.509 CommonName。GOPROXY 現(xiàn)在支持跳過返回錯誤的代理钥屈,并添加了新的嵌入式 tzdata 包悟民。

------------

------------

###? 2.go 在線編輯代碼運行

? ? ? ? git clone git@github.com:thetimetravel/gpgsync.git

? ? ? ? cd gpgsync/

? ? ? ? node server.js

? ? ? ? http://localhost:8086/

? ? ? ? github地址:https://github.com/syumai/gpgsync

? ? ? ? Demo:https://gpgsync.herokuapp.com/

------------

##? 3.通過 Gob 包序列化二進(jìn)制數(shù)據(jù)

Go 官方還提供了 encoding/gob 包將數(shù)據(jù)序列化為二進(jìn)制流以便通過網(wǎng)絡(luò)進(jìn)行傳輸

我們在前面 Go 入門教程中已經(jīng)介紹過 Gob 包作為二進(jìn)制數(shù)據(jù)編解碼工具的基本使用,這里簡單演示下如何將 Gob 編碼后的二進(jìn)制數(shù)據(jù)寫入磁盤文件:

```go

package main

import (

? ? "bytes"

? ? "encoding/gob"

? ? "fmt"

? ? "io/ioutil"

)

type Article struct {

? ? Id int

? ? Title string

? ? Content string

? ? Author string

}

// 寫入二進(jìn)制數(shù)據(jù)到磁盤文件

func write(data interface{}, filename string)? {

? ? buffer := new(bytes.Buffer)

? ? encoder := gob.NewEncoder(buffer)

? ? err := encoder.Encode(data)

? ? if err != nil {

? ? ? ? panic(err)

? ? }

? ? err = ioutil.WriteFile(filename, buffer.Bytes(), 0600)

? ? if err != nil {

? ? ? ? panic(err)

? ? }

}

// 從磁盤文件加載二進(jìn)制數(shù)據(jù)

func read(data interface{}, filename string) {

? ? raw, err := ioutil.ReadFile(filename)

? ? if err != nil {

? ? ? ? panic(err)

? ? }

? ? buffer := bytes.NewBuffer(raw)

? ? dec := gob.NewDecoder(buffer)

? ? err = dec.Decode(data)

? ? if err != nil {

? ? ? ? panic(err)

? ? }

}

func main()? {

? ? article := Article{

? ? ? ? Id: 1,

? ? ? ? Title: "基于 Gob 包編解碼二進(jìn)制數(shù)據(jù)",

? ? ? ? Content: "通過 Gob 包序列化二進(jìn)制數(shù)據(jù)以便通過網(wǎng)絡(luò)傳輸",

? ? ? ? Author: "學(xué)院君",

? ? }

? ? write(article, "article_data")

? ? var articleData Article

? ? read(&articleData, "article_data")

? ? fmt.Printf("%#v\n", articleData)

}

```

------------

## 4.使用 encoding/csv 包讀寫 CSV 文件

在 Go 語言中焕蹄,可以通過官方提供的 encoding/csv 包來操作 CSV 文件的寫入和讀取逾雄,我們新建一個 csv.go 文件,并編寫一段示例代碼如下:

可以看到新建文件、打開文件鸦泳、關(guān)閉文件和上篇教程操作普通的磁盤文件并無區(qū)別银锻,不過這里為了支持通過 CSV 格式寫入和讀取文件,我們在文件句柄之上套了一層 CSV Writer 和 CSV Reader做鹰,這有點像適配器模式击纬,然后我們就可以通過 CSV Writer 寫入數(shù)據(jù)到 CSV 文件,通過 CSV Reader 讀取 CSV 文件了:

// 初始化一個 csv writer钾麸,并通過這個 writer 寫入數(shù)據(jù)到 csv 文件

writer := csv.NewWriter(csvFile)

...

// 初始化一個 csv reader更振,并通過這個 reader 從 csv 文件讀取數(shù)據(jù)

reader := csv.NewReader(file)?

// 寫入 UTF-8 BOM,防止中文亂碼

csvFile.WriteString("\xEF\xBB\xBF")

// 初始化一個 csv writer饭尝,并通過這個 writer 寫入數(shù)據(jù)到 csv 文件

writer := csv.NewWriter(csvFile)

? ```

? package main

import (

"encoding/csv"

"fmt"

"os"

"strconv"

)

type Tutorial struct {

Id int

Title string

Summary string

Author string

}

func main()? {

// 創(chuàng)建一個 tutorials.csv 文件

csvFile, err := os.Create("tutorials.csv")

if err != nil {

panic(err)

}

defer csvFile.Close()

// 初始化字典數(shù)據(jù)

tutorials := []Tutorial{

Tutorial{Id: 1, Title: "Go 入門編程", Summary: "Go 基本語法和使用示例2", Author: "學(xué)院君"},

Tutorial{Id: 2, Title: "Go Web 編程", Summary: "Go Web 編程入門指南", Author: "學(xué)院君"},

Tutorial{Id: 3, Title: "Go 并發(fā)編程", Summary: "通過并發(fā)編程提升性能", Author: "學(xué)院君"},

Tutorial{Id: 4, Title: "Go 微服務(wù)開發(fā)", Summary: "基于 go-micro 框架開發(fā)微服務(wù)", Author: "學(xué)院君"},

}

csvFile.WriteString("\xEF\xBB\xBF") // 寫入 UTF-8 BOM肯腕,防止中文亂碼

//這是因為 Excel 默認(rèn)并不是 UTF-8 編碼,因此要解決這個亂碼問題钥平,可以在對應(yīng)的 CSV 文件寫入 UTF-8 BOM 頭实撒,告知 Excel 通過 UTF-8 編碼打開這個文件

// 初始化一個 csv writer,并通過這個 writer 寫

// 入數(shù)據(jù)到 csv 文件

writer := csv.NewWriter(csvFile)

for _, tutorial := range tutorials {

line := []string{

strconv.Itoa(tutorial.Id),? // 將 int 類型數(shù)據(jù)轉(zhuǎn)化為字符串

tutorial.Title,

tutorial.Summary,

tutorial.Author,

}

// 將切片類型行數(shù)據(jù)寫入 csv 文件

err := writer.Write(line)

if err != nil {

panic(err)

}

}

// 將 writer 緩沖中的數(shù)據(jù)都推送到 csv 文件涉瘾,至此就完成了數(shù)據(jù)寫入到 csv 文件

writer.Flush()

// 打開這個 csv 文件

file, err := os.Open("tutorials.csv")

if err != nil {

panic(err)

}

defer file.Close()

// 初始化一個 csv reader知态,并通過這個 reader 從 csv 文件讀取數(shù)據(jù)

reader := csv.NewReader(file)

// 設(shè)置返回記錄中每行數(shù)據(jù)期望的字段數(shù),-1 表示返回所有字段

reader.FieldsPerRecord = -1

// 通過 readAll 方法返回 csv 文件中的所有內(nèi)容

record, err := reader.ReadAll()

if err != nil {

panic(err)

}

// 遍歷從 csv 文件中讀取的所有內(nèi)容立叛,并將其追加到 tutorials2 切片中

var tutorials2 []Tutorial

for _, item := range record {

id, _ := strconv.ParseInt(item[0], 0, 0)

tutorial := Tutorial{Id: int(id), Title: item[1], Summary: item[2], Author: item[3]}

tutorials2 = append(tutorials, tutorial)

}

// 打印 tutorials2 的第一個元素驗證 csv 文件寫入/讀取是否成功

fmt.Println(tutorials2[0].Id)

fmt.Println(tutorials2[0].Title)

fmt.Println(tutorials2[0].Summary)

fmt.Println(tutorials2[0].Author)

}

? ```

------------

## 5.實現(xiàn) HTTP 斷點續(xù)傳多線程下載

參考網(wǎng)站:https://mp.weixin.qq.com/s/dt5emM2IsJ3DKmL502Zm8A

### 5.1. HTTP斷點續(xù)傳多線程下載

一個比較常見的場景,就是斷點續(xù)傳/下載,在網(wǎng)絡(luò)情況不好的時候,可以在斷開連接以后,僅繼續(xù)獲取部分內(nèi)容. 例如在網(wǎng)上下載軟件,已經(jīng)下載了 95% 了,此時網(wǎng)絡(luò)斷了,如果不支持范圍請求,那就只有被迫重頭開始下載.但是如果有范圍請求的加持,就只需要下載最后 5% 的資源,避免重新下載.

另一個場景就是多線程下載,對大型文件,開啟多個線程, 每個線程下載其中的某一段,最后下載完成之后, 在本地拼接成一個完整的文件,可以更有效的利用資源.

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/1c260b033a68cfbd81d4f107120f6438?showdoc=.jpg)

### 5.2. Range & Content-Range

HTTP1.1 協(xié)議(RFC2616)開始支持獲取文件的部分內(nèi)容,這為并行下載以及斷點續(xù)傳提供了技術(shù)支持. 它通過在 Header 里兩個參數(shù)實現(xiàn)的,客戶端發(fā)請求時對應(yīng)的是 Range ,服務(wù)器端響應(yīng)時對應(yīng)的是 Content-Range.

$ curl --location --head 'https://download.jetbrains.com/go/goland-2020.2.2.exe'

date: Sat, 15 Aug 2020 02:44:09 GMT

content-type: text/html

content-length: 138

location: https://download-cf.jetbrains.com/go/goland-2020.2.2.exe

server: nginx

strict-transport-security: max-age=31536000; includeSubdomains;

x-frame-options: DENY

x-content-type-options: nosniff

x-xss-protection: 1; mode=block;

x-geocountry: United States

x-geocode: US

Range

The Range 是一個請求首部,告知服務(wù)器返回文件的哪一部分. 在一個 Range 首部中,可以一次性請求多個部分,服務(wù)器會以 multipart 文件的形式將其返回. 如果服務(wù)器返回的是范圍響應(yīng),需要使用 206 Partial Content 狀態(tài)碼. 假如所請求的范圍不合法,那么服務(wù)器會返回 416 Range Not Satisfiable 狀態(tài)碼,表示客戶端錯誤. 服務(wù)器允許忽略 Range 首部,從而返回整個文件,狀態(tài)碼用 200 .Range:(unit=first byte pos)-[last byte pos]

Range 頭部的格式有以下幾種情況:

Range: <unit>=<range-start>-

Range: <unit>=<range-start>-<range-end>

Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end>

Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end>, <range-start>-<range-end>

Content-Range

假如在響應(yīng)中存在 Accept-Ranges 首部(并且它的值不為 “none”),那么表示該服務(wù)器支持范圍請求(支持?jǐn)帱c續(xù)傳). 例如,您可以使用 cURL 發(fā)送一個 HEAD 請求來進(jìn)行檢測.curl -I http://i.imgur.com/z4d4kWk.jpg

HTTP/1.1 200 OK

...

Accept-Ranges: bytes

Content-Length: 146515

在上面的響應(yīng)中, Accept-Ranges: bytes 表示界定范圍的單位是 bytes . 這里 Content-Length 也是有效信息,因為它提供了要檢索的圖片的完整大小.

如果站點未發(fā)送 Accept-Ranges 首部,那么它們有可能不支持范圍請求.一些站點會明確將其值設(shè)置為 “none”,以此來表明不支持.在這種情況下,某些應(yīng)用的下載管理器會將暫停按鈕禁用.

### 5.3. Golang代碼實現(xiàn)HTTP斷點續(xù)傳多線程下載

通過以下代碼您可以了解到多線程下載的原理, 同時給您突破百度網(wǎng)盤下載提供思路.

```

package main

import (

"crypto/sha256"

"encoding/hex"

"errors"

"fmt"

"io/ioutil"

"log"

"mime"

"net/http"

"os"

"path/filepath"

"strconv"

"sync"

"time"

)

func parseFileInfoFrom(resp *http.Response) string {

contentDisposition := resp.Header.Get("Content-Disposition")

if contentDisposition != "" {

? _, params, err := mime.ParseMediaType(contentDisposition)

? if err != nil {

? panic(err)

? }

? return params["filename"]

}

filename := filepath.Base(resp.Request.URL.Path)

return filename

}

//FileDownloader 文件下載器

type FileDownloader struct {

fileSize? ? ? int

url? ? ? ? ? ? string

outputFileName string

totalPart? ? ? int //下載線程

outputDir? ? ? string

doneFilePart? []filePart

}

//NewFileDownloader .

func NewFileDownloader(url, outputFileName, outputDir string, totalPart int) *FileDownloader {

if outputDir == "" {

? wd, err := os.Getwd() //獲取當(dāng)前工作目錄

? if err != nil {

? log.Println(err)

? }

? outputDir = wd

}

return &FileDownloader{

? fileSize:? ? ? 0,

? url:? ? ? ? ? ? url,

? outputFileName: outputFileName,

? outputDir:? ? ? outputDir,

? totalPart:? ? ? totalPart,

? doneFilePart:? make([]filePart, totalPart),

}

}

//filePart 文件分片

type filePart struct {

Index int? ? //文件分片的序號

From? int? ? //開始byte

To? ? int? ? //解決byte

Data? []byte //http下載得到的文件內(nèi)容

}

func main() {

startTime := time.Now()

var url string //下載文件的地址

url = "https://download.jetbrains.com/go/goland-2020.2.2.dmg"

downloader := NewFileDownloader(url, "", "", 10)

if err := downloader.Run(); err != nil {

? // fmt.Printf("\n%s", err)

? log.Fatal(err)

}

fmt.Printf("\n 文件下載完成耗時: %f second\n", time.Now().Sub(startTime).Seconds())

}

//head 獲取要下載的文件的基本信息(header) 使用HTTP Method Head

func (d *FileDownloader) head() (int, error) {

r, err := d.getNewRequest("HEAD")

if err != nil {

? return 0, err

}

resp, err := http.DefaultClient.Do(r)

if err != nil {

? return 0, err

}

if resp.StatusCode > 299 {

? return 0, errors.New(fmt.Sprintf("Can't process, response is %v", resp.StatusCode))

}

//檢查是否支持 斷點續(xù)傳

//https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Ranges

if resp.Header.Get("Accept-Ranges") != "bytes" {

? return 0, errors.New("服務(wù)器不支持文件斷點續(xù)傳")

}

d.outputFileName = parseFileInfoFrom(resp)

//https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Length

return strconv.Atoi(resp.Header.Get("Content-Length"))

}

//Run 開始下載任務(wù)

func (d *FileDownloader) Run() error {

fileTotalSize, err := d.head()

if err != nil {

? return err

}

d.fileSize = fileTotalSize

jobs := make([]filePart, d.totalPart)

eachSize := fileTotalSize / d.totalPart

for i := range jobs {

? jobs[i].Index = i

? if i == 0 {

? jobs[i].From = 0

? } else {

? jobs[i].From = jobs[i-1].To + 1

? }

? if i < d.totalPart-1 {

? jobs[i].To = jobs[i].From + eachSize

? } else {

? //the last filePart

? jobs[i].To = fileTotalSize - 1

? }

}

var wg sync.WaitGroup

for _, j := range jobs {

? wg.Add(1)

? go func(job filePart) {

? defer wg.Done()

? err := d.downloadPart(job)

? if err != nil {

? ? log.Println("下載文件失敗:", err, job)

? }

? }(j)

}

wg.Wait()

return d.mergeFileParts()

}

//下載分片

func (d FileDownloader) downloadPart(c filePart) error {

r, err := d.getNewRequest("GET")

if err != nil {

? return err

}

log.Printf("開始[%d]下載from:%d to:%d\n", c.Index, c.From, c.To)

r.Header.Set("Range", fmt.Sprintf("bytes=%v-%v", c.From, c.To))

resp, err := http.DefaultClient.Do(r)

if err != nil {

? return err

}

if resp.StatusCode > 299 {

? return errors.New(fmt.Sprintf("服務(wù)器錯誤狀態(tài)碼: %v", resp.StatusCode))

}

defer resp.Body.Close()

bs, err := ioutil.ReadAll(resp.Body)

if err != nil {

? return err

}

if len(bs) != (c.To - c.From + 1) {

? return errors.New("下載文件分片長度錯誤")

}

c.Data = bs

d.doneFilePart[c.Index] = c

return nil

}

// getNewRequest 創(chuàng)建一個request

func (d FileDownloader) getNewRequest(method string) (*http.Request, error) {

r, err := http.NewRequest(

? method,

? d.url,

? nil,

)

if err != nil {

? return nil, err

}

r.Header.Set("User-Agent", "mojocn")

return r, nil

}

//mergeFileParts 合并下載的文件

func (d FileDownloader) mergeFileParts() error {

log.Println("開始合并文件")

path := filepath.Join(d.outputDir, d.outputFileName)

mergedFile, err := os.Create(path)

if err != nil {

? return err

}

defer mergedFile.Close()

hash := sha256.New()

totalSize := 0

for _, s := range d.doneFilePart {

? mergedFile.Write(s.Data)

? hash.Write(s.Data)

? totalSize += len(s.Data)

}

if totalSize != d.fileSize {

? return errors.New("文件不完整")

}

//https://download.jetbrains.com/go/goland-2020.2.2.dmg.sha256?_ga=2.223142619.1968990594.1597453229-1195436307.1493100134

if hex.EncodeToString(hash.Sum(nil)) != "3af4660ef22f805008e6773ac25f9edbc17c2014af18019b7374afbed63d4744" {

? return errors.New("文件損壞")

} else {

? log.Println("文件SHA-256校驗成功")

}

return nil

}

```

輸出結(jié)果:

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/4a537e610d66dddd41ab758b56da0e0d?showdoc=.jpg)

------------

## 6. logrus 日志使用教程

logrus是目前 Github 上 star 數(shù)量最多的日志庫,目前(2018.12,下同)star 數(shù)量為 8119,fork 數(shù)為 1031. logrus功能強(qiáng)大,性能高效,而且具有高度靈活性,提供了自定義插件的功能.很多開源項目,如docker,prometheus,dejavuzhou/ginbro[1]等,都是用了 logrus 來記錄其日志.

golang標(biāo)準(zhǔn)庫的日志框架非常簡單,僅僅提供了print,panic和fatal三個函數(shù)對于更精細(xì)的日志級別负敏、日志文件分割以及日志分發(fā)等方面并沒有提供支持. 所以催生了很多第三方的日志庫,但是在 golang 的世界里,沒有一個日志庫像 slf4j 那樣在 Java 中具有絕對統(tǒng)治地位.golang 中,流行的日志框架包括 logrus、zap秘蛇、zerolog其做、seelog

zap 是 Uber 推出的一個快速、結(jié)構(gòu)化的分級日志庫.具有強(qiáng)大的 ad-hoc 分析功能,并且具有靈活的儀表盤.zap 目前在 GitHub 上的 star 數(shù)量約為 4.3k. seelog 提供了靈活的異步調(diào)度彤叉、格式化和過濾功能.目前在 GitHub 上也有約 1.1k.

完全兼容 golang 標(biāo)準(zhǔn)庫日志模塊:logrus 擁有六種日志級別:debug庶柿、info、warn秽浇、error浮庐、fatal 和 panic,這是 golang 標(biāo)準(zhǔn)庫日志模塊的 API 的超集.如果您的項目使用標(biāo)準(zhǔn)庫日志模塊,完全可以以最低的代價遷移到 logrus 上柬焕。

- logrus.Debug("Useful debugging information.")

- logrus.Info("Something noteworthy happened!")

- logrus.Warn("You should probably take a look at this.")

- logrus.Error("Something failed but I'm not quitting.")

- logrus.Fatal("Bye.") // log 之后會調(diào)用 os.Exit(1)

- logrus.Panic("I'm bailing.") // log 之后會 panic()

可擴(kuò)展的 Hook 機(jī)制:允許使用者通過 hook 的方式將日志分發(fā)到任意地方,如本地文件系統(tǒng)审残、標(biāo)準(zhǔn)輸出、logstash斑举、elasticsearch或者mq等,或者通過 hook 定義日志內(nèi)容和格式等.

可選的日志輸出格式:logrus 內(nèi)置了兩種日志格式,JSONFormatter和TextFormatter,如果這兩個格式不滿足需求,可以自己動手實現(xiàn)接口 Formatter,來定義自己的日志格式.

Field機(jī)制:logrus鼓勵通過 Field 機(jī)制進(jìn)行精細(xì)化的搅轿、結(jié)構(gòu)化的日志記錄,而不是通過冗長的消息來記錄日志.

logrus是一個可插拔的、結(jié)構(gòu)化的日志框架.

Entry: logrus.WithFields 會自動返回一個 *Entry,Entry 里面的有些變量會被自動加上

-? time:entry被創(chuàng)建時的時間戳

- msg:在調(diào)用.Info()等方法時被添加

- level

使用方法:

go get github.com/sirupsen/logrus

go get golang.org/x/sys/windows和golang.org/x/sys/internal/unsafeheader(使用go get 導(dǎo)入異常富玷,因為這個包已經(jīng)不存在了嚼蚀,golang.org/x/sys/=https://github.com/golang/sys)

https://github.com/golang/sys 下載源碼,解壓文件抖拦,復(fù)制windows和internal目錄粘貼到golang.org/x/sys/目錄下(也可以使用 go get go get? -u github.com/golang/sys/tree/master/windows ,然后把github.com/golang/sys/windows和internal目錄復(fù)制到 golang.org/x/sys/)

go get github.com/zbindenren/logrus_mail

go get github.com/sirupsen/logrus

go get github.com/johntdyer/slackrus

go get github.com/bluele/logrus_slack

go get github.com/lestrrat-go/file-rotatelogs

go get github.com/spf13/viper

### 6.1 基本用法

```

package main

import (

log "github.com/sirupsen/logrus"

)

//1.基本用法

//logrus與 golang 標(biāo)準(zhǔn)庫日志模塊完全兼容,因此您可以使用log“github.com/sirupsen/logrus”替換所有日志導(dǎo)入. logrus可以通過簡單的配置,來定義輸出、格式或者日志級別等.

func main() {

log.WithFields(log.Fields{

"animal": "walrus",

}).Info("A walrus appears")

}

```

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/5b97b89ddca8daa5fc666a836812d11f?showdoc=.jpg)

### 6.2 簡單定義格式

```

package main

import (

log "github.com/sirupsen/logrus"

"os"

)

func init() {

// 設(shè)置日志格式為json格式

log.SetFormatter(&log.JSONFormatter{})

// 設(shè)置將日志輸出到標(biāo)準(zhǔn)輸出(默認(rèn)的輸出為stderr,標(biāo)準(zhǔn)錯誤)

// 日志消息輸出可以是任意的io.writer類型

log.SetOutput(os.Stdout)

// 設(shè)置日志級別為warn以上

log.SetLevel(log.WarnLevel)

}

//logrus與 golang 標(biāo)準(zhǔn)庫日志模塊完全兼容,因此您可以使用log“github.com/sirupsen/logrus”替換所有日志導(dǎo)入. logrus可以通過簡單的配置,來定義輸出幻工、格式或者日志級別等.

func main() {

log.WithFields(log.Fields{

"animal": "walrus",

"size":? 10,

}).Info("A group of walrus emerges from the ocean")

log.WithFields(log.Fields{

"omg":? ? true,

"number": 122,

}).Warn("The group's number increased tremendously!")

log.WithFields(log.Fields{

"omg":? ? true,

"number": 100,

}).Fatal("The ice breaks!")

}

```

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/b32ee8fec55753662d1c8b7129ac80a6?showdoc=.jpg)

### 6.3 輸入json格式和設(shè)置行號

```

package main

import (

"fmt"

"github.com/sirupsen/logrus"

"os"

)

func init() {

fmt.Println("gggg")

}

// logrus提供了New()函數(shù)來創(chuàng)建一個logrus的實例.

// 項目中,可以創(chuàng)建任意數(shù)量的logrus實例.

var log = logrus.New()

//如果想在一個應(yīng)用里面向多個地方log,可以創(chuàng)建 Logger 實例. logger是一種相對高級的用法, 對于一個大型項目, 往往需要一個全局的logrus實例,即logger對象來記錄項目所有的日志.如

func main() {

// 為當(dāng)前l(fā)ogrus實例設(shè)置消息的輸出,同樣地,

// 可以設(shè)置logrus實例的輸出到任意io.writer

log.Out = os.Stdout

log.SetReportCaller(true) //設(shè)置行號

// 為當(dāng)前l(fā)ogrus實例設(shè)置消息輸出格式為json格式.

// 同樣地,也可以單獨為某個logrus實例設(shè)置日志級別和hook,這里不詳細(xì)敘述.

log.Formatter = &logrus.JSONFormatter{}

log.WithFields(logrus.Fields{

"animal": "walrus",

"size":? 10,

"go":"dd",

}).Info("A group of walrus emerges from the ocean")

}

```

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/483f34aaf87b87645386e038f848567e?showdoc=.jpg)

### 6.4 自定義 Logger

如果想在一個應(yīng)用里面向多個地方log,可以創(chuàng)建 Logger 實例. logger是一種相對高級的用法, 對于一個大型項目, 往往需要一個全局的logrus實例,即logger對象來記錄項目所有的日志

<center>logrus4-main.go</center>

```

package main

import (

log "github.com/sirupsen/logrus"

"os"

iy "test/runtime/PProf/logrus/redire"

)

//2.自定義 Logger

//如果想在一個應(yīng)用里面向多個地方log,可以創(chuàng)建 Logger 實例. logger是一種相對高級的用法, 對于一個大型項目, 往往需要一個全局的logrus實例,即logger對象來記錄項目所有的日志.如:

func main() {

log.SetOutput(os.Stdout)

iy.InitLog("./log/", "test", "gb18030")

log.WithFields(log.Fields{"animal": "walrus","goo":1}).Info("A walrus appears")

log.Info("測試中文1")

}

```

<center>logrus4.go</center>

```

package redire

import (

"bytes"

"errors"

"fmt"

"os"

"path/filepath"

"runtime"

"strconv"

"strings"

//"test/runtime/PProf/logrus"

"time"

log "github.com/sirupsen/logrus"

)

//日志自定義格式

type LogFormatter struct{}

//格式詳情

func (s *LogFormatter) Format(entry *log.Entry) ([]byte, error) {

timestamp := time.Now().Local().Format("0102-150405.000")

var file string

var len int

if entry.Caller != nil {

file = filepath.Base(entry.Caller.File)

len = entry.Caller.Line

}

//fmt.Println(entry.Data)

msg := fmt.Sprintf("%s [%s:%d][GOID:%d][%s] %s\n", timestamp, file, len, getGID(), strings.ToUpper(entry.Level.String()), entry.Message)

return []byte(msg), nil

}

func getGID() uint64 {

b := make([]byte, 64)

b = b[:runtime.Stack(b, false)]

b = bytes.TrimPrefix(b, []byte("goroutine "))

b = b[:bytes.IndexByte(b, ' ')]

n, _ := strconv.ParseUint(string(b), 10, 64)

return n

}

type logFileWriter struct {

file? ? *os.File

logPath? string

fileDate string //判斷日期切換目錄

appName? string

encoding string

}

func (p *logFileWriter) Write(data []byte) (n int, err error) {

if p == nil {

return 0, errors.New("logFileWriter is nil")

}

if p.file == nil {

return 0, errors.New("file not opened")

}

//判斷是否需要切換日期

fileDate := time.Now().Format("20060102")

//fmt.Println("f:",p.fileDate,'=',fileDate)

if p.fileDate != fileDate {

p.file.Close()

fmt.Println("oo:",os.ModePerm)

err = os.MkdirAll(fmt.Sprintf("%s/%s", p.logPath, fileDate), os.ModePerm)

if err != nil {

return 0, err

}

filename := fmt.Sprintf("%s/%s/%s-%s.log", p.logPath, fileDate, p.appName, fileDate)

fmt.Println("po:", p.logPath,"=",p.fileDate,"-",filename)

//p.file, err = os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE|os.O_SYNC, 0600)

if err != nil {

return 0, err

}

}

if p.encoding != "" {

timestamp := time.Now().Local().Format("2006/01/02 15:04:05")

dataToEncode := ConvertStringToByte(timestamp,string(data))

//fmt.Println("loog:","=",string(data),"=")

n, e := p.file.Write(dataToEncode)

return n, e

}

n, e := p.file.Write(data)

return n,e

//timestamp := time.Now().Local().Format("2006/01/02 15:04:05")

//fmt.Println("gg:",timestamp,"=",p.appName,"=",fileDate)

//msg := fmt.Sprintf("%s [%s] %s\n", timestamp, strings.ToUpper(p.appName), fileDate)

//var err2 error

//return []byte(msg), err2

}

func ConvertStringToByte(str1,str2 string) []byte{

//str2=str2\

str3 := ""

arr_str2 := strings.Split(str2," ")

for index,value:=range arr_str2{

if(index!=0){

if(index>=2){

str3+=value+" "

} else{

str3+=value

}

}

//fmt.Println("ind:",index," ",value)

}

//str2=arr_str2[1]+" "+arr_str2[2]

//fmt.Println("arr_str2:", len(arr_str2),"=",arr_str2)

b := []byte(str1+" "+str3)

fmt.Println("str1:",str1," str3: ",str3)

return b

}

//初始化日志

func InitLog(logPath string, appName string, encoding string) {

fileDate := time.Now().Format("20060102")

//創(chuàng)建目錄

err := os.MkdirAll(fmt.Sprintf("%s/%s", logPath, fileDate), os.ModePerm)

if err != nil {

log.Error(err)

return

}

filename := fmt.Sprintf("%s/%s/%s-%s.log", logPath, fileDate, appName, fileDate)

file, err := os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE|os.O_SYNC, 0600)

if err != nil {

log.Error(err)

return

}

//fmt.Println("filename:",filename,"=",logPath,"=",fileDate,"=",encoding)

fileWriter := logFileWriter{file, logPath, fileDate, appName, encoding}

log.SetOutput(&fileWriter)

//設(shè)置行號

log.SetReportCaller(true)

//自定義 Logger

log.SetFormatter(new(LogFormatter))

}

```

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/7b6e3edf3b8efd0d33062f26da4ac98f?showdoc=.jpg)

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/231d698fbebdbcc4a98b4be23a44e596?showdoc=.jpg)

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/54f5a4894fdd2e7c7021a9175fdb6038?showdoc=.jpg)

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/48a906d81c36ba0f14e33c16fba31a7c?showdoc=.jpg)

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/de24b74c1344cbbdfff78d76407fbbe1?showdoc=.jpg)

### 6.5 Hook 接口用法 logrus每次在執(zhí)行輸出之前都會調(diào)用這個hook方法

```

package main

import (

"fmt"

log6 "github.com/sirupsen/logrus"

)

type AppHook struct {

AppName string

}

func (h *AppHook) Levels() []log6.Level {

return log6.AllLevels

}

func (h *AppHook) Fire(entry *log6.Entry) error {

entry.Data["app"] = h.AppName

fmt.Println("app:",entry.Message)

return nil

}

//還可以為logrus設(shè)置鉤子,每條日志輸出前都會執(zhí)行鉤子的特定方法黎茎。所以囊颅,我們可以添加輸出字段、根據(jù)級別將日志輸出到不同的目的地傅瞻。

// logrus也內(nèi)置了一個syslog的鉤子踢代,將日志輸出到syslog中。這里我們實現(xiàn)一個鉤子嗅骄,在輸出的日志中增加一個app=awesome-web字段胳挎。

func main() {

h := &AppHook{AppName: "awesome-web"}

log6.AddHook(h)

log6.Error("fff")

log6.Info("info msg")

}

```

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/dbdd79042198d7655ebdb314710b2202?showdoc=.jpg)

### 6.6 原生方法實現(xiàn)日志管理

```

package main

import (

"io"

"log"

"os"

)

var (

Info? ? *log.Logger

Warning *log.Logger

Error? *log.Logger

)

func init() {

errFile, err := os.OpenFile("errors.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)

if err != nil {

log.Fatalln("打開日志文件失敗:", err)

}

Info = log.New(os.Stdout, "Info:", log.Ldate|log.Ltime|log.Lshortfile)

Warning = log.New(os.Stdout, "Warning:", log.Ldate|log.Ltime|log.Lshortfile)

Error = log.New(io.MultiWriter(os.Stderr, errFile), "Error:", log.Ldate|log.Ltime|log.Lshortfile)

}

func main() {

Info.Println("Info log...")

Warning.Printf("Warning log...")

Error.Println("Error log...")

}

```

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/83df724434425dbff9dfee93d9176bc0?showdoc=.jpg)

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/a89f5a7ea14ff6e01b5ab6ed9d63b990?showdoc=.jpg)

### 6.7 日志分割溺森,把日志變小

最好是在管理員權(quán)限執(zhí)行該文件串远,windows就打開管理員cmd,進(jìn)入目錄,go run Logrus-Hook.go

每隔 1 分鐘輪轉(zhuǎn)一個新文件儿惫,保留最近 3 分鐘的日志文件,多余的自動清理掉

```

package main

import (

rotatelogs "github.com/lestrrat-go/file-rotatelogs"

log9 "github.com/sirupsen/logrus"

"time"

)

func init() {

/* 日志輪轉(zhuǎn)相關(guān)函數(shù)

`WithLinkName` 為最新的日志建立軟連接

`WithRotationTime` 設(shè)置日志分割的時間伸但,隔多久分割一次

WithMaxAge 和 WithRotationCount二者只能設(shè)置一個

? `WithMaxAge` 設(shè)置文件清理前的最長保存時間

? `WithRotationCount` 設(shè)置文件清理前最多保存的個數(shù)

*/

// 下面配置日志每隔 1 分鐘輪轉(zhuǎn)一個新文件肾请,保留最近 3 分鐘的日志文件,多余的自動清理掉更胖。

path := "D:\\study\\gohome\\logs.log"

writer, _ := rotatelogs.New(

path + ".%Y%m%d.log",

// 生成軟鏈铛铁,指向最新日志文件

rotatelogs.WithLinkName(path),

// 設(shè)置最大保存時間

rotatelogs.WithMaxAge(time.Duration(180)*time.Second),

// 設(shè)置日志切割時間間隔

rotatelogs.WithRotationTime(time.Duration(60)*time.Second),

)

log9.SetOutput(writer)

log9.SetFormatter(&log9.JSONFormatter{})

}

// 日志分隔 把日志變小,變成多個日志文件

//却妨,每隔 1 分鐘輪轉(zhuǎn)一個新文件饵逐,保留最近 3 分鐘的日志文件,多余的自動清理掉

func main() {

for {

log9.Info("hello, world!")

time.Sleep(5 * time.Second)

}

}

```

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/cc2d7faa82a77e86a91f71c6e1d5e888?showdoc=.jpg)

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/ef01802dbd2f0b6a2d8ca5e39fc4c46b?showdoc=.jpg)

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/d032d745e80f23950613903f859b87ec?showdoc=.jpg)

### 6.8 hook 只調(diào)用一次

<center>logrus-hook-main.go</center>

```

package main

import (

uuid "github.com/satori/go.uuid"

log5 "github.com/sirupsen/logrus"

"test/runtime/PProf/logrus/hook"

)

func initLog() {

uuids, _ := uuid.NewV4()

//fmt.Println("dddd",uuids)

log5.AddHook(hook.NewTraceIdHook(uuids.String() +" "))

}

func main() {

initLog()

log5.WithFields(log5.Fields{

"age": 12,

"name":? "xiaoming",

"sex": 1,

}).Info("小明來了")

log5.WithFields(log5.Fields{

"age": 13,

"name":? "xiaohong",

"sex": 0,

}).Error("小紅來了")

log5.WithFields(log5.Fields{

"age": 14,

"name":? "xiaofang",

"sex": 1,

}).Fatal("小芳來了")

}

```

<center>hood.go</center>

```

package hook

import (

"fmt"

"github.com/sirupsen/logrus"

)

type TraceIdHook struct {

TraceId? string

}

func NewTraceIdHook(traceId string) logrus.Hook {

hook := TraceIdHook{

TraceId:? traceId,

}

fmt.Println("ff:",traceId)

return &hook

}

func (hook *TraceIdHook) Fire(entry *logrus.Entry) error {

entry.Data["traceId"] = hook.TraceId

return nil

}

func (hook *TraceIdHook) Levels() []logrus.Level {

return logrus.AllLevels

}

```

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/03658466c6d1101a57b690c99095bd14?showdoc=.jpg)

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/40a1d57696bd5316ca5e67635f43acbf?showdoc=.jpg)

### 6.9 使用hook實現(xiàn)郵件警醒 郵件警報hook

```

package main

import (

"github.com/sirupsen/logrus"

"github.com/zbindenren/logrus_mail"

//"fmt"

"time"

)

//使用hook實現(xiàn)郵件警醒 郵件警報hook

func main() {

//from2 :="<766496095@qq.com>",

logger := logrus.New()

logrus.SetReportCaller(true)

logger.SetReportCaller(true) //設(shè)置行號

hook, err := logrus_mail.NewMailAuthHook(

"濱州服務(wù)器",

"smtp.qq.com",

25,

"766496095@qq.com",

"304311271@qq.com",

"766496095@qq.com",

"aycpqeqjjiykbffg",

)

if err == nil {

logger.Hooks.Add(hook)

}

//生成*Entry

var filename = "123.txt"

contextLogger := logger.WithFields(logrus.Fields{

"file":? ? filename,

"content": "GG",

})

//設(shè)置時間戳和message

contextLogger.Time = time.Now()

contextLogger.Message = "這是一個hook發(fā)來的郵件"

//只能發(fā)送Error,Fatal,Panic級別的log

contextLogger.Level = logrus.ErrorLevel

//contextLogger2 := logrus.Fields{

// "good":? ? "em",

// "ee": "GG",

//}

//

//contextLogger.Data=contextLogger2

//使用Fire發(fā)送,包含時間戳彪标,message

hook.Fire(contextLogger)

}

```

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/75ab671869796328c2eaf614fa044dc0?showdoc=.jpg)

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/ee168ed297a86f28d6ee4629fd499a30?showdoc=.jpg)

## 7.從零開始構(gòu)建全文搜索引擎(譯)

參考網(wǎng)址:

https://cloud.tencent.com/developer/article/1682841

https://github.com/akrylysov/simplefts

** https://mojotv.cn/404#Go%E8%BF%9B%E9%98%B6

https://hanyajun.com/golang/go_article_2019/

1. 打開https://github.com/akrylysov/simplefts 這個網(wǎng)站倍权,把代碼下載下來,解壓到路徑下

2.打開 https://dumps.wikimedia.org/enwiki/latest/ 捞烟,下載文件enwiki-latest-abstract1.xml.gz薄声,把文件解壓到gopath路徑,

3. 運行代碼的main.go,沒有報錯题画,說找不到文件進(jìn)行默辨,

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/d7799eba440f0887862d71637d3be71d?showdoc=.jpg)

## 8.go trace 剖析? 1.4占式調(diào)度

參考地址:

https://mp.weixin.qq.com/s/iXkbF018fxgTWtMqxZfo6g

https://mp.weixin.qq.com/s/Wp15aOLeYhZYla275TzISw(比較1.13和1.14的區(qū)別)

<center>trace2.go</center>

```

package main

import (

"fmt"

"os"

"runtime/trace"

)

func main() {

fmt.Println("fff")

trace.Start(os.Stderr)

defer trace.Stop()

ch:= make(chan string)

go func(){

ch<- "EDDYCJY"

}()

<-ch

}

```

1.? go run trace2.go 2> trace.out //就會看見一個trace.out

2.? 在命令行執(zhí)行:go tool trace trace.out,

3.? 在瀏覽器打開 http://localhost:http://localhost:12413/

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/2137249d0c7f6e8e5a4d2e0b90efc564?showdoc=.jpg)

名詞解釋:

- View trace:查看跟蹤(這個是今天要使用的重點)苍息,能看到一段時間內(nèi) goroutine 的調(diào)度執(zhí)行情況缩幸,包括事件觸發(fā)鏈壹置;

- Goroutine analysis:Goroutine 分析,能看到這段時間所有 goroutine 執(zhí)行的一個情況表谊,執(zhí)行堆棧钞护,執(zhí)行時間;

- Network blocking profile:網(wǎng)絡(luò)阻塞概況(分析網(wǎng)絡(luò)的一些消耗)

- Synchronization blocking profile:同步阻塞概況(分析同步鎖的一些情況)

- Syscall blocking profile:系統(tǒng)調(diào)用阻塞概況(分析系統(tǒng)調(diào)用的消耗)

- Scheduler latency profile:調(diào)度延遲概況(函數(shù)的延遲占比)

- User defined tasks:自定義任務(wù)

- User defined regions:自定義區(qū)域

- Minimum mutator utilization:最低 Mutator 利用率

### 8.1 Scheduler latency profile

在剛開始時铃肯,我們一般先查看 “Scheduler latency profile”患亿,我們能通過 Graph 看到整體的調(diào)用開銷情況,如下:

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/403dc98534e09d25c6f4caa4166377da?showdoc=.jpg)

因為演示程序比較簡單押逼,因此這里就兩塊步藕,一個是 trace 本身,另外一個是 channel 的收發(fā)挑格。

### 8.2 Goroutine analysis

第二步看 “Goroutine analysis”咙冗,我們能通過這個功能看到整個運行過程中,每個函數(shù)塊有多少個有 Goroutine 在跑漂彤,并且觀察每個的 Goroutine 的運行開銷都花費在哪個階段雾消。如下:

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/c0a67b82e50f1e5a62a6545cd9747201?showdoc=.jpg)

通過上圖我們可以看到共有 3 個 goroutine,分別是 runtime.main挫望、 runtime/trace.Start.func1立润、 main.main.func1,那么它都做了些什么事呢媳板,接下來我們可以通過點擊具體細(xì)項去觀察桑腮。如下:

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/cae093887e351262a1883733d007a64b?showdoc=.jpg)

同時也可以看到當(dāng)前 Goroutine 在整個調(diào)用耗時中的占比,以及 GC 清掃和 GC 暫停等待的一些開銷蛉幸。如果你覺得還不夠破讨,可以把圖表下載下來分析,相當(dāng)于把整個 Goroutine 運行時掰開來看了奕纫,這塊能夠很好的幫助我們對 Goroutine 運行階段做一個的剖析提陶,可以得知到底慢哪,然后再決定下一步的排查方向匹层。如下:

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/5a35c4c2d8bc29f8eb725172d4712acf?showdoc=.jpg)

### 8.3 View trace

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/2673e3d3e04a11b2c9f28b3f82918c97?showdoc=.jpg)

1. 時間線:顯示執(zhí)行的時間單元隙笆,根據(jù)時間維度的不同可以調(diào)整區(qū)間,具體可執(zhí)行 shift + ? 查看幫助手冊又固。

2. 堆:顯示執(zhí)行期間的內(nèi)存分配和釋放情況仲器。

3. 協(xié)程:顯示在執(zhí)行期間的每個 Goroutine 運行階段有多少個協(xié)程在運行,其包含 GC 等待(GCWaiting)仰冠、可運行(Runnable)乏冀、運行中(Running)這三種狀態(tài)。

4. OS 線程:顯示在執(zhí)行期間有多少個線程在運行洋只,其包含正在調(diào)用 Syscall(InSyscall)辆沦、運行中(Running)這兩種狀態(tài)昼捍。

5. 虛擬處理器:每個虛擬處理器顯示一行,虛擬處理器的數(shù)量一般默認(rèn)為系統(tǒng)內(nèi)核數(shù)肢扯。

6. 協(xié)程和事件:顯示在每個虛擬處理器上有什么 Goroutine 正在運行妒茬,而連線行為代表事件關(guān)聯(lián)。

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/dc8331e5038ab50d409ed8f385ad4ca6?showdoc=.jpg)

點擊具體的 Goroutine 行為后可以看到其相關(guān)聯(lián)的詳細(xì)信息蔚晨,這塊很簡單乍钻,大家實際操作一下就懂了。文字解釋如下:

- Start:開始時間

- Wall Duration:持續(xù)時間

- Self Time:執(zhí)行時間

- Start Stack Trace:開始時的堆棧信息

- End Stack Trace:結(jié)束時的堆棧信息

- Incoming flow:輸入流

- Outgoing flow:輸出流

- Preceding events:之前的事件

- Following events:之后的事件

- All connected:所有連接的事件

View Events

我們可以通過點擊 View Options-Flow events铭腕、Following events 等方式银择,查看我們應(yīng)用運行中的事件流情況。如下:

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/089e3855ab9cc015f38fd057357f32b9?showdoc=.jpg)

通過分析圖上的事件流累舷,我們可得知這程序從 G1 runtime.main 開始運行浩考,在運行時創(chuàng)建了 2 個 Goroutine,先是創(chuàng)建 G18 runtime/trace.Start.func1被盈,然后再是 G19 main.main.func1 析孽。而同時我們可以通過其 Goroutine Name 去了解它的調(diào)用類型,如:runtime/trace.Start.func1 就是程序中在 main.main 調(diào)用了 runtime/trace.Start 方法只怎,然后該方法又利用協(xié)程創(chuàng)建了一個閉包 func1 去進(jìn)行調(diào)用袜瞬。

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/fe003673e118d92f14e933a8d0c36075?showdoc=.jpg)

結(jié)合實戰(zhàn)

今天生產(chǎn)環(huán)境突然出現(xiàn)了問題,機(jī)智的你早已埋好 _"net/http/pprof" 這個神奇的工具身堡,你麻利的執(zhí)行了如下命令:

curl http://127.0.0.1:6060/debug/pprof/trace\?seconds\=20 > trace.out

go tool trace trace.out

## 9.Go語言自定義自己的SSH-Server

go get golang.org/x/crypto/ssh

go get? golang.org/x/crypto/ed25519

go get golang.org/x/crypto/poly1305

go get golang.org/x/crypto/chacha20

go get golang.org/x/crypto/curve25519

go get golang.org/x/crypto/internal/subtle

go get golang.org/x/crypto/blowfish

https://github.com/golang/crypto? 下載這個壓縮包吞滞,解壓,放到制定位置

## 10.cavaliercoder/grab 支持?jǐn)帱c續(xù)傳

go get github.com/cavaliercoder/grab

Grab是一個Go軟件包盾沫,用于從Internet下載具有以下rad功能的文件:

- 同時監(jiān)控下載進(jìn)度

- 自動恢復(fù)不完整的下載

- 從內(nèi)容標(biāo)題或URL路徑猜測文件名

- 使用context.Context安全地取消下載

- 使用校驗和驗證下載

- 同時下載大量文件

- 應(yīng)用速率限制器

### 10.1 簡單例子

```

package main

import (

"fmt"

"github.com/cavaliercoder/grab"

"log"

)

func main() {

path := `H:/BaiduNetdiskDownload/Java性能調(diào)優(yōu)(10-10)`

resp, err := grab.Get("src/uiprogress/week5", path)

if err != nil {

log.Fatal(err)

}

fmt.Println("Download saved to", resp.Filename)

}

```

### 10.2 第二次運行該示例時,它將自動恢復(fù)以前的下載并更快地退出

```

package main

import (

"fmt"

"os"

"time"

"github.com/cavaliercoder/grab"

)

//第二次運行該示例時殿漠,它將自動恢復(fù)以前的下載并更快地退出赴精。

func main() {

// create client

client := grab.NewClient()

req, _ := grab.NewRequest(".", "http://www.golang-book.com/public/pdf/gobook.pdf")

// start download

fmt.Printf("Downloading %v...\n", req.URL())

resp := client.Do(req)

fmt.Printf("? %v\n", resp.HTTPResponse.Status)

// start UI loop

t := time.NewTicker(500 * time.Millisecond)

defer t.Stop()

Loop:

for {

select {

case <-t.C:

fmt.Printf("? transferred %v / %v bytes (%.2f%%)\n",

resp.BytesComplete(),

resp.Size(),

100*resp.Progress())

case <-resp.Done:

// download is complete

break Loop

}

}

// check for errors

if err := resp.Err(); err != nil {

fmt.Fprintf(os.Stderr, "Download failed: %v\n", err)

os.Exit(1)

}

fmt.Printf("Download saved to ./%v \n", resp.Filename)

// Output:

// Downloading http://www.golang-book.com/public/pdf/gobook.pdf...

//? 200 OK

//? transferred 42970 / 2893557 bytes (1.49%)

//? transferred 1207474 / 2893557 bytes (41.73%)

//? transferred 2758210 / 2893557 bytes (95.32%)

// Download saved to ./gobook.pdf

}

```

### 10.3 支持多個文件同時下載

```

package main

import (

"fmt"

"github.com/cavaliercoder/grab"

"io/ioutil"

"os"

"time"

)

func download(dst string,urls ...string)? {

n:=len(urls)

re,err:=grab.GetBatch(n,dst,urls...)

if err!=nil {

fmt.Print(err)

return

}

t:=time.NewTicker(time.Millisecond*10)

complete:=0

progress:=0

responses:=make([]*grab.Response,0)

for complete<n {

select {

case r:=<-re:

responses=append(responses,r)

case <-t.C:

progress=0

for k,v:=range responses{

if v!=nil {

//var test1? = (v.Size)

//var test2? =decimal.NewFromInt(test1)

//fmt.Printf("v:%v %T %v \n",test2,test2,k)

//return;

if v.IsComplete() {

if v.Err()==nil {

fmt.Printf("%s %s/%s %d%%\n",v.Filename,ShowBytes(float64(v.BytesComplete())),ShowBytes2(v.Size()),int(v.Progress()*100))

} else {

fmt.Printf("%s:%s\n",v.Filename,v.Err())

}

responses[k]=nil

complete++

} else {

fmt.Printf("%s %s/%s %d%%\n",v.Filename,ShowBytes(float64(v.BytesComplete())),ShowBytes2(v.Size()),int(v.Progress()*100))

progress++

}

}

}

if progress>0 {

fmt.Printf("%d downloading\n",progress)

}

}

}

t.Stop()

}

func ShowBytes(test2 float64) string {

//var test3 int64 = test2

//var test4? float64 =decimal.NewFromInt(test3).Float64()

//fmt.Printf("te: %v? %T\n",test4)

if test2<1024 {

return fmt.Sprintf("%.2fB",test2)

} else if test2<1024*1024 {

return fmt.Sprintf("%.2fKB",test2/1024.0)

} else if test2<1024*1024*1024 {

return fmt.Sprintf("%.2fMB",test2/1024.0/1024.0)

} else {

return fmt.Sprintf("%.2fGB",test2/1024.0/1024.0/1024.0)

}

}

func ShowBytes2(test2 int64) string {

//var test3 int64 = test2

//var test4? float64 =decimal.NewFromInt(test3).Float64()

//fmt.Printf("te: %v? %T\n",test4)

if test2<1024 {

return fmt.Sprintf("%.2fB",test2)

} else if test2<1024*1024 {

return fmt.Sprintf("%.2fKB",test2/1024.0)

} else if test2<1024*1024*1024 {

return fmt.Sprintf("%.2fMB",test2/1024.0/1024.0)

} else {

return fmt.Sprintf("%.2fGB",test2/1024.0/1024.0/1024.0)

}

}

func main()? {

urls:=[]string{

"https://rpic.douyucdn.cn/live-cover/appCovers/2018/08/31/3279944_20180831104533_small.jpg",

"https://rpic.douyucdn.cn/live-cover/roomCover/2018/11/06/0a699f47dc4fc55deaa82402cc0876ea_big.png",

"https://rpic.douyucdn.cn/live-cover/appCovers/2018/10/19/5230163_20181019161115_small.jpg",

}

//xfiles, _ := GetAllFiles2(`H:/BaiduNetdiskDownload/Java性能調(diào)優(yōu)/01-開篇詞 (1講)`)

//urls =append(urls,xfiles...)

fmt.Println("shu:",urls)

download("src/uiprogress/week5",urls...)

}

```

## 11. 獲取目錄所有文件

```

package main

import (

"fmt"

"io/ioutil"

"os"

"strings"

)

//獲取指定目錄下的所有文件和目錄

func GetFilesAndDirs(dirPth string) (files []string, dirs []string, err error) {

dir, err := ioutil.ReadDir(dirPth)

if err != nil {

return nil, nil, err

}

PthSep := string(os.PathSeparator)

//suffix = strings.ToUpper(suffix) //忽略后綴匹配的大小寫

for _, fi := range dir {

if fi.IsDir() { // 目錄, 遞歸遍歷

dirs = append(dirs, dirPth+PthSep+fi.Name())

GetFilesAndDirs(dirPth + PthSep + fi.Name())

} else {

// 過濾指定格式

ok := strings.HasSuffix(fi.Name(), ".go")

if ok {

files = append(files, dirPth+PthSep+fi.Name())

}

}

}

return files, dirs, nil

}

//獲取指定目錄下的所有文件,包含子目錄下的文件

func GetAllFiles(dirPth string) (files []string, err error) {

var dirs []string

dir, err := ioutil.ReadDir(dirPth)

if err != nil {

return nil, err

}

PthSep := string(os.PathSeparator)

//suffix = strings.ToUpper(suffix) //忽略后綴匹配的大小寫

for _, fi := range dir {

if fi.IsDir() { // 目錄, 遞歸遍歷

dirs = append(dirs, dirPth+PthSep+fi.Name())

GetAllFiles(dirPth + PthSep + fi.Name())

} else {

// 過濾指定格式

//ok := strings.HasSuffix(fi.Name(), ".go")

//if ok {

// files = append(files, dirPth+PthSep+fi.Name())

//}

files = append(files, dirPth+PthSep+fi.Name())

}

}

// 讀取子目錄下文件

for _, table := range dirs {

temp, _ := GetAllFiles(table)

for _, temp1 := range temp {

files = append(files, temp1)

}

}

return files, nil

}

func main() {

path := `H:/BaiduNetdiskDownload/Java性能調(diào)優(yōu)`

files, dirs, _ := GetFilesAndDirs(path)

for _, dir := range dirs {

fmt.Printf("獲取的文件夾為[%s]\n", dir)

}

for _, table := range dirs {

temp, _, _ := GetFilesAndDirs(table)

for _, temp1 := range temp {

files = append(files, temp1)

}

}

for _, table1 := range files {

fmt.Printf("獲取的文件為[%s]\n", table1)

}

fmt.Printf("=======================================\n")

xfiles, _ := GetAllFiles(`H:\\BaiduNetdiskDownload\\Java性能調(diào)優(yōu)\\01-開篇詞 (1講)`)

for _, file := range xfiles {

fmt.Printf("獲取的文件為[%s]\n", file)

}

}

```

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/7988228f70c9a5e461b0570c34b9f0ce?showdoc=.jpg)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市绞幌,隨后出現(xiàn)的幾起案子蕾哟,更是在濱河造成了極大的恐慌,老刑警劉巖莲蜘,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谭确,死亡現(xiàn)場離奇詭異,居然都是意外死亡票渠,警方通過查閱死者的電腦和手機(jī)逐哈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來问顷,“玉大人昂秃,你說我怎么就攤上這事禀梳。” “怎么了肠骆?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵算途,是天一觀的道長。 經(jīng)常有香客問我蚀腿,道長嘴瓤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任莉钙,我火速辦了婚禮廓脆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘胆胰。我一直安慰自己狞贱,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布蜀涨。 她就那樣靜靜地躺著瞎嬉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪厚柳。 梳的紋絲不亂的頭發(fā)上氧枣,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機(jī)與錄音别垮,去河邊找鬼便监。 笑死,一個胖子當(dāng)著我的面吹牛碳想,可吹牛的內(nèi)容都是我干的烧董。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼胧奔,長吁一口氣:“原來是場噩夢啊……” “哼逊移!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起龙填,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤胳泉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后岩遗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體扇商,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年宿礁,在試婚紗的時候發(fā)現(xiàn)自己被綠了案铺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡梆靖,死狀恐怖红且,靈堂內(nèi)的尸體忽然破棺而出坝茎,到底是詐尸還是另有隱情,我是刑警寧澤暇番,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布嗤放,位于F島的核電站,受9級特大地震影響壁酬,放射性物質(zhì)發(fā)生泄漏次酌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一舆乔、第九天 我趴在偏房一處隱蔽的房頂上張望岳服。 院中可真熱鬧,春花似錦希俩、人聲如沸吊宋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽璃搜。三九已至,卻和暖如春鳞上,著一層夾襖步出監(jiān)牢的瞬間这吻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工篙议, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留唾糯,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓鬼贱,卻偏偏與公主長得像移怯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子这难,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345