SpringCloud Ribbon 负载均衡
目录
- 服务端负载均衡与客户端负载均衡
- springcloud的服务调用流程
- ribbon内置的负载均衡策略(7种)
- 设置负载均衡策略
- 自定义负载均衡策略
- 通用示例
- nacos做注册中心之优先调用当前集群中的服务
- nacos做注册中心之调用指定版本的服务
- ribbon的饥饿加载
服务端负载均衡与客户端负载均衡
假设user-server要调用order-server:user-server -> order-server
是user-server向order-server发起调用请求,user-server是客户端,order-server是服务端,提供服务。
- 如果在客户端(消费者)获取服务端(提供者)的节点列表、选择一个节点来使用,即在客户端实现的负载均衡,称为客户端负载均衡;
- 如果用nginx之类的反向代理服务端,客户端向nginx发起请求,nginx通过负载均衡算法转发给服务端的某个节点,即在服务端实现的负载均衡,称为服务端负载均衡。
eureka client、nacos client、zuul、gateway都内置了ribbon,以实现客户端的负载均衡。
springcloud的服务调用流程
eg. user-service的某个节点要调用order-service
1、向内置的ribbon发起调用order-service的负载均衡请求
2、ribbon查询缓存中有没有order-service的节点列表,有就直接使用,没有就从注册中心拉取
3、ribbon使用指定的负载均衡算法从节点列表中选取一个节点
4、user-service向返回的order-service节点发起调用
对于一个提供者服务,ribbon会在缓存中维护2个List:
- 该服务所有节点的信息列表,从注册中心拉取,后续ribbon会轮询注册中心此节点列表是否有更新,如果有更新会从注册中心重新获取拉取。
- 该服务有效节点的信息列表:ribbon会将拉取到的节点列表复制一份作为有效节点列表,定时检测有效节点列表中的节点是否还有效,若无效则从有效节点列表中删除该节点。
调用提供者时,ribbon先从本地缓存的该服务可用节点列表中获取一个节点,如果列表中没有可用的节点,再从注册中心更新该服务的节点列表。
ribbon内置的负载均衡策略(7种)
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
@EnableEurekaClient
public 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();
//获取目标服务的所有节点,可能包含无效节点
List<Server> allList = loadBalancer.getAllServers();
//获取目标服务的所有可用节点
List<Server> upList = loadBalancer.getReachableServers();
for (Server server : upList) {
//有效且空闲
if (server.isAlive() && server.isReadyToServe()) {
return server;
}
}
return null;
}
}
配置方式和内置策略的配置方式相同。
相比于eureka,nacos提供了更多的属性、方法,用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()方法 */
@Slf4j
public 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 {
//通过被调服务的名称筛选出所有健康的被调服务
List<Instance> allInstance = namingService.selectInstances(name, true);
if (CollectionUtils.isEmpty(allInstance)){
log.error("注册中心没有可用的{}服务实例!",name);
return null;
}
//过滤出与当前服务在同一集群中的被调服务
List<Instance> 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<Instance> hosts){
return Balancer.getHostByRandomWeight(hosts);
}
}
nacos做注册中心之调用指定版本的服务
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()方法 */
@Slf4j
public 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());
//获取目标服务版本
Map<String, String> metadata = discoveryProperties.getMetadata();
String targetVersion = metadata.get("targetVersion");
//获取被调服务的负载均衡器
BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
//被调服务的名称
String name = loadBalancer.getName();
try {
//通过被调服务的名称筛选出所有健康的被调服务
List<Instance> allInstance = namingService.selectInstances(name, true);
if (CollectionUtils.isEmpty(allInstance)){
log.error("注册中心没有可用的{}服务实例!",name);
return null;
}
//过滤出指定版本的被调服务实例
List<Instance> 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<Instance> hosts){
return Balancer.getHostByRandomWeight(hosts);
}
}
ribbon的饥饿加载
ribbon从注册中心获取提供者节点列表时,默认使用懒加载:第一次调用服务提供者时,才会从注册中心加载该服务提供者的节点列表,初次调用某个服务有点慢。
可以设置为饥饿加载,在应用(消费者)启动时就加载服务提供者节点列表
ribbon:
eager-load:
#开启饥饿加载。默认false
enabled: true
#指定要饥饿加载哪些服务提供者,其余提供者仍使用懒加载方式
clients: order-server,msg-server
还没有评论,来说两句吧...