知乎問答:為什麼 Go 的 Web 框架速度還不如 Java?

2021-02-20 Go語言中文網

在知乎上看到一個問答:為什麼 Go 的 Web 框架速度還不如 Java?連結:https://www.zhihu.com/question/360929863 。

提問者由此問題的根據來自:https://www.techempower.com/benchmarks/#section=data-r18&hw=ph&test=json,不過提問時說:Go 不是編譯型語言嗎,速度怎麼還不如 Java?顯然這個說法有問題。

截個圖看看這個網站的跑分:

這裡主要整理兩個回答。

北南的回答

首先呢,Java 也是編譯型的語言。Java 的編譯分為兩個步驟,第一步是從原始碼到 bytecode,也就是.class 文件。我們日常看到的 java jar 包,其實就是 bytecode 的分發形式。第二步是在運行時,java 虛擬機會和 jit 合作,把 bytecode 在需要的時候編譯成 native code。所以說呢,你這個連結裡的跑分比較,其實大家跑的都是編譯後的 native code。

其次,web 框架不單單要看中對語言的執行效率,還要看 IO 的效率。當然這也沒什麼花頭,java 的很多框架和 go 的異步 IO 如出一轍, 說白了還是藉助 linux 作業系統中的 epoll 等來減少進程堵塞。於是很多跑分中,語言的執行效率變得次要,在充分優化和合理使用 nonblocking IO 的情況下,一些解釋型語言也能取得很好的成績。

最後,也是最重要的,其實你看這些跑分,框架的性能都好的不行不行的,遠遠超過一般應用的需要。其實在實際應用中,我們的業務邏輯會更複雜,上下遊服務也會更多,其實就算是好多人瞧不起的增刪改查操作,並發大了也極容易產生性能瓶頸。換句話說,程式語言和框架本身是不容易成為性能瓶頸的。所以,我建議應該更多的從開發難易程度,以及以後項目長期維護的成本上來選擇語言和框架,而不是看跑分。

阿里巴巴淘系技術的回答

該回複分析了這個跑分。

各種框架的應用場景不同導致其優化側重點不同,下面我們展開詳細分析。

http server 概述

首先描述一下一個簡單的 web server 的請求處理過程:

Net 層讀取數據包後經過 HTTP Decoder 解析協議,再由 Route 找到對應的 Handler 回調,處理業務邏輯後設置相應 Response 的狀態碼等,然後由 HTTP Encoder 編碼相應的 Response,最後由 Net 寫出數據。

而 Net 之下的一層由內核控制,雖然也有很多優化策略,但這裡主要比較 web 框架本身,那麼暫時不考慮 Net 之下的優化。

看了下 techempower 提供的壓測框架源碼[1],各類框架基本上都是基於 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[2])中物理機下 Plaintext 下的 QPS 在七百萬級,而 Data updates 在萬級別,相差百倍以上),所以這邊主要分析 Json serialization 和 Plaintext 兩種相對能比較體現出框架本身 http 性能的排名。

在 Round 19[3] Json serialization 中 Java 性能最高的框架是 firenio-http-lite (QPS: 1,587,639),而 Go 最高的是 fasthttp-easyjson-prefork(QPS: 1,336,333),按照這裡面的數據是 Java 性能高。

firenio-http-lite

從 fasthttp-easyjson-prefork 的 pprof 看除了 read 和 write 外, json (相當於 Business logic) 佔了 4.5%,fasthttp 自身(HTTP Decoder, HTTP Encoder, Router)佔了 15%,僅看 Json serialization 似乎會有一種 Java 比 Go 性能高的感覺。

fasthttp

那我們繼續把業務邏輯簡化,看一下 Plaintext 的排名,Plaintext 模式其實是在使用 HTTP pipeline 模式下壓測的,在 Round 19[4] 中 Java 和 Go 已經幾乎一樣的 QPS 了,在 Round 19 之後的一次測試[5]中 gnet 已經排在所有語言的第二,但是前幾個框架 QPS 其實差別很微小。

這時候其實主要瓶頸都在 net 層,而 go 官方的 net 庫包含了處理 goroutine 相關的邏輯,像 gonet 之類的直接操作 epoll 的會少一些這方面的消耗,Java 的 nio 也是直接操作的 epoll 。

net

拿了 gnet 的測試源碼跑了下壓測,看到 pprof 如下,其實這裡 gnet 還有更進一步的性能優化空間:time.Time.AppendFormat 佔用 30% CPU。

gnet

可以使用如下提前 Format ,允許減少獲取當前時間精度的情況下大幅減少這部分的消耗。

var timetick atomic.Value

func 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 的內存分配,是由於這個壓測代碼中還存在下面的部分沒有復用內存:

runtimepipeline code

其實 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 構建,並且使用同樣的壓測腳本[6],在阿里雲 4 核獨享機器測試下 go fasthttp-easyjson-prefork 框架 Json serialization 的性能要高於 Java wizzardo-http 和 firenio-http-lite 30% 以上且延遲更低的,這可能和內核有關)。

以上為淘系架構團隊風弈的個人見解,歡迎前來討論。

同時也歡迎帶上簡歷發送到郵箱:fengyi.shy@alibaba-inc.com,加入淘系架構團隊,一起並肩作戰或前來指導工作,為上層淘系各業務提升基礎性能和研發效率。

總結

我認為,大部分時候,框架的性能不是關鍵,更何況 Go、Java 等語言相比 PHP 等動態語言性能相對更好。何況 PHP 做 Web 開發,語言本身在大部分時候都不是問題呢。

你怎麼看?歡迎留言。

參考資料[1]

壓測框架源碼: https://github.com/TechEmpower/FrameworkBenchmarks

[2]

Round 19: https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=update

[3]

Round 19: https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=update

[4]

Round 19: https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=update

[5]

一次測試: https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext

[6]

同樣的壓測腳本: https://tfb-status.techempower.com/unzip/results.2020-05-13-21-08-06-543.zip/results/20200509183726/fasthttp-easyjson-prefork/json/raw.txt

相關焦點

  • 為什麼做java的web開發我們會使用struts2,springMVC和spring這樣的框架?
    今年我一直在思考web開發裡的前後端分離的問題,到了現在也頗有點心得了,隨著這個問題的深入,再加以現在公司很多web項目的控制層的技術框架由
  • 最受歡迎的 Java Web 開發框架盤點
    Java開發框架排名列前3名,包括以下: 1) Spring網址:http://projects.spring.io/spring-framework/ 2) JSF網址:http://www.oracle.com/technetwork/java/javaee/download-139288
  • Go Web開發框架基本組成
    服務)層,負責處理業務邏輯DAO(Repository):負責數據與存儲相關功能一、加載配置加載演示配置yaml文件如下:name: "go_web"mode: "dev"port: 8084version
  • 雲原生時代,Java還是Go?
    Oracle 的GraalVm允許將字節碼編譯成Linux可執行文件(ELF),而Rad Heat的Quarkus以及其他框架,則立志讓響應式服務這件事變得更簡單。Quarkus以Netty和Vertx.x為核心,可以用來構建非常高效的響應式Web服務。Java編譯成可執行二進位文件,以毫秒級的速度啟動,內存佔用很小。
  • Go 每日一庫之雲原生 go-zero 微服務框架
    0. go-zero 介紹 go-zero 是一個集成了各種工程實踐的 web 和 rpc 框架。通過彈性設計保障了大並發服務端的穩定性,經受了充分的實戰檢驗。,我們決定:2. go-zero 框架設計思考 對於微服務框架的設計,我們期望保障微服務穩定性的同時,也要特別注重研發效率。
  • Python 最好用的web框架
    速度為先當下,python都已經更新到了 Python3.9.3 了,如果你還沒有使用過 asyncio、和 Python3.5 新增的 async/await 語法,那說明你可能真的是桃花源人,問今是何世,不知有漢,無論魏晉了。在當下,基於 async/await 語法的異步 Web 框架也有很多,在 github 上找一找比比皆是是,那究竟應該選哪一款呢?
  • 一文讀懂雲原生 go-zero 微服務框架
    go-zero 是一個集成了各種工程實踐的web和rpc框架。1. go-zero框架背景18年初,曉黑板後端在經過頻繁的宕機後,決定從Java+MongoDB的單體架構遷移到微服務架構,經過仔細思考和對比,我們決定:基於Go語言高效的性能簡潔的語法
  • 6個最好的Go語言Web框架
    Beego: 一個Go語言下開源的,高性能Web框架 https://github.com/astaxie/beego https://beego.meBuffalo: 一個Go語言下快速Web開發框架 https://github.com/gobuffalo/buffalo
  • Web前端和Web後端的區別
    (包括資料庫)為什麼jsp是後端呢?主要是jsp的運行原理是在tomcat伺服器運行的。javaweb是java開發中的一個方向 java有搞安卓的,搞web的,搞嵌入式的等。javaweb就是指搞web方向的,javaweb分兩塊,一塊是伺服器端叫後端,另一塊叫前端,也就是web前端。前端就是用戶能看到的部分,比如淘寶網,qq空間,網頁上你能看到的都是web前端做的。
  • 2018 年 Java,Web 和移動程式設計師學習的 12 個框架
    如果2018年還沒有決定學什麼,那麼你來對地方了。在今天的文章中,我將分享一些你可以學習的最好框架,以提升你在移動和Web開發以及大數據技術方面的知識。在當今世界,對各種框架的了解是非常重要的。它們使你可以快速開發原型和實際項目。如果你在創業公司工作,那麼你肯定希望能夠立馬裝備一些炫酷的東西,那正是框架知識發光發熱的地方。
  • 2016年7款最佳 Java 框架
    在其完全擁抱 MVC框架之後,Spring 不斷的發展與變化著,轉變成了一個功能全面的面向網際網路應用程式的Java框架, 它能提供給軟體工程師的是一個用於web應用程式開發的強大工具集,以及針對安全項目的應用程式配置。作為程式設計師世界最受歡迎的一員,Spring所擁有有發育良好的生態提供了許許多多的附加選擇: REST API 或者是 SOAP web 服務, 安全(認證和授權、加密)等等。
  • 2020年9個最流行的Java框架
    但是,僅僅為你的下一個web應用程式開發項目選擇這門頂級語言是不夠的。在選擇Java web框架時,你仍需要做出正確的選擇。那麼,你是否想知道如何為項目選擇一個恰當的 Java框架呢?實際上,這並不容易,而且需要深入了解為特定行業業務選擇特定java框架進行軟體開發的優缺點。現在,讓我們一起來詳細研究一下2020年這9大頂級Java框架。
  • 『GCTT 出品』6 款最棒的 Go 語言 Web 框架簡介
    https://github.com/speedwheel/awesome-go-web-frameworks/blob/master/README.md#popularity學習曲線https://github.com/speedwheel/awesome-go-web-frameworks/blob/master/README.md#learning-curveastaxie 和 kataras
  • 我要學Go了!2500字深度分析!
    這一點與java相同。go語言的語法接近C語言,但在變量聲明上不同,此外,go同C++相比,不具有枚舉、繼承、虛函數等C++中的典型特徵,但是go增加了切片、並發和垃圾回收、接口等新的特性。2.為啥要學Go語言那麼為什麼要學習go語言呢,這就要從go語言的語言特色來講起。
  • java三大框架介紹
    常聽人提起三大框架,關於三大框架,做了如下了解: 三大框架:Struts+Hibernate+Spring java
  • ​百度雲好課分享[第02套-Go語言視頻零基礎入門到精通項目實戰web編程Golang 2018年新教程] 百度網盤分享
    Python3+TensorFlow打造人臉識別智能小程序mksz328 - 2小時快速上手Spring Boot 紅包程序 盡顯極客浪漫Stylemksz331 - PHP開發晉升課程 BAT資深工程師解析Tp5+Tp6底層源碼mksz332 - 零基礎入門 全角度解讀企業主流資料庫MySQL8.0mksz333 - Django高級實戰 開發企業級問答網站
  • 2020 最受歡迎的九大頂級 Java 框架
    但是,僅僅為你的下一個web應用程式開發項目選擇這門頂級語言是不夠的。在選擇Java web框架時,你仍需要做出正確的選擇。那麼,你是否想知道如何為項目選擇一個恰當的 Java框架呢?實際上,這並不容易,而且需要深入了解為特定行業業務選擇特定java框架進行軟體開發的優缺點。現在,讓我們一起來詳細研究一下2020年這9大頂級Java框架。
  • Web App與框架簡介
    前端:HTML、CSS、JavaScript後端:java、PHP、Python資料庫:MySQL、Oracle、MongoDB容器:Windows(IIS)、Linux(Nginx、Apache)在過程中涉及到的協議:TCP、DNS、HTTP、HTTPS輸入URL,DNS解析URL得到ip地址,根據ip地址找出對應伺服器
  • [GO夜讀] #62 Go-Micro微服務框架101 基礎概念
    * 下文圖示均出自原分享PPT什麼是 Go-Micro簡單來說,Go-Micro 是一個用來構建微服務的一個框架和工具集,旨在提高微服務下服務代碼開發的速度和質量,與此相關的還有一個運行時工具集叫做 Micro。目前以上兩個框架都由一家位於英國倫敦的公司負責維護開發工作。
  • Go 語言 Web 框架 Echo 系列教程
    GithubAll200012139732747683712BenchmarkGoJsonRest_GithubAll20007857961343712737BenchmarkGoRestful_GithubAll30052381886896724519BenchmarkGorillaMux_GithubAll100102577262118402272BenchmarkHttpRouter_GithubAll2000010541413792167BenchmarkHttpTreeMux_GithubAll1000031993465856671BenchmarkKocha_GithubAll1000020944223304843BenchmarkLARS_GithubAll200006256500BenchmarkMacaron_GithubAll200011612702041942000BenchmarkMartini_GithubAll20099917132265492325BenchmarkPat_GithubAll2005590793149956827435BenchmarkPossum_GithubAll1000031976884448609BenchmarkR2router_GithubAll1000030513477328979BenchmarkRivet_GithubAll1000013213416272167BenchmarkTango_GithubAll3000552754638261618BenchmarkTigerTonic_GithubAll100014394832391045374BenchmarkTraffic_GithubAll10011383067265932921848BenchmarkVulcan_GithubAll500039425319894609