系统服务(这里不局限于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))
}
}
