前言
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,,它基于Netflix Ribbon实现,在本文中我们主要来介绍如何使用Ribbon来实现客户端的负载均衡。
客户端负载均衡
我们通常说的负载俊航一般是指服务端的负载均衡,包括硬件负载均衡和软件负载均衡,其中硬件负载均衡主要是通过在服务器节点之间安装专门用来负载均衡的设备,比如F5负载均衡器,而软件负载均衡则是通过在服务器上安装一些具有负载均衡功能的软件来完成请求分发工作,比如Nginx,这两种均衡方式都类似于下面这种架构方式:
硬件负载均衡的设备或是软件负载均衡的软件模块都会维护一个可用的服务清单,通过心跳检测来剔除故障的服务端节点以保证清单中都是可以正常访问的服务端节点。当客户端发送请求到负载均衡设备的时候,该设备按某种算法(比如线性轮询、权重、流量等)从维护的可用服务端清单中取出一台服务端的地址,然后进行转发。而客户端负载均衡和服务端负载均衡最大的不同点在于上面所提到服务清单的存储位置。在客户端负载均衡中,所有客户端节点都维护着自己要访问的服务端清,单而这个服务端清单来自于服务注册中心。
通过Spring Cloud Ribbon的封装,我们在微服务架构中使用客户端负载均衡调用需要完成如下两步:
- 服务提供者只需要启动多个服务实例并注册到一个注册中心或是多个相关联的服务注册中心
- 服务消费者直接通过被@LoadBalanced注解修饰过的RestTemplate来实现面向服务的接口调用
详情可参照Eureka入门实例
RestTemplate详解
GET请求
在我们之前的实例中用到了getForEntity函数,该方法返回的是ResponseEntity,是Spring对HTTP请求响应的封装,其中主要存储了HTTP的几个重要元素,比如HTTP请求状态码的枚举对象HttpStatus(也就是我们常说的404、500这些错误码)、在它的父类HttpEntity中存储着HTTP请求的头信息对象HttpHeaders以及泛型类型的请求体对象。
来看下面这个实例,返回的是String类型和User对象类型
@RequestMapping(value="/ribbon-consumer",method=RequestMethod.GET)
public String helloConsumer() {
ResponseEntity<String> responseEntity=restTemplate.getForEntity(requestUrl, String.class);
return responseEntity.getBody();
}
@RequestMapping(value="/ribbon-consumerV2",method=RequestMethod.GET)
public User helloConsumerV2() {
return restTemplate.getForEntity(requestUrl, User.class).getBody();
}
getForEntity函数
上面的例子是比较常用的方法,getForEntity函数实际上提供了以下三种不同的重载实现。
- getForEntity(String url, Class responseType, Object… uriVariables)
三个参数分别是请地址url,请求响应体body的包装类型responseType,url中绑定的参数uriVariables。GET请求的参数绑定通过使用url拼接的方式,如http://MY-SERVICE/user?name={1},然后使用getForEntity(“http://HELLO-SERVICE/user?name={1}",String.class,"Amy”)
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables)
throws RestClientException {
RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
return nonNull(execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables));
}
- getForEntity(String url, Class responseType, Map<String, ?> uriVariables)
这个方法提供的参数中,只有uriVariables的参数类型与上面的方法不同,这里是使用了Map类型,所以使用该方法进行参数绑定时需要在占位符中指定Map中参数的key值。
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables)
throws RestClientException {
RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
return nonNull(execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables));
}
在Map类型的uriVariables中,我们需要put一个key参数来绑定url中的占位符,比如:
RestTemplate restTemplate=new RestTemplate();
Map<String,String> params=new HashMap<>();
params.put("name","Amy");
ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/user?name={name}",String.class.params);
- getForEntity(URI url, Class responseType)
该方法使用URI对象来替代之前的url和urlVariables参数来指定访问地址和参数绑定.
public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException {
RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
return nonNull(execute(url, HttpMethod.GET, requestCallback, responseExtractor));
}
URI是JDK java.net包下的一个类,它表示一个统一资源标识符引用,例如下面这个例子:
RestTemplate restTemplate=new RestTemplate();
UriComponents uriComponents=UriComponentsBuilder.fromUrilString("http://HELLO_SERVICE/user?name={name}").build().expand("Amy").encode();
URI uri=uriComponents.toUri();
ResponseEntity<String> responseEntity=restTemplate.getForEntity(uri,String.class).getBody();
getForObject函数
getForObject函数可以理解为对getForEntity的进一步封装,它通过HttpMessageConverterExtractor对HTTP的请求响应体body内容进行对象转换,实现请求直接返回包装好的对象内容,比如:
RestTemplate restTemplate=new RestTemplate();
String result =restTemplate.getForObject(uri,String.class);
当body是一个User对象时,可以直接这样实现:
RestTemplate restTemplate=new Resttemplate();
User result=restRemplat.getForObject(uri,User.class);
当不需要关注请求响应除body外的其他内容时,可以少一个从Response中获取body的步骤。它与getForEntity函数类似,也提供了三个不同的重载实现。
@Override
@Nullable
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
}
@Override
@Nullable
public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
}
@Override
@Nullable
public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException {
RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
return execute(url, HttpMethod.GET, requestCallback, responseExtractor);
}
POST请求
postForEntity函数
该方法同为GET请求中的getForEntity类似,会在调用后返回ResponseEntity对象,其中T为请求响应的body类型。比如下面这个例子,使用postForEntity提交POST请求到HELLO-SERVICE服务的/user接口,提交的body内容为user对象,请求响应返回的body类型为String。
RestTemplate restTemplate=new RestTemplate();
User user=new User("Amy",18);
ResponseEntity<String> responseEntity=restTemplate.postForEntity("http://HELLO-SERVICE/user",user,String.class);
String body=responseEntity.getBody();
postForEntity函数也实现了三种不同的重载方法
@Override
public <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request,
Class<T> responseType, Object... uriVariables) throws RestClientException {
RequestCallback requestCallback = httpEntityCallback(request, responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
return nonNull(execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables));
}
@Override
public <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request,
Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
RequestCallback requestCallback = httpEntityCallback(request, responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
return nonNull(execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables));
}
@Override
public <T> ResponseEntity<T> postForEntity(URI url, @Nullable Object request, Class<T> responseType)
throws RestClientException {
RequestCallback requestCallback = httpEntityCallback(request, responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
return nonNull(execute(url, HttpMethod.POST, requestCallback, responseExtractor));
}
PUT请求
在RestTemplate中,对PUT请求可以通过put方式进行调用实现,比如:
RestTemplate restTemplate =new RestTemplate();
Long id=10001L;
User user=new User("Andy",10);
restTemplate.put("http://HELLO-SERVICE/user/{1}",user,id);
put函数也实现了三种不同的重载方法:
@Override
public void put(String url, @Nullable Object request, Object... uriVariables)
throws RestClientException {
RequestCallback requestCallback = httpEntityCallback(request);
execute(url, HttpMethod.PUT, requestCallback, null, uriVariables);
}
@Override
public void put(String url, @Nullable Object request, Map<String, ?> uriVariables)
throws RestClientException {
RequestCallback requestCallback = httpEntityCallback(request);
execute(url, HttpMethod.PUT, requestCallback, null, uriVariables);
}
@Override
public void put(URI url, @Nullable Object request) throws RestClientException {
RequestCallback requestCallback = httpEntityCallback(request);
execute(url, HttpMethod.PUT, requestCallback, null);
}
put函数为void类型,没有返回内容,也就没有其他函数定义的responseType参数,除此之外的其他传入参数定义与用法跟postForObject基本一致。
DELETE请求
在RestTemplate中,对DELETE请求可以通过delete方法进行调用实现,比如:
RestTemplate restTemplate=new RestTemplate();
Long id=10001L;
restTemplate.delete("http://HELLO-SERVICE/user/{1}",id);
delete函数也实现了三种不同的重载方法:
@Override
public void delete(String url, Object... uriVariables) throws RestClientException {
execute(url, HttpMethod.DELETE, null, null, uriVariables);
}
@Override
public void delete(String url, Map<String, ?> uriVariables) throws RestClientException {
execute(url, HttpMethod.DELETE, null, null, uriVariables);
}
@Override
public void delete(URI url) throws RestClientException {
execute(url, HttpMethod.DELETE, null, null);
}
由于我们在进行REST请求时,通常都将DELETE请求的唯一标识拼接在url中,所以DELETE请求也不需要request的body信息,url指定DELETE请求的位置,uriVaribales绑定url中的参数即可。