具体代码如下:
package com.springcloudtest.order.config;
import org.springframework.cloud.client.ServiceInstance;
import java.util.List;
public interface LoadBalance {
ServiceInstance instances(List<ServiceInstance> serviceInstanceList);
}
该接口有两个作用:
1.对外暴露,当需要使用负载均衡功能时,可以调用该接口的 instances方法,传入微服务实例List,
返回一个经过负载均衡算法选择后的具体实例
2.进行不同负载均衡算法的实现,当需要实现其他类型的负载均衡算法时,可以实现该接口,然后再具体
写出负载均衡算法的实现
package com.springcloudtest.order.config;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@Component
public class MyLoadBalance implements LoadBalance {
public ServiceInstance instances(List<ServiceInstance> serviceInstanceList) {
return null;
}
}
注意:
注意@Component,需要把该类注入到IOC容器中,以便后续的@Resource获取
package com.springcloudtest.order.config;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@Component
public class MyLoadBalance implements LoadBalance {
//设置原子Integer,用于保证服务在调用时的线程安全问题
private AtomicInteger atomicInteger = new AtomicInteger(0);
/**
* 通过CAS +自旋锁的方式 返回一个调用该微服务的次数
* @return
*/
public final int getServiceCallSubscript(){
//当前值
int current;
//修改值
int next;
//自旋锁
do{
//获取当前值
current = this.atomicInteger.get();
next = current >=Integer.MAX_VALUE ? 0 :current+1;
//CAS 比较并替换 ,如果满足该条件则结束循环,不满足该条件继续自旋
}while (!this.atomicInteger.compareAndSet(current,next));
System.out.println("next:--------"+next);
return next;
}
public ServiceInstance instances(List<ServiceInstance> serviceInstanceList) {
return null;
}
}
采用CAS+自旋锁的原因:由于负载均衡使用的场景是高并发,而轮询算法的核心是得到一个整数型的下标,在高并发的场景下,需要保证该下标的数据一致性,CAS可以保证该下标的数据一致性,自旋锁可以使该次请求不断的访问重试直到成功为止,CAS+自旋锁的原因是可以在高效率和安全性的情况下保证微服务的高可用。
——————————————————————————————————————
//设置原子Integer,用于保证服务在调用时的线程安全问题
private AtomicInteger atomicInteger = new AtomicInteger(0);
采用AtomicInteger 的原因:
由于该下标是共享变量,用于保证对该下标进行操作时的原子性和可见性,让每一个线程都可以访问到该
下标的值,是使用CAS的前提
——————————————————————————————————————
this.atomicInteger.compareAndSet(current,next)
CAS操作
comapreAndSet底层采用了comapreAndSwap,比较并替换是一个在硬件方面上实现的原子性操作把,
比较并替换变成了一步操作(可以理解成相当于加了一个锁),这样就可以保证在多线程的情况下,该下标只能
被一个线程比较并替换,这样就保证了共享变量的安全性
——————————————————————————————————————
do{
//获取当前值
current = this.atomicInteger.get();
next = current >=Integer.MAX_VALUE ? 0 :current+1;
//CAS 比较并替换 ,如果满足该条件则结束循环,不满足该条件继续自旋
}while (!this.atomicInteger.compareAndSet(current,next));
System.out.println("next:--------"+next);
return next;
}
自旋锁:
自旋锁的实现方式有很多种,这里采用了do....while循环,这里自旋锁的作用是,当某一个线程不满足CAS
操作时,就会继续进行比较并替换的操作直到成功为止,自旋锁不是重量级锁,他可以提高多线程情况下效率
current = this.atomicInteger.get();
current是获取当前下标的值,用于CAS的比较
next = current >=Integer.MAX_VALUE ? 0 :current+1;
next的赋值才用了三目运算符,如果该服务调用的次数超过了整型的最大值就重置为0,否则就加一,其目的是修改当前下标的值
public ServiceInstance instances(List<ServiceInstance> serviceInstanceList) {
//采用取余的方式获取下标
int index = getServiceCallSubscript()%serviceInstanceList.size();
// 获取 serviceInstanceList中该下标对应的ServiceInstance对象
return serviceInstanceList.get(index);
}
采用取余法获取需要调用的微服务实例下标:
公式: index = current % 微服务实例个数
index 是 List 中的下标
current 是通过轮询算法得到的结果
微服务实例个数 是 List的长度
最后再返回通过该下标获取到的实例
package com.springcloudtest.order.controller;
import com.springcloudtest.order.config.LoadBalance;
import com.springcloudtest.order.config.MyLoadBalance;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Controller;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.net.URI;
import java.util.List;
@Controller
public class TestController {
@Value("${server.port}")
private String serverPort;
@Resource
private LoadBalance myLoadBalance;
@Resource
private DiscoveryClient discoveryClient;
@Resource
private RestTemplate restTemplate;
@RequestMapping("/order/myLoadBalance")
@ResponseBody
public String myLoadBalance(){
//获取 名称为payment-springcloud的微服务实例List
List<ServiceInstance> serviceInstanceList = discoveryClient.getInstances("payment-springcloud");
//如果该微服务List为空,说明该微服务没有可用的实例,返回null
if(CollectionUtils.isEmpty(serviceInstanceList)){
return null;
}
//把该List传入自定义负载均衡类中,获取出调用的微服务实例
ServiceInstance serviceInstance = myLoadBalance.instances(serviceInstanceList);
//获取该实例的访问地址
URI uri = serviceInstance.getUri();
return restTemplate.postForObject(uri+"/payment/consul",null,String.class);
}
}
具体步骤:
1.获取payment-springcloud该微服务名称的微服务List
2.调用上面自定义的负载均衡轮询类中的instances方法,获取出调用的微服务实例
3.获取该实例中的URI地址,使用restTemplate进行服务调用,即可实现负载均衡
注意:
1.想要获取discoveryClient这个对象,必须要在application启动类中加入@EnableDiscoveryClient注解
2.想要获取restTemplate这个对象,必须要先注入restTemplate该对象