在 Spring Cloud中,提供的服务调用是 Ribbon和 OpenFeign。Ribbon是 Netflix公司开发的组件,OpenFeign也是基于 Ribbon实现的工具。
Spring Cloud Netflix Ribbon是一个基于HTTP和TCP的客户端负载均衡的组件工具,它对 Netflix Ribbon的二次封装。通过Spring Cloud的二次封装,可以让开发者轻松地将面向服务的 REST模版请求自动转换成客户端负载均衡的服务调用。
Spring Cloud Netflix Ribbon虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关等组件那样需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。
微服务之间的调用往往被称为客户端负载均衡
即是当浏览器向后台发出请求的时候,客户端会向 Eureka Server 读取注册到服务器的可用服务实例清单列表,并基于负载均衡算法,选择其中微服务的一个具体实例来发送请求。
一、Netflix Ribbon简介
1、Ribbon简介
Ribbon(/rɪbən/)
是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)
后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机等)去连接这些机器。也可以使用Ribbon实现自定义的负载均衡算法。
Ribbon实现的关键点是为 Ribbon定制的
RestTemplate
,Ribbon利用了 RestTemplate的拦截器机制,在拦截器中实现Ribbon的负载均衡。负载均衡的基本实现就是利用applicationName从服务注册中心获取可用的服务地址列表,然后通过一定算法负载,决定使用哪一个服务地址来进行http调用。
2、负载均衡简介
负载均衡(Load Balance,简称LB)
分为硬件负载均衡(比如 F5)和软件负载均衡(比如 Nginx)。这里讨论软件负载均衡。
负载均衡
,是一种计算机技术,用来在多个计算机(计算机集群)、网络连接、CPU、磁盘驱动器或其他资源中分配负载,以达到最优化资源使用、最大化吞吐率、最小化响应时间、同时避免过载的目的。
可以理解为:是将负载(工作任务,访问请求)进行平衡、分摊到多个操作单元(服务器,组件)上进行执行。是解决高性能,单点故障(高可用),扩展性(水平伸缩)的终极解决方案。
(1)负载均衡技术具有一些优势:
- 高可用和高性能: 负载均衡技术将业务较均衡的分担到多台设备或链路上,通过心跳机制检测服务节点,保证了高可用,提高了整个系统的性能;
- 可扩展性: 负载均衡技术可以方便的增加集群中设备或链路的数量,在不降低业务质量的前提下满足不断增长的业务需求;
- 高可靠性: 单个甚至多个设备或链路法神故障也不会导致业务中断,提高了整个系统的可靠性;
- 可管理性: 大量的管理共组都集中在使用负载均衡技术的设备上,设备集群或链路集群只需要维护通过的配置即可;
(2)负载均衡需要解决的两个最基本的问题
- 从哪里获取服务实例
服务获取是指微服务实例作为 Eureka的客户端,从 Eureka服务治理中心获取其他服务实例清单的功能。服务获取会将服务实例清单列表缓存到本地,并且按一定的时间间隔刷新。 - 如何选择服务实例调用
Ribbon会根据请求的 URL知道调用的是哪个微服务,然后再服务获取的清单列表中,通过一种负载均衡算法(默认采用轮询策略)选择其中一个具体实例进行服务调用。。
二、Ribbon(RestTemplate)的使用
使用Ribbon,主要就是 RestTemplate的使用。
1、搭建项目
传送门:Eureke服务治理中心
上篇文章已经搭建好项目,也是用了一下 RestTemplate,下面也说明一下Ribbon整合RestTemplate。然后详解RestTemplate的具体使用。
2、Ribbon整合RestTemplate
在启动类或者创建一个配置类都可以,然后只需要在 RestTemplate类添加@LoadBalanced注解
即可,就可以实现负载均衡。
@SpringBootApplication
@EnableEurekaClient
public class UserApplication {
// 负载均衡
@LoadBalanced
@Bean
public RestTemplate initRestTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}
@LoadBalanced 注解原理
Spring Cloud 给我们做了大量的自动化配置。通过在spring-cloud-commons-2.2.6.RELEASE.jar中查看 LoadBalancerAutoConfiguration
的源码,可以看到这里维护了一个 @LoadBalanced 的 RestTemplate 列表。
3、RestTemplate的具体使用
这里通过 postman访问 User服务来调用 Order服务。注意Order服务的端口(轮询策略),数据都是json。
(1)使用 GET请求
GET请求相关的方法:
getForObject 和 getForEntity 的区别主要体现在返回值的差异上:
getForObject的返回值就是服务提供者返回的数据,无法获取到响应头。
getForEntity的返回值是一个 ResponseEntity 的对象,包含了响应数据以及响应头。
- Order服务
@GetMapping("/get1/{id}")
public String get1(@PathVariable Long id) {
return port + "--" + "order服务getForObject返回数据 >>>>>>> " + id;
}
@GetMapping("/get2")
public String get2(Long id) {
return port + "--" + "order服务getForEntity返回数据 >>>>>>> " + id;
}
- User服务
@GetMapping("/get1/{id}")
public String get1(@PathVariable Long id) {
String url = "http://ORDER/order/get1/" + id;
String res = restTemplate.getForObject(url, String.class);
return res;
}
@GetMapping("/get2")
public String get2(Long id) {
String url = "http://ORDER/order/get2?id={1}";
ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class, id);
HttpStatus statusCode = responseEntity.getStatusCode();
int statusCodeValue = responseEntity.getStatusCodeValue();
HttpHeaders headers = responseEntity.getHeaders();
// 获取返回的数据
String res = responseEntity.getBody();
return res;
}
- 测试
(2)使用 POST请求
POST请求相关的方法:
postForEntity 和 postForObject 与 GET请求雷同,参数的传递可以是 key/value 的形式,也可以是 JSON 数据。
postForLocation方法的返回值是一个 URI对象,因为 POST请求一般用来添加数据,有时需要将刚刚添加成功的数据的 URL返回来,此时就可以使用这个方法。
注意:
URI实际上是指响应头的 Location 字段,所以,服务提供者中 register 接口的响应头必须要有 Location字段(即请求的接口实际上是一个重定向的接口),不要返回json就行,否则 postForLocation 方法的返回值为null。
比如:用户注册成功之后,可能就自动跳转到登录页面了,此时就可以使用该方法。这里简单点就返回get1的URI,然后请求它。
- Order服务
这里 @RestController 改为 @Controller
@Controller
public class OrderController {
@Value("${server.port}")
private String port;
@Autowired
private RestTemplate restTemplate;
@GetMapping("/get1/{id}")
@ResponseBody
public String get1(@PathVariable Long id) {
return port + "--" + "order服务getForObject返回数据 >>>>>>> " + id;
}
@RequestMapping("/post2Register")
public String post2Register(@RequestBody OrderDO orderDO) throws UnsupportedEncodingException {
orderDO.setId(200L);
return "redirect:/get1/" + URLEncoder.encode(orderDO.getId().toString(),"UTF-8");
}
// ...
}
- User服务
@PostMapping("/post1")
public OrderDO post1(@RequestBody OrderDO orderDO) {
String url = "http://ORDER/order/post1";
OrderDO res = restTemplate.postForObject(url, orderDO, OrderDO.class);
return res;
}
@PostMapping("/post2")
public String post2(@RequestBody OrderDO orderDO) throws MalformedURLException {
String url = "http://ORDER/order/post2Register";
Map map = new HashMap();
map.put("orderName", orderDO.getOrderName());
map.put("createData", orderDO.getCreateData());
URI uri = restTemplate.postForLocation(url, map);
String res = restTemplate.getForObject(uri, String.class);
return res;
}
- 测试
postForLocation访问之后,简单跟踪了一下,后面访问 uri跑了两次,localhost 无法访问报错 ,应用名访问ok,最终结果报错,有点没搞懂。
(3)使用 PUT请求
PUT请求相关的方法
可以用 key/value 的形式传参,也可以用 JSON 的形式传参
- Order服务
@PutMapping("/put1")
@ResponseBody
public void put1(@RequestBody OrderDO orderDO) {
orderDO.setId(300L);
System.out.println(orderDO);
}
- User服务
@PutMapping("/put1")
public void put1(@RequestBody OrderDO orderDO) {
String url = "http://ORDER/order/put1";
restTemplate.put(url, orderDO);
}
- 测试
(4)使用 DELETE请求
DELETE请求相关的方法
参数只能在地址栏传送,可以是直接放在路径中,也可以用 key/value 的形式传递.
- Order服务
@DeleteMapping("/delete1")
@ResponseBody
public void delete1(Long id) {
System.out.println("删除成功==" + id);
}
- User服务
@DeleteMapping("/delete1")
public void delete1(Long id) {
String url = "http://ORDER/order/delete1?id={1}";
Map<String,Object> map = new HashMap<>();
map.put("id", id);
restTemplate.delete(url, map);
}
- 测试
(5)使用通用 exchange方式
exchange相关的方法
参数和前面的差不多,注意:
多了一个请求类型的参数,需要创建一个 HttpEntity 作为参数来传递。
exchange方式需要开发者对请求进行封装,当你在调用时并指定请求类型,即它既能做 GET 请求,也能做 POST 请求,也能做其它各种类型的请求。
- Order服务,不变
- User服务,通过 exchange方式请求 delete和 post方式
@DeleteMapping("/delete1Exchange")
public void delete1Exchange(Long id) {
String url = "http://ORDER/order/delete1?id={id}"; // 位置从1开始{1},最好使用字段名{id}
Map<String,Object> map = new HashMap<>();
map.put("id", id);
HttpHeaders headers = new HttpHeaders();
headers.add("cookie","cookievalue");
HttpEntity<MultiValueMap<String,String>> httpEntity = new HttpEntity<>(null,headers);
ResponseEntity<Void> responseEntity = restTemplate.exchange(url, HttpMethod.DELETE, httpEntity, Void.class, map);
}
@PostMapping("/post1Exchange")
public OrderDO post1Exchange(@RequestBody OrderDO orderDO) throws URISyntaxException {
String url = "http://ORDER/order/post1";
ResponseEntity<OrderDO> responseEntity = restTemplate.exchange(
new RequestEntity<OrderDO>(orderDO, HttpMethod.POST, new URI(url)), //请求的信息
new ParameterizedTypeReference<OrderDO>() {
} // 响应数据的类型
);
OrderDO res = responseEntity.getBody();
return res;
}
- 测试ok
三、常见负载均衡策略
Ribbon自带负载均衡策略:
策略名 | 策略声明 | 策略描述 | 实现说明 |
---|---|---|---|
BestAvailableRule | public class BestAvailableRule extends ClientConfigEnabledRoundRobinRule | 选择一个最小的并发请求的server | 逐个考察Server,如果Server被tripped了,则忽略,在选择其中ActiveRequestsCount最小的server |
AvailabilityFilteringRule | public class AvailabilityFilteringRule extends PredicateBasedRule | 过滤掉那些因为一直连接失败的被标记为circuit tripped的后端server,并过滤掉那些高并发的的后端server(active connections 超过配置的阈值) | 使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查status里记录的各个server的运行状态 |
WeightedResponseTimeRule | public class WeightedResponseTimeRule extends RoundRobinRule | 根据相应时间分配一个weight,相应时间越长,weight越小,被选中的可能性越低。 | 一 个后台线程定期的从status里面读取评价响应时间,为每个server计算一个weight。Weight的计算也比较简单responsetime 减去每个server自己平均的responsetime是server的权重。当刚开始运行,没有形成statas时,使用roubine策略选择 server。 |
RetryRule | public class RetryRule extends AbstractLoadBalancerRule | 对选定的负载均衡策略机上重试机制。 | 在一个配置时间段内当选择server不成功,则一直尝试使用subRule的方式选择一个可用的server |
RoundRobinRule | public class RoundRobinRule extends AbstractLoadBalancerRule | roundRobin方式轮询选择server | 轮询index,选择index对应位置的server |
RandomRule | public class RandomRule extends AbstractLoadBalancerRule | 随机选择一个server | 在index上随机,选择index对应位置的server |
ZoneAvoidanceRule | public class ZoneAvoidanceRule extends PredicateBasedRule | 复合判断server所在区域的性能和server的可用性选择server | 使 用ZoneAvoidancePredicate和AvailabilityPredicate来判断是否选择某个server,前一个判断判定一个 zone的运行性能是否可用,剔除不可用的zone(的所有server),AvailabilityPredicate用于过滤掉连接数过多的 Server。 |
Ribbon在大部分情况下都不需要自定义,也不建议修改它们。遇到再说吧,Ribbon的使用就到这里了。
参考文章:
—— Stay Hungry. Stay Foolish. 求知若饥,虚心若愚。