Go小課02:第一次Say Hello
一、Say Hello請求
1、環境配置
- 安裝Go的包依賴管理命令列工具
govendor
go get -u github.com/kardianos/govendor
複製程式碼
- 建立專案資料夾
mkdir -p $GOPATH/src/github.com/yourusername/project && cd "$_"
複製程式碼
- govendor初始化
govendor init
複製程式碼
- 獲取go的web框架gin
govendor fetch github.com/gin-gonic/gin
複製程式碼
- 在專案根路徑下,新建檔案main.go檔案
2、Get請求和Post請求程式碼
//main.go
package main
import (
"github.com/gin-gonic/gin"
"net/http"
"strconv"
)
func main() {
// gin的Default方法建立一個路由handler。然後通過HTTP方法繫結路由規則和路由函式,gin把request和response都封裝到gin.Context的上下文環境。
// 最後啟動路由的Run方法監聽埠。
router := gin.Default()
// get請求
router.GET("/user/welcome",func(c *gin.Context) {
//獲取url中的引數資訊
namestr := c.DefaultQuery("name","Guest")
agestr := c.DefaultQuery("age","18")
//各種處理邏輯
ageValue,err := strconv.Atoi(agestr)
role := "小夥子"
if err != nil {
role = "可疑的人"
} else {
if ageValue > 28 {
role = "大哥"
}
}
//返回
c.String(http.StatusOK,"%s %s,你好呀",role,namestr)
})
//curl http://127.0.0.1:8000/user/welcome\?name\=jack\&age\=18
router.POST("/user/postdata",func(c *gin.Context) {
message := c.PostForm("message")
namestr := c.DefaultPostForm("name","anonymous")
agestr := c.DefaultPostForm("age","18")
//各種處理邏輯
ageValue,err := strconv.Atoi(agestr)
role := "小夥子"
if err != nil {
role = "可疑的人"
} else {
if ageValue > 28 {
role = "大哥"
}
}
message = role + namestr + " 你好呀"
c.JSON(http.StatusOK,gin.H{
"status": gin.H{
"code": http.StatusOK,"status": "ok",},"data": gin.H{
"message": message,})
})
//curl -X POST http://127.0.0.1:8000/user/postdata -H "Content-Type:application/x-www-form-urlencoded" -d "name=jack&age=28" | python -m json.tool
router.Run(":8000")
}
複製程式碼
說明:啟動服務後,可以用curl命令工具來發請求,也可以用Postman來發請求,藉此來測試介面是否可以訪問。
二、介面聯調
1、GET請求聯調
-
介面名:/user/welcome
-
埠:80000
curl http://127.0.0.1:8000/user/welcome\?name\=jack\&age\=38
大哥 jack,你好呀**%**
複製程式碼
curl http://127.0.0.1:8000/user/welcome\?name\=jack\&age\=18
小夥子 jack,你好呀**%**
複製程式碼
curl http://127.0.0.1:8000/user/welcome\?name\=jack\&age\=hhah
可疑的人 jack,你好呀**%**
複製程式碼
說明:可以使用本機的ip地址。
2、POST請求聯調
-
介面名:/user/postdata
-
埠:80000
curl -X POST http://127.0.0.1:8000/user/postdata -H "Content-Type:application/x-www-form-urlencoded" -d "name=jack&age=28" | python -m json.tool
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 123 100 107 100 16 104k 16000 --:--:-- --:--:-- --:--:-- 120k
{
"data": {
"message": "54GP5b+O57Su54Cb5oGTYWNrIOa1o+eKsuOCvemNm+KCrA=="
},"status": {
"code": 200,"status": "ok"
}
}
複製程式碼
三、介面效能測試
1、介面效能指標
衡量介面效能可以從下面幾個指標來看:
-
QPS(TPS):每秒鐘 Request/事務 數量,在網際網路領域,指每秒響應請求數(指http請求)
-
事務: 使用者某一步或幾步操作的集合,我們要保證它有一個完整意義。比如使用者對某一個頁面的一次請求,使用者對某系統的一次登入,淘寶使用者對商品的一次確認支付過程。這些我們都可以看作一個事務
-
響應時間:系統對一個請求做出響應的平均時間。例如系統處理一個HTTP請求需要200ms,這個200ms就是系統的響應時間(我認為這裡應該僅包含處理時間,網路傳輸時間忽略)
-
併發數:系統同時處理的request / 事務數
-
吞吐量:單位時間內處理的請求數量(通常由QPS與併發數決定);
2、效能測試工具安裝
我們將利用wrk工具簡單測試下介面的QPS和響應時間,wrk是輕量級的 HTTP 效能測試工具,在這裡我們使用wrk簡單測試下我們剛寫介面的QPS和響應時間
#安裝
brew install wrk
複製程式碼
3、介面效能測試
#以get請求為例
wrk -t4 -c1000 -d30s -T30s --latency http://127.0.0.1:8000/user/welcome\?name\=jack\&age\=38
Running 30s test @ http://127.0.0.1:8000/user/welcome?name=jack&age=38
4 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 2.48ms 1.15ms 36.87ms 80.11%
Req/Sec 24.44k 2.48k 33.65k 72.67%
Latency Distribution
50% 2.33ms
75% 2.81ms
90% 3.71ms
99% 6.53ms
2919566 requests in 30.03s,389.80MB read
Socket errors: connect 751,read 89,write 0,timeout 0
Requests/sec: 97228.78
Transfer/sec: 12.98MB
複製程式碼
解釋1:用4個執行緒來模擬1000個併發連線,整個測試持續30秒,連線超時30秒,打印出請求的延遲統計資訊。 wrk 使用非同步非阻塞的 io,並不是用執行緒去模擬併發連線,因此不需要設定很多的執行緒,一般根據 CPU 的核心數量設定即可。(網路通訊不會阻塞執行緒執行,用很少的執行緒模擬大量網路連線)
解釋2:
- Socket errors socket 錯誤的數量
- Requests/sec 每秒請求數量,也就是併發能力
- Latency 響應時間
Avg:平均、Max:最大、Stdev:標準差、+/- Stdev: 正負一個標準差佔比
複製程式碼
4、效能說明
-
使用Go寫一個簡單介面,在我們的開發機器上的QPS大約是9w7左右,哈哈,我們也算寫了一個"高效能,低延遲"介面,當然這歸功於優秀的go語言的併發處理能力和web框架gin
-
實際的後臺介面的延遲不可能這麼快,它涉及到業務處理,服務間呼叫,複雜的網路環境,真實的QPS遠遠沒有這麼誇張。
#測試百度首頁的QPS和Latency
wrk -t4 -c1000 -d30s -T30s https://www.baidu.com
Running 30s test @ https://www.baidu.com
4 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 196.47ms 44.94ms 1.18s 88.71%
Req/Sec 310.42 59.95 494.00 69.06%
37095 requests in 30.09s,548.20MB read
Socket errors: connect 754,read 111,timeout 0
Requests/sec: 1232.74
Transfer/sec: 18.22MB
複製程式碼
四、小課堂
1、govendor
-
基於
vendor
機制實現的 Go 包依賴管理命令列工具。與原生 vendor 無侵入性融合,也支援從其他依賴管理工具遷移,可以很方便的實現同一個包在不同專案中不同版本、以及無相互侵入的開發和管理。 -
最開始的時候,Go 並沒有提供較為妥當的包管理工具。從 1.5 版本開始提供了 vendor 特性,但需要手動設定環境變數
GO15VENDOREXPERIMENT=1
。 -
在執行
go build
或go run
命令時,會按照以下順序去查詢包:- 當前包下的 vendor 目錄
- 向上級目錄查詢,直到找到 src 下的 vendor 目錄
- 在 GOROOT 目錄下查詢
- 在 GOPATH 下面查詢依賴包
-
在釋出 1.6 版本時,該環境變數的值已經預設設定為 1 了,該值可以使用
go env
命令檢視;在釋出 1.7 版本時,已去掉該環境變數,預設開啟vendor
特性。 -
govendor一些常用命令如下:
//初始化
govendor init
//將已被引用且在 $GOPATH 下的所有包複製到 vendor 目錄
govendor add +external
//僅從 $GOPATH 中複製指定包
govendor add gopkg.in/yaml.v2
//列出程式碼中所有被引用到的包及其狀態
govendor list
//列出一個包被哪些包引用
govendor list -v fmt
//從遠端倉庫新增或更新某個包(不會在 $GOPATH 也存一份)
govendor fetch golang.org/x/net/context
//安裝指定版本的包,eg:
govendor fetch golang.org/x/net/context@a4bbce9fcae005b22ae5443f6af064d80a6f5a55
govendor fetch golang.org/x/net/context@v1 # Get latest v1.*.* tag or branch.
govendor fetch golang.org/x/net/context@=v1 # Get the tag or branch named "v1".
複製程式碼
2、C10k問題
-
C10K 是 Client 10000 問題,即「在同時連線到伺服器的客戶端數量超過 10000 個的環境中,即便硬體效能足夠, 依然無法正常提供服務」,簡而言之,就是單機1萬個併發連線問題。
-
計算機程式可依據其瓶頸分為磁碟IO瓶頸型,CPU計算瓶頸型,網路頻寬瓶頸型,分散式場景下有時候也會外部系統而導致自身瓶頸。
-
Web系統打交道最多的是網路,無論是接收,解析使用者請求,訪問儲存,還是把響應資料返回給使用者,都是要走網路的。在沒有epoll/kqueue之類的系統提供的IO多路複用介面之前,多個核心的現代計算機最頭痛的是C10k問題,C10k問題會導致計算機沒有辦法充分利用CPU來處理更多的使用者連線,進而沒有辦法通過優化程式提升CPU利用率來處理更多的請求。
-
自從Linux實現了epoll,FreeBSD實現了kqueue,這個問題基本解決了,我們可以藉助核心提供的API輕鬆解決當年的C10k問題,也就是說如今如果你的程式主要是和網路打交道,那麼瓶頸一定在使用者程式而不在作業系統核心。
-
隨著時代的發展,程式語言對這些系統呼叫又進一步進行了封裝,如今做應用層開發,幾乎不會在程式中看到epoll之類的字眼,大多數時候我們就只要聚焦在業務邏輯上就好。Go 的 net 庫針對不同平臺封裝了不同的syscall API,http庫又是構建在net庫之上,所以在Go語言中我們可以藉助標準庫,很輕鬆地寫出高效能的http服務。