您好,登錄后才能下訂單哦!
Docker容器中怎么部署Java微服務,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
要確定 docker 容器內存超限問題的直接原因并不難。直接進入docker容器,執行 top
命令,我們發現宿主機是一臺8核16G的機器,而且 docker 并不會屏蔽這些信息,也就是 JVM 會認為自己工作于一臺 16G 內存的機器上。而查看 demo 服務的 Dockerfile,發現運行服務時并沒有對 JVM 的內存進行任何限制,于是 JVM 會根據默認的設置來工作 —— 最大堆內存為物理內存的1/4(這里的描述并不完全準確,因為 JVM 的默認堆內存大小限制比例其實是根據物理內存有所變化的,具體內容請自行搜索資料),而基于模板創建的 ServiceStage 流水線,在部署應用堆棧的時候會把 docker 容器的內存配額默認設置為 512M,于是容器就會在啟動的時候內存超限了。至于以前沒有碰到過這種問題的原因,只是因為以前沒將這么高規格的 ECS 服務器用于流水線部署應用堆棧。
在查詢過相關資料后,我們找到了兩種問題解決方案,一個是直接在 jar 包運行命令里加上 -Xmx
參數來指定最大堆內存,不過這種方式只能將 JVM 堆內存限制為一個固定的值;另一個方法是在執行 jar 包時加上 -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap
參數,讓 JVM 能夠感知到docker容器所設置的 cgroup
限制,相應地調整自身的堆內存大小,不過這個特性是 JDK 8u131 以上的版本才具備的。
最終,我們提醒 ServiceStage 流水線的同學將 CSEJavaSDK demo 的創建模板做了改進,在 Dockerfile 中將打包的基礎鏡像版本由原先的 java:8u111-jre-alpine
升級為了 openjdk:8u181-jdk-alpine
,并且在運行服務 jar 包的命令中加上了 -Xmx256m
參數。問題至此已經解決了。
雖然問題已經解決,但是在好奇心的驅使下,我還是打算自己找個 demo 實際去觸發一下問題,另外看看從網上搜到的解決方法到底好不好用 : )
創建云上工程
首先需要在華為云 ServiceStage 創建一個云上工程。
在 ServiceStage -> 應用開發 -> 微服務開發 -> 工程管理 -> 創建云上工程中,選擇“基于模板創建”,語言選擇 Java, 框架選擇 CSE-Java (SpringMVC)
,部署系統選擇“云容器引擎CCE”,給你的云上工程取一個名字,比如test-memo-consuming
,最后選擇存放代碼的倉庫,就可以完成云上工程的創建了。
之后云上工程會根據你的選項自動地生成腳手架代碼,上傳到你指定的代碼倉庫中,并且為你創建一條流水線,完成代碼編譯、構建、打包、歸檔鏡像包的操作,并且使用打好的 docker 鏡像包在 CCE 集群中部署一個應用堆棧。
創建云上工程和流水線不是本文的重點,我就不詳細講操作了 : )。同一個應用堆棧的實例可以部署多個,在這里為了實驗方便就按照默認值1個來部署。
登錄到 demo 服務所部署的容器,使用curl
命令可以調用 demo 服務的 helloworld 接口,可以看到此時服務已經可以正常工作。
增加實驗代碼
為了能夠觸發微服務實例消耗更多的內存,我在項目代碼中增加了如下接口,當調用/allocateMemory
接口時,微服務實例會不停申請內存,直到 JVM 拋出 OOM 錯誤或者容器內存超限被 kill 掉。
private HashMap<String, long[]> cacheMap = new HashMap<>(); @GetMapping(value = "/allocateMemory") public String allocateMemory() { LOGGER.info("allocateMemory() is called"); try { for (long i = 0; true; ++i) { cacheMap.put("key">
此時用來打鏡像包的基礎鏡像是openjdk:8u181-jdk-alpine
,jar 包啟動命令中加上了-Xmx256m
參數。
執行流水線,應用堆棧部署成功后,調用/allocateMemory
接口觸發微服務實例消耗內存,直到 JVM 拋出 OOM 錯誤,可以在 ServiceStage -> 應用上線 -> 應用管理中選擇相應的應用,點擊進入概覽頁面,查看應用使用內存的情況。
應用使用的內存從 800M+ 陡然下降的時間點就是我重新打包部署的時間,而之后由于調用/allocateMemory
接口,內存占用量上升到了接近 400M,并且在這個水平穩定了下來,顯示-Xmx256m
參數發揮了預期的作用。
現在將 demo 工程中的 Dockerfile 修改一下,將基礎鏡像改為 java:8u111-jre-alpine
,并且刪除啟動命令中的-Xmx256m
參數,將其提交為noLimit_oldBase
分支,推送到代碼倉庫中。然后編輯流水線,將 source 階段的任務所使用的代碼分支改為noLimit_oldBase
分支,保存并重新運行流水線,將新的代碼打包部署到應用堆棧中。
在微服務實例列表中查詢到新的微服務實例的 endpoint IP 后,調用/allocateMemory
接口,觀察內存情況,內存從接近 400M 突然掉下去一下,然后又上升到約 450M 的時間點就是修改代碼后的微服務實例部署成功的時間點,之后內存占用量突然下跌就是因為調用/allocateMemory
接口導致容器內存超限被 kill 掉了。
如果你事先使用docker logs -f
命令查看容器日志的話,那么日志大概是這個樣子的
2018-11-23 15:40:04,920 INFO SCBEngine:152 - receive MicroserviceInstanceRegisterTask event, check instance Id... 2018-11-23 15:40:04,920 INFO SCBEngine:154 - instance registry succeeds for the first time, will send AFTER_REGISTRY event. 2018-11-23 15:40:04,925 WARN VertxTLSBuilder:116 - keyStore [server.p12] file not exist, please check! 2018-11-23 15:40:04,925 WARN VertxTLSBuilder:136 - trustStore [trust.jks] file not exist, please check! 2018-11-23 15:40:04,928 INFO DataFactory:62 - Monitor data sender started. Configured data providers is {com.huawei.paas.cse.tcc.upload.TransactionMonitorDataProvider,com.huawei.paas.monitor.HealthMonitorDataProvider,} 2018-11-23 15:40:04,929 INFO ServiceCenterTask:51 - read MicroserviceInstanceRegisterTask status is FINISHED 2018-11-23 15:40:04,939 INFO TestmemoconsumingApplication:57 - Started TestmemoconsumingApplication in 34.81 seconds (JVM running for 38.752) 2018-11-23 15:40:14,943 INFO AbstractServiceRegistry:258 - find instances[1] from service center success. service=default/CseMonitoring/latest, old revision=null, new revision=28475010.1 2018-11-23 15:40:14,943 INFO AbstractServiceRegistry:266 - service id=8b09a7085f4011e89f130255ac10470c, instance id=8b160d485f4011e89f130255ac10470c, endpoints=[rest://100.125.0.198:30109?sslEnabled=true] 2018-11-23 15:40:34,937 INFO ServiceCenterTaskMonitor:39 - sc task interval changed from -1 to 30 2018-11-23 15:47:03,823 INFO SPIServiceUtils:76 - Found SPI service javax.ws.rs.core.Response$StatusType, count=0. 2018-11-23 15:47:04,657 INFO TestmemoconsumingImpl:39 - allocateMemory() is called Killed
可以看到allocateMemory
方法被調用,然后 JVM 還沒來得及拋出 OOM 錯誤,整個容器就被 kill 掉了。
這里也給大家提了一個醒:不要以為自己的服務容器能啟動起來就萬事大吉了,如果沒有特定的限制,JVM 會在運行時繼續申請堆內存,也有可能造成內存用量超過 docker 容器的配額!
前文提到還有另外一種方法解決 JVM 內存超限的問題,這種方法可以讓 JVM 自動感知 docker 容器的 cgroup
限制,從而動態的調整堆內存大小,感覺挺不錯的。我們也來試一下這種方法,看看效果如何 ; )
回到demo項目代碼的master
分支,將 Dockerfile 中啟動命令參數的-Xmx256m
替換為-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap
,提交為useCGroupMemoryLimitForHeap
分支,推送到代碼倉庫里。再次運行流水線進行構建部署。
等 demo 服務部署成功后,再次調用/allocateMemory
接口,容器的內存占用情況如上圖所示(最右邊的那一部分連續曲線),內存上升到一定程度后,JVM 拋出了 OOM 錯誤,沒有繼續申請堆內存。看來這種方式也是有效果的。不過,仔細觀察容器的內存占用情況,可以發現容器所使用的內存僅為不到 300M,而我們對于這個容器的內存配額限制為 512M,也就是還有 200M+ 是閑置的,并不會被 JVM 利用。這個利用率,比起上文中直接設置-Xmx256m
的內存利用率要低 : ( 。推測是因為 JVM 并不會感知到自己是部署在一個 docker 容器里的,所以它把當前的環境當成一個物理內存只有 512M 的物理機,按照比例來限制自己的最大堆內存,另一部分就被閑置了。
關于Docker容器中怎么部署Java微服務問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。