目錄
? 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)