目录
配套资料,免费下载
链接:https://pan.baidu.com/s/1la_3-HW-UvliDRJzfBcP_w
提取码:lxfx
复制这段内容后打开百度网盘手机App,操作更方便哦
第一章 Ribbon介绍
1.1、什么是Ribbon
Ribbon 是一个基于 HTTP 和 TCP 的客服端负载均衡工具,它是基于 Netflix Ribbon 实现的。它不像 Spring Cloud 服务注册中心、配置中心、API 网关那样独立部署,但是它几乎存在于每个 Spring Cloud 微服务中。包括 Feign 提供的声明式服务调用也是基于该 Ribbon 实现的。
Ribbon 默认提供很多种负载均衡算法,例如轮询、随机等等,甚至包含自定义的负载均衡算法。
1.2、为啥用Ribbon
Ribbon 提供了一套微服务的负载均衡解决方案。目前业界主流的负载均衡方案可分成两类:
- 集中式负载均衡(服务器负载均衡),即在 consumer 和 provider 之间使用独立的负载均衡设施(可以是硬件,如 F5,也可以是软件,如 nginx),由该设施负责把访问请求通过某种策略转发至 provider;
- 进程内负载均衡(客户端负载均衡),将负载均衡逻辑集成到 consumer,consumer 从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的 provider。Ribbon 属于后者,它只是一个类库,集成于 consumer 进程,consumer 通过它来获取 provider 的地址。
第二章 Ribbon入门案例
2.1、项目准备
我们接下来的所有操作均是在Eureka
最后完成的工程上进行操作,相关代码请到配套资料中寻找。
2.2、添加服务提供者
既然我们要做服务的负载均衡,那肯定需要有很多服务,一个服务再怎么负载均衡还是他自己,因此,我们需要在原工程的基础上在添加一个服务提供者,配置基本和service-provider8001
差不多,只不过端口不一样而已,接下来会给出具体不一样的配置。
(1)修改启动类为ServiceProvider8002Application
@EnableEurekaClient
@SpringBootApplication
public class ServiceProvider8002Application {
public static void main(String[] args) {
SpringApplication.run(ServiceProvider8002Application.class);
}
}
(2)修改配置文件application.yaml
server:
port: 8002
spring:
application:
#该名称在集群模式下应该保持一致
name: service-provider
#度量指标监控与健康检查
management:
endpoints:
web:
exposure:
#开启 shutdown 端点访问
include: shutdown
endpoint:
#开启 shutdown 实现优雅停服
shutdown:
enabled: true
eureka:
instance:
#是否使用 ip 地址注册
prefer-ip-address: true
#该实例注册到服务中心的唯一ID
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
#设置服务注册中心地址
service-url:
defaultZone: http://root:123456@eureka-server7001.com:7001/eureka/,http://root:123456@eureka-server7002.com:7002/eureka/
(3)分别修改service-provider8001
和service-provider8002
的控制层ProductController
代码,都加入一段新的控制器方法,代码如下:
service-provider8001的ProductController
@RequestMapping("/provider/product/findByPid")
public String findByPid(@RequestParam("pid") Integer pid) {
return "8001 findByPid ...";
}
service-provider8002的ProductController
@RequestMapping("/provider/product/findByPid")
public String findByPid(@RequestParam("pid") Integer pid) {
return "8002 findByPid ...";
}
(4)启动注册中心
- 启动eureka-server7001
- 启动eureka-server7002
(5)启动服务提供者
- 启动service-provider8001
- 启动service-provider8002
(6)查看服务是否注册到注册中心,登录账户为root,登录密码为123456
在浏览器打开地址:http://localhost:7001/
2.3、修改服务消费者
2.3.1、方式一:LoadBalancerClient
(1)修改service-consumer9001
的ProductController
控制器代码,在里边新增以下代码
// Ribbon 负载均衡器
// import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
@Autowired
private LoadBalancerClient loadBalancerClient;
@RequestMapping("/consumer/product/findByPid")
public String findByPid(@RequestParam("pid") Integer pid) {
//从注册中心获取一个SERVICE-PROVIDER服务的实例对象
ServiceInstance si = loadBalancerClient.choose("SERVICE-PROVIDER");
if (si == null) {
return null;
}
String findByPidUrl = si.getUri() + "/provider/product/findByPid" + "?pid=" + pid;
String product = restTemplate.getForObject(findByPidUrl, String.class);
return product;
//这里只是做模拟返回了一个字符串,当然你也可以返回一个商品对象,这取决于你
}
(2)启动service-consumer9001
,然后打开浏览器输入地址访问:http://localhost:9001/consumer/product/findByPid?pid=1
2.3.2、方式二:@LoadBalanced
(1)修改service-consumer9001
的ServiceConsumer9001Application
启动类代码,在里边新增以下代码
@EnableEurekaClient
@SpringBootApplication
public class ServiceConsumer9001Application {
public static void main(String[] args) {
SpringApplication.run(ServiceConsumer9001Application.class);
}
@Bean
@LoadBalanced // Ribbbon 负载均衡注解,加上这个注解,restTemplate默认就有负载均衡的能力了
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
(2)修改service-consumer9001
的ProductController
控制器代码,在里边新增以下代码
@RequestMapping("/consumer/product/findByPid2")
public String findByPid2(@RequestParam("pid") Integer pid) {
String findByPidUrl = "http://SERVICE-PROVIDER/provider/product/findByPid" + "?pid=" + pid;
String product = restTemplate.getForObject(findByPidUrl, String.class);
System.out.println(product);
return product;
}
(3)重启service-consumer9001
,然后打开浏览器输入地址访问:http://localhost:9001/consumer/product/findByPid2?pid=1
第三章 Ribbon负载均衡策略
3.1、轮询策略(默认)
策略对应类名:RoundRobinRule
实现原理:轮询策略表示每次都顺序取下一个 provider,比如一共有 5 个 provider,第 1 次取第 1 个,第 2 次取第 2 个,第 3 次取第 3 个,以此类推。
3.2、权重轮询策略
策略对应类名:WeightedResponseTimeRule
实现原理:
- 根据每个 provider 的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性越低。
- 原理:一开始为轮询策略,并开启一个计时器,每 30 秒收集一次每个 provider 的平均响应时间,当信息足够时,给每个 provider 附上一个权重,并按权重随机选择 provider,高权越重的 provider 会被高概率选中。
3.3、随机策略
策略对应类名:RandomRule
实现原理:从 provider 列表中随机选择一个。
3.4、最少并发数策略
策略对应类名:BestAvailableRule
实现原理:选择正在请求中的并发数最小的 provider,除非这个 provider 在熔断中。
3.5、重试策略
策略对应类名:RetryRule
实现原理:其实就是轮询策略的增强版,轮询策略服务不可用时不做处理,重试策略服务不可用时会重新尝试集群中的其他节点。
3.6、可用性敏感策略
策略对应类名:AvailabilityFilteringRule
实现原理:过滤性能差的 provider
- 第一种:过滤掉在 Eureka 中处于一直连接失败的 provider。
- 第二种:过滤掉高并发(繁忙)的 provider。
3.7、区域敏感性策略
策略对应类名:ZoneAvoidanceRule
实现原理:
- 以一个区域为单位考察可用性,对于不可用的区域整个丢弃,从剩下区域中选可用的 provider。
- 如果这个 ip 区域内有一个或多个实例不可达或响应变慢,都会降低该 ip 区域内其他 ip 被选中的权
重。
第四章 Ribbon负载均衡设置
4.1、全局替换
在启动类或配置类中注入负载均衡策略对象,所有服务请求均使用该策略。
@EnableEurekaClient
@SpringBootApplication
public class ServiceConsumer9001Application {
public static void main(String[] args) {
SpringApplication.run(ServiceConsumer9001Application.class);
}
@Bean
@LoadBalanced // Ribbbon 负载均衡注解,加上这个注解,restTemplate默认就有负载均衡的能力了
public RestTemplate restTemplate(){
return new RestTemplate();
}
@Bean
public RandomRule randomRule() {
return new RandomRule();
}
}
重启消费者然后多次访问:http://localhost:9001/consumer/product/findByPid2?pid=1,结果如下:
4.2、局部替换
请注释掉刚才的全局设置,避免影响结果,修改配置文件指定服务的负载均衡策略。格式:服务应用名.ribbon.NFLoadBalancerRuleClassName
# 负载均衡策略
# service-provider 为调用的服务的名称
service-provider:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
重启消费者然后多次访问:http://localhost:9001/consumer/product/findByPid2?pid=1,结果如下:
第五章 Ribbon手写负载均衡
5.1、负载均衡算法
5.2、轮询策略源码
核心:自旋锁,private int incrementAndGetModulo(int modulo)
方法用到了自旋锁
定义:
自旋锁(spin lock)是一种非阻塞锁,也就是说,如果某线程需要获取锁,但该锁已经被其他线程占用时,该线程不会被挂起,而是在不断的消耗CPU的时间,不停的试图获取锁。线程反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种忙等待。
作用:
自旋锁避免了进程上下文的调度开销。线程一直处于用户态,减少了用户态到内核态的开销与损耗(减少了上下文切换)。
适用场景:
1、多线程
2、使用者占有锁的时间短
轮询源码:
public class RoundRobinRule extends AbstractLoadBalancerRule {
private AtomicInteger nextServerCyclicCounter;
private static final boolean AVAILABLE_ONLY_SERVERS = true;
private static final boolean ALL_SERVERS = false;
private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);
public RoundRobinRule() {
this.nextServerCyclicCounter = new AtomicInteger(0);
}
public RoundRobinRule(ILoadBalancer lb) {
this();
this.setLoadBalancer(lb);
}
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
log.warn("no load balancer");
return null;
} else {
Server server = null;
int count = 0;
while(true) {
if (server == null && count++ < 10) {
List<Server> reachableServers = lb.getReachableServers();
List<Server> allServers = lb.getAllServers();
int upCount = reachableServers.size();
int serverCount = allServers.size();
if (upCount != 0 && serverCount != 0) {
int nextServerIndex = this.incrementAndGetModulo(serverCount);
server = (Server)allServers.get(nextServerIndex);
if (server == null) {
Thread.yield();
} else {
if (server.isAlive() && server.isReadyToServe()) {
return server;
}
server = null;
}
continue;
}
log.warn("No up servers available from load balancer: " + lb);
return null;
}
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: " + lb);
}
return server;
}
}
}
private int incrementAndGetModulo(int modulo) {
int current;
int next;
do {
current = this.nextServerCyclicCounter.get();
next = (current + 1) % modulo;
} while(!this.nextServerCyclicCounter.compareAndSet(current, next));
return next;
}
public Server choose(Object key) {
return this.choose(this.getLoadBalancer(), key);
}
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
5.3、手写负载均衡
(1)创建一个插件包名字叫:com.caochenlei.plugin
(2)在这个插件包中新建类:MyLoadBalanced
(3)手写负载均衡器来替代默认的负载均衡器注解@LoadBalanced
,源码如下:
@Component
public class MyLoadBalanced {
private AtomicInteger atomicInteger = new AtomicInteger(0);
//获取服务提供者实例下标
public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
int index = getAndIncrement() % serviceInstances.size();
return serviceInstances.get(index);
}
//使用自旋锁进行累加操作
private final int getAndIncrement() {
int current;
int next;
do {
current = this.atomicInteger.get();
next = current >= Integer.MAX_VALUE ? 0 : current + 1;
//第一个参数是期望值,第二个参数是修改值
} while (!this.atomicInteger.compareAndSet(current, next));
System.out.println("访问次数:" + next);
return next;
}
}
(4)在控制器类ProductController
中新增如下测试代码:
@Autowired
private MyLoadBalanced loadBalanced;
@RequestMapping("/consumer/product/findByPid3")
public String findByPid3(@RequestParam("pid") Integer pid) {
//获取注册中心服务列表
List<ServiceInstance> instances = discoveryClient.getInstances("SERVICE-PROVIDER");
if (CollectionUtils.isEmpty(instances)) {
return null;
}
//使用自定义负载均衡器
ServiceInstance si = loadBalanced.instances(instances);
String findByPidUrl = si.getUri() + "/provider/product/findByPid" + "?pid=" + pid;
String product = restTemplate.getForObject(findByPidUrl, String.class);
System.out.println(product);
return product;
}
(5)注释配置文件中配置的随机策略
# 负载均衡策略
# SERVICE-PROVIDER 为调用的服务的名称
#SERVICE-PROVIDER:
# ribbon:
# NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
(6)注释Ribbon默认的负载均衡器注解
@EnableEurekaClient
@SpringBootApplication
public class ServiceConsumer9001Application {
public static void main(String[] args) {
SpringApplication.run(ServiceConsumer9001Application.class);
}
@Bean
//@LoadBalanced // Ribbbon 负载均衡注解,加上这个注解,restTemplate默认就有负载均衡的能力了
public RestTemplate restTemplate(){
return new RestTemplate();
}
// @Bean
// public RandomRule randomRule() {
// return new RandomRule();
// }
}
5.4、测试最终效果
重启消费者然后多次访问:http://localhost:9001/consumer/product/findByPid3?pid=1,结果如下:
注意:此时如果你访问http://localhost:9001/consumer/product/findByPid2?pid=1,则会报错,因为这个方法中使用的是默认的负载均衡器,刚才我们已经注释掉了。