微服务横向热扩展和自定义负载均衡策略

492 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第16天,点击查看活动详情

微服务横向热扩展和自定义负载均衡策略

一、准备工作

  1. docker & 容器相关知识

  2. Consul注册发现服务中心

  3. Provider微服务

  4. Consumer微服务

二、原理

image.png

 

三、实战微服务横向热扩展

  1. 启动Consul

首次:

docker run --name consul -d -p 8500:8500 -p 8300:8300 -p 8301:8301 -p 8302:8302 -p 8600:8600 consul:1.2.2 agent -dev -bind=0.0.0.0 -client=0.0.0.0

 之后:

docker start consul

 2. 启动Provider微服务

确保启动端口为8080

  1. 启动consumer微服务

确保启动端口为8090

  1. 查看consul管理后台

确保已经看到provider和consumer健康微服务实例

  1. 浏览器访问:http://localhost:8090/test,返回json:
{"id":862,"name":"consumer","port":8090,"providerPOJO":{"id":872,"name":"hello","port":8080}}
  1. 启动Provider微服务另一个实例

确保端口改为8081

  1. 查看consul管理后台,确保provider微服务实例已经增加到2个

  2. 再次浏览器访问:http://localhost:8090/test,返回json:

{"id":863,"name":"consumer","port":8090,"providerPOJO":{"id":197,"name":"hello","port":8081}}

多刷新几次:

{"id":864,"name":"consumer","port":8090,"providerPOJO":{"id":873,"name":"hello","port":8080}}

 注:

(1)从返回json可以观察到:consumer从两个provider实例得到所需服务

(2)部署provider的第二个实例过程并没有影响原先系统:没有停止或重启任何原先已经部署的服务,这就是横向热扩展的含义

(3)provider第二个实例启动后,需要过十几秒左右才能生效。原因是consumer对provider的服务发现是一个向consul发起http查询请求的过程,需要一定的触发条件。另外consul对刚刚注册的provider新服务实例,也需要时间执行健康检查,只有健康检查返回OK的provider服务实例才会被consumer拿到(可以在启动provider实例时,马上查看consul管理后台,观察对应的服务实例是否健康)。

  1. 停止端口为8080的provider微服务

  2. 再次浏览器访问:http://localhost:8090/test,返回json:

{"id":871,"name":"consumer","port":8090,"providerPOJO":{"id":202,"name":"hello","port":8081}}

注:

(1)刷新,观察返回的json可以发现providerPOJO中的port总是为8081

(2)实时生效。因为provider停止时会执行consul解注册,consul中provider可用服务实例列表马上会更新

 

四、自定义ribbon负载均衡策略

注:以下实现都在consumer中。

  1. 实现自定义ribbon配置类:src/main/java/com/example/consumer/config/MyConsulRibbonClientConfiguration.java
package com.example.consumer.config;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.IPing;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.ServerList;
import com.netflix.loadbalancer.ZoneAvoidanceRule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.consul.discovery.ConsulDiscoveryProperties;
import org.springframework.cloud.consul.discovery.ConsulPing;
import org.springframework.cloud.consul.discovery.ConsulRibbonClientConfiguration;
import org.springframework.cloud.consul.discovery.ConsulServerList;
import org.springframework.context.annotation.Bean;


@Slf4j
public class MyConsulRibbonClientConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public IRule ribbonRule(IClientConfig config) {
        log.info("MyConsulRibbonClientConfiguration:ribbonRule()");
        ZoneAvoidanceRule rule = new MyZoneAvoidanceRule();
        rule.initWithNiwsConfig(config);
        return rule;
        // return new AvailabilityFilteringRule();
        // return new RandomRule();
        // return new BestAvailableRule();
        // return new RoundRobinRule();
        // return new WeightedResponseTimeRule();
        // return new RetryRule();
    }
}

注:这里可以返回负载均衡各种已经实现的规则,也可以实现自定义的规则,比如这里的MyZoneAvoidanceRule。

2. 实现自定义ribbon rule类:src/main/java/com/example/consumer/config/MyZoneAvoidanceRule.java

package com.example.consumer.config;

import com.google.common.base.Optional;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ZoneAvoidanceRule;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.consul.discovery.ConsulServer;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Slf4j
public class MyZoneAvoidanceRule extends ZoneAvoidanceRule {
    @Override
    public Server choose(Object key) {
        log.info("MyZoneAvoidanceRule:choose()");
        ILoadBalancer lb = getLoadBalancer();
        List<Server> allServers = lb.getAllServers();
        logServers(allServers);
        Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(allServers, key);
        if (server.isPresent()) {
            return server.get();
        } else {
            return null;
        }
    }

    private void logServers(List<Server> allServers) {
        for (Server server : allServers) {
            log.info("server: " + server.getHostPort());
        }
    }
}

注:这里的实现只是简单从ZoneAvoidanceRule继承,然后打印了从consul获得的服务列表。真实负载均衡业务可以在此定制展开。

  1. 启动自定义Ribbon配置
package com.example.consumer;

import com.example.consumer.config.MyConsulRibbonClientConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.cloud.openfeign.EnableFeignClients;


@SpringBootApplication
@EnableFeignClients
@RibbonClients(defaultConfiguration = MyConsulRibbonClientConfiguration.class)
public class ConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }
}

 注:起作用的是这个注解:@RibbonClients(defaultConfiguration = MyConsulRibbonClientConfiguration.class)

 4. 重启consumer微服务,按上述第二大步中描述的方式运行测试

访问浏览器http://localhost:8090/test时观察consumer日志,可以看到类似如下日志输出:

... consumer.config.MyZoneAvoidanceRule  : MyZoneAvoidanceRule:choose()
... consumer.config.MyZoneAvoidanceRule  : server: xxxxx:8081
... consumer.config.MyZoneAvoidanceRule  : server: xxxxx:8080

 可知代码已经运行到我们自定义的规则中

 

五、Todo

  1. feign源代码分析和原理探究

  2. feign和ribbon和consul关系

  3. ribbon深层个性化配置