最近我们需要一些带有些特殊特征的负载平衡器。现成可取并不是我们想要的获取此类特征的途径。
因此我们着手调研怎样才能写出我们自己的软件负载平衡器。由于我们的大部分代码库和专业知识都基于Scala,所以基于java虚拟机来创建此平衡器是个自然之选。
另一方面,很多人,也包括在 Fortytwo的我们自己——经常但不总是——会做一些毫无根据的假设,即JAVA虚拟机比本地编译语言要慢。
由于负载平衡器常是性能极其关键的组件,因此可能一个其他的编程语言/环境会比较好些?
我们不是很乐意走入奇特的世界写C/C++,所以我们开始找寻一种折中的方法,既可以给我们带来传说中的本地代码的性能优势,同时也具有一些高级的特性,如垃圾回收以及内置的并发原语。一个立即浮现出来的这样的语言是Google的相对较新的Go语言。本机编译而且完美内置了并发构造。是不是很完美?
我们决定与最近的WebSockets和TCP发送 相似的时髦方法,在Go 和 Scala基础之上,对TCP网络栈处理能力做基准测试。
我们写了一个简单的“ping-pong”客户端和服务器分别用go语言
//SERVER package main import ( "net" "runtime" ) func handleClient(conn net.Conn) { defer conn.Close() var buf [4]byte for { n, err := conn.Read(buf[0:]) if err!=nil {return} if n>0 { _, err = conn.Write([]byte("Pong")) if err!=nil {return} } } } func main() { runtime.GOMAXPROCS(4) tcpAddr, _ := net.ResolveTCPAddr("tcp4", ":1201") listener, _ := net.ListenTCP("tcp", tcpAddr) for { conn, _ := listener.Accept() go handleClient(conn) } }
//CLIENT package main import ( "net" "fmt" "time" "runtime" ) func ping(times int, lockChan chan bool) { tcpAddr, _ := net.ResolveTCPAddr("tcp4", "localhost:1201") conn, _ := net.DialTCP("tcp", nil, tcpAddr) for i:=0; i<int(times); i++ { _, _ = conn.Write([]byte("Ping")) var buff [4]byte _, _ = conn.Read(buff[0:]) } lockChan<-true conn.Close() } func main() { runtime.GOMAXPROCS(4) var totalPings int = 1000000 var concurrentConnections int = 100 var pingsPerConnection int = totalPings/concurrentConnections var actualTotalPings int = pingsPerConnection*concurrentConnections lockChan := make(chan bool, concurrentConnections) start := time.Now() for i:=0; i<concurrentConnections; i++{ go ping(pingsPerConnection, lockChan) } for i:=0; i<int(concurrentConnections); i++{ <-lockChan } elapsed := 1000000*time.Since(start).Seconds() fmt.Println(elapsed/float64(actualTotalPings)) }和Scala语言
//SERVER import java.net._ import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent._ object main{ def handleClient(s: Socket) : Unit = { val in = s.getInputStream val out = s.getOutputStream while(s.isConnected){ val buffer = Array[Byte](4) in.read(buffer) out.write("Pong".getBytes) } } def main(args: Array[String]){ val server = new ServerSocket(1201) while(true){ val s: Socket = server.accept() future { handleClient(s) } } } }
//CLIENT import scala.concurrent._ import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global import java.net._ object main{ def ping(timesToPing: Int) : Unit = { val socket = new Socket("localhost", 1201) val out = socket.getOutputStream val in = socket.getInputStream for (i <- 0 until timesToPing) { out.write("Ping".getBytes) val buffer = Array[Byte](4) in.read(buffer) } socket.close } def main(args: Array[String]){ var totalPings = 1000000 var concurrentConnections = 100 var pingsPerConnection : Int = totalPings/concurrentConnections var actualTotalPings : Int = pingsPerConnection*concurrentConnections val t0 = (System.currentTimeMillis()).toDouble var futures = (0 until concurrentConnections).map{_ => future(ping(pingsPerConnection)) } Await.result(Future.sequence(futures), 1 minutes) val t1 = (System.currentTimeMillis()).toDouble println(1000*(t1-t0)/actualTotalPings) } }
后者和 WebSockets vs. TCP benchmark一文中用到的完全一致。两者操作都很简单并有提升的空间。实际的测试代码中包括的功能性测试能处理一些连接错误,此处省略不作赘述。
客户端想服务器发出一系列持久并发的连接请求并且发送一定数量的ping(即字符串“Ping”)。服务器对每个“Ping”请求做出回应并回复“Pong”。
实验是在2.7G赫兹的四核苹果笔记本上演示的。客户端和服务器分别运行,以用来更好地测试程序运行的系统开销。
客户端能生成100个并发请求并发出100万次的ping到服务器端,并通过连接平均分布ping。我们测试了全程的“ping-pong”往返时间。
令我们吃惊的是, Scala比Go好的不止一点,平均往返时间约1.6微秒 (0.0016毫秒)对比于Go的 约11微妙 (0.011毫秒). Go的数字当然仍然是相当的快,但如果所有软件在做的都是接收一个tcp包,再传递给另一个终点,这样就会在最大吞吐量方面带来很大的差异。
相反的需要注意的是,Go服务器具有的内存封装仅仅是10MB,而Scala将近200MB。
Go仍然很新,随着它的成熟可能会有性能的改进,而且它的简单的并发原语可能使付出这些性能的损失是值得的。
尽管如此,这个结果实在有点令人惊讶,至少对我们是如此。我们想在评论中听到一些关于此的想法。
2KB项目(www.2kb.com,源码交易平台),提供担保交易、源码交易、虚拟商品、在家创业、在线创业、任务交易、网站设计、软件设计、网络兼职、站长交易、域名交易、链接买卖、网站交易、广告买卖、站长培训、建站美工等服务