系統服務(這里不局限於golang寫的后台服務,也包括c++,java等后台語言)需要考慮的兩個問題
1. 系統的資源使用情況(cpu利用率,內存分配情況等,runtime和syscall都提供了支持,這個是系統內部性質,往往是設計系統資源問題,需要在設計的時候慎重考慮)
2. 系統的服務情況(就是服務時延,這個是client可以直接感知的,往往是client最關注的,決定了服務的qps)
提前評估系統的資源消耗是很重要的,在公司里面,能提供選擇的機器類型是很多種的。比如騰訊,往往動則上億qq號的量,很多時候需要在內存中cache用戶的實時信息,如果內存評估不對,后面如果要進行機器遷移,則比較麻煩。另外,cpu的數目太少,則並發性弱,影響服務的性能,特別是在一台機器上部署了多個服務后,搶cpu,搶內存的情況則格外嚴重,並且,如果把io密集程序跟cpu密集程序放到一起,io密集程序還可能會把耗內存的cpu程序給擠到swap中去導致服務出現間歇性的大量失敗。
我最近在用golang寫一個server。壓力測試過程卻發現反應比較慢,但是由於設計的方案,中間的操作都是串行的,無法知道在哪個操作消耗了比較多時間。
一開始想到的是打log。但是單個請求又是很快的,於是想到如下方案 (by 魏加恩 本文地址http://www.cnblogs.com/weijiaen/p/3970466.html)
在調用每個函數的時候,統計該函數的時耗,然后利用channel把同一個函數調用發送到同一個地方,利用map進行累計統計(這里可以更近一步,比如統計每個worker甚至每個services的狀態,包括最長請求時間,最短請求時間,平均消耗等等,如果加上runtime還可以記錄其他的運行相關信息)。
一開始在思考是否有一個類似c++的static變量的東西,好在golang的閉包就支持有狀態的行為,而且比static更進一步,閉包的每次調用都能得到一個新的局部變量實體,所以,並發的時耗,在不同的地方,調用就可以得到不同的結果。
直接上代碼,關注點,系統資源利用統計,系統的服務時延統計,至於后續問題,比如何時log到文件等,以后有時間再補充進來。
func preReportRusage(interval time.Duration) func(usage1 *syscall.Rusage, utilProf *SystemUtilProf) int {
var lastUtime int64
var lastStime int64
var waitInterval time.Duration = interval
a := func(usage1 *syscall.Rusage, utilProf *SystemUtilProf) int {
if err := syscall.Getrusage(syscall.RUSAGE_SELF, usage1); err == nil {
utime := usage1.Utime.Sec*1000000000 + usage1.Utime.Usec
stime := usage1.Stime.Sec*1000000000 + usage1.Stime.Usec
utilProf.userCPUUtil = float64(utime-lastUtime) * 100 / float64(waitInterval) //用戶cpu時間
utilProf.sysCPUUtil = float64(stime-lastStime) * 100 / float64(waitInterval) //系統cpu時間
utilProf.memUtil = uint64(usage1.Maxrss * 1024) //系統內存使用情況
utilProf.memStats = runtime.MemStats{}
runtime.ReadMemStats(&utilProf.memStats)
lastUtime = utime
lastStime = stime
return kSuccess
} else {
return kFail
}
}
return a
}
func preReportStatus(funcName string) func(reportType int, input chan<- *RequestProf) {
var startTime = time.Now()
var reportFunc = funcName
a := func(reportType int, input chan<- *RequestProf) {
consumeTime := int64(time.Now().Sub(startTime) / 1000)
requestProf := &RequestProf{
apiName: reportFunc,
consumeTime: consumeTime,
invokeTimes: 1,
successCount: 0,
errorCount: 0,
}
if reportType == kSuccess {
requestProf.successCount = 1
} else {
requestProf.errorCount = 1
}
input <- requestProf
}
return a
}
var requestProfChan = make(chan *RequestProf,500)
var requestProfMap = make(map[string]*RequestProf)
func funcA() error {
report := preReportStatus("funcA")
err := funcB()
if err != nil {
report(kFail, requestProfChan)
return errors.New("funcA error.")
}
report(kSuccess, requestProfChan)
return nil
}
func funcC() {
rusageReport := preReportRusage(time.Minute)
//print rusage status...
usage1 := &syscall.Rusage{}
utilProf := &SystemUtilProf{}
if result := rusageReport(usage1, utilProf); result == kSuccess {
content = fmt.Sprintf("%3.2f,%3.2f,%s,%s,%s,%s\n",
utilProf.userCPUUtil, utilProf.sysCPUUtil, toH(utilProf.memUtil),
toH(utilProf.memStats.HeapSys), toH(utilProf.memStats.HeapAlloc), toH(utilProf.memStats.HeapIdle))
}
}
