一、背景
首先简单描述下大概的背景,我们的微服务架构采用的是 eureka + gateway,eureka 和 gateway 以及其他微服务都是以容器的方式部署在 K8s 中。最近,出现了一个问题,gateway 所在节点到某微服务所在节点的网络出现了问题,导致请求失败,错误信息如下:
2022-06-30 11:22:58.059[WARN ]reactor.netty.http.client.HttpClientConnect: 299-[id: 0x2add3421, L:/172.22.20.49:37826 - R:172.22.9.118/172.22.9.118:21004] The connection observed an error
io.netty.channel.unix.Errors$NativeIoException: syscall:read(..) failed: Connection reset by peer
at io.netty.channel.unix.FileDescriptor.readAddress(..)(Unknown Source)
注:这里的 L:/172.22.20.49:37826 为 gateway 所在节点,R:172.22.9.118/172.22.9.118:21004 为微服务所在节点
问题的示意图如下:
从上图,我们看到当客户端访问「 微服务1」的时候,通过 ribbon 将请求转发到「微服务1-2」上。由于网络原因(「gateway所在节点」 到 「微服务1-2 所在节点」 的网络)导致请求无法送达,造成请求失败。
二、解决方案
也许有的同学会问,Eureka 不是应该下掉「微服务1-2」这个节点吗?让我们仔细想一下,Eureka 能够正常接收到来自「微服务1-2」的心跳,所以,对 Eureka 来说「微服务1-2」是健康的,既然是健康的节点,那不被下掉就是必然的了。
所以,想从 Eureka 入手解决这个问题,看样子是行不通啦(因为 Eureka 和 微服务1-2 都是正常的),那该如何是好呢?!
Ribbon 作为 「客户端负载均衡」,不但给我们提供了丰富的负载均衡策略。同时也提供了实例检查策略,也就是对客户端本地存储的服务列表中的实例进行健康检查(我们可以通过实现 com.netflix.loadbalancer.IPing 来自定义检查策略)。
如此看来,Ribbon 的实例检查 能够很好的解决我们上边提到的问题了!!!
三、方案实施
下面说下具体的方案实施(两步搞定)
1、自定义实例检查策略
通过对接口 com.netflix.loadbalancer.IPing 的实现,如下图:
代码如下:
import com.netflix.loadbalancer.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
/**
* @Author: cab5
* @Date: 2022/7/13 15:27
* @Description: 自定义 ribbon 的对微服务的健康检查
*/
@Slf4j
public class HealthChecker implements IPing {
@Autowired
private RestTemplate restTemplate;
@Override
public boolean isAlive(Server server) {
String url = "http://" + server.getId() + "/actuator/info";
try {
ResponseEntity<String> heath = restTemplate.getForEntity(url, String.class);
if (heath.getStatusCode() == HttpStatus.OK) {
log.info("ping " + url + " success and response is " + heath.getBody());
return true;
}
log.info("ping " + url + " error(status code = " + heath.getStatusCode() + ") and response is " + heath.getBody());
return false;
} catch (Exception e) {
log.error("ping " + url + " failed", e);
return false;
}
}
}
主要逻辑是通过调用微服务的 /actuator/info 接口,判断返回的状态码是否为 200。这里的 /actuator/info 接口是微服务引入 spring-boot-starter-actuator 得到的。
2、配置自定义检查策略
通过代码的方式将「步骤一」中的 HealthChecker 进行配置,如下:
import com.netflix.loadbalancer.IPing;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author: cab5
* @Date: 2022/7/13 15:24
* @Description:
*/
@Configuration
public class GatewayConfig {
/**
* ribbon 健康检查
* @return
*/
@Bean
public IPing iPing() {
return new HealthChecker();
}
}
OK,大功告成!!!