本文共 10367 字,大约阅读时间需要 34 分钟。
假设user-server要调用order-server:user-server -> order-server
是user-server向order-server发起调用请求,user-server是客户端,order-server是服务端,提供服务。
eureka client、nacos client、zuul、gateway都内置了ribbon,以实现客户端的负载均衡。
eg. user-service的某个节点要调用order-service
1、向内置的ribbon发起调用order-service的负载均衡请求
2、ribbon查询缓存中有没有order-service的节点列表,有就直接使用,没有就从注册中心拉取
3、ribbon使用指定的负载均衡算法从节点列表中选取一个节点
4、user-service向返回的order-service节点发起调用
对于一个提供者服务,ribbon会在缓存中维护2个List:
调用提供者时,ribbon先从本地缓存的该服务可用节点列表中获取一个节点,如果列表中没有可用的节点,再从注册中心更新该服务的节点列表。
1、RoundRobinRule 轮询(默认策略)
轮询适合节点性能都差不多的情况。从前往后依次轮询节点列表中的每个节点,谁空闲就调用谁。2、RetryRule 重试
先轮询,如果未获取到节点,则在指定的时间内(默认500ms)重试。3、RandomRule 随机
4、BestAvailableRule 最可用,选择负载最小的节点
5、AvailabilityFilteringRule 可用过滤
先过滤掉处于断路状态(断路器打开)、负载很大的节点,再使用轮询。6、ZoneAvoidanceRule 根据大区性能、节点可用性综合筛选
7、WeightedResponseTimeRule 权重响应时间
根据节点的平均响应时间计算权重,响应快的权重大,被选中的机率就越大。
eureka client的依赖中已经包含了ribbon的依赖,不用额外添加依赖。
负载均衡策略是在消费者中设置的,有2种方式
配置文件方式
#设置调用order-service的负载均衡策略order-service: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
代码方式
@SpringBootApplication@EnableEurekaClientpublic class UserServiceApplication { //如果直接使用RestTemplate进行服务调用,需要加上此方法 @LoadBalanced @Bean public RestTemplate restTemplate() { return new RestTemplate(); } //设置负载均衡策略 @Bean public RandomRule getRule(){ return new RandomRule(); } public static void main(String[] args) { SpringApplication.run(UserServiceApplication.class, args); }}
配置文件方式设置的负载均衡策略只对指定的服务调用有效,代码设置方式对所有的服务调用都有效。
如果要对所有服务调用都设置相同的负载均衡策略,使用代码设置方式;如果要针对各个提供者设置不同的负载均衡策略,使用yml配置方式。
一般使用默认的轮询即可。
import com.netflix.client.config.IClientConfig;import com.netflix.loadbalancer.AbstractLoadBalancerRule;import com.netflix.loadbalancer.ILoadBalancer;import com.netflix.loadbalancer.Server;import java.util.List;/** * 自定义的负载均衡策略 * 继承AbstractLoadBalancerRule,重写choose()方法 */public class MyRule extends AbstractLoadBalancerRule { @Override public void initWithNiwsConfig(IClientConfig iClientConfig) { } /** * @param o ribbon读取到的目标服务的所有节点的信息,会自动传入。很多时候都是ribbon缓存的数据,可能含有无效的节点 * @return 选择使用的节点 */ @Override public Server choose(Object o) { //获取所要调用服务的负载均衡器 ILoadBalancer loadBalancer = this.getLoadBalancer(); //获取目标服务的所有节点,可能包含无效节点 ListallList = loadBalancer.getAllServers(); //获取目标服务的所有可用节点 List upList = loadBalancer.getReachableServers(); for (Server server : upList) { //有效且空闲 if (server.isAlive() && server.isReadyToServe()) { return server; } } return null; }}
配置方式和内置策略的配置方式相同。
相比于eureka,nacos提供了更多的属性、方法,用nacos做注册中心可以自定义更多的负载均衡策略,比如优先调用当前集群中的服务、调用指定版本的服务。
此处的集群可以理解为数据中心,比如按地域划分为华东、东北、西南…每个区域都建立全套服务器,eg. tomcat集群中包含多个user-server节点、order-server节点,user-server调用order-server时优先调用当前区域集群中的order集群节点。
spring: application: name: user-server cloud: nacos: discovery: server-addr: 127.0.0.1:8848 #部署服务时需要给节点指定所属的集群 cluster-name: SW
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;import com.alibaba.cloud.nacos.NacosServiceManager;import com.alibaba.cloud.nacos.ribbon.NacosServer;import com.alibaba.nacos.api.exception.NacosException;import com.alibaba.nacos.api.naming.NamingService;import com.alibaba.nacos.api.naming.pojo.Instance;import com.alibaba.nacos.client.naming.core.Balancer;import com.netflix.client.config.IClientConfig;import com.netflix.loadbalancer.AbstractLoadBalancerRule;import com.netflix.loadbalancer.BaseLoadBalancer;import com.netflix.loadbalancer.Server;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.util.CollectionUtils;import java.util.List;import java.util.Objects;import java.util.stream.Collectors;/** * 自定义的负载均衡策略 * 继承AbstractLoadBalancerRule,重写choose()方法 */@Slf4jpublic class LocalClusterRule extends AbstractLoadBalancerRule { @Autowired private NacosDiscoveryProperties discoveryProperties; @Override public void initWithNiwsConfig(IClientConfig iClientConfig) { } /** * @param o ribbon读取到的目标服务的所有节点的信息,会自动传入。很多时候都是ribbon缓存的数据,可能含有无效的节点 * @return 选择使用的节点 */ @Override public Server choose(Object o) { NacosServiceManager nacosServiceManager = new NacosServiceManager(); NamingService namingService = nacosServiceManager.getNamingService(discoveryProperties.getNacosProperties()); //获取当前服务节点的集群名称 String clusterName = discoveryProperties.getClusterName(); //获取被调服务的负载均衡器 BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer(); //被调服务的名称 String name = loadBalancer.getName(); try { //通过被调服务的名称筛选出所有健康的被调服务 ListallInstance = namingService.selectInstances(name, true); if (CollectionUtils.isEmpty(allInstance)){ log.error("注册中心没有可用的{}服务实例!",name); return null; } //过滤出与当前服务在同一集群中的被调服务 List localInstance = allInstance.stream().filter(instance -> Objects.equals(instance.getClusterName(), clusterName) ).collect(Collectors.toList()); if (CollectionUtils.isEmpty(localInstance)){ log.warn("当前集群{}中没有可用的{}服务,将跨集群调用该服务",clusterName,name); }else{ allInstance = localInstance; } //使用nacos提供的方法随机选择一个节点 Instance instance = ExtendBalancer.getHostByRandomWeight(allInstance); //可用直接输出instanceId,instanceId中包含了ip、port、所属集群、所属分组、服务名称 log.info("选择使用的{}服务实例是{}集群的{}:{}",name,instance.getClusterName(),instance.getIp(),instance.getPort()); return new NacosServer(instance); } catch (NacosException e) { log.error("ribbon使用自定义的负载均衡策略获取被调服务{}时发生异常",name); e.printStackTrace(); } return null; }}/** * Balancer的getHostByRandomWeight()权限时protected,继承改为public暴露出去 */class ExtendBalancer extends Balancer { public static Instance getHostByRandomWeight(List hosts){ return Balancer.getHostByRandomWeight(hosts); }}
spring: application: name: user-server cloud: nacos: discovery: server-addr: 127.0.0.1:8848 metadata: #指定当前服务版本 version: 1.0 #指定被调服务版本 targetVersion: 1.0
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;import com.alibaba.cloud.nacos.NacosServiceManager;import com.alibaba.cloud.nacos.ribbon.NacosServer;import com.alibaba.nacos.api.exception.NacosException;import com.alibaba.nacos.api.naming.NamingService;import com.alibaba.nacos.api.naming.pojo.Instance;import com.alibaba.nacos.client.naming.core.Balancer;import com.netflix.client.config.IClientConfig;import com.netflix.loadbalancer.AbstractLoadBalancerRule;import com.netflix.loadbalancer.BaseLoadBalancer;import com.netflix.loadbalancer.Server;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.util.CollectionUtils;import java.util.List;import java.util.Map;import java.util.Objects;import java.util.stream.Collectors;/** * 自定义的负载均衡策略 * 继承AbstractLoadBalancerRule,重写choose()方法 */@Slf4jpublic class VersionRule extends AbstractLoadBalancerRule { @Autowired private NacosDiscoveryProperties discoveryProperties; @Override public void initWithNiwsConfig(IClientConfig iClientConfig) { } /** * @param o ribbon读取到的目标服务的所有节点的信息,会自动传入。很多时候都是ribbon缓存的数据,可能含有无效的节点 * @return 选择使用的节点 */ @Override public Server choose(Object o) { NacosServiceManager nacosServiceManager = new NacosServiceManager(); NamingService namingService = nacosServiceManager.getNamingService(discoveryProperties.getNacosProperties()); //获取目标服务版本 Mapmetadata = discoveryProperties.getMetadata(); String targetVersion = metadata.get("targetVersion"); //获取被调服务的负载均衡器 BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer(); //被调服务的名称 String name = loadBalancer.getName(); try { //通过被调服务的名称筛选出所有健康的被调服务 List allInstance = namingService.selectInstances(name, true); if (CollectionUtils.isEmpty(allInstance)){ log.error("注册中心没有可用的{}服务实例!",name); return null; } //过滤出指定版本的被调服务实例 List versionInstance = allInstance.stream().filter(instance -> Objects.equals(instance.getMetadata().get("version"), targetVersion) ).collect(Collectors.toList()); if (CollectionUtils.isEmpty(versionInstance)){ log.error("注册中心没有{}服务的{}版本实例)!", name, targetVersion); return null; } //使用nacos提供的方法随机选择一个节点 Instance instance = ExtendBalancer.getHostByRandomWeight(versionInstance); //可用直接输出instanceId,instanceId中包含了ip、port、所属集群、所属分组、服务名称 log.info("选择使用的{}服务实例是{}集群的{}:{}",name,instance.getClusterName(),instance.getIp(),instance.getPort()); return new NacosServer(instance); } catch (NacosException e) { log.error("ribbon使用自定义的负载均衡策略获取被调服务{}时发生异常",name); e.printStackTrace(); } return null; }}/** * Balancer的getHostByRandomWeight()权限时protected,继承改为public暴露出去 */class ExtendBalancer extends Balancer { public static Instance getHostByRandomWeight(List hosts){ return Balancer.getHostByRandomWeight(hosts); }}
ribbon从注册中心获取提供者节点列表时,默认使用懒加载:第一次调用服务提供者时,才会从注册中心加载该服务提供者的节点列表,初次调用某个服务有点慢。
可以设置为饥饿加载,在应用(消费者)启动时就加载服务提供者节点列表
ribbon: eager-load: #开启饥饿加载。默认false enabled: true #指定要饥饿加载哪些服务提供者,其余提供者仍使用懒加载方式 clients: order-server,msg-server
转载地址:http://gzhlb.baihongyu.com/