給 SpringBoot 服務(wù)添加健康檢查
引子
公司的后端服務(wù)基于 SpringBoot 開發(fā),部署在 K8S 集群中。在平時開發(fā)過程中,由于部署頻繁,經(jīng)常導(dǎo)致服務(wù)隊(duì)對外表現(xiàn)為 502 不可用,非常影響前端開發(fā)體驗(yàn)。
如果服務(wù)長時間不可用,那么前端只能啟用 mock 模式進(jìn)行開發(fā)了。具體做法可以參見《前后端分離開發(fā)中前端需要克服的挑戰(zhàn)》一文。
盡管前端有辦法,但是作為后端服務(wù),還是得保證服務(wù)的可用性,不能隨意找借口搪塞。

實(shí)際上,公司的 dev 環(huán)境也在 AWS 集群上,配置并不低,而且需要的話可以繼續(xù)調(diào)高配置。在部署時,觀察 pod 的狀態(tài),的確是滾動更新的。
之所以在部署時服務(wù)會不可用,是因?yàn)闆]有配置服務(wù)就緒探針,K8S 沒有辦法獲取 pod 里應(yīng)用的狀態(tài),只能檢查 pod 的狀態(tài)。只要 pod 起來了,流量就會路由給它,但這時如果應(yīng)用并未啟動完畢,就會導(dǎo)致對外表現(xiàn)為 502 Bad Gateway。
因此,要解決這個問題,實(shí)現(xiàn)服務(wù)無宕機(jī)更新,就只需要配置好探針就行了。
給 SpringBoot 應(yīng)用添加 actuator
測試
第一步,先把測試寫好,文檔化我們期待的行為:
import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.availability.ApplicationAvailability;import org.springframework.boot.availability.AvailabilityChangeEvent;import org.springframework.boot.availability.LivenessState;import org.springframework.boot.availability.ReadinessState;import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.context.ApplicationContext;import org.springframework.test.context.ActiveProfiles;import org.springframework.test.context.junit4.SpringRunner;import org.springframework.test.web.servlet.MockMvc;import static org.assertj.core.api.Assertions.assertThat;import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;@RunWith(SpringRunner.class)@ActiveProfiles("test")@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)@AutoConfigureMockMvcpublic class HealthCheckControllerTest {@Autowiredprivate ApplicationAvailability applicationAvailability;@Autowiredprivate MockMvc mockMvc;@Autowired private ApplicationContext context;@Testpublic void testHealthCheck() throws Exception {assertThat(applicationAvailability.getLivenessState()).isEqualTo(LivenessState.CORRECT);mockMvc.perform(get("/healthz/liveness")).andExpect(status().isOk()).andExpect(jsonPath("$.status.code").value("UP"));AvailabilityChangeEvent.publish(context, ReadinessState.REFUSING_TRAFFIC);assertThat(applicationAvailability.getReadinessState()).isEqualTo(ReadinessState.REFUSING_TRAFFIC);mockMvc.perform(get("/healthz/readiness")).andExpect(status().isServiceUnavailable()).andExpect(jsonPath("$.status.code").value("OUT_OF_SERVICE"));}}
添加依賴
以 maven 項(xiàng)目為例,在 pom 文件中添加:
<dependency><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-actuatorartifactId>dependency>
配置項(xiàng)目
可以在 bootstrap.yml 中增加這樣的配置:
management:endpoint:health:probes:enabled: truelivenessState:enabled: truereadinessState:enabled: trueendpoints:web:: /:health: healthz
這時測試可以通過了。也可以本地啟動進(jìn)行手動端到端驗(yàn)證。先啟動:
mvn clean install && mvn spring-boot:run -pl your-representation-layer執(zhí)行:
curl http://localhost:your-port/healthz/liveness
curl http://localhost:your-port/healthz/readiness
在 deployment 文件里配置探針
在 deployment 文件中增加如下配置項(xiàng):
readinessProbe:httpGet:path: /healthz/readinessport: 8080initialDelaySeconds: 30timeoutSeconds: 10livenessProbe:httpGet:path: /healthz/livenessport: 8080initialDelaySeconds: 130timeoutSeconds: 10
大功告成,提交本次改動,后面的服務(wù)更新就不會影響前端開發(fā)了。當(dāng)然,這只從運(yùn)維層解決了服務(wù)的可用。對于開發(fā)層面,一定要注意,作為服務(wù)提供者,對于接口的修改,必須保證向前兼容。永遠(yuǎn)不要假設(shè)和要求前端和你同時更新(根本做不到,比如對于微信小程序,發(fā)布后,仍然有最長 24 小時的更新延遲;如果是原生 APP,用戶不一定自動更新的)。
總結(jié)
監(jiān)控和可觀測性對于分布式系統(tǒng)至關(guān)重要,這兩點(diǎn)要求系統(tǒng)在運(yùn)行時提供健康檢查機(jī)制。假如現(xiàn)有的系統(tǒng)沒有這個機(jī)制,那么可以參考《利用 Vector 從日志創(chuàng)建指標(biāo)來提高系統(tǒng)的可觀測性》一文,從日志的角度來彌補(bǔ)。然而,正如本文開頭提到的,這依然會影響平時的開發(fā)體驗(yàn),所以,本文詳細(xì)介紹了如何在 SpringBoot 應(yīng)用中增加健康檢查機(jī)制,一來解決了開發(fā)體驗(yàn)問題,二來為后續(xù)捕獲有用的健康指標(biāo)從而與流行的工具集成以提高系統(tǒng)的可觀測性打下了基礎(chǔ)。
