Spring Cloud微服务实践

Spring Cloud Ribbon客户端负载均衡

Spring Cloud中的Ribbon是什么?

我们通常说的负载均衡是指将一个请求均匀地分摊到不同的节点单元上执行,负载均和分为硬件负载均衡和软件负载均衡:

硬件负载均衡比如 F5、深信服、Array 等;

软件负载均衡比如 Nginx、LVS、HAProxy 等;

硬件负载均衡或是软件负载均衡,他们都会维护一个可用的服务端清单,通过心跳检测来剔除故障的服务端节点以保证清单中都是可以正常访问的服务端节点。当客户端发送请求到负载均衡设备的时候,该设备按某种算法(比如轮询、权重、 最小连接数等)从维护的可用服务端清单中取出一台服务端的地址,然后进行转发。

Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,是一个基于HTTP和TCP的客户端负载均衡工具。

Spring Cloud对Ribbon做了二次封装,可以让我们使用RestTemplate的服务请求,自动转换成客户端负载均衡的服务调用。

Ribbon支持多种负载均衡算法,还支持自定义的负载均衡算法。

Ribbon只是一个工具类框架,比较小巧,Spring Cloud对它封装后使用也非常方便,它不像服务注册中心、配置中心、API网关那样需要独立部署,Ribbon只需要在代码直接使用即可;

Ribbon 与 Nginx 的区别

Ribbon是客户端的负载均衡工具,而客户端负载均衡和服务端负载均衡最大的区别在于服务清单所存储的位置不同,在客户端负载均衡中,所有客户端节点下的服务端清单,需要自己从服务注册中心上获取,比如Eureka服务注册中心。同服务端负载均衡的架构类似,在客户端负载均衡中也需要心跳去维护服务端清单的健康性,只是这个步骤需要与服务注册中心配合完成。在Spring Cloud中,由于Spring Cloud对Ribbon做了二次封装,所以默认会创建针对Ribbon的自动化整合配置;

在Spring Cloud中,Ribbon主要与RestTemplate对象配合起来使用,Ribbon会自动化配置RestTemplate对象,通过@LoadBalanced开启RestTemplate对象调用时的负载均衡。

Ribbon实现客户端负载均衡

由于Spring Cloud Ribbon的封装, 我们在微服务架构中使用客户端负载均衡调用非常简单, 只需要如下两步:

1、启动多个服务提供者实例并注册到一个服务注册中心或是服务注册中心集群。

2、服务消费者通过被@LoadBalanced注解修饰过的RestTemplate来调用服务提供者。

这样,我们就可以实现服务提供者的高可用以及服务消费者的负载均衡调用。

Ribbon负载均衡策略

Ribbon的负载均衡策略是由IRule接口定义, 该接口由如下实现:

RandomRule 随机
RoundRobinRule 轮询
AvailabilityFilteringRule 先过滤掉由于多次访问故障的服务,以及并发连接数超过阈值的服务,然后对剩下的服务按照轮询策略进行访问;
                                                                  WeightedResponseTimeRule                                                                                     根据平均响应时间计算所有服务的权重,响应时间越快服务权重就越大被选中的概率即越高,如果服务刚启动时统计信息不足,则使用RoundRobinRule策略,待统计信息足够会切换到该WeightedResponseTimeRule策略;
RetryRule 先按照RoundRobinRule策略分发,如果分发到的服务不能访问,则在指定时间内进行重试,分发其他可用的服务;
BestAvailableRule 先过滤掉由于多次访问故障的服务,然后选择一个并发量最小的服务;
ZoneAvoidanceRule 综合判断服务节点所在区域的性能和服务节点的可用性,来决定选择哪个服务;

Rest请求模板类解读

当我们从服务消费端去调用服务提供者的服务的时候,使用了一个极其方便的对象叫RestTemplate,当时我们只使用了RestTemplate中最简单的一个功能getForEntity发起了一个get请求去调用服务端的数据,同时,我们还通过配置@LoadBalanced注解开启客户端负载均衡,RestTemplate的功能非常强大,那么接下来我们就来详细的看一下RestTemplate中几种常见请求方法的使用。

在日常操作中,基于Rest的方式通常是四种情况,它们分表是:

GET请求 --查询数据

POST请求 –添加数据

PUT请求 – 修改数据

DELETE请求 –删除数据

下面我们逐一解读。

RestTemplate的GET请求

有两种方式:

第一种:getForEntity

该方法返回一个ResponseEntity对象,ResponseEntity是Spring对HTTP请求响应的封装,包括了几个重要的元素,比如响应码、contentType、contentLength、响应消息体等;

ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/hello", String.class);
String body = responseEntity.getBody();
HttpStatus statusCode = responseEntity.getStatusCode();
int statusCodeValue = responseEntity.getStatusCodeValue();
HttpHeaders headers = responseEntity.getHeaders();

System.out.println(body);
System.out.println(statusCode);
System.out.println(statusCodeValue);
System.out.println(headers);

以上代码:

getForEntity方法第一个参数为要调用的服务的地址,即服务提供者提供的http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/hello接口地址,注意这里是通过服务名调用而不是服务地址,如果改为服务地址就无法使用Ribbon实现客户端负载均衡了。

getForEntity方法第二个参数String.class表示希望返回的body类型是String类型,如果希望返回一个对象,也是可以的,比如User对象;

另外两个重载方法:

public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) throws RestClientException

比如:

restTemplate.getForEntity("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/hello?id={1}&name={2}", String.class, "{1, '张无忌'}").getBody();
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException

比如:

Map<String, Object> paramMap = new ConcurrentHashMap<>();
paramMap.put("id", 1);
paramMap.put("name", "张无忌");
restTemplate.getForEntity("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/hello?id={id}&name={name}", String.class, paramMap).getBody();   

第二种:getForObject()

与getForEntity使用类似,只不过getForObject是在getForEntity基础上进行了再次封装,可以将http的响应体body信息转化成指定的对象,方便我们的代码开发;

当你不需要返回响应中的其他信息,只需要body体信息的时候,可以使用这个更方便;

它也有两个重载的方法,和getForEntity相似;

<T> T getForObject(URI url, Class<T> responseType) throws RestClientException;

<T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException;

<T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;

RestTemplate的POST请求:

Post与Get请求非常类似:

restTemplate.postForObject()
restTemplate.postForEntity()
restTemplate.postForLocation()

RestTemplate的PUT请求:

restTemplate.put();

RestTemplate的DELETE请求:

restTemplate.delete();

全部教程