Skip to main content

Golang http server performance tuning practice (Golang HTTP服务器性能调优实践)

  Golang 1.8后本身自带了pprof的这个神器,可以帮助我们很方便的对服务做一个比较全面的profile。对于一个Golang的程序,可以从多个方面进行profile,比如memory和CPU两个最基本的指标,也是我们最关注的,同时对特有的goroutine也有运行状况profile。关于golang profiling本身就不做过多的说明,可以从官方博客中了解到详细的过程。
  Profile的环境

  1. Ubuntu 14.04.4 LTS (GNU/Linux 3.19.0-25-generic x86_64)
  2. go version go1.9.2 linux/amd64
 profile的为一个elassticsearch数据导入接口,承担接受上游数据,根据元数据信息写入相应的es索引中。目前的状况是平均每秒是1.3Million的Doc数量。
  在增加了profile后,从CPU层面发现几个问题。
  1. runtime mallocgc 占用了17.96%的CPU。 SVG部分图如下





通过SVG图,可以看到调用链为:
ioutil.ReadAll -> buffer.ReadFrom -> makeSlice -> malloc.go 

然后进入ReadAll的源码。

readAll()方法
func readAll(r io.Reader, capacity int64) (b []byte, err error) {
buf := bytes.NewBuffer(make([]byte, 0, capacity))
// If the buffer overflows, we will get bytes.ErrTooLarge. // Return that as an error. Any other panic remains. 
defer func() {
e := recover()
if e == nil {
return }
if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge {
err = panicErr } else {
panic(e)
}
}()
_, err = buf.ReadFrom(r)
return buf.Bytes(), err}

可以看到,每次调用readAll时,每次都会NewBuffer,大小为bytes.MinRead=512.
再进入核心的方法。 buf.ReadFrom()

func (b *Buffer) ReadFrom(r io.Reader) (n int64, err error) {
b.lastRead = opInvalid // If buffer is empty, reset to recover space. if b.off >= len(b.buf) {
b.Reset()
}
for {
if free := cap(b.buf) - len(b.buf); free < MinRead {
// not enough space at end newBuf := b.buf if b.off+free < MinRead {
// not enough space using beginning of buffer; // double buffer capacity newBuf = makeSlice(2*cap(b.buf) + MinRead)
}
copy(newBuf, b.buf[b.off:])
b.buf = newBuf[:len(b.buf)-b.off]
b.off = 0 }
m, e := r.Read(b.buf[len(b.buf):cap(b.buf)])
b.buf = b.buf[0 : len(b.buf)+m]
n += int64(m)
if e == io.EOF {
break }
if e != nil {
return n, e }
}
return n, nil // err is EOF, so return nil explicitly
}


这个方法时从io.Reader中读取数据,并且copy到我们刚才new出来的buffer中。如果buffer的大小不够,则对buffer进行扩展,大小为之前的2倍,然后把数据copy进去。

同时,分析线上数据发现,平均一个请求体的大小为5MB大小。每次请求都会造成大量的makeSlice操作和随之而来的GC,造成CPU的浪费。
解决问题的思路也很清晰。 
1. 减少make([]byte,0,bytes.MinRead)操作 
2. 避免频繁的makeSlice(对数组的扩展操作)和copy操作。

使用一个 buffer的pool可以解决问题。参考在shadowsocks在leakybuf中实现,并且针对业务来做了如下修改。


// Put add the buffer into the free buffer pool for reuse. Panic if the buffer// size is not the same with the leaky buffer's. This is intended to expose// error usage of leaky buffer.func (lb *LeakyBuf) Put(b []byte) {
if len(b) != lb.bufSize {
// 源码中panic,这里为了防止[]byte数组被修改后放进来,是默认重新赋值为默认的大小
b = make([]byte, lb.bufSize)
}
select {
case lb.freeList <- b:
default:
}
return}
const leakyBufSize = 5 * 1024 * 1024 // 5MB 根据统计,解决大部分请求时再申请扩大内存的操作



代替ioutil.ReadAll的代码如下

cacheBuf := leakyBuf.Get()
defer leakyBuf.Put(cacheBuf) // 用完后放回去
buf := bytes.NewBuffer(cacheBuf)
buf.Reset() // buffer在用之前rest是个好习惯
_, err = buf.ReadFrom(request.Body) // 从http body中读取数据if err != nil {
return
} 
body := buf.Bytes()


Comments

Popular posts from this blog

Elasticsearch error when the field exceed limit 32kb

When we post data that the field exceed the limit, elasticsearch will reject the data which error: {"error":{"root_cause":[{"type":"remote_transport_exception","reason":"[cs19-2][10.200.20.39:9301][indices:data/write/index]"}],"type":"illegal_argument_exception","reason":"Document contains at least one immense term in field=\"field1\" (whose UTF8 encoding is longer than the max length 32766), all of which were skipped.  Please correct the analyzer to not produce such terms.  The prefix of the first immense term is You can handle this: 1. you can udpate the mapping part at any time PUT INDEXNAME/_mapping/TYPENAME { "properties" : { "logInfo" : { "type" : "string" , "analyzer" : "keyword" , "ignore_above" : 32766 } } } ignore_above means that we will only keep 32766 bytes 2...

Scrapy ERROR :ImportError: Error loading object 'scrapy.telnet.TelnetConsole': No module named conch

原文: https://stackoverflow.com/questions/17263509/error-while-using-scrapy-scrapy-telnet-telnetconsole-no-module-named-conch/17264705#17264705 On Ubuntu, you should avoid using  easy_install  wherever you can. Instead, you should be using  apt-get ,  aptitude , "Ubuntu Software Center", or another of the distribution-provided tools. For example, this single command is all you need to install scrapy - along with every one of its dependencies that is not already installed: $ sudo apt - get install python - scrapy easy_install  is not nearly as good at installing things as  apt-get . Chances are the reason you can't get it to work is that it didn't quite install things sensibly, particularly with respect to what was already installed on the system. Sadly, it also leaves no record of what it did, so uninstallation is difficult or impossible. You may now have a big mess on your system that prevents proper installations from working as well (or maybe not...

学习服务器配置之路~~

第一个常见的小问题:MySQL安装 os : fedora 20 mysql: mysql-server(5.5) 所有假设你的系统是没有经过特殊配置的。 1: yum install mysql-server 2: mysql 报错:socket连接不上 3: service mysqld start   注意这步是 mysqld 不是mysql 这样就解决。网上的方法好像有点麻烦。 第二个小问题:解压一些文件(.tar.gz)时报错 http://itsfoss.com/how-solve-stdin-gzip-format/ 上面介绍的很清楚,总之要先确认你下载的文件类型。 第三个小问题。配置tomcat服务器 主要问题是比如我的域名是 cqupt.me 而你tomcat服务器的项目在/webapps/{your projectname} 这时你很蛋疼的要 cqupt.me:8080/{your projectname}/index.html。 如果要cqupt.me就可以完成。这样配置: 都是在tomcat下/conf/server.xml 第一步端口。简单 不废话 第二部。 <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true" xmlValidation="false" xmlNamespaceAware="false"> </Host> 在标签中间插入: <Context path=""  docBase="xbwl"  debug="0" reloadable="true"/> docBase="xbwl" xbwl 即为指定的项目。即({your projectname}_ 完整如下: <Host name="localhost" appBase="webapps" ...