持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第16天,点击查看活动详情
微服务横向热扩展和自定义负载均衡策略
一、准备工作
-
docker & 容器相关知识
-
Consul注册发现服务中心
-
Provider微服务
-
Consumer微服务
二、原理
三、实战微服务横向热扩展
- 启动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
- 启动consumer微服务
确保启动端口为8090
- 查看consul管理后台
确保已经看到provider和consumer健康微服务实例
{"id":862,"name":"consumer","port":8090,"providerPOJO":{"id":872,"name":"hello","port":8080}}
- 启动Provider微服务另一个实例
确保端口改为8081
-
查看consul管理后台,确保provider微服务实例已经增加到2个
{"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管理后台,观察对应的服务实例是否健康)。
-
停止端口为8080的provider微服务
{"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中。
- 实现自定义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获得的服务列表。真实负载均衡业务可以在此定制展开。
- 启动自定义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
-
feign源代码分析和原理探究
-
feign和ribbon和consul关系
-
ribbon深层个性化配置