1.是什么
SpringCloud Ribbon是基于Netflix RIbbon实现的一套客户端,负载均衡的工具。简单的说,Ribbon是Netflix发布的开源项目主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置如连接超时,重试等。简单是说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如加单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的高可用。(Ribbon + RestTemplate实现远程调用以及负载均衡)
2.工作原理及架构
Ribbon其实就是一个软负载均衡的客户端组件,它可以和其他所需请求的客户端结合使用,和Eureka结合只是其中一个实例。
Ribbon在工作是分两步:
第一步:先选择EurekaServer,它优先选择在同一个区域内负载均衡较少的server。
第二步:再根据用户指定的策略,再从server取到的服务注册列表中选择一个地址。
其中Ribbon提供了多种策略:比如轮询、随机、和根据响应时间加权。
3.配合Eureka快速使用Ribbon
一.构建Eureka Server服务注册中心
https://blog.csdn.net/xxxxu5811/article/details/119353675
二.构建服务提供者(1台)和服务消费者(2台)注册到注册中心
三.Ribbon+RestTemplate实现远程调用和负载均衡(@LoadBalanced注解)
服务消费者编写配置类,提供RestTemplate远程调用服务提供者
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced //实现负载均衡
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
服务消费方编写Controller
@RestController
@Slf4j
public class OrderController {
//public static final String PAYMENT_URL="http://localhost:8001";
//配置集群后,使用微服务名来调用支付服务
public static final String PAYMENT_URL="http://CLOUD-PAYMENT-SERVICE";
//是一种简单便捷的远程访问HTTP的restful服务的模板类
@Autowired
private RestTemplate restTemplate;
/**
* 远程调用PaymentController的接口
* @return
*/
@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") Long id){
return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
}
}
服务提供方编写Controller
@Controller
@ResponseBody
@Slf4j
public class PaymentController {
@Autowired
private PaymentService paymentService;
@Value("${server.port}") //用于在响应页面打印,判断是否实现负载均衡
private String serverPort;
@GetMapping("/payment/get/{id}")
public CommonResult<Payment> get(@PathVariable("id") Long id) {
Payment payment = paymentService.getById(id);
log.info("插入结果" + payment);
if (payment != null) {
return new CommonResult(200, "查询成功" + serverPort, payment);
} else {
return new CommonResult(444, "没有对应记录");
}
}
}
测试,服务消费者远程调用服务提供者
4.Ribbon核心组件IRule以及负载均衡算法替换
IRule接口:根据特定算法中从服务列表中选取一个要访问的服务。
Ribbon在默认情况下使用的是轮询算法,如何修改Ribbon的默认算法。
修改服务消费者,新增myrule包(注意:官方文档给出了明确警告,这个自定义配置类不能放在@ComponentScan所扫描的当前包以及子包下(不能放在主启动类当前包及子包下),否则我们 自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的)。
1.在myrule包下新建MySelfRule配置类,往容器注入我们需要的算法实现
/**
* 替换Ribbon负载均衡算法(默认为轮询)
*/
@Configuration
public class MySelfRule {
@Bean
public IRule myRule(){
return new RandomRule();//指定为随机
}
}
2.服务消费者主启动类添加@RibbonClient注解,指定要调用的服务和算法配置
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "cloud-payment-service",configuration = MySelfRule.class)//指定调用的服务名和自定义规则类
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class,args);
}
}
3.测试查看结果
5.Ribbon负载均衡算法
根据RoundRobinRule的choose方法得到以下负载均衡算法原理,根据此原理我们可以手写一个负载均衡算法。
原理:负载均衡算法:rest接口第几次请求 % 服务器集群总数量 = 实际调用服务器位置下标,每次服务重启后rest接口计数从1开始
修改服务提供者,定义LoadBalance 接口,提供从服务器集群中获取到处理当前请求的服务的方法。
public interface LoadBalance {
ServiceInstance instances(List<ServiceInstance> list);
}
编写LoadBalance 实现类,定义获取请求访问次数方法,再根据得到的负载均衡实现原理实现获取当前请求实例方法。
/**
* Ribbon
* 自定义负载均衡算法
*/
@Component
public class MyLB implements LoadBalance {
private AtomicInteger atomicInteger=new AtomicInteger(0);
//请求访问次数
private final int getAndIncrement(){
int current;
int next;
do{
current=atomicInteger.get();
next=current >= 2147483647 ? 0 :current+1;
}while (!this.atomicInteger.compareAndSet(current,next));
System.out.println("*************访问次数next"+next);
return next;
}
//获取到当前处理请求的实例
@Override
public ServiceInstance instances(List<ServiceInstance> list) {
int index = getAndIncrement() % list.size();//请求访问此时 % 实例集群数=执行当前请求的服务的下标
return list.get(index);
}
}
编写服务消费者的Controller
@RestController
@Slf4j
public class OrderController {
/**
* Ribbon
* 引入自定义的负载均衡算法
*/
@Autowired
private LoadBalance loadBalance;
@Autowired
private DiscoveryClient discoveryClient;//DiscoveryClient可以获取到注册的微服务的信息
/**Ribbon
* 使用自定义的负载均衡算法
* @return
*/
@GetMapping("/consumer/payment/lb")
public String getPaymentLB(){
//获取到CLOUD-PAYMENT-SERVICE的所有实例(集群个数)
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
if(CollectionUtils.isEmpty(instances)){
return null;
}
//根据算法拿到执行请求的实例
ServiceInstance serviceInstance = loadBalance.instances(instances);
URI uri = serviceInstance.getUri(); //处理本次请求的实例地址
//拿到该实例的uri使用restTemplate远程调用
return restTemplate.getForObject(uri+"/payment/lb",String.class);
}
}
测试,我们需要去掉RestTemplate的@LoadBalanced注解(防止使用Ribbon的负载均衡)。