一、概述
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。简单点说,其主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接,权重等)去连接这些机器。
- LB负载均衡(Load Balance)是什么?
简单的说就是将用户的请求平摊的分配到多个服务器上,从而达到系统的HA(高可用)。常见的负载均衡的软件有Nginx、LVS和硬件F5等。
- Ribbon本地负载均衡客户端与Nginx服务端负载均衡区别?
-
Nginx是服务器负载均衡,客户端所有请求都会交给nginx,然后由nginx实现转发请求,即负载均衡是由服务端实现的。即集中式LB,在服务端消费方和提供方之间使用独立的LB设施(可以是硬件,如F5,也可以是软件,如nginx),由该设施负责把访问请求通过某种策略转发至服务的提供方。
-
Ribbon本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到本地JVM中,从而在本地实现RPC远程服务调用技术。即进程内LB,将LB逻辑集成到消费方,消费方从服务服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fhlQRDnL-1630605750148)(https://obohe.com/i/2021/09/02/xuym7p.jpg)]
二、基本使用
- 服务提供者只需要启动多个服务实例并注册到一个注册中心或多个相关联的服务注册中心。
- 服务消费者直接通过调用被@LoadBalanced注解修饰的Restemplate来实现面向服务的接口调用。
1. 使用@LoadBalanced
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RemoteClientConfiguration {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
2. 调用服务提供者
公用java资源
public class ResultDto<T> {
private Integer msgCode;
private String msgTip;
private T data;
public Integer getMsgCode() {
return msgCode;
}
public void setMsgCode(Integer msgCode) {
this.msgCode = msgCode;
}
public String getMsgTip() {
return msgTip;
}
public void setMsgTip(String msgTip) {
this.msgTip = msgTip;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public static ResultDto error(Integer msgCode, String msgTip){
ResultDto objectResultDto = new ResultDto();
objectResultDto.setMsgCode(msgCode);
objectResultDto.setMsgTip(msgTip);
return objectResultDto;
}
public static <T> ResultDto success(Integer msgCode,String msgTip,T data){
ResultDto objectResultDto = new ResultDto();
objectResultDto.setMsgCode(msgCode);
objectResultDto.setMsgTip(msgTip);
objectResultDto.setData(data);
return objectResultDto;
}
}
I、 GET请求
- 基本使用示例
//服务提供者名称
private static final String REMOTE_URL = "http://CLOUD-PAYMENT-SERVICE";
// getForEntity
@GetMapping("/payment/list")
public ResultDto listPayment(){
ResponseEntity<ResultDto> forEntity = restTemplate.getForEntity(REMOTE_URL + "/payment/list", ResultDto.class);
return forEntity.getBody();
}
// getForObject
@GetMapping("/payment/list")
public ResultDto listPayment(){
return restTemplate.getForObject(REMOTE_URL + "/payment/list", ResultDto.class);
}
- getForEntity方法详解
/**
* 一、 getForEntity(String url, Class<T> responseType, Object... uriVariables)
* url: 为请求地址
* responseType: 响应结果类型
* uriVariables: url中参数绑定,为变长参数(即数组),可以多个;它的顺序对应url中占位符定义的数字顺序。
*/
@GetMapping("/payment/listEntity")
public ResultDto listEntity(@RequestParam String uuid,@RequestParam String paymentType){
//占位符中的数字对应变长参数的顺序
ResponseEntity<ResultDto> forEntity = restTemplate.getForEntity(REMOTE_URL + "/payment/list?uuid={1}&paymentType={2}", ResultDto.class,uuid,paymentType);
return forEntity.getBody();
}
/**
* 二、 getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables)
* url: 为请求地址
* responseType: 响应结果类型
* uriVariables: url中参数绑定,为map类型,key为url中占位符的值
*/
@GetMapping("/payment/listEntity")
public ResultDto listEntity(@RequestParam String uuid,@RequestParam String paymentType){
Map<String,String> queryParams = new HashMap<>();
queryParams.put("uuid",uuid);
queryParams.put("paymentType",paymentType);
//key为url中占位符的值
ResponseEntity<ResultDto> forEntity = restTemplate.getForEntity(REMOTE_URL + "/payment/list?uuid={uuid}&paymentType={paymentType}", ResultDto.class,queryParams);
return forEntity.getBody();
}
/**
*三、getForEntity(URI url, Class<T> responseType)
* url: 指定访问地址和参数绑定
* responseType: 响应结果类型
*/
@GetMapping("/payment/listEntity")
public ResultDto listEntity(@RequestParam String uuid,@RequestParam String paymentType){
UriComponents uriComponents = UriComponentsBuilder.fromUriString(REMOTE_URL + "/payment/list?uuid={uuid}&paymentType={paymentType}")
.build()
.expand(uuid)
.expand(paymentType)
.encode();
URI uri = uriComponents.toUri();
ResponseEntity<ResultDto> forEntity = restTemplate.getForEntity(uri, ResultDto.class);
return forEntity.getBody();
}
- getForObject详解
//参数及其示例同getForEntity,不在重复写
1. getForObject(String url, Class<T> responseType, Object... uriVariables)
2. getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)
3. getForObject(URI url, Class<T> responseType)
II、POST请求
- 请求示例
// 1. postForObject
@PostMapping("/save")
public ResultDto save(@RequestBody Orders orders){
Payment payment = new Payment();
payment.setCreateTime(orders.getCreateTime());
payment.setSerialPayment(orders.getSerialOrder());
ResultDto resultDto = restTemplate.postForObject("http://localhost:8888" + "/payment/add", payment, ResultDto.class);
return resultDto;
}
//2. postForEntity
@PostMapping("/save")
public ResultDto save(@RequestBody Orders orders){
Payment payment = new Payment();
payment.setCreateTime(orders.getCreateTime());
payment.setSerialPayment(orders.getSerialOrder());
ResultDto resultDto = restTemplate.postForObject("http://localhost:8888" + "/payment/add", payment, ResultDto.class);
return resultDto;
}
//3. postForLocation,暂时没有想到应用场景,先只给一个使用方法
URI uri = restTemplate.postForLocation(REMOTE_URL + "/save", payment);
III、PUT请求
1. void put(String var1, @Nullable Object var2, Object... var3)
2. void put(String var1, @Nullable Object var2, Map<String, ?> var3)
3. void put(URI var1, @Nullable Object var2)
//其用法和postForObject基本类似,不再赘述
XI、DELETE请求
1. void delete(String var1, Object... var2);
2. void delete(String var1, Map<String, ?> var2);
3. void delete(URI var1);
//其用法和postForObject基本类似,不再赘述
三、Ribbon工作流程
- 选择EurekaServer(注册中心),优先选择同一个区域内负载最少的EurekaServer
- 根据用户指定的策略,从注册中心获取的服务列表中选择一个服务地址并执行
其中Ribbon提供了多种策略,如下:
名称 | 功能 |
---|---|
RoundRobinLoadBalancer | 轮询 |
RandomRule | 随机 |
RetryRule | 先按照RoundRobinLoadBalancer的策略获取服务,如果获取服务失败,则在指定时间内重试,获取可用的服务 |
WeightedResponseTimeRule | 对RoundRobinLoadBalancer的拓展,响应速度越快的实例权重越大,越容易被选择 |
BestAvailableRule | 优先过滤掉由于访问故障而处于断路状态器跳闸的服务,然后选一个并发量最小的服务 |
AvailabilityFilteringRule | 先过滤掉故障服务,再选择并发小的实例 |
ZoneAvoidanceRule | 默认规则,复合判断server所在区域的性能和server的可用性选择服务器 |
四、Ribbon更换默认策略
- java bean
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RemoteClientConfiguration {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
//更换策略为随机
@Bean
public IRule myRule(){
return new RandomRule();
}
}
- application.yml
#设置负载均衡策略 cloud-payment-service 为调用的服务的名称 value为策略全限定名
cloud-payment-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
五、自定义负载均衡策略
注意:官方文档明确指出,自定义配置类不能放在@ComponentScan所扫描的当前包下及其子包下,否则我们自定义的配置类就会被所有的Ribbon客户端所共享,达步到特殊化定制的目的。
- 在Order(ComponentScan能扫描到的最跟目录)上新建同级目录myrule
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b9oFI5SC-1630605750150)(https://obohe.com/i/2021/09/03/2hkg5z.png)] - 在myrule包下新建MySelfRule
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MySelfRule {
@Bean
public IRule myRule(){
//这里可以自定义规则,为了演示方便,直接使用了随机策略
return new RandomRule(); //随机
}
}
- 在主启动类上添加
@RibbonClient
@EnableSwagger2 //swagger
@EnableEurekaClient //eureka
@EnableDiscoveryClient //服务发现
@SpringBootApplication //springboot启动注解
@RibbonClient(name = "cloud-payment-service",configuration = MySelfRule.class) //name为要使用的服务名字,configuration指向自己定义的配置
@MapperScan("com.yuyue.online.springcloud.order.mapper")
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class,args);
}
}