上一篇文章当中我们介绍了微服务在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文档