网关 Spring Cloud Zuul 健康检查以及自动剔除失效实例上线自动恢复

310 阅读4分钟

文章目录

前言

假设负载后台2个实例

  • 192.1.1.101:80
  • 192.1.1.102:80【失败】

如果没有监控检查,则会192.1.1.101:80成功,192.1.1.102:80失败【等到超时才能感知】,192.1.1.101:80成功,192.1.1.102:80失败【等到超时才能感知】。。。。

这样很浪费资源,我们想达到能动态感知后台失效的实例,并剔除掉无效实例,达到一直请求192.1.1.101:80成功,当192.1.1.102:80恢复正常后再把正常实例加进来,192.1.1.101:80成功,192.1.1.102:80成功。。。

来看下Ribbon是如何实现这个实用的功能。

正常监控检查原理

典型的主动检查需要客户端至少有一个 ping thread,定期检测服务器是否健康,流程如下图:

  1. Ping thread 检测到两个服务器都正常工作
  2. 于是客户端会将请求分发到两个服务器上
  3. 此时 Server 2 宕机,发送到该机器的请求处理失败,返回错误
  4. 由于新一轮的检测还没有开始,客户端没有意识到服务器已宕机,依旧将请求分发给 Server 2
  5. 新一轮 Ping 检测到 Server 2 宕机,将其标记为不可用
  6. 于是客户端将所有请求分发到 Server 1
  7. 此时 Server 2 恢复正常,但由于还没有被客户端检测到,不会向它分发请求
  8. 新一轮 Ping 检测到 Server 2 恢复,将其标记为可用
  9. 客户端重新将请求分配给 Server 2

Ribbon原理

ribbon负载均衡是会从ServerList获取Server对象,其中有个属性isAliveFlag

public class Server {
   	// 标记是否这台机器是否是活着的 默认值是false
    private volatile boolean isAliveFlag; 

留有扩展接口IPing以检查其是否活动的接口,类似于心跳检测。

public interface IPing {
	// 检查给定的Server是否为“活动的”,这为在负载平衡时选出一个可用的候选Server
	public boolean isAlive(Server server);
}

它内部有很多实现如下

  • PingConstant:永远返回一个bool常量:true or false。

  • NoOpPing:永远返回true

  • AbstractLoadBalancerPing:和LoadBalancer有关的一种实现,用于探测服务器节点的适用性。

  • DummyPing:它是AbstractLoadBalancerPing的一个空实现

    • 它是默认的ping实现,Spring Cloud默认也是使用的它作为默认实现
    • ribbon-eureka模块下有NIWSDiscoveryPing这个实现,它基于服务注册中心来判断服务的健康状态
  • PingUrl:它使用发送真实的Http请求的方式来做健康检查,若返回的状态码是200就证明能够ping通,返回true

如何设置失效实例,以及动态剔除呢?

IPing.isAlive接口调用的地方如下:

唯一被BaseLoadBalancer中的SerialPingStrategy.pingServers

IPingStrategy的默认实现串行执行ping操作,如果您的IPing实现速度慢或服务器数量众多,则可能不希望这样做。

 private static class SerialPingStrategy implements IPingStrategy {
        @Override
        public boolean[] pingServers(IPing ping, Server[] servers) {
            for (int i = 0; i < numCandidates; i++) {
                results[i] = false; /* Default answer is DEAD. */
                try {
                    if (ping != null) {
                        results[i] = ping.isAlive(servers[i]);
                    }

SerialPingStrategy.pingServers被调用如下:

BaseLoadBalancer.Pinger

class Pinger {
		...
		public void runPinger() throws Exception {
			boolean[] results = null;
			...
			results = pingerStrategy.pingServers(ping, allServers);
			...
				// 这里就是核心:只有ping后是活着的,就会把这个机器添加到up列表里
				boolean isAlive = results[i];
                if (isAlive) {
                     newUpList.add(svr);
                 }
			...
		}
		...
	}

这就是isAlive()方法的作用:true -> 表示该机器是up的,从而得到新的up列表就是最新的可用的机器列表了

BaseLoadBalancer.Pinger又是由Timer定时调用

// 任务Task
	class PingTask extends TimerTask {
		@Override
		public void run() {
			new Pinger(pingStrategy).runPinger();
		}
	}
	
	// 这里是它的PingTask的唯一调用处
	void setupPingTask() {
		...
		lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000);
		...
	}

IPing#isAlive()方法是由Timer定时调用的,pingIntervalSeconds默认值是30s,也就说30s会去心跳一次Server,看它活着与否。当然你可以通过key:NFLoadBalancerPingInterval自己配置(单位是秒)

拓展

相关自动化配置类RibbonClientConfiguration

  • IClientConfig - 默认:DefaultClientConfigImpl

  • IRule- 默认:ZoneAvoidanceRule

  • IPing- 默认:DummyPing

  • ServerList - 默认:ConfigurationBasedServerList

    • 默认从配置文件listOfServers读取
  • ServerListUpdater - 默认就是:PollingServerListUpdater DynamicServerListLoadBalancer的策略可用于执行动态服务器列表更新的不同方式。

    • 定时默认每30s重新拉取最新的ServerList,如果是默认的则可以从配置文件读取最新的配置listOfServers
  • ILoadBalancer - 默认:ZoneAwareLoadBalancer

  • ServerListFilter - 默认:ZonePreferenceServerListFilter

  • RibbonLoadBalancerContext

  • RetryHandler

  • ServerIntrospector - 默认:DefaultServerIntrospector

自定义监控检查

自定义配置

先自定义2个配置

读取配置值用

        pingAppendString = clientConfig.getPropertyAsString(
                new CommonClientConfigKey<Boolean>("LakerPingAppendString") {
                },
                "");
        isSecure = clientConfig.getPropertyAsBoolean(
                new CommonClientConfigKey<Boolean>("LakerIsSecure") {
                },
                false);

自定义HealthCheck

自定义HealthCheck继承AbstractLoadBalancerPing接口

@Slf4j
public class HealthCheck extends AbstractLoadBalancerPing {
    String pingAppendString = "";
    boolean isSecure = false;
    @Override
    public boolean isAlive(Server server) {
        ... check
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
		... 属性初始化
    }
}

配置文件

roadnet-service:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule # 配置负载均衡算法
    NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList # 配不配都行默认也是这个
    NFLoadBalancerPingClassName: com.laker.zuul.ext.zuul.ping.HealthCheck # 配置我们的自定义监控检查
    LakerPingAppendString: /abc
    LakerIsSecure: false
    NFLoadBalancerPingInterval: 10 # 默认是30s
    listOfServers: http://10.7.11.13:9006,http://localhost:8081
    ConnectTimeout: 1000
    ReadTimeout: 3000
    MaxTotalHttpConnections: 500
    MaxConnectionsPerHost: 100
    MaxAutoRetries: 1
    MaxAutoRetriesNextServer: 1

参考:

🍎QQ群【837324215】
🍎关注我的公众号【Java大厂面试官】,一起学习呗🍎🍎🍎
🍎个人vxlakernote