這是一個類似 ELK 的 monitor,他可以很輕鬆的建置並偵測到 CPU & Memory 等 Matrix 的資訊,如果是自己的 side project 很推薦使用,因為他有 100GB 每月使用量,比起你自架來得省事省時間。但 ELK 也是很推薦,畢竟是 open source 但需要考慮的事情就會變多,ex database storage size, data rotation time, Infra Problems。
package article
import (
"fmt"
"sync"
"testing"
)
type MutexChannelExample struct {
lock sync.Mutex
ArrInt []int
updated chan bool
}
func (m *MutexChannelExample) InsetValue(val int) {
m.lock.Lock()
defer m.lock.Unlock()
m.ArrInt = append(m.ArrInt, val)
m.updated <- true
}
func (m *MutexChannelExample) ReadValue() int {
m.lock.Lock()
defer m.lock.Unlock()
return m.ArrInt[len(m.ArrInt)-1]
}
func TestMutexChannelExample(t *testing.T) {
m := &MutexChannelExample{
ArrInt: make([]int, 0),
updated: make(chan bool),
}
go func() {
for <-m.updated {
// When m.ReadValue() is called, it will lock. However, in
// line 44, m.InsetValue inputs a bool into the channel.
// Consequently, line 19 waits for the value to be
// retrieved and released, triggering another call to
// m.ReadValue(), which results in a deadlock.
fmt.Println(m.ReadValue())
}
}()
for i := 0; i < 10; i++ {
m.InsetValue(i)
fmt.Println("do once ===>")
}
}
CPU & RAM usage is high, but it is expected to be low
在使用 Time 套件的時候,需要非常小心,以time.Tick為例,當他沒有被執行完成的時候,不會觸發 garbage recycle 所以cpu 不會被release 因此 cpu memory 都不會因為func結束而釋出資源。
package article
import (
"fmt"
"runtime"
"testing"
"time"
)
type TickUsageExample struct {
}
func (e *TickUsageExample) RaiseGoroutine(c chan bool) {
go func() {
time.Sleep(time.Microsecond * 900)
c <- true
}()
select {
case <-time.Tick(time.Second):
return
case <-c:
return
}
}
func TestTickUsageExample(t *testing.T) {
forever := make(chan bool)
ex := TickUsageExample{}
for i := 0; i < 1; i++ {
go ex.RaiseGoroutine(make(chan bool))
}
fmt.Println("goruntine number:= ", runtime.NumGoroutine())
time.Sleep(time.Second)
fmt.Println("goruntine number:= ", runtime.NumGoroutine())
<-forever
}
Linux Out-Of-Memory Killer
當我們使用記憶體,到達os 的上限的時候,linux os 會將最不健康的 process 刪除,因此要注要在使用Goroutine的時候,需要考慮到他的記憶體使用率,以免踩到!
docker run -it --rm -v $PWD:/source -m 100m golang:1.19-alpine3.17 sh
package article
import "testing"
func TestOOm(t *testing.T) {
for i := 0; i < 3; i++ {
go func() {
for true {
var arr []string = make([]string, 100000)
arr[0] = "test_str"
}
}()
}
forever := make(chan bool)
<-forever
}
以下方程式碼為例:這是一個使用 ant pool 的一個例子,line 14 宣告 pool size,從這裡可以有效的管理 CPU, Memory ,下方圖片的例子分別設定 10 和 100 size 的 pool,但跑一樣的一份程式碼,CPU, Memory Usage 是有相當大的差異。透過這樣的方式可以有效的避免 OOM 的機制的觸發。
docker run -it --rm -v $PWD:/source -m 100m golang:1.19-alpine3.17 sh
package article
import (
"sync"
"testing"
"time"
"github.com/panjf2000/ants/v2"
)
func TestPool(t *testing.T) {
var wg sync.WaitGroup
// it can adjusted the pool size, expired_time, log... a lots of opts.
pool, _ := ants.NewPool(10, ants.WithPreAlloc(true))
for i := 0; i < 100; i++ {
pool.Submit(func() {
defer wg.Done()
var arr []string = make([]string, 100000)
arr[0] = "test_str"
time.Sleep(time.Second * 10)
})
wg.Add(1)
}
wg.Wait()
}
Use to write the unit-test to find out the deadlock situation
預防或者發現Deadlock的狀況,最簡單的方式就是透過 unit-test ,透過測試容易使得這一類問題提早發現。Deadlock 往往都是在真的 program 上線了之後才會發現有這樣的問題。
How to find out:
Write the edge case for each func
撰寫Edge case test (極端測試) 在每一隻Goroutine程式,好處是可以去發現 deadlock的狀況,也比較能夠發現 Resource Usage (CPU, Memory) 的問題
Integration and Pression Test
整合測試與壓力測試實際上在軟體開發中非常重要,做這樣的測試的前提是,要使用 Monitor 工具,觀察每一個 Transaction (程式行為)的執行時間,還有執行片段。透過觀察Metrix的變化,是一個有效debug 的方式。