在SpringCloud中Ribbon负载均衡客户端,会从eureka注册中心服务器端上获取服务注册信息列表,缓存到本地,然后在本地实现轮训负载均衡策略。
那么Ribbon和Nginx有什么区别呢?
- Nginx是客户端所有请求统一交给Nginx,由nginx进行实现负载均衡请求转发,属于服务器端负载均衡(服务器端转发)。
- 在客户端转发:Ribbon是从eureka注册中心服务器端上获取服务注册信息列表,缓存到本地,让后在本地实现轮训负载均衡策略(客户端转发)。
- Nginx适合于服务器端实现负载均衡 比如Tomcat(服务器端负载均衡)。
- Ribbon适合与在微服务中RPC远程调用实现本地服务负载均衡,比如Dubbo、SpringCloud中都是采用本地负载均衡(客户端负载均衡)。
Ribbon流程图:
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 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消费者客户端通过轮询的方式来实现本地负载均衡。
RestTemplate是Spring用于同步client端的核心类,简化了与http服务的通信,并满足RestFul原则,程序代码可以给它提供URL,并提取结果。能大幅简化了提交表单数据的难度,并且附带了自动转换JSON数据的功能。(说白了,库室HttpClient的封装,像Apache HttpComponents、Netty和OkHttp。)
该类的入口主要是根据HTTP的六个方法制定:
Http MethodRestTemplate MethodsGETgetForObject、getForEntifyDELETEdeletePOSTpostForLocation、postForObjectPUTputHEADheadForHeadersOPTIONSoptionsForAllowANYexchange、execute此外,exchange和excute可以通用上述方法。
在内部,RestTemplate默认使用HttpMessageConverter实例将HTTP消息转换成POJO或者从POJO转换成HTTP消息。默认情况下会注册主mime类型的转换器,但也可以通过setMessageConverters注册其他的转换器。
HttpMessageConverter.java源码:
public interface HttpMessageConverter {
//指示此转换器是否可以读取给定的类。
boolean canRead(Class clazz, @Nullable MediaType mediaType);
//指示此转换器是否可以写给定的类。
boolean canWrite(Class clazz, @Nullable MediaType mediaType);
//返回List
List getSupportedMediaTypes();
//读取一个inputMessage
T read(Class 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 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
,ResponseEntity
是Spring对HTTP请求响应的封装,包括了几个重要的元素,如响应码、contentType、contentLength、响应消息体等。
@Test
public void rtGetEntity(){
RestTemplate restTemplate = new RestTemplate();
ResponseEntity 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 body1 = status.body(Notice.class);
Class 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()
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 map= new LinkedMultiValueMap();
map.add("email", "xxx@qq.com");
HttpEntity request = new HttpEntity(map, headers);
ResponseEntity 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 源码分析参考:《客户端负载均衡Ribbon之源码解析》 ServerList:可以响应客户端的特定服务的服务器列表。 ServerListFilter:可以动态获得的具有所需特征的候选服务器列表的过滤器。 ServerListUpdater:用于执行动态服务器列表更新。 Rule:负载均衡策略,用于确定从服务器列表返回哪个服务器。 Ping:客户端用于快速检查服务器当时是否处于活动状态。 LoadBalancer:负载均衡器,负责负载均衡调度的管理。
这里的重试并不是报错以后的重试,而是负载均衡客户端发现远程请求实例不可到达后,去重试其他实例。
Ribbon重试机制配置:
名称解析ribbon.OkToRetryOnAllOperationsfalse(是否所有操作都重试)ribbon.MaxAutoRetriesNextServer2(重试负载均衡其它的实例最大重试次数,不包括Server)ribbon.MaxAutoRetries1(同一台实例最大重试次数,不包括首次调用)spring.cloud.loadbalancer.retry.enabledtrue(重试机制开关) 04 总结代码已上传至Github,有兴趣的同学可以下载来看看:https://github.com/ylw-github/SpringCloud-Ribbon-Demo