微服務無感發布(二):微服務優雅的啟動

吃了睡發表於2024-10-11

上一篇文章當中我們介紹了微服務在k8s環境下無感發布存在的問題和解決思路,今天來看一看程式碼的實現方式。

預熱以及快取載入

服務在容器啟動之後,可能需要載入一些必要快取,在這些快取載入之後,才能夠提供對外服務,註冊到註冊中心。在spring-cloud下,服務註冊是透過監聽WebServerInitializedEvent事件來觸發的,所以只需要保證在該事件之前載入完快取以及預熱完成即可。

預熱邏輯介面

首先,我們需要一個介面來定義預熱方法:

/**
 * 預熱 邏輯介面
 */
public interface BaseWarmupHandler {

    /**
     * 預熱方法
     */
    void warmup();
}
http預熱

接下來,我們實現一個預熱HTTP請求的方法

@RestController
@RequestMapping
public class WarmupController {

    /**
     * 系統預熱的介面
     * 參考專案: <link url="https://github.com/steinsag/warm-me-up"/>
     * @param request 請求引數
     * @return  返回
     */
    @PostMapping("/warmup")
    public Response<String> post(@RequestBody @Valid WarmupRequest request) {
        return Response.success(request.toString());
    }
}

在這個控制器中,我們定義了一個/warmup的POST介面,用於觸發web容器相關預熱邏輯。

預熱邏輯實現
@Slf4j
@Service
public class ServletWarmupHandler implements BaseWarmupHandler{

    @Resource
    private Environment environment;

    @Resource
    private OkHttpClient okHttpClient;
    @Override
    public void warmup() {
        final String serverPort = environment.getProperty("local.server.port");
        final String baseUrl = "http://localhost:" + serverPort;
        final String warmUpEndpoint = baseUrl + "/warmup";

        log.debug("Sending REST request to warm up...");

        // 建立 MediaType
        MediaType mediaType = MediaType.get("application/json; charset=utf-8");
        // 建立 RequestBody
        RequestBody body = RequestBody.create(mediaType, JSON.toJSONString(createSampleMessage()));
        Request request = new Request.Builder()
                .url(warmUpEndpoint)
                .post(body)
                .build();

        try (Response response = okHttpClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                log.error("warm up request error:{}", response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private WarmupRequest createSampleMessage() {
        final WarmupRequest warmUpRequestDto = new WarmupRequest();
        warmUpRequestDto.setInputMessage("warm me up");
        warmUpRequestDto.setPatternString("paretern");
        warmUpRequestDto.setSomeNumber(BigDecimal.TEN);
        warmUpRequestDto.setEm(WarmupEnum.ME);

        return warmUpRequestDto;
    }
}
啟動時自動預熱

最後,我們需要在服務啟動時自動觸發預熱邏輯。這可以透過監聽ApplicationReadyEvent事件來實現:

@Service
public class WarmupService implements ApplicationListener<ApplicationReadyEvent> {

    @Resource
    private ApplicationContext applicationContext;

    @Resource
    private ApplicationEventPublisher eventPublisher;

    @Override
    public void onApplicationEvent(@NonNull ApplicationReadyEvent event) {
        log.debug("warm up start .........");

        Map<String, BaseWarmupHandler> warmupHandlerMap = applicationContext.getBeansOfType(BaseWarmupHandler.class);
        for (Map.Entry<String, BaseWarmupHandler> entry : warmupHandlerMap.entrySet()) {
            try {
                entry.getValue().warmup();
            } catch (Exception e) {
                log.warn("warm up error, handler:{}, error:", entry.getKey(), e);
            }
        }

        log.debug("warm up done!");
        eventPublisher.publishEvent(new WarmupEndEvent(this));
    }
}

這個事例僅僅提供了一個預熱web容器http請求的思路,在實際專案中要根據自身需求編寫@Valid,jdbc或者快取相關的預載入程式碼。

微服務就緒介面與Kubernetes就緒探針配置

在微服務架構中,每個服務的就緒時機可能各不相同,有的可能需要等待快取載入完成,有的則需要確保已在註冊中心成功註冊。為了準確反映服務的就緒狀態,專案應提供一個專門的就緒介面。以下是一個簡單的Java介面示例:

@RestController("/lifeCycle")
public class StartedController implements ApplicationListener<InstanceRegisteredEvent<?>> {
    
    /** 專案是否啟動完成 */
    private boolean started = false;
    
    @GetMapping("/started")
    public boolean started() {
        if (started) {
            return true;
        }
        // 這裡根據專案定義的異常型別返回
        throw new RuntimeException("服務尚未啟動");
    }

    @Override
    public void onApplicationEvent(InstanceRegisteredEvent<?> event) {
        // InstanceRegisteredEvent 是spring-cloud 提供的註冊中心模版完成註冊時傳送的事件。
        // 這個裡可以根據專案調整專案成功的時機
        this.started = true;
    }
}

在Kubernetes環境中,就緒探針(Readiness Probe)用於判斷服務是否已經準備好接收流量。以下是一個Kubernetes容器配置就緒探針的示例:

readinessProbe:
  httpGet:
      # host:連線使用的主機名,預設是 Pod 的 IP。也可以在 HTTP 頭中設定 "Host" 來代替。
      # scheme:用於設定連線主機的方式(HTTP 還是 HTTPS)。預設是 "HTTP"。
      # path:訪問 HTTP 服務的路徑。預設值為 "/"。
      path: /lifeCycle/started
      # httpHeaders:請求中自定義的 HTTP 頭。HTTP 頭欄位允許重複。
      httpHeaders:
        - name: Accept
          value: application/json
      # port:訪問容器的埠號或者埠名。如果數字必須在 1~65535 之間。
      port: 10087
  initialDelaySeconds: 5
  # 執行探測的時間間隔(單位是秒)。預設是 10 秒。最小值是 1。 當容器未就緒時,ReadinessProbe 可能會在除配置的
  periodSeconds: 5

提示:就緒探針透過HTTP請求訪問指定的路徑和埠,如果介面返回的狀態碼大於或等於200且小於400,則表示探測成功,服務已就緒;否則,表示探測失敗,服務尚未就緒。

引用

shelltea.warmup-spring-boot-starter
kubernetes文件

相關文章