前言
项目使用的框架为springboot+consul+feign+kafka+redisson ,使用k8s进行服务部署,目前已知存在的问题如下:
- 发版过程中报redisson is shutdown异常。
- 发版过程中节点注册到consul报节点冲突问题。
- 发版过程中接口调用失败。
发版过程中报redisson is shutdown异常
问题定位
redisson客户端的销毁时机是bean被回收时,项目中kafka的消费者的销毁时机同样是bean被回收时,这就导致可能存在redisson已经被回收了,但是kafka的消费者还在消费的情况。
解决方案
强制要求kafka的消费者销毁时机早于redisson,我选择将kafka消费者的销毁时机统一提前到容器关闭的时候。
@Component
@Slf4j
public class KafkaConsumerDeregister implements ApplicationListener<ContextClosedEvent> {
@Override
public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
//销毁kafka消费者
//等待消费者线程池处理结束,大概10秒。
}
}
发版过程中节点注册到consul短暂失败。
错误日志
2023/11/30 16:01:22 [WARN] manager: No servers available
2023/11/30 16:01:22 [ERR] agent: failed to sync remote state: No known Consul servers
2023/11/30 16:01:23 [INFO] agent: (LAN) joined: 0 Err: 3 errors occurred:
* Failed to join XXX: Member 'XXX-59f47dd479-cjwl7' has conflicting node ID '93648b00-ae08-538c-60f0-5bd2538beaa8' with this agent's ID
* Failed to join XXX: Member 'XXX-59f47dd479-cjwl7' has conflicting node ID '93648b00-ae08-538c-60f0-5bd2538beaa8' with this agent's ID
* Failed to join XXX: Member 'XXX-59f47dd479-cjwl7' has conflicting node ID '93648b00-ae08-538c-60f0-5bd2538beaa8' with this agent's ID
2023/11/30 16:01:23 [WARN] agent: Join LAN failed: <nil>, retrying in 30s
原因定位
- 节点注册时使用ip作为节点id,使用实例名作为节点名。
- k8s中部署单元为固定ip池,新旧服务,IP会被重复利用,但实例名,一直在变化。
- consul对于不可用的节点不会实时删除。
- 假如旧节点用节点A-IPA组合进行注册,新节点用节点B-IPA组合进行注册时就会报冲突,同一个节点id有两个不同的节点名。
解决方案
注册时用实例名作为节点id。
步骤
- 修改consul agent的启动脚本 ,将ip作为节点id修改为实例名作为节点id
nohup /app/consul/bin/consul agent -node-id=$实例名 -config-dir=/app/consul/conf -bind=$ip >/dev/null 2>&1 &
发版过程中接口调用失败
问题定位
spring cloud consul会缓存服务列表1秒,ribbon会缓存服务列表30秒,发版过程中旧服务已下线,但是其他服务节点未更新该缓存,依旧往旧服务发起请求,导致请求异常。
解决方案
- 停止旧服前,先将服务从consul中心移除
- 等待35秒,确保各服务节点的服务列表缓存已刷新
- 停止旧服务
- 关闭consul代理
具体操作
- k8s部署单元修改终止宽限期修改为60秒,防止停服脚本还没执行完,k8s容器就被回收了
- 修改容器停止脚本
- 先执行关闭java服务
kill $java服务
- 再停止consul agent
/app/consul/bin/consul leave
- 先执行关闭java服务
3.编写代码,将consul服务提前注销
@Component
@Slf4j
public class ConsulDeregister implements ApplicationListener<ContextClosedEvent> {
@Autowired(required = false)
private ConsulServiceRegistry consulServiceRegistry;
@Autowired
private ConsulRegistration reg;
@Value("${consul.deregister.prepose.sleep:30000}")
private Long sleep;
@Override
public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
if(consulServiceRegistry != null){
log.info("consul 服务注销开始");
consulServiceRegistry.deregister(reg);
log.info("consul 服务注销结束");
//等待30秒,其他服务的ribbon缓存刷新后,继续关闭服务
ThreadUtil.sleep(sleep);
}
}
}
结果
使用JMETER工具 20条线程不间断发起请求,在k8s上重启服务,每个请求都能得到正常返回。
附录
/app/consul/bin/consul members 输出服务节点列表
/app/consul/bin/consul leave consul代理节点下线