您当前的位置: 首页 >  负载均衡

微服务技术系列教程(21) - SpringCloud- 负载均衡器Ribbon

杨林伟 发布时间:2019-12-02 11:37:36 ,浏览量:3

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 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 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方法的返回值是一个ResponseEntityResponseEntity是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:负载均衡器,负责负载均衡调度的管理。

05 负载均衡器重试机制

这里的重试并不是报错以后的重试,而是负载均衡客户端发现远程请求实例不可到达后,去重试其他实例。

Ribbon重试机制配置:

名称解析ribbon.OkToRetryOnAllOperationsfalse(是否所有操作都重试)ribbon.MaxAutoRetriesNextServer2(重试负载均衡其它的实例最大重试次数,不包括Server)ribbon.MaxAutoRetries1(同一台实例最大重试次数,不包括首次调用)spring.cloud.loadbalancer.retry.enabledtrue(重试机制开关) 04 总结

代码已上传至Github,有兴趣的同学可以下载来看看:https://github.com/ylw-github/SpringCloud-Ribbon-Demo 在这里插入图片描述

关注
打赏
1688896170
查看更多评论

杨林伟

暂无认证

  • 3浏览

    0关注

    3183博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文
立即登录/注册

微信扫码登录

0.0479s