深度好文:雲函數 SCF + KonaJDK11 + Spring + 提速降存一把梭

一、背景

騰訊 KonaJDK 團隊最近對外開源了KonaJDK11, 該版本 JDK 是經過內部超大規模生產環境驗證的定製 JDK,該版本在啓動性能、峯值性能以及事物處理能力方面,相對於前一版本 Kona JDK8 都有了綜合性提升,沉澱了騰訊雲與大數據團隊在大數據/機器學習、雲原生場景下的深度優化,並且通過了 JCK 驗證,確保充分的 Java SE 標準兼容。通過工業標準 Benchmark 表明,Kona JDK11 對比 Kona JDK8 大多數場景在峯值性能上具有非常明顯的提升,個別性能提升接近 50%。

KonaJDK11 如此優秀,我們能不能把它引入到Serverless呢? 另外,最近筆者也在考慮怎麼樣讓 Java spring 框架在 SCF 中順滑的跑起來,所以藉着這個機會,索性來一把 KonaJDK + Spring 在 SCF 上的實踐總結。

多說無用,Show you my code!

二、SCF使用JDK11

騰訊雲Serverless雲函數SCF產品中內置Java8支持,但是並沒有高版本JDK的環境支持,那麼如何實現SCF的Java11雲函數呢?

實際上,SCF雲函數提供的CustomRuntime功能已經解鎖了用戶使用編程語言的限制,目前已經有webassembly,swift,rust等成功例子。我們可以同樣藉助這個功能來將KonaJDK11引入SCF,從而實現高版本Java的支持。

過程如下:

  1. 下載KonaJDK11,https://github.com/Tencent/TencentKona-11/releases
  2. 由於KonaJDK11的二進制包比較大,需要使用SCF層的概念來上傳KonaJDK11程序包

首先需要創建層,由於KonaJDK11程序包超過50MB,所以可以選擇COS方式,現將KonaJDK11安裝包上傳到騰訊雲COS,之後在創建層時指定路徑即可, 具體使用可以參考產品說明https://cloud.tencent.com/document/product/583/45760

  1. 創建雲函數, 注意這裏需要使用CustomRuntime,我們選擇Shell函數示例,再次基礎上拓展我們的KonaJDK11的支持.

進入【高級配置】->【層配置】->【添加層】

按照下圖所示配置好【層】【超時時間】與【內存】點擊【完成】

  1. 根據SCF CustomRuntime的使用說明,需要編寫CustomRuntime的啓動文件 Bootstrap,SCF CustomRuntime會在函數啓動時第一步找到並執行這個名爲bootstrap的可執行文件。

  2. 我們的bootstrap中需要配置環境變量,並啓動Java程序. 我們先假設我寫了一個名爲Hello的class,裏面只打印hello SCF 字符串。 之後將bootstrap文件和Hello.class文件一起打包成一個zip文件,上傳到SCF部署,這時bootstrap的內容如下:

可以看到就是簡單的環境變量配置和執行java -version 與 Hello程序。

之後點擊【測試】觸發執行,之後我們可以看到函數執行日誌如下:

我們已經可以從日誌裏看到 openjdk version "11.0.9.1-ga"的 Java版本,並且看到了Hello程序正常輸出。至此,KonaJDK 11 已經順利跑在了雲函數環境中。

注意此處顯示【測試失敗】是正常的,因爲我們還沒有編寫處理【函數事件】的邏輯,也就是還沒有實現具體的雲函數。

三、實現spring雲函數

現在讓我們來用spring框架實現一個能跑在KonaJDK11上的雲函數。爲了清晰,我們寫一個最簡單的springboot Demo, 它的controller長這樣:

入口函數長這樣:

OK,目前這個Demo可以接受 http Get localhost:8080/hello 請求並返回 hello, this is a springboot demo! 字符串。 那麼如何將它改編成雲函數呢?

從 SCF CustomRuntime 文檔以及一些公開的資料,可以看到編寫CustomRuntime的函數,只需要兩個關鍵步驟:

  1. 編寫可執行啓動程序bootstrap,在bootstrap裏面啓動我們的spring雲函數
  2. 編寫雲函數。這一步首先需要了解CustomRuntime工作的流程,從這篇文章可以看到,主要流程如下:

具體說來,就是在bootstrap啓動雲函數以後,sping雲函數在自身初始化時需要先POST一個Ready的httpRequest給 SCF服務端, 目的是通知SCF函數初始化完畢,可以獲得下發的事件了。

之後,spring需要一個循環,循環內部通過向SCF服務端發送HTTP GET請求,獲得待處理事件,再調用內部邏輯,處理完事件之後通過POST請求發送給SCF服務端,循環等待下一次事件下發。

針對Springboot, 我們的雲函數主要有以下幾個需要處理的地方:

  1. 事件下發: Springboot雲函數主要是啓動並監聽雲函數內部的一個自定義http端口,通過http請求完成處理任務。 SCF雲函數目前http請求主要通過API Gateway事件下發,也就是說,spring雲函數的邏輯裏面,需要將API Gateway事件轉換成http事件之後再發給函數內部的springboot監聽的端口。好在整個這一套邏輯的轉換SCF其實已經提供給了我們,就在SCF java event的代碼中,可以從 https://github.com/tencentyun/SCF-java-libs/blob/master/SCF-java-events/src/main/java/com/qcloud/SCF/runtime/AbstractSpringHandler.java 這個代碼直接抽取複用。

  2. 初始化: 也就是在第一次啓動雲函數的時候,我們需要啓動springboot,另其建立httpserver並監聽端口。 之後每次事件下發,只需要發送httprequest即可。

  3. 監聽事件: 這裏就是按照 SCF CustomRutime 的要求,寫一個循環,使用http GET請求獲取event,併發送給內部springboot監聽的端口。

經過上面的梳理,邏輯已經基本上清晰了:首先,需要在 cold launch階段啓動springboot入口函數, 通知SCF服務端,springboot雲函數初始化完畢,等待接收消息。之後就是一個大循環,循環裏面工作如下:

  • 通過 Http GET 請求從SCF服務端獲得 ApiGateway 下發的event

  • Api GW event轉換成 http request 併發送到 springboot 監聽的端口,等待返回處理結果
  • springboot 返回的 event 轉換爲 ApiGateway Response, 通過POST請求返回給SCF 服務端
  • 進入下一次循環,等待下一次事件下發.

處理流程代碼也很簡單:

至此,我們已經完成了雲函數的編寫,之後我們可以測試一下,將bootstrap和編譯後的 springboot-application.jar 打包到一個zip文件,然後上傳到SCF雲函數進行部署。

之後按照如下配置 apiGW 的 event,注意這裏配置 Get,“/hello” 是由於我們的springboot 雲函數的controller配置成了接收Get, “/hello” 請求並打印和返回字符串,實際上用戶需要根據自己的業務,修改apiGW這裏event相應的內容。

然後點擊[測試]: 稍等一下就可以看到如下log:

springboot已經啓動, 然後我們還可以看到:

函數已經正常響應了GET /hello的請求。

四、利用appCDS特性提速降存

在上面的springboot雲函數中,我們可以看到一次冷啓動耗時和內存如下:

同時log中也包含了springboot的啓動時間

總體來說就是耗時6秒多,使用了168MB的內存。

那麼,如何提高啓動速度減少內存使用呢?

JDK11裏面自帶appCDS功能,具openJDK官方說法,該功能可以減少java類加載時間同時減少內存佔用量,提高啓動速度。 這不正是我們想要的麼,我們現在已經有了KonaJDK+springboot的雲函數,那麼怎麼在KonaJDK中使用起來這個功能呢?

  1. appCDS功能使用步驟:

按照JDK官方文檔, appCDS使用方式主要是以下幾個步驟:

  • 生成待 dump 的類文件列表, 使用 -Xshare:off -XX:+UseAppCDS -XX:DumpLoadedClassList=classes.lst JVM選項運行程序,會生成classes.lst文件
  • 使用 -Xshare:dump -XX:+UseAppCDS -XX:SharedClassListFile=classes.lst \ -XX:SharedArchiveFile=dump.jsa 生成dump.jsa文件
  • 使用appCDS正常啓動java程序,使用JVM選項 -Xshare:on -XX:+UseAppCDS -XX:SharedArchiveFile=dump.jsa
  1. 雲函數中enable appCDS

針對雲函數SCF的場景,主要需要以下適配工作

  • 由於在雲函數中,目前只有/tmp目錄是可寫目錄,所以1中的步驟我們需要將所有涉及到的文件路徑變更爲 /tmp/classes.list/tmp/dump.jsa
  • 由於我們期望最終生成的dump.jsa可以在多個雲函數實例中使用,我們需要得到/tmp/dump.jsa文件,然後將其和雲函數一起打包,這樣在使用時候,我們只需要指定Jvm參數 -XX:SharedArchiveFile=dump.jsa 即可複用 dump.jsa 文件。 所以我們需要獲得生成的 /tmp/dump.jsa 文件,由於SCF不能直接下載 /tmp目錄的文件,所以我們根據COS的文檔寫了一小段程序,幫助我們在生成 /tmp/dump.jsa 文件後上傳到指定的COS中,具體可以參考COS java 的sdk
  • 在得到 dump.jsa之後,我們就可以對整個雲函數重新打包,最終打包的文件中包含3個子文件, 雲函數 CustomRuntime的啓動腳本bootstrap, springboot雲函數的實現 SCF-springboot-web-1.0-SNAPSHOT.jar ,以及appCDS的archive文件 dump.jsa,我們將這3個文件打包重新部署。
  • 再部署之後,我們需要添加 JAVA_TOOL_OPTIONS 環境變量 JAVA_TOOL_OPTIONS=-Xshare:on -XX:SharedArchiveFile=dump.jsa

這樣就可以在啓動雲函數時使用這些 jvm 選項了。

  1. 效果

在使用AppCDS之後出發雲函數的冷啓動,可以看到如下效果:

  • 內存使用 從原來的169MB降低到了100MB

  • springboot啓動時間從原來的6.137s提高到了4.772s

總結

至此,我們在騰訊 Serverless 雲函數上藉助 CustomRuntime 完成了KonaJDK11 + SpringBoot雲函數的使用,並利用KonaJDK11中AppCDS特性優化了雲函數冷啓動的速度與內存損耗。 文中利用CustomeRuntime引入KonaJDK11的方法可以作爲騰訊雲Faas上解鎖多語言或高版本Java語言runtime的一種通用方式。

在未來騰訊KonaJDK團隊會進一步針對騰訊雲業務Faas場景的特點提供更多的功能與性能提升,敬請關注。

One More Thing

立即體驗騰訊雲 Serverless Demo,領取 Serverless 新用戶禮包 👉 serverless/start

歡迎訪問:Serverless 中文網

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章