阿里中间件比赛

最近刚刚写完了阿里中间件比赛初赛,我们的队伍以两百多名的成绩晋级复赛,benchmark 5076IOPS。这个成绩算不上太好,但是在初赛当中还是积累了一些经验。

比赛内容

初赛的题目是实现一个Service Mesh Agent。具体来说,客户会通过HTTP协议发送请求并得到结果,而服务器则是使用Dubbo协议,我们需要构建起客户端和服务器端的一个桥梁,完成HTTP和Dubbo协议的双向转换。与此同时,比赛设定中服务器有3台(可以参照下图),负载能力各不相同,因此为了尽可能提高请求处理的效率,我们需要做负载均衡,将请求按照一定的方法分配到不同的服务器当中去。

上图中我们还可以看到中间件还要与etcd进行通信,etcd在这里的作用是服务的注册和发现,是客户端用于发现服务端并获取服务端信息的途径。这个比赛中关键就在于负载均衡以及中间件处理并发请求的能力。

比赛过程

我所在的队伍共有三个人,程序大都由Go编写。我负责Provider Agent,Consumer Agent和协议转换部分分别由其他两个同学负责。协议转换在这里不是性能瓶颈,对服务性能影响较大的在负载均衡以及网络通信部分。

在负载均衡方面,我们尝试了几种方案,主要都是通过从发送请求到收到结果之间的时间,也就是延时的大小来决定向那个服务端发送请求。然而效果并不是十分理想。到了最后,是通过把权重硬写到负载均衡部分,然后人工调整向不同服务端发送请求的比例。

而在初赛快要结束的时候,我们还意外地发现了一个比较大的问题。在Consumer Agent端在处理网络通信时会出现较大延时。在对log进行分析后发现应该是Go调度器在资源调度时产生的延时。至于为何Consumer Agent对调度器造成了这么大的压力最后也不太清楚,有可能是使用了较多的go routine。尽管这样,我们还是作了一些尝试,甚至动用到了Go的runtime库来控制调度器的行为。但是到了比赛结束我们还是没能解决这一问题。

经验积累

虽然说这一次初赛在负载均衡以及在性能方面我们的实现都不算出色,但是还是累积了一点使用Go的经验。

pprof

在Go当中,有一个pprof库用于对Go程序进行性能分析。这个库使用http协议传输数据。要使用这个库,首先要导入net/http/pprof这个包。导入这个包会有注册一个http handler的副作用,我们实际上不需要用到这个包里面的函数和变量。然后我们只需要运行一个http服务器监听请求即可。具体示例如下

package main

import (
    "fmt"
    "log"
    "net/http"
    _ "net/http/pprof" // import pprof for profiling, will serve /debug/pprof
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintln(w, "hi there")
    }) 
    log.Fatal(http.ListenAndServe(":8080", nil))
}

上面的示例当中,我们在localhost的8080端口监听http请求,并返回一个字符串。实际上,我们导入pprof包时它会注册一个handler以处理发送到/debug/pprof的http请求。在上面的http服务器开始运行后,如果我们使用浏览器打开localhost:8080/debug/pprof,我们就可以直接在浏览器中查看正在运行服务器的堆栈信息。如果需要进行profile的程序本身不会启动http服务器,那么我们要另外开启一个http服务器以传输profile的数据。

我们也可以使用go自带的pprof工具来查看profile的结果,甚至可以生成调用链的图。

在运行上面程序后,要使用pprof只需在终端中输入

$ go tool pprof http://localhost:8080/debug/pprof/profile

即可让pprof在30s内对程序运行进行采样(整个采样需要等待30s)。如果我们不去访问http://localhost:8080 的话,服务器将一直在idle状态,pprof将没有样本可采,所以可以在profile过程中使用浏览器访问http://localhost:8080 ,给服务器一点负载。

pprof会在程序运行的某些时间对程序运行状态进行采样,而且pprof需要启动http服务器,所以pprof的使用会影响到程序的性能。