最近也收到很多后端同学的提问,为什么Go的web框架速度还不如Java?为什么许多原本的 Java 项目都试图用 go 进行重写开源?Java会不会因为容器的兴起而没落?Java这个20多年的后端常青树难道真的要走下坡路了?橙子邀请了淘系技术部的同学对以上问题进行解答,也欢迎大家一起交流。

Q:为什么Go的web框架速度还不如Java?


风弈:华山论剑,让我们索性把各框架的性能分析跑一下再说话。

各种框架的应用场景不同导致其优化侧重点不同,下面我们展开详细分析。

  http server 概述

首先描述一下一个简单的 web server 的请求处理过程:

Net 层读取数据包后经过 HTTP Decoder 解析协议,再由 Route 找到对应的 Handler 回调,处理业务逻辑后设置相应 Response 的状态码等,然后由 HTTP Encoder 编码相应的 Response,最后由 Net 写出数据。

而 Net 之下的一层由内核控制,虽然也有很多优化策略,但这里主要比较 web 框架本身,那么暂时不考虑 Net 之下的优化。

看了下 techempower 提供的压测框架源码,各类框架基本上都是基于 epoll 的处理,那么各类框架的性能差距主要体现在上述这些模块的性能了。

▐  关于各类压测的简述

我们再看 techempower 的各项性能排名,有JSON serialization, Single query, Multiple queries, Cached queries, Fortunes, Data updates 和 Plaintext 这几大类的排名。

其中 JSON serialization 是对固定的 Json 结构编码并返回 (message: hello word), Single query 是单次 DB 查询,Multiple queries 是多次 DB 查询,Cached queries 是从内存数据库中获取多个对象值并以json返回,Fortunes 是页面渲染后返回,Data updates 是对 DB 的写入,Plaintext 是最简单的返回固定字符串。

这里的 json 编码,DB 操作,页面渲染和固定字符串返回就是相应的业务逻辑,当业务逻辑越重(耗时越大)时,则相应的业务逻辑逐渐就成为了瓶颈,例如 DB 操作其实主要是在测试相应 DB 库和 DB 本身处理逻辑的性能,而框架本身的基础功能消耗随着业务逻辑的繁重将越来越忽略不计(Round 19 中物理机下 Plaintext 下的 QPS 在七百万级,而 Data updates 在万级别,相差百倍以上),所以这边主要分析 Json serialization 和 Plaintext两种相对能比较体现出框架本身 http 性能的排名。

在 Round 19 Json serialization 中 Java 性能最高的框架是 firenio-http-lite (QPS: 1,587,639),而 Go 最高的是 fasthttp-easyjson-prefork(QPS: 1,336,333),按照这里面的数据是Java性能高。

从 fasthttp-easyjson-prefork 的 pprof 看除了 read 和 write 外, json (相当于 Business logic) 占了 4.5%,fasthttp 自身(HTTP Decoder, HTTP Encoder, Router)占了 15%,仅看 Json serialization 似乎会有一种 Java 比 Go 性能高的感觉。

那我们继续把业务逻辑简化,看一下 Plaintext 的排名,Plaintext 模式其实是在使用 HTTP pipeline 模式下压测的,在 Round 19 中 Java 和 Go 已经几乎一样的 QPS 了,在 Round 19 之后的一次测试中 gnet 已经排在所有语言的第二,但是前几个框架QPS其实差别很微小。

这时候其实主要瓶颈都在 net 层,而 go 官方的 net 库包含了处理 goroutine 相关的逻辑,像 gonet 之类的直接操作 epoll 的会少一些这方面的消耗,Java 的 nio 也是直接操作的 epoll 。

拿了 gnet 的测试源码跑了下压测,看到 pprof 如下,其实这里 gnet 还有更进一步的性能优化空间:time.Time.AppendFormat 占用 30% CPU。

可以使用如下提前 Format ,允许减少获取当前时间精度的情况下大幅减少这部分的消耗。

var timetick atomic.Valuefunc NowTimeFormat() []byte {return timetick.Load().([]byte)
}func tickloop() {timetick.Store(nowFormat())for range time.Tick(time.Second) {timetick.Store(nowFormat())}
}func nowFormat() []byte {return []byte(time.Now().Format("Mon, 02 Jan 2006 15:04:05 GMT"))
}func init() {timetick.Store(nowFormat())go tickloop()
}

这样优化后接下来的瓶颈在于 runtime 的内存分配,是由于这个压测代码中还存在下面的部分没有复用内存:

其实 gnet 本身的消耗已经做到非常小了,而 c++ 的 ulib 也是类似这样使用的非常简单的 HTTP 编解码操作来压测。

▐  分析

对于这里面测试的框架,影响因素主要如下:

1、直接基于epoll的简单http: 没有完整的 http decoder 和 route (如gnet, ulib 直接简单的字节拼接,固定的路由 handler回调)

2、zero copy 和内存复用: 内部处理字节的 0 拷贝(go 官方 http 库为了减少开发者的出错概率,没有使用 zero copy,否则开发者可能在无意中引用了已经放回 buff 池内的的数据造成没有意识到的并发问题等等),而内存复用,大部分框架或多或少都已经做了。

3、prefork:注意到 go 框架中有使用了 prefork 进程的方式(比如 fasthttp-prefork),这是 fork 出多个子进程,共享同一个 listen fd,且每个进程使用单核但并发(1 个 P)处理的逻辑可以避免 go runtime 内部的锁竞争和 goroutine 调度的消耗(但是 go runtime 中为了并发和 goroutine 调度而存在的相关“无用”代码的消耗还是会有一些)

4、语言本身的性能差异

对于第一点,其实简化了各种编解码和路由之后,虽然提高了性能,但是往往会降低框架的易用性,对于一般的业务而言,不会出现如此高的QPS,同时选择框架的时候往往还需要考虑易用性和可扩展性等,同时还需要考虑到公司内部原有中间件或者 SDK 所使用的框架集成复杂度。

对于第二点,如果是作为一个网络代理而言,没有业务方的开发,往往可以使用真正的完全 zero copy,但是作为业务开发框架提供出去的话是需要考虑一定的业务出错概率,往往牺牲一部分性能是划算的。

第三点 prefork , java netty 等是直接对于线程操作,可以更加定制化的优化性能,而 go 的 goroutine 需要的是一个通用协程,目的是降低编写并发程序的难度,在这个层次上难免性能比不上一个优化的非常出色的 Java 基于线程操作的框架;但是直接操作线程的话需要合理控制好线程数,这是个比较头疼的调优问题(特别是对于新手来说),而 goroutine 则可以不关心池子的大小,使得代码更加优雅和简洁,这对于工程质量保障其实是一个提升。另外这里存在 prefork 是由于 go 没法直接操作线程,而 fasthttp 提供了 prefork 的能力,使用多进程方式来对标 Java 的多线程来进一步提高性能。

第四点,语言本身来说 Java 还是更加的成熟,包括 JVM 的 Jit 能力也使得在热代码中和 Go 编译型语言的差异不大,何况 Go 本身的编译器还不是特别成熟,比如逃逸分析等方面的问题, Go 本身的内存模型和 GC 的成熟度也比不上 Java。还有很重要的一点,Go 的框架成熟度和 Java 也不在一个级别,但相信这些都会随着时间逐步成熟。

总之,对于这个框架压测数据意义在于了解性能天花板,判断继续优化的空间和ROI (投入产出比)。具体选择框架还是要根据使用场景,性能,易用性,可扩展性,稳定性以及公司内部的生态等作出选择,语言和性能分别只是其中一个因素。

各种框架的应用场景不同导致其优化侧重点不同,如 spring web 为了易用性,可扩展性和稳定性而牺牲了性能,但它同样拥有庞大的社区和用户。再比如 Service Mesh Sidecar 场景下 Go 的天然并发编程上的优势,以及小内存占用,快速启动,编译型语言等特点使得比 Java 更加适合。

(附:其实我使用上述代码和 dockerfile 构建,并且使用同样的压测脚本,在阿里云4核独享机器测试下 go fasthttp-easyjson-prefork 框架 Json serialization 的性能要高于 Java wizzardo-http 和 firenio-http-lite 30% 以上且延迟更低的,这可能和内核有关)。

Q:为什么许多原本的 Java 项目都试图用 go 进行重写开源?


空蒙:Java还是go核心是生态问题。

生态发展会经历起步、发展、繁荣、停滞、消亡几个阶段,Java目前至少还在繁荣阶段,go还是发展阶段,不同阶段在开发人员的数量与质量、开源能力丰富性、工程配套上是有巨大差异的,go是在狂补这三块。另外不同公司还有个公司内部小生态的所处阶段问题,也会影响技术的选型判断。

现阶段go的火热,很大因素是云原生裹挟着大家往前,k8s operator go语言实现的自带光环,各种中间件能力在下沉与k8s融合,带动着一波基础中间件能力的go实现潮头,但基础的中间件能力相对是有限集合,如RPC、config、messagequeue等,这些中间件能力,以及云原生k8s对上层业务而言应该做的是开发语言的中立性,让业务基于公司的小生态和整个语言技术的大生态去抉择,如果硬逼着业务也用go语言开发那就是耍流氓了。

总结来说,基础中间件能力需要与k8s的融合需要会有go语言的动力,但整个开源生态其他能力并不见得是必须;业务开发依据公司生态和技术大生态选择最合适的开发语言,不要盲目的追从而导致在人、开源能力、工程配套上的尴尬。go语言能否在业务研发上发力,还有待其生态的进一步发展。

Q:Java会不会因为容器的兴起而没落?


玄力:近年来以容器为核心的云原生技术,让服务端部署的伸缩性、可协作性,得到巨大的提升。使得原本开发语言本身选取的重要性,有一定程度的减弱。但不妨碍Java语言本身继续保持活力。

毕竟,作为研发而言,研发输出效率也是蛮关键的一个考量点,得益于Java完善而有庞大的开发者生态,提供了比大多数语言都要丰富的类库/框架,也得益于Java强大的IDE工具,开发起来往往事半功倍。

而且,Java自身也有一些变种语言(如Scala),也是在朝更灵活更好用的方向发展;

另一方面,在大数据领域,Java仍在大放异彩,我们所熟知的 ES、Kafka、Spark、Hadoop。

我们评估和预测一个技术的生命力的时候,往往不会孤立地只看技术本身,同时也会结合它背后的整个生态。一个具有顽强生命力的技术的背后往往都有一个成熟的生态体系支撑,上面也提到Java在多个领域都有完善而庞大的生态,因此,我们认为Java的生命力仍然是顽强的。

但由于众所周知的原因,客观来讲,Java本身在使用上,也会有一定的限制性。并且,在容器场景中,Java进程的内存配置,是需要小心谨慎的。

总的来说,Java的地位仍难撼动,而且在云原生场景中,也仍绽放着生命力。

今日话题:

大家还有什么话题想要了解,欢迎评论区留言,我们下期见~

✿  拓展阅读

作者|风弈、空蒙、玄力

编辑|橙子君

出品|阿里巴巴新零售淘系技术

Java真的要没落了?相关推荐

  1. 从最新的编程语言排行看,Java真的要凉了吗?

    作为一个历经无数风雨的编程语言,Java近些年好像拿到了悲惨的"人设剧本",网络上对它的质疑越来越高,例如: "Java真的要凉了吗?" "Pytho ...

  2. CODE大全告诉你java是否开始没落了

    CODE大全告诉你java是否开始没落了! 22 岁,对于一个技术人来说可谓正当壮年.但对于一门编程语言来说,情况可能又有不同.各类编程语言横空出世,纷战不休,然而 TIOBE 的语言排行榜上,Jav ...

  3. 学习Java真的可以改变你的人生?

    学习Java真的可以改变你的人生?在疫情期间远程办公的方式越来越流行,互联网的发展也越来越迅速,学习一门技术以备后患.或许学习Java真的可以改变你的工作和生活方式! 大学毕业后从销售到食品行业几经周 ...

  4. python 的没落_为什么很多企业转向了 Golang? Java, Python, C#没落了吗?

    Go语言是谷歌2009发布的第二款开源编程语言. Go语言专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C或C++代码的速度,而且更加安全.支持并行进程. 为什么很多企业转向 ...

  5. Java真的是一门编译型的语言吗——即时编译器JIT

    Java真的是一门编译型的语言吗--即时编译器JIT 如有错误请大佬指正 JIT是什么 JIT(Just-in-Time,实时编译)一直是Java语言的灵魂特性之一. 让我们回忆一下Java程序是如何 ...

  6. java斗图表情_程序员之间的斗图表情包, java真的输惨了!

    表情包成了人与人聊天中不可少的分量,陌生人聊天表情包丢出去妥妥的拉近关系变熟络啊(¬_¬) 而且可以用表情包化解尴尬.缓解气氛,可以没话找话.忙的时候也可以当做结束语,显得比较有礼貌! 对于沉默寡言. ...

  7. 博客园----你真的没有没落.感恩博客园。。。

    前言 拒绝谩骂!~~~~~~欢迎拍砖.!!!我不是博客园的托哦!~我只是一个普普通通的诶提(IT)人而已!~~好吧! 我打从心底的感恩博客园. 大家都知道,这些有一篇帖子引起了大家的关注<即将没 ...

  8. java真的是值传递么?

    2019独角兽企业重金招聘Python工程师标准>>> 在java中我们在给方法传值时,真的是我们认为的值传递么?如果不是,那么是引用传递么? 其实在java中方法传值时,java的 ...

  9. 使用了 23 的 Java 真的收费了吗?

    作者 | 局长 本文经授权转载自开源中国 几个月前,在 Java 11 到来之际,开源中国曾发起一项 Java 常用版本的调查,根据调查结果显示,国内有近 70% 的用户表示仍在使用 Java 8. ...

最新文章

  1. java虚拟机学习(四)类的加载过程
  2. k8s常用对象图示:Deployment、ReplicaSet、Pod它们的关系
  3. Google AdSense 帐户已被停用
  4. ucontext族函数的使用及原理分析
  5. android+引用非+android+工程,[非原创]编译android C++工程找不到exception handle的解决办法...
  6. Logistic Regression 之 Sigmoid
  7. iOS9 访问http网络
  8. 和大家分享2015年我逐步形成的六个管理认识
  9. 如何从wireshark 抓包中的RTP导出 H.264 PAYLOAD,变成可用暴风直接播放的H264 裸码流文件
  10. 纯HTML个人清新网站源码
  11. platform.pk8,platform.x509.pem生成keystore的方法
  12. 重磅:国家基金委八大学部公布“优先发展领域及主要研究方向”
  13. recall和precise的区别
  14. 罗技k380键盘-数字键上的字符对不上怎么办?
  15. linux中负载值为多少正常_Linux系统Load average负载详细解释
  16. vue中获取/操作组件中的dom元素
  17. 计算机给文件重命名快捷键,批量重命名文件 一个F2快捷键即可全部搞定
  18. 【牛客刷题】SQL专项错题记录三
  19. Matlab中ind2gray函数用法
  20. C语言实验——时间间隔

热门文章

  1. Python字符串格式化输出语法汇总
  2. iFonts字体管理工具(神同步PS操作)
  3. 飘浮广告显示脚本类(VBS,JS双版)
  4. jquery各个版本下载
  5. 系统移植2:bootloader的选择(u-boot)和移植
  6. 一站式开发一个安卓APP-测试与发布篇(build variants的应用)
  7. 谷歌浏览器或者采用Chromium内核浏览器,枫树,金山猎豹等等不显示复选框的解决办法
  8. IBM Eclipse简史
  9. 必读 | 深入理解 Flutter 的布局约束
  10. 华为ensp模拟双isp出口