三、负载均衡算法
3.1、轮询算法思路
轮训的算法 怎么去实现 两台机器 A B A B A B 代码实现轮训的算法 List<机器> 请求次数 int index = 1 % size list.get(index); % 取模 取余好处是一个周期函数 让得到的结果 总是小于 除数的 1 / 2 1 % 2 1%2=1 2%2=0 3%2=1 4%2=0 全局顶一个int i = 0 i++ 线程不安全的 i % size 怎么能做一个线程安全的轮训算法 加锁 效率极低 CAS 自旋锁 没有线程的等待和唤醒的开销 CAS 优点 性能好 java层面无锁的状态 但是在jvm层面 有锁的cmpxchg CAS 缺点 会导致短暂时间内 CPU 飙升 还有ABA 问题
3.2、实战—项目中选择负载均衡算法
方式一:指定某个服务使用轮询算法(配置文件)
application.yaml:指定某个服务来使用对应的
# 访问不用的服务可以使用不用的算法规则 provider: # 先写服务提供者的应用名称 ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #几种算法的全限定类名
检验:在BaseLoadBalancer类中的chooseServer()中的下列行打上断点即可
方式二:全局配置负载均衡算法(注入bean方式)
在配置类中手动注入某个负载均衡算法:
//手动注入一个负载均衡器 @Bean public IRule loadBalanced() { return new RandomRule(); }
方式三:自己实现一个负载均衡算法
package com.changlu.ribbonconsumer.config;
import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.Server; /** * @Description: 我的自定义负载均衡器 * @Author: changlu * @Date: 10:21 PM */ public class MyRule implements IRule { @Override public Server choose(Object key) { return null; } @Override public void setLoadBalancer(ILoadBalancer lb) { } @Override public ILoadBalancer getLoadBalancer() { return null; } }
实现好了自定义的负载均衡算法后,我们使用之前的方式一、方式二即可。
四、负载均衡源码分析
入口从下面的choose()开始:
//方式二:使用ribbon提供的客户端来进行负载均衡获取服务 @Autowired private LoadBalancerClient loadBalancerClient; @GetMapping("/testRibbon2") public String testRibbon2(String serviceId) { //根据服务来进行选择一个实例 ServiceInstance choose = loadBalancerClient.choose(serviceId); return choose.toString(); }
RibbonLoadBalancerClient.java:
public ServiceInstance choose(String serviceId) { //1、走this.choose return this.choose(serviceId, (Object)null); } public ServiceInstance choose(String serviceId, Object hint) { //2、走getServer Server server = this.getServer(this.getLoadBalancer(serviceId), hint); return server == null ? null : new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server)); } protected Server getServer(ILoadBalancer loadBalancer, Object hint) { //3、走loadBalancer.chooseServer【=> ZoneAwareLoadBalancer】 return loadBalancer == null ? null : loadBalancer.chooseServer(hint != null ? hint : "default"); }
ZoneAwareLoadBalancer.java:
public Server chooseServer(Object key) { if (ENABLED.get() && this.getLoadBalancerStats().getAvailableZones().size() > 1) { ... } else { logger.debug("Zone aware logic disabled or there is only one zone"); //走这里的选择服务器(key=default) return super.chooseServer(key); } }
BaseLoadBalancer.java:这里实际上会进行使用当前实例的rule规则来进行负载均衡
public Server chooseServer(Object key) { if (this.counter == null) { this.counter = this.createCounter(); } this.counter.increment(); if (this.rule == null) { return null; } else { try { //根据对应的rule规则来进行选择key //当前this.rule => ZoneAvoidancePredicate return this.rule.choose(key); } catch (Exception var3) { logger.warn("LoadBalancer [{}]: Error choosing server for key {}", new Object[]{this.name, key, var3}); return null; } } }
ZoneAvoidanceRule#(其父类为:PredicateBasedRule,choose在其父类方法中)
注意默认走的是这个ZoneAvoidanceRule规则。
PredicateBasedRule.java:
public Server choose(Object key) { ILoadBalancer lb = this.getLoadBalancer(); //走这个chooseRoundRobinAfterFiltering Optional<Server> server = this.getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key); return server.isPresent() ? (Server)server.get() : null; }
AbstractServerPredicate.java:
public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) { List<Server> eligible = this.getEligibleServers(servers, loadBalancerKey); //走其中的this.incrementAndGetModulo() return eligible.size() == 0 ? Optional.absent() : Optional.of(eligible.get(this.incrementAndGetModulo(eligible.size()))); } //就是一个cas轮询操作 //modulo = 2(2.3中项目) private int incrementAndGetModulo(int modulo) { for (;;) { int current = nextIndex.get(); int next = (current + 1) % modulo; if (nextIndex.compareAndSet(current, next) && current < modulo) return current; } }
五、ribbon负载均衡详细配置
ribbon: eager-load: enabled: false # ribbon它只有自己的话 能不能做服务发现 借助eureka # ribbon需要去eureka中获取服务列表 如果false就懒加载 eureka: enabled: true # 开启eureka支持 # 若是想要选择其他网络请求工具,可以来进行下面选择配置 http: # 我们使用ribbon 用的restTemplate发请求 java.net.HttpUrlConnection 发的请求 很方便 但是它不支持连接池 client: # 发请求的工具有很多 httpClient 它支持连接池 效率更好 如果你想改请求的工具 记得加这个依赖即可 enabled: false okhttp: # 这个也是请求工具 移动端用的比较多 轻量级的请求 enabled: false
六、Ribbon总结
可以使用Eureka+Ribbon来做分布式项目!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hQ9okMP1-1657598291528)(C:\Users\93997\AppData\Roaming\Typora\typora-user-images\image-20220705223200391.png)]
Ribbon 是客户端实现负载均衡的远程调用组件,用法简单 Ribbon 源码核心: ILoadBalancer 接口:起到承上启下的作用 1. 承上:从 eureka 拉取服务列表 2. 启下:使用 IRule 算法实现客户端调用的负载均衡
设计思想:
每一个服务提供者都有自己的 ILoadBalancer userService---》客户端有自己的 ILoadBalancer TeacherService---》客户端有自己的 ILoadBalancer 在客户端里面就是 Map<String,ILoadBalancer> iLoadBalancers Map<String,ILoadBalancer> iLoadBalancers 消费者端 服务提供者的名称 value (服务列表 算法规则 )
如何实现负载均衡的呢?
iloadBalancer loadbalance = iloadBalancers.get(“user-service”) List<Server> servers = Loadbalance.getReachableServers();//缓存起来 Server server = loadbalance .chooseServer(key) //key 是区 id,--》IRule 算法 chooseServer 下面有一个 IRule 算法