Ribbon
在微服务架构中,通常服务注册中心以及服务提供者大都是集群部署,当客户端与众多微服务交互过程中,如何保证服务的高可用性(避免出现某些服务出现请求过载,某些服务比较"闲"),也就是常说的负载均衡,而Ribbon就是在客户端实现软负载均衡的一种组件,通常与Eureka结合使用。简单总结就是RestTemplate+负载均衡
Ribbon负载均衡和Rest调用
这里我事先准备两个订单微服务和一个消费者服务并集群注册到Eureka集群
在消费者服务中通过RestTemplate远程请求两个订单微服务并实现负载均衡
- RestTemplate配置
/**
* @Description RestTemplate配置类
* @Author Fangchenjiang
* @Date 2021/4/3 14:53
*/
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
其中标有@LoadBalanced注解,说明该RestTemplate客户端默认采用轮训的负载均衡算法,当消费者端通过Rest远程访问服务提供者,就能看到负载均衡的效果
/**
* @Description 消费者COntroller
* @Author Fangchenjiang
* @Date 2021/4/3 23:03
*/
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
private static String URL="http://xf-order/";
@Autowired
RestTemplate restTemplate;
@GetMapping("/getById/{id}")
public String getOrderByID(@PathVariable("id")Long id){
String object = restTemplate.getForObject(URL + "/order/getPay/"+id, String.class);
return object;
}
}
负载均衡核心组件
在NetFlix提供的Ribbon中,其中实现的核心组件是com.netflix.loadbalancer
包下的IRule接口,该接口有多个实现类,每个实现类就对应着具体的负载均衡算法,常用的有比如有轮训,随机,重试,权重,对应实现类如下:
若需要去掉默认的轮训规则,只需要新建一个自定义配置类,构造返回具体的实现规则即可。注意有一点,这个替换规则配置类不能与SpringBoot在同一个@Component扫描包以及其子包下,配置类如下:
/**
* @Description Ribbon规则替换类
* @Author Fangchenjiang
* @Date 2021/4/4 17:19
*/
@Configuration
public class MyRule {
@Bean
public IRule iRule(){
//替换成随机算法
return new RandomRule();
}
}
接着在客户端启动类中加上Ribbon客户端配置
/**
* @Description 消费者客户端
* @Author Fangchenjiang
* @Date 2021/4/3 22:51
*/
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(value = "xf-order",configuration = MyRule.class) //指定具体的负载均衡替换类和key
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class,args);
}
}
当客户端发起Rest请求,就可以看到由原来的的轮训算法替换成了随机算法
负载均衡轮训算法原理
原理:Reset接口第几次请求%服务器集群总数=实际调用的服务器位置下标,每次服务器重启接口默认又从第一次请求开始计数,而远程服务器集群实例是保存在一个List中,每次通过模运算获取到下标index后,通过list.get(index)来作用到具体的服务器。RoundRobinRule源码如下:
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;
}
}
}
//通过CAS获取合适的数值
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;
}
手写负载均衡轮训算法
根据上面了解到负载均衡轮训原理,现在我们参照上面的源码手写一遍具体实现过程:
- 自定义一个LoadBalancer
public interface XfLoadBalancer {
ServiceInstance getInstance(List<ServiceInstance>serviceInstanceList);
}
- 具体实现
/**
* @Description 自定义负载均衡类
* @Author Fangchenjiang
* @Date 2021/4/4 18:47
*/
@Component
public class MyLB implements XfLoadBalancer{
@Autowired
DiscoveryClient discoveryClient;
public AtomicInteger serverCount=new AtomicInteger(0);
@Override
public ServiceInstance getInstance(List<ServiceInstance> serviceInstanceList) {
int index=getNextIndex()%serviceInstanceList.size();
return serviceInstanceList.get(index);
}
public final int getNextIndex(){
int current;
int next;
do{
current=this.serverCount.get();
next=current>=2147483167?0:current+1;
}while (!this.serverCount.compareAndSet(current,next));
System.out.println("第"+next+"几次访问");
return next;
}
}
- Rest调用
/**
* @Description 消费者COntroller
* @Author Fangchenjiang
* @Date 2021/4/3 23:03
*/
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
DiscoveryClient discoveryClient;
@Autowired
MyLB myLB;
private static String URL="http://xf-order/";
@Autowired
RestTemplate restTemplate;
@GetMapping("/getById/{id}")
public String getOrderByID(@PathVariable("id")Long id){
List<ServiceInstance> instances = discoveryClient.getInstances("xf-order");
ServiceInstance instance = myLB.getInstance(instances);
URI uri = instance.getUri();
String url=uri+"/order/getPay/"+id;
String object = restTemplate.getForObject(url, String.class);
return object;
}
}
如果在浏览器请求消费者接口,能看到负载均衡效果,说明我们成功的实现了轮训的实现过程
总结
Ribbon是客户端实现负载均衡的一种方式,具体到其里面的IRule组件,提供不同的实现类,也就对应着不同的负载均衡算法。其中我们由Eureka集群到服务端集群搭建,以及到轮训的原理。通过这一次过程,相信应该对Ribbon有了更深刻的认识以及负载均衡的运用场景。