背景:搜索服务承载5k+ QPS,单机500+ QPS,传统发布流程导致100ms+响应抖动,影响用户体验。
一、问题定位与根因分析
1.1 核心问题现象
-
启动阶段流量冲击:服务注册过早,实例未完成初始化即接收生产流量
-
响应时间尖刺:首次请求触发懒加载组件初始化(DB连接池/Redis连接/动态配置)
1.2 技术根因溯源
| 问题点 | 具体表现 | 影响范围 |
|---|---|---|
| Nacos注册时机过早 | 监听WebServerInitializedEvent事件注册,此时部分Bean未完成初始化 | 所有依赖延迟加载的组件 |
| 中间件连接懒加载 | Hikari连接池、Lettuce客户端等采用首次调用时建立连接 | 数据库/Redis操作 |
| 动态配置加载滞后 | @RefreshScope Bean在首次请求时触发初始化 | 配置相关业务逻辑 |
| 未使用jit优化 | 没有jit优化,代码是边解释边执行 | 全部代码 |
通过Spring生命周期追踪与源码插桩,发现Nacos注册与资源就绪存在时序差。 在阅读了源码之后,可以得出结论nacos的注册时机是依托于spring的生命周期机制,我们发现监听的是WebServerInitializedEvent,也就是内置的Tomcat启动完成的时刻,所以你会看到nacos的beat心跳紧贴在tomcat的17000端口启动之后进行注册并完成的。意味着从这一刻开始,nacos上就有这台机器,对应的请求也会过来。
二、解决方案设计
2.1 优化方案
| 方案 | 优点 |
|---|---|
| 延迟注册 | 简单易实现 |
| 健康检查驱动注册 | 精准判断服务真实可用状态 |
| 接口预热 | 主动触发 |
| 逐步预热上线 | 自定义权重配置 |
2.2 最终方案架构
A[Spring容器启动] --> B{触发ApplicationRunner}
B --> C[执行首次健康检查]
C --> D[启动异步健康监控线程]
D --> E{持续检查就绪状态?}
E -->|未就绪| F[等待5秒重试]
G -->|已就绪| H[进行接口预热]
I -->|预热完毕| J[触发Nacos手动注册]
K --> L[10%流量接入] --> M[50%流量接入] --> N[100%流量接入]
三、关键技术实现
3.1 注册时机控制
- 注册时机后移 关闭自动注册,进行手动注册,并进行流量的逐步接入:
/**
* 进行nacos手动注册
*/
private void doNacosRegister(){
log.warn("nacos手动注册流程开始");
try {
// 临时获取权限拿参数
Field declaredField = nacosAutoServiceRegistration.getClass().getDeclaredField("registration");
declaredField.setAccessible(true);
NacosRegistration nacosRegistration = (NacosRegistration) declaredField.get(nacosAutoServiceRegistration);
declaredField.setAccessible(false);
// 如果开启了自动注册 那么就直接返回
if (nacosRegistration.isRegisterEnabled()) {
log.warn("nacos已打开自动注册,跳过手动注册!");
return;
}
NacosDiscoveryProperties nacosDiscoveryProperties = nacosRegistration.getNacosDiscoveryProperties();
// 手动注册,初始0.1流量
nacosDiscoveryProperties.setRegisterEnabled(true);
nacosDiscoveryProperties.setWeight(0.1F);
nacosAutoServiceRegistration.start();
// TODO 这里start() 偶现权重设置不生效 下面用maintain重新设置0.1
// 获取维护client
NamingMaintainService maintainService = nacosServiceManager.getNamingMaintainService(nacosDiscoveryProperties.getNacosProperties());
String serviceName = nacosDiscoveryProperties.getService();
String groupName = nacosDiscoveryProperties.getGroup();
// 创建要更新的实例
Instance instance = new Instance();
instance.setIp(nacosDiscoveryProperties.getIp());
instance.setPort(nacosDiscoveryProperties.getPort());
// 预热30秒
// 更新实例
instance.setWeight(0.1);
maintainService.updateInstance(serviceName, groupName, instance);
Thread.sleep(30 * 1000);
log.warn("预热结束:1500, 0.1");
// 更新实例
instance.setWeight(0.5);
maintainService.updateInstance(serviceName, groupName, instance);
// 预热30秒
Thread.sleep(30 * 1000);
log.warn("预热结束:1500, 0.5");
// 更新实例
instance.setWeight(1);
maintainService.updateInstance(serviceName, groupName, instance);
log.warn("预热结束:1");
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
log.warn("nacos手动注册流程结束");
}
}
3.2 健康检查增强
- 使用健康检查进行连接预热:
private void handleCommandLineArguments(ApplicationArguments args) {
// 获取并处理命令行参数
System.out.println("---命令行参数:---");
for (String arg : args.getSourceArgs()) {
System.out.println(arg);
}
// 获取并处理应用程序参数
System.out.println("---应用程序参数:---");
for (String name : args.getOptionNames()) {
System.out.println(name + "=" + args.getOptionValues(name));
}
// 如果在启动参数手动设置了不注册nacos,就跳过手动注册,为了开发环境和backend
if ( !checkDisableNacos(args.getSourceArgs()) ) {
// 初次健康检查,预热
this.firstHealthCheck();
// 异步健康检查
CompletableFuture.supplyAsync(() -> {
log.warn("异步监测健康状态开始");
Boolean isUp = false;
// 等待5秒才注册
try {
for (int i = 1; i <= CHECK_HEALTH_NACOS_REGISTER_MAX_TIMES; i++) {
isUp = this.isUpStatus();
log.warn("第{}次异步健康检测:{}", i, isUp);
if (isUp){
// 如果已启动,注册并中断循环
this.doNacosRegister();
break;
}
Thread.sleep(5000); // 模拟耗时操作
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return isUp;
}).thenAccept(result -> {
if (result) {
log.warn("异步监测健康状态结束");
} else {
System.exit(99);
log.error("异步监测健康状态一直失败,请检查!");
}
});
}
}
-
中间件预热:
以mysql举例,使用的Hikari连接池在datasource创建的时候采用的是懒加载模式,直到第一次调用getConnection才会真正和mysql进行连接。而spring actuator的健康检查机制可以解决此类问题,针对所有的中间件,不管你是否有主动进行getConnection,它都会在检查时主动getConnection。
通过DataSourceHealthIndicator主动触发连接池初始化,避免首次请求时建立连接。
四、实施效果验证
4.1 性能指标对比
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 启动阶段最大响应时间 | 100ms | 7ms |
| 服务注册到接收流量间隔 | 3-5s | 按需可控 |
| 发布期间错误率 | 0.8% | 0.02% |
4.2 典型日志轨迹
textCopy Code
[14:52:59] 首次健康检查:OUT_OF_SERVICE
[14:53:04] 异步检测通过 → DB/Redis连接就绪
[14:53:04] 触发手动注册 → 服务进入可用状态
五、方案扩展性
- 动态等待策略:根据健康检查结果动态调整重试间隔(指数退避算法)
- 分级就绪机制:区分核心依赖与次要依赖的健康状态
- 熔断降级集成:与Sentinel联动实现异常状态自动下线
后续计划:将该能力沉淀为Spring Cloud Starter组件,实现企业级标准化发布流程