Spring Cloud 使用 Ribbon 来实现客户端负载均衡
文章目录
Spring cloud 使用 Ribbon 来实现客户端负载均衡
前言
在Spring cloud 中当统一类型多个服务开始注册到服务注册中心中,次数服务即是集群 消费端(客户端)消费的时候需要进行选择调用服务。
服务注册 点击
Spring cloud 中默认调用是依赖eureka 的 ribbon 来实现客户端负载均衡策略
负载均衡
负载均衡⼀般分为服务器
端负载均衡和客户端
负载均衡
所谓服务器端负载均衡
,⽐如Nginx
、F5
这些,请求到达服务器之后由这些负载均衡
器根据⼀定的算法将请求路由到⽬标服务器处理。
所谓客户端负载均衡
,⽐如我们要说的Ribbon
,服务消费者客户端会有⼀个服务器
地址列表,调⽤⽅在请求前通过⼀定的负载均衡算法选择⼀个服务器进⾏访问,负
载均衡算法的执⾏是在请求客户端进⾏。
Ribbon是Netflix发布的负载均衡器。Eureka⼀般配合Ribbon进⾏使⽤,Ribbon利 ⽤从Eureka中读取到服务信息,在调⽤服务提供者提供的服务时,会根据⼀定的算 法进⾏负载。
我们先来注册两个服务
然后采用 ribbon 来实现负载均衡策略
ribbon依赖
ribbon 依赖eureka进行实现负载均衡,所以我们不需要导入包,默认在eureka 依赖中存在
实现负载均衡
仅仅需要在 上面增加 @LoadBalanced注解即可
@EnableDiscoveryClient
@SpringBootApplication
public class EurekaclientApplication {
//开启负载均衡
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(EurekaclientApplication.class, args);
}
}
消费调用
@RestController
public class GetUserController {
@Autowired
RestTemplate restTemplate;
//服务发现对象
@Autowired
DiscoveryClient discoveryClient;
@RequestMapping("get")
public User getUser(int id){
//user微服务的名字 用来想此服务发送请求
String servceId = "user-server";
String url = "http://" + servceId + "/getUser?id=" + id;
System.out.println(url + " = " + url);
User forObject = restTemplate.getForObject(url, User.class);
}
测试
打印每次调用的 url 和 端口
@Autowired
LoadBalancerClient loadBalancerClient;
for (int i = 0; i < 100; i++) {
ServiceInstance choose = loadBalancerClient.choose("user-server");
System.out.println("第 " + (i+1) + "次 执行 " + choose.getPort() + choose.getUri());
}
return forObject;
}
结果如下
我们可以看到每次是轮训调用,一个接着一个循环调用请求(总共2个服务)。
默认策略
ribbon自动配置中没有指定配置策略默认实现轮训策略
设置随机策略
设置负载均衡为随机策略
serviceId.ribbon.NFLoadBalancerRuleClassName=自定义的负载均衡策略类
在配置文件里面配置
user-server: # 这个配置是请求的服务名
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
或者通过硬编码方式
@Configuration
public class RibbonConfiguration {
@Bean
public IRule ribbonRule() {
// 负载均衡规则,改为随机
return new RandomRule();
}
默认情况下 配置文件大于硬编码方式
测试结果
每次请求随机访问
自定义策略
Ribbon 默认通过 IRule 接口来实现负载均衡策略,默认实现通过ZoneAvoidanceRule 轮训负载
- 结构图
另外自带的负载均衡策略
不依赖eureka实现Ribbon负载均衡
ribbon可以脱离eureka实现负载均衡如下
#取消Ribbon使用Eureka
ribbon.eureka.enabled=false
#配置Ribbon能访问 的微服务节点,多个节点用逗号隔开
user-server.ribbon.listOfServers=localhost:6869,localhost:6870
最后可能会遇到的问题
可能会报错
springcloud ribbon实现负载均衡的时候,提示Request URI does not contain a valid hostname: http://PRODUCT_SERVICE/
原因是因为服务名不能 有 下划线
代码地址 传送门
Ribbon默认负载均衡源码解析
从自动装配开始
SpringClientFactory 类中默认构造方法
如果配置文件没配置则默认轮训负载均衡
LoadBalancerAutoConfiguration 类
可以看到在使用RestTemplate的时候生效
可以看到给每一个的
注⼊resttemplate对象到集合待⽤,注⼊resttemplate定制器
然后给每一个注⼊resttemplate定制器添加一个拦截器
然后LoadBalancerInterceptor拦截器
可以看到 获取服务id
进入execute方法可以看到
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
Server server = getServer(loadBalancer, hint);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
//将服务封装成一个RibbonServer 对象
RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
serviceId), serverIntrospector(serviceId).getMetadata(server));
return execute(serviceId, ribbonServer, request);
}
Server server = getServer(loadBalancer, hint);
选择服务
具体代码在ZoneAwareLoadBalancer
类#chooseServer方法
中
@Override
public Server chooseServer(Object key) {
//一个服务直接走这个
if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
logger.debug("Zone aware logic disabled or there is only one zone");
return super.chooseServer(key);
}
Server server = null;
try {
LoadBalancerStats lbStats = getLoadBalancerStats();
Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
logger.debug("Zone snapshots: {}", zoneSnapshot);
if (triggeringLoad == null) {
triggeringLoad = DynamicPropertyFactory.getInstance().getDoubleProperty(
"ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".triggeringLoadPerServerThreshold", 0.2d);
}
if (triggeringBlackoutPercentage == null) {
triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty(
"ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".avoidZoneWithBlackoutPercetage", 0.99999d);
}
Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
logger.debug("Available zones: {}", availableZones);
if (availableZones != null && availableZones.size() < zoneSnapshot.keySet().size()) {
String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
logger.debug("Zone chosen: {}", zone);
if (zone != null) {
BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
server = zoneLoadBalancer.chooseServer(key);
}
}
} catch (Exception e) {
logger.error("Error choosing server using zone aware logic for load balancer={}", name, e);
}
if (server != null) {
return server;
} else {
logger.debug("Zone avoidance logic is not invoked.");
return super.chooseServer(key);
}
}
…往下看不懂了… 以后再看
LoadBalancerRetryProperties类
可以看到默认是生效的
转载自:https://juejin.cn/post/7231801234738233399