目录
LB(负载均衡)Ribbon :负载均衡+RestTemplate调用
恢复eureka集群环境,并启动,以便接下来的练习。
检查环境:eureka7001.com:7001
简介
![](https://i-blog.csdnimg.cn/blog_migrate/4008e708e793f6ef74a277c75ed637ab.png)
官网:https://github.com/Netflix/ribbon/wiki/Getting-Started
Ribbon目前也进入维护模式
未来替换方案:
Ribbon能干什么?
LB(负载均衡)
![](https://i-blog.csdnimg.cn/blog_migrate/4aabc254fe367295257232e97c901232.png)
![](https://i-blog.csdnimg.cn/blog_migrate/91e2a27059473b846686e1f6fe2896a5.png)
![](https://i-blog.csdnimg.cn/blog_migrate/9f190df271be4656a890bc51f10f20f1.png)
Ribbon :负载均衡+RestTemplate调用
Ribbon负载均衡演示
Ribbon是客户端(消费者)负载均衡的工具。
总结:Ribbon其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。
spring-cloud-starter-netflix-eureka-client自带了spring-cloud-starter-netflix-ribbon
也可以单独加依赖
<!--Ribbon的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
RestTemplate的使用
官网:RestTemplate (Spring Framework 5.2.2.RELEASE API)
getForObject方法/getForEntity方法
postForObject/postForEntity
GET请求方法 - 测试getForEntity方法
在消费者cloud-consumer-order80的OrderController方法中添加:
@GetMapping("/consumer/payment/getForEntity/{id}")
public CommonResult<Payment> getPayment2(@PathVariable("id") Long id){
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
//getStatusCode获取状态码,is2xxSuccessful如果是状态码是2xx
if(entity.getStatusCode().is2xxSuccessful()){
//返回body
return entity.getBody();
}else{
return new CommonResult<>(444, "操作失败");
}
}
测试:
POST请求方法
@GetMapping("/consumer/payment/createEntity")
public CommonResult<Payment> create2(Payment payment){
log.info("********插入的数据:" + payment);
//postForObject分别有三个参数:请求地址,请求参数,返回的对象类型
// return restTemplate.postForObject(PAYMENT_URL + "/payment/create", payment, CommonResult.class);
ResponseEntity<CommonResult> entity = restTemplate.postForEntity(PAYMENT_URL + "/payment/create", payment, CommonResult.class);
if(entity.getStatusCode().is2xxSuccessful()){
return entity.getBody();
}else{
return new CommonResult<>(444, "操作失败");
}
}
输入http://localhost//consumer/payment/createEntity?serial=emily,插入一条数据。
Ribbon核心组件IRule
IRule:根据特定算法从服务列表中选取一个要访问的服务
IRule接口的实现类:(快捷键double shift 弹出search everywhere)
主要的落地实现类:
默认为RoundRobinRule轮询。
替换
1.修改cloud-consumer-order80
2.注意配置细节
所以Ribbon的自定义配置类不能放在springcloud包下,要在aurora包下再新建一个myrule包。
1.新建package com.aurora.myrule
2.在myrule包下新建MySelfRule规则类
@Configuration
public class MySelfRule {
@Bean
public IRule myRule(){
return new RandomRule(); //负载均衡机制改为随机
}
}
3.在主启动类添加@RibbonClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class
name为指定的服务名(服务名必须与注册中心显示的服务名大小写一致)
configuration为指定服务使用自定义配置(自定义负载均衡机制)
4.测试
启动80 浏览器输入:http://localhost/consumer/payment/get/31 8001 8002 随机出现 替换了默认的轮询
Ribbon负载均衡算法
原理:
![](https://i-blog.csdnimg.cn/blog_migrate/ba385b31379cc0ce14ec1c7bed45f3cc.png)
RoundRobinRule源码
RoundRobinRule的核心为choose方法:
public class RoundRobinRule extends AbstractLoadBalancerRule {
//AtomicInteger原子整形类
private AtomicInteger nextServerCyclicCounter;
...
public RoundRobinRule() {
//此时nextServerCyclicCounter是一个原子整形类,并且value为0
nextServerCyclicCounter = new AtomicInteger(0);
}
...
//ILoadBalancer选择的负载均衡机制,这里lb为轮询
public Server choose(ILoadBalancer lb, Object key) {
//如果传入的lb没有负载均衡,为空
if (lb == null) {
log.warn("no load balancer");
return null;
}
Server server = null;
int count = 0;
//还没选到执行的server,并且选择的次数没超过10次,进行选择server
while (server == null && count++ < 10) {
//lb.getReachableServers获取所有状态是up的服务实例
List<Server> reachableServers = lb.getReachableServers();
//lb.getAllServers获取所有服务实例
List<Server> allServers = lb.getAllServers();
//状态为up的服务实例的数量
int upCount = reachableServers.size();
//所有服务实例的数量
int serverCount = allServers.size();
//如果up的服务实例数量为0或者服务实例为0,打印日志log.warn并返回server=null
if ((upCount == 0) || (serverCount == 0)) {
log.warn("No up servers available from load balancer: " + lb);
return null;
}
//获取到接下来server的下标
int nextServerIndex = incrementAndGetModulo(serverCount);
//获取下一个server
server = allServers.get(nextServerIndex);
//如果
if (server == null) {
//线程让步,线程会让出CPU执行权,让自己或者其它的线程运行。(让步后,CPU的执行权也有可能又是当前线程)
Thread.yield();
//进入下次循环
continue;
}
//获取的server还活着并且还能工作,则返回该server
if (server.isAlive() && (server.isReadyToServe())) {
return (server);
}
//否则server改为空
server = null;
}
//选择次数超过10次,打印日志log.warn并返回server=null
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: "
+ lb);
}
return server;
}
private int incrementAndGetModulo(int modulo) {
//CAS加自旋锁
//CAS(Conmpare And Swap):是用于实现多线程同步的原子指令。CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
//自旋锁:是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
for (;;) {
//获取value,即0
int current = nextServerCyclicCounter.get();
//取余,为1
int next = (current + 1) % modulo;
//进行CAS判断,如果此时在value的内存地址中,如果value和current相同,则为true,返回next的值,否则就一直循环,直到结果为true
if (nextServerCyclicCounter.compareAndSet(current, next))
return next;
}
}
...
}
AtomicInteger的compareAndSet方法:
public class AtomicInteger extends Number implements java.io.Serializable {
...
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
...
}
手写一个轮询自定义配置类
1.7001/7002集群启动
2.8001和8002微服务改造
在8001和8002的PaymentController中加上这个方法,用于测试我们的自定义轮询:
@GetMapping(value = "/payment/lb")
public String getPaymentLB(){
return serverPort;
}
80订单微服务改造
1.去掉ApplicationContextConfig里restTemplate方法上的@LoadBalanced注解。
2.在springcloud包下新建lb.ILoadBalancer接口(自定义负载均衡机制(面向接口))
public interface LoadBalancer {
//传入具体实例的集合,返回选中的实例
ServiceInstance instances(List<ServiceInstance> serviceInstance);
}
3.在lb包下新建自定义ILoadBalancer接口的实现类MyLB
@Component //加入容器
public class MyLB implements LoadBalancer {
//新建一个原子整形类
private AtomicInteger atomicInteger = new AtomicInteger(0);
public final int getAndIncrement(){
int current;
int next;
do{
current = this.atomicInteger.get();
//如果current是最大值,重新计算,否则加1(防止越界)
next = current >= Integer.MAX_VALUE ? 0 : current + 1;
//进行CAS判断,如果不为true,进行自旋
}while (!this.atomicInteger.compareAndSet(current, next)); //第一个参数是期望值,第二个参数是修改值是
System.out.println("****第几次访问,次数next:" + next);
return next;
}
@Override
public ServiceInstance instances(List<ServiceInstance> serviceInstance) { //得到机器的列表
//非空判断
/* if(serviceInstance.size() <= 0){
return null;
}*/
//进行取余
int index = getAndIncrement() % serviceInstance.size(); //得到服务器的下标位置
//返回选中的服务实例
return serviceInstance.get(index);
}
}
4.OrderController
@Resource
private LoadBalancer loadBalancer;
@Resource
private DiscoveryClient discoveryClient;
@GetMapping("/consumer/payment/lb")
public String getPaymentLB(){
//获取CLOUD-PAYMENT-SERVICE服务的所有具体实例
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
if(instances == null || instances.size() <= 0){
return null;
}
ServiceInstance serviceInstance = loadBalancer.instances(instances);
URI uri = serviceInstance.getUri();
System.out.println(uri);
return restTemplate.getForObject(uri + "/payment/lb", String.class);
}
5.测试
浏览器输入:http://localhost/consumer/payment/lb