本篇是整個系列的最后一篇了,本來打算在系列的最后一兩篇寫一下關于k8s部署相關的內容,在構思的過程中覺得自己對k8s知識的掌握還很不足,在自己沒有理解掌握的前提下我覺得也很難寫出自己滿意的文章,大家看了可能也會覺得內容沒有干貨。我最近也在學習k8s的一些最佳實踐以及閱讀k8s的源碼,等待時機成熟的時候可能會考慮單獨寫一個k8s實戰系列文章。
下面列出了整個系列的每篇文章,這個系列文章的主要特點是貼近真實的開發場景,并針對高并發請求以及常見問題進行優化,文章的內容也是循序漸進的,先是介紹了項目的背景,接著進行服務的拆分,拆分完服務進行API的定義和表結構的設計,這和我們實際在公司中的開發流程是類似的,緊接著就是做一些數據庫的CRUD基本操作,后面用三篇文章來講解了緩存,因為緩存是高并發的基礎,沒有緩存高并發系統就無從談起,緩存主要是應對高并發的讀,接下來又用兩篇文章來對高并發的寫進行優化,最后通過分布式事務保證為服務間的數據一致性。如果大家能夠對每一篇文章都能理解透徹,我覺得對于工作中的絕大多數場景都能輕松應對。
對于文章配套的示例代碼并沒有寫的很完善,有幾點原因,一是商城的功能點非常多,很難把所有的邏輯都覆蓋到;二是多數都是重復的業務邏輯,只要大家掌握了核心的示例代碼,其他的業務邏輯可以自己把代碼down下來進行補充完善,這樣我覺得才會進步。如果有不理解的地方大家可以在社區群中問我,每個社區群都可以找到我。
go-zero微服務實戰系列(八、如何處理每秒上萬次的下單請求)
軟件測試由單元測試開始(unit test)。更復雜的測試都是在單元測試之上進行的。如下所示測試的層級模型:
單元測試(unit test)是最小、最簡單的軟件測試形式、這些測試用來評估某一個獨立的軟件單元,比如一個類,或者一個函數的正確性。這些測試不考慮包含該軟件單元的整體系統的正確定。單元測試同時也是一種規范,用來保證某個函數或者模塊完全符合系統對其的行為要求。單元測試經常被用來引入測試驅動開發的概念。
go語言的測試依賴go test
工具,它是一個按照一定約定和組織的測試代碼的驅動程序。在包目錄內,所有以_test.go
為后綴的源代碼文件都是 go test
測試的一部分,不會被go build
編譯到最終的可執行文件。
在*_test.go
文件中有三種類型的函數,單元測試函數、基準測試函數和示例函數:
類型 | 格式 | 作用 |
---|---|---|
測試單數 | 函數名前綴為Test | 測試程序的一些邏輯行為是否正確 |
基準函數 | 函數名前綴為Benchmark | 測試函數的性能 |
示例函數 | 函數名前綴為Example | 提供示例 |
go test
會遍歷所有*_test.go
文件中符合上述命名規則的函數,然后生成一個臨時的main包用于調用相應的測試函數。
每個測試函數必須導入testing
包,測試函數的基本格式如下:
func?TestName(t?*testing.T)?{ //?...... }
測試函數的名字必須以Test
開頭,可選的后綴名必須以大寫字母開頭,示例如下:
func?TestDo(t?*testing.T)?{?//......?} func?TestWrite(t?*testing.T)?{?//?......?}
testing.T
用于報告測試失敗和附加的日志信息,擁有的主要方法如下:
Name()?string Fail() Failed()?bool FailNow() logDepth(s?string,?depth?int) Log(args?...any) Logf(format?string,?args?...any) Error(args?...any) Errorf(format?string,?args?...any) Fatal(args?...any) Fatalf(format?string,?args?...any) Skip(args?...any) Skipf(format?string,?args?...any) SkipNow() Skipped()?bool Helper() Cleanup(f?func()) Setenv(key?string,?value?string)
在這個路徑下lebron/apps/order/rpc/internal/logic/createorderlogic.go:44
有一個生成訂單id的函數,函數如下:
func?genOrderID(t?time.Time)?string?{ s?:=?t.Format("20060102150405") m?:=?t.UnixNano()/1e6?-?t.UnixNano()/1e9*1e3 ms?:=?sup(m,?3) p?:=?os.Getpid()?%?1000 ps?:=?sup(int64(p),?3) i?:=?atomic.AddInt64(&num,?1) r?:=?i?%?10000 rs?:=?sup(r,?4) n?:=?fmt.Sprintf("%s%s%s%s",?s,?ms,?ps,?rs) return?n }
我們創建createorderlogic_test.go
文件并為該方法編寫對應的單元測試函數,生成的訂單id長度為24,單元測試函數如下:
func?TestGenOrderID(t?*testing.T)?{ oid?:=?genOrderID(time.Now()) if?len(oid)?!=?24?{ t.Errorf("oid?len?expected?24,?got:?%d",?len(oid)) } }
在當前路徑下執行 go test
命令,可以看到輸出結果如下:
PASS ok?? github.com/zhoushuguang/lebron/apps/order/rpc/internal/logic 1.395s
還可以加上 -v
輸出更完整的結果,go test -v
輸出結果如下:
===?RUN???TestGenOrderID ---?PASS:?TestGenOrderID?(0.00s) PASS ok?? github.com/zhoushuguang/lebron/apps/order/rpc/internal/logic 1.305s
在執行 go test
命令的時候可以添加 -run
參數,它對應一個正則表達式,又有函數名匹配上的測試函數才會被 go test
命令執行,例如我們可以使用 go test -run=TestGenOrderID
來值運行 TestGenOrderID
這個單測。
表格驅動測試不是工具,它只是編寫更清晰測試的一種方式和視角。編寫好的測試并不是一件容易的事情,但在很多情況下,表格驅動測試可以涵蓋很多方面,表格里的每一個條目都是一個完整的測試用例,它包含輸入和預期的結果,有時還包含測試名稱等附加信息,以使測試輸出易于閱讀。使用表格測試能夠很方便的維護多個測試用例,避免在編寫單元測試時頻繁的復制粘貼。
在 lebron/apps/product/rpc/internal/logic/checkandupdatestocklogic.go:53
我們可以編寫如下表格驅動測試:
func?TestStockKey(t?*testing.T)?{ tests?:=?[]struct?{ name???string input??int64 output?string }{ {"test?one",?1,?"stock:1"}, {"test?two",?2,?"stock:2"}, {"test?three",?3,?"stock:3"}, } for?_,?ts?:=?range?tests?{ t.Run(ts.name,?func(t?*testing.T)?{ ret?:=?stockKey(ts.input) if?ret?!=?ts.output?{ t.Errorf("input:?%d?expectd:?%s?got:?%s",?ts.input,?ts.output,?ret) } }) } }
執行命令 go test -run=TestStockKey -v
輸出如下:
===?RUN???TestStockKey ===?RUN???TestStockKey/test_one ===?RUN???TestStockKey/test_two ===?RUN???TestStockKey/test_three ---?PASS:?TestStockKey?(0.00s) ????---?PASS:?TestStockKey/test_one?(0.00s) ????---?PASS:?TestStockKey/test_two?(0.00s) ????---?PASS:?TestStockKey/test_three?(0.00s) PASS ok?? github.com/zhoushuguang/lebron/apps/product/rpc/internal/logic 1.353s
表格驅動測試中通常會定義比較多的測試用例,而go語言又天生支持并發,所以很容易發揮自身優勢將表格驅動測試并行化,可以通過t.Parallel()
來實現:
func?TestStockKeyParallel(t?*testing.T)?{ ??t.Parallel() tests?:=?[]struct?{ name???string input??int64 output?string }{ {"test?one",?1,?"stock:1"}, {"test?two",?2,?"stock:2"}, {"test?three",?3,?"stock:3"}, } for?_,?ts?:=?range?tests?{ ts?:=?ts t.Run(ts.name,?func(t?*testing.T)?{ t.Parallel() ret?:=?stockKey(ts.input) if?ret?!=?ts.output?{ t.Errorf("input:?%d?expectd:?%s?got:?%s",?ts.input,?ts.output,?ret) } }) } }
測試覆蓋率是指代碼被測試套件覆蓋的百分比。通常我們使用的都是語句的覆蓋率,也就是在測試中至少被運行一次的代碼占總的代碼的比例。go提供內置的功能來檢查代碼覆蓋率,即使用 go test -cover
來查看測試覆蓋率:
PASS coverage:?0.6%?of?statements ok?? github.com/zhoushuguang/lebron/apps/product/rpc/internal/logic 1.381s
可以看到我們的覆蓋率只有 0.6% ,哈哈,這是非常不合格滴,大大的不合格。go還提供了一個 -coverprofile
參數,用來將覆蓋率相關的記錄輸出到文件 go test -cover -coverprofile=cover.out
:
PASS coverage:?0.6%?of?statements ok?? github.com/zhoushuguang/lebron/apps/product/rpc/internal/logic 1.459s
然后執行 go tool cover -html=cover.out
,使用cover
工具來處理生成的記錄信息,該命令會打開本地的瀏覽器窗口生成測試報告
對于單測中的依賴,我們一般采用mock的方式進行處理,gomock是Go官方提供的測試框架,它在內置的testing包或其他環境中都能夠很方便的使用。我們使用它對代碼中的那些接口類型進行mock,方便編寫單元測試。對于gomock的使用請參考gomock文檔
mock依賴interface,對于非interface場景下的依賴我們可以采用打樁的方式進行mock數據,monkey是一個Go單元測試中十分常用的打樁工具,它在運行時通過匯編語言重寫可執行文件,將目標函數或方法的實現跳轉到樁實現,其原理類似于熱補丁。monkey庫很強大,但是使用時需注意以下事項:
-gcflags=-l
關閉Go語言的內聯優化。社區中經常有人問畫圖用的是什么工具,本系列文章中的插圖工具主要是如下兩個
代碼不光是要實現功能,很重要的一點是代碼是寫給別人看的,所以我們對代碼的質量要有一定的要求,要遵循規范,可以參考go官方的代碼review建議
https://github.com/golang/go/wiki/CodeReviewComments
時間過得賊快,不知不覺間這個系列已經寫到十一篇了。按照每周更新兩篇的速度也寫了一個多月了。寫文章是個體力活且非常的耗時,又生怕有寫的不對的地方,對大家產生誤導,所以還需要反復的檢查和查閱相關資料。平均一篇文章要寫一天左右,平時工作日比較忙,基本都是周六日來寫,因此最近一個月周六日基本沒有休息過。
但我覺得收獲也非常大,在寫文章的過程中,對于自己掌握的知識點,是一個復習的過程,可以讓自己加深對知識點的理解,對于自己沒有掌握的知識點就又是一個學習新知識的過程,讓自己掌握了新的知識,所以我和讀者也是一起在學習進步呢。大家都知道,對于自己理解的知識,想要說出來或者寫出來讓別人也理解也是不容易的,因此寫文章對自己的軟實力也是有很大的提升。
所以,我還是會繼續堅持寫文章,堅持輸出,和大家一起學習成長。同時,我也歡迎大家來 "微服務實踐" 公眾號來投稿??赡苡行┤擞X得自己的水平不行,擔心寫的內容不高端,沒有逼格,我覺得大可不必,只要能把知識點講明白就非常棒了,可以是基礎知識,也可以是最佳實踐等等。kevin會對投稿的每一篇文章都認真審核,寫的不對的地方他都會指出來,所有還有和kevin一對一交流學習的機會,小伙伴們抓緊行動起來呀。
非常感謝大家這一個多月以來的支持??吹矫科恼掠心敲炊嗟狞c贊,我十分的開心,也更加的有動力,所以,也在計劃寫下個系列的文章,目前有兩個待選的主題,分別是《go-zero源碼系列》和《gRPC實戰源碼系列》,歡迎小伙伴們在評論區留下你的評論,說出你更期待哪個系列,如果本篇文章點贊數超過66的話,咱就繼續開整。
代碼倉庫: https://github.com/zhoushuguang/lebron
https://github.com/zeromicro/go-zero
https://gitee.com/kevwan/go-zero
歡迎使用 go-zero
并 star 支持我們!
關注『微服務實踐』公眾號并點擊 交流群 獲取社區群二維碼。
|