负载均衡
负载均衡的原理(通用)
@LoadBalanced注解
用来拦截它所标记的RestTemplate
发起的http请求, 底层是利用了一个名为Ribbon
的组件来实现负载均衡功能(Cloud高版本已经弃用)
LoadBalancerInterceptor的intercept方法
会对RestTemplate的请求进行拦截
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {private LoadBalancerClient loadBalancer;private LoadBalancerRequestFactory requestFactory;public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {this.loadBalancer = loadBalancer;this.requestFactory = requestFactory;}public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));}public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {// 获取请求uri,如http://userservice/user/1URI originalUri = request.getURI()// 获取uri路径的主机名,其实就是服务名称userserviceString serviceName = originalUri.getHost();Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri); // 处理服务名称和用户请求return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));}
}
RibbonLoadBalancerClient的execute方法
根据服务名称获取服务实例地址列表,随后利用负载均衡算法得到一个真实的服务地址信息
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {// 根据服务名称得到去Eureka服务端中获取服务的实例地址列表并保存DynamicServerListLoadBalancer到的allServeList属性当中ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);// 利用内置的负载均衡算法,从服务的实例地址列表中选择一个实例地址Server server = this.getServer(loadBalancer, hint);if (server == null) {throw new IllegalStateException("No instances available for " + serviceId);} else {RibbonServer ribbonServer = new RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));return this.execute(serviceId, (ServiceInstance)ribbonServer, (LoadBalancerRequest)request);}
}
负载均衡规则IRule
: RibbonLoadBalancerClient的getServer方法最终调用父类BaseLoadBalancer的chooseServer方法
public class BaseLoadBalancer extends AbstractLoadBalancer implements PrimeConnectionListener, IClientConfigAware {private static Logger logger = LoggerFactory.getLogger(BaseLoadBalancer.class);// IRule的实现类不同表示负载均衡的策略不同,RoundRobinRule表示轮循调度private static final IRule DEFAULT_RULE = new RoundRobinRule();private static final BaseLoadBalancer.SerialPingStrategy DEFAULT_PING_STRATEGY = new BaseLoadBalancer.SerialPingStrategy((SyntheticClass_1)null);private static final String DEFAULT_NAME = "default";private static final String PREFIX = "LoadBalancer_";protected IRule rule;protected IPingStrategy pingStrategy;protected IPing ping;public Server chooseServer(Object key) {if (this.counter == null) {this.counter = this.createCounter();}this.counter.increment();if (this.rule == null) {return null;} else {try {// 选出一个负载均衡的策略return this.rule.choose(key);} catch (Exception var3) {logger.warn("LoadBalancer [{}]: Error choosing server for key {}", new Object[]{this.name, key, var3});return null;}}}
}
负载均衡策略
负载均衡的规则都定义在IRule接口中,而IRule有很多不同的实现类,默认的实现就是ZoneAvoidanceRule表示区域内轮询方案
自定义负载均衡规则
编程方式(全局)
:在order-service模块
中启动类中注册一个IRule类型的Bean到Spring容器中,对order-service服务
想要调用的微服务均有效
- 优缺点: 配置灵活但修改时需要重新打包发布
@Bean
public IRule randomRule(){// 创建负载均衡的实现类对象return new RandomRule();
}
配置文件方式(局部)
:在order-service模块
的配置文件中添加userservice.ribbon.NFLoadBalancerRuleClassName
配置,只针对调用的某个微服务有效
- 优缺点: 直观方便无需重新打包发布,但是无法做全局配置,只能指定调用的某一个微服务的负载均衡规则
userservice: # 给user-service服务添加负载均衡规则ribbon:NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
饥饿加载(通用)
Ribbon默认是采用懒加载
,只有在用户第一次访问时才会创建LoadBalanceClient负载均衡类做服务拉取的动作
,这样用户第一次访问时就大大增加了请求时间
采用饥饿加载
是在项目启动时就创建LoadBalanceClient负载均衡类加载指定的服务
, 这样用户第一次访问时就大大缩短了请求时间
在order-service模块
的application.yml配置文件中添加新的配置属性ribbon.eager-load.enabled/clients
ribbon:eager-load:enabled: true # 开启饥饿加载# 指定对userservice单个服务进行饥饿加载clients: userservice # 指定对多个服务进行饥饿加载clients: - userservice- xxxservice