01 引言
在SpringCloud中Ribbon负载均衡客户端,会从eureka注册中心服务器端上获取服务注册信息列表,缓存到本地,然后在本地实现轮训负载均衡策略。
那么Ribbon和Nginx有什么区别呢?
- Nginx是客户端所有请求统一交给Nginx,由nginx进行实现负载均衡请求转发,属于服务器端负载均衡(服务器端转发)。
- 在客户端转发:Ribbon是从eureka注册中心服务器端上获取服务注册信息列表,缓存到本地,让后在本地实现轮训负载均衡策略(客户端转发)。
- Nginx适合于服务器端实现负载均衡 比如Tomcat(服务器端负载均衡)。
- Ribbon适合与在微服务中RPC远程调用实现本地服务负载均衡,比如Dubbo、SpringCloud中都是采用本地负载均衡(客户端负载均衡)。
Ribbon流程图:
02 DiscoveryClient负载本地负载均衡
1.搭建Eureka-Service-A1(服务提供者)、Eureka-Service-A2(服务提供者)、Eureka-Service-B(服务消费者)和Eureka-Server(注册中心),环境搭建参考:《 SpringCloud- 服务治理Eureka(搭建注册中心)》
2.Eureka-Service-B(服务消费者)负载均衡代码:
@RestController public class ServiceBController { @Autowired private RestTemplate restTemplate; @Autowired private DiscoveryClient discoveryClient; private int requestCount = 1; @RequestMapping("/discoveryClient") public String discoveryClient() { String serviceUrl = getServiceUrl() + "/getMember"; return "请求地址为 -> " + serviceUrl; } @RequestMapping("/getServiceUrl") private String getServiceUrl() { List<ServiceInstance> instances = discoveryClient.getInstances("app-service-a"); if (instances == null || instances.size() == 0) { return null; } int size = instances.size(); int index = requestCount % size; requestCount++; return instances.get(index).getUri().toString(); } }
2.启动Eureka注册中心服务(http://localhost:8100/)、服务器消费者(http://localhost:8000/、http://localhost:8001/)、服务器提供者(http://localhost:8000/)。
3.登录Eureka注册中心:http://localhost:8100/,可以看到都注册上了。
4.服务消费者消费,浏览器输入:http://localhost:8002/discoveryClient
再次刷新:
可以看出,Eureka消费者客户端通过轮询的方式来实现本地负载均衡。
03 RestTemplate
RestTemplate是Spring用于同步client端的核心类,简化了与http服务的通信,并满足RestFul原则,程序代码可以给它提供URL,并提取结果。能大幅简化了提交表单数据的难度,并且附带了自动转换JSON数据的功能。(说白了,库室HttpClient的封装,像Apache HttpComponents、Netty和OkHttp。)
该类的入口主要是根据HTTP的六个方法制定:
Http Method | RestTemplate Methods |
GET | getForObject、getForEntify |
DELETE | delete |
POST | postForLocation、postForObject |
PUT | put |
HEAD | headForHeaders |
OPTIONS | optionsForAllow |
ANY | exchange、execute |
此外,exchange和excute可以通用上述方法。
在内部,RestTemplate默认使用HttpMessageConverter实例将HTTP消息转换成POJO或者从POJO转换成HTTP消息。默认情况下会注册主mime类型的转换器,但也可以通过setMessageConverters注册其他的转换器。
HttpMessageConverter.java源码:
public interface HttpMessageConverter<T> { //指示此转换器是否可以读取给定的类。 boolean canRead(Class<?> clazz, @Nullable MediaType mediaType); //指示此转换器是否可以写给定的类。 boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType); //返回List<MediaType> List<MediaType> getSupportedMediaTypes(); //读取一个inputMessage T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException; //往output message写一个Object void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException; }
在内部,RestTemplate默认使用SimpleClientHttpRequestFactory和DefaultResponseErrorHandler来分别处理HTTP的创建和错误,但也可以通过setRequestFactory和setErrorHandler来覆盖。
演示实例前,先定义POJO:
public class Notice { private int status; private Object msg; private List<DataBean> data; } public class DataBean { private int noticeId; private String noticeTitle; private Object noticeImg; private long noticeCreateTime; private long noticeUpdateTime; private String noticeContent; }
3.1 Get请求
getForEntity:getForEntity方法的返回值是一个ResponseEntity<T>
,ResponseEntity<T>
是Spring对HTTP请求响应的封装,包括了几个重要的元素,如响应码、contentType、contentLength、响应消息体等。
@Test public void rtGetEntity(){ RestTemplate restTemplate = new RestTemplate(); ResponseEntity<Notice> entity = restTemplate.getForEntity("http://fantj.top/notice/list/1/5" , Notice.class); HttpStatus statusCode = entity.getStatusCode(); System.out.println("statusCode.is2xxSuccessful()"+statusCode.is2xxSuccessful()); Notice body = entity.getBody(); System.out.println("entity.getBody()"+body); ResponseEntity.BodyBuilder status = ResponseEntity.status(statusCode); status.contentLength(100); status.body("我在这里添加一句话"); ResponseEntity<Class<Notice>> body1 = status.body(Notice.class); Class<Notice> body2 = body1.getBody(); System.out.println("body1.toString()"+body1.toString()); } statusCode.is2xxSuccessful()true entity.getBody()Notice{status=200, msg=null, data=[DataBean{noticeId=21, noticeTitle='aaa', ... body1.toString()<200 OK,class com.waylau.spring.cloud.weather.pojo.Notice,{Content-Length=[100]}>
getForObjet:getForObject函数实际上是对getForEntity函数的进一步封装,如果你只关注返回的消息体的内容,对其他信息都不关注,此时可以使用getForObject,举一个简单的例子,如下:
/** * 带参数与不带参的get请求 */ @Test public void restTemplateGetTest(){ RestTemplate restTemplate = new RestTemplate(); Notice notice = restTemplate.getForObject("http://xxx.top/notice/list/1/5", Notice.class); //Notice notice = restTemplate.getForObject("http://fantj.top/notice/list/{1}/{2}" , Notice.class,1,5); System.out.println(notice); }
3.2 Post请求
如果你只关注,返回的消息体,可以直接使用postForObject。用法和getForObject一致。
@Test public void rtPostObject(){ RestTemplate restTemplate = new RestTemplate(); String url = "http://47.xxx.xxx.96/register/checkEmail"; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); MultiValueMap<String, String> map= new LinkedMultiValueMap<>(); map.add("email", "xxx@qq.com"); HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers); ResponseEntity<String> response = restTemplate.postForEntity( url, request , String.class ); System.out.println(response.getBody()); }
3.3 Put请求、Delete请求
跟上面大同小异,具体的看API。参考文章:《Springboot — 用更优雅的方式发HTTP请求(RestTemplate详解)》
最后,先看下代码:
@SpringBootApplication @EnableEurekaClient public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } @Bean //@LoadBalanced//@LoadBalanced就能让这个RestTemplate在请求时拥有客户端负载均衡的能力(增加@LoadBalanced,就不能使用127.0.0.1,只能使用应用名) RestTemplate restTemplate() { return new RestTemplate(); } }
需要注意的是:增加@LoadBalanced,就不能使用本机ip例如(127.0.0.1、localhost),只能使用应用名。
04 源码分析
ServerList:可以响应客户端的特定服务的服务器列表。
ServerListFilter:可以动态获得的具有所需特征的候选服务器列表的过滤器。
ServerListUpdater:用于执行动态服务器列表更新。
Rule:负载均衡策略,用于确定从服务器列表返回哪个服务器。
Ping:客户端用于快速检查服务器当时是否处于活动状态。
LoadBalancer:负载均衡器,负责负载均衡调度的管理。
05 负载均衡器重试机制
这里的重试并不是报错以后的重试,而是负载均衡客户端发现远程请求实例不可到达后,去重试其他实例。
Ribbon重试机制配置:
名称 | 解析 |
ribbon.OkToRetryOnAllOperations | false(是否所有操作都重试) |
ribbon.MaxAutoRetriesNextServer | 2(重试负载均衡其它的实例最大重试次数,不包括Server) |
ribbon.MaxAutoRetries | 1(同一台实例最大重试次数,不包括首次调用) |
spring.cloud.loadbalancer.retry.enabled | true(重试机制开关) |
04 总结
代码已上传至Github,有兴趣的同学可以下载来看看:https://github.com/ylw-github/SpringCloud-Ribbon-Demo