Spring Cloud第四篇 | 客户端负载均衡Ribbon

本文是Spring Cloud专栏的第四篇文章,了解前三篇文章内容有助于更好的理解本文:git

  1. Spring Cloud第一篇 | Spring Cloud前言及其经常使用组件介绍概览web

  2. Spring Cloud第二篇 | 使用并认识Eureka注册中心算法

  3. Spring Cloud第三篇 | 搭建高可用Eureka注册中心spring

1、Ribbon是什么

    Ribbon是一个基于HTTP和TCP的客户端负载均衡器,当使用Ribbon对服务进行访问的时候,他会扩展Eureka客户端的服务发现功能,实现从Eureka注册中心获取服务端列表,并经过Eureka客户端来肯定服务端是否已经启动。Ribbon在Eureka客户端服务发现的基础上,实现对服务实例的选择策略,从而实现对服务的负载均衡消费。负载均衡在系统架构中是一个很是重要的内容,由于负载均衡是对系统的高可用、网络的压力的缓冲和处理能力扩容的重要手段之一,咱们一般说的负载均衡都是指的是服务端的负载均衡,其中分为硬件负载均衡和软件负载均衡。服务器

  • 硬件负载均衡:主要经过服务器节点之间安装专门用于负载均衡的设备,好比F5,深信服,Array等。网络

  • 软件负载均衡:则是经过服务器上安装一些具备负载功能或模块的软件来完成请求分发工做,好比Nginx、LVS、HAProxy等。架构

    硬件负载均衡的设备或是软件负载均衡的软件模块都会维护一个下挂可用的服务端清单,经过心跳检测来剔除故障的服务端节点保证清单中都是能够正常访问的服务端节点。当客户端发送请求到负载均衡的设备时候,该设备按某种算法(好比线性轮询、按权重负载、按流量负载等)从维护的可用服务端清单中取出一台服务端地址,而后进行转发。并发

    Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,是一个基于HTTP和TCP的客户端负载均衡工具。Spring Cloud对Ribbon作了二次封装,可让咱们使用 RestTemplate的服务请求,自动转换成客户端负载均衡的服务调用。Ribbon支持多种负载均衡算法,还支持自定义的负载均衡算法。Ribbon只是一个工具类框架,比较小巧, Spring Cloud对它封装后使用也非 常方便,它不像服务注册中心、配置中心、AP网关那样须要独立部署, Ribbon 只须要在代码直接使用便可。app

Ribbon与 Nginx的区别:负载均衡

  • 都是软负载

  • Ribbon是客户端负载均衡

  • Nginx是服务器段负载均衡

区别在于:

    服务清单所存储的位置不一样,在客户端负载均衡中,全部客户端节点下的服务端清单,须要本身从服务注册中心上获取,好比Eureka服务注册中心。同服务端负载均衡的架构相似,在客户端负载均衡中也须要心跳去维护服务端清单的健康性,只是这个步骤须要与服务注册中心配合完成,在SpringCloud实现的服务治理框架中,默认会建立针对各个服务治理框架到的Ribbon自动化整合配置,好比Eureka中的org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,在实际使用的时候,咱们能够经过查看这个类的实现,以找到他们的配置详情来帮助咱们更好的使用它。

    经过Spring Cloud Ribbon的封装,咱们在微服务架构中使用客户端负载均衡调用很是的简单,只须要以下两步:

  1. 服务提供者只须要启动多个服务实例并注册到一个注册中心或是多个相关联的服务注册中心上

  2. 服务消费者直接经过调用被@LoadBalanced注解修饰过的RestTemplate来实现面向服务的接口调用。

这样咱们就能够将服务提供者的高可用以及服务消费者的负载均衡用一块儿实现了。

  • 服务端的负载均衡是提早配置好的:Nginx

  • 客户端的负载均衡是从注册中心找的:Ribbon

    在SpringCloud中,Ribbon主要与RestTemplate对象配合使用,Ribbon会自动化配置RestTemplate对象,经过@LoadBalance开启RestTemplate对象调用时的负载均衡,Ribbon所处的做用如图:

 

 

 

图片来源网络

2、Ribbon实现客户端负载均衡

    一、前面提到过,经过Spring Cloud Ribbon的封装,咱们在微服务架构中使用客户端负载均衡调用很是的简单,只须要以下两步:

  1. 服务提供者只须要启动多个服务实例并注册到一个注册中心或是多个相关联的服务注册中心上

  2. 服务消费者直接经过调用被@LoadBalanced注解修饰过的RestTemplate来实现面向服务的接口调用。

    二、咱们复制服务提供者(springcloud-service-provider)而且命名为springcloud-service-provider-02,修改controlle响应结果内容,区别服务提供者(springcloud-service-provider)的内容。修改服务提供者(springcloud-service-provider-02)端口为8081,具体详细代码查看案例源码。注册中心咱们之后使用8700单节点,只是为了方便。

    三、在消费者的RestTemplate中添加以下代码:

//使用Ribbon实现负载均衡调用,默认是轮询
    @LoadBalanced //加入ribbon的支持,那么在调用时,便可改成使用服务名称来访问
 @Bean public RestTemplate restTemplate(){ return new RestTemplate(); }

    四、查看Eureka的web页面显示提供者两个实例

    五、启动消费者,进行访问如图:

 

 

 

provider-01和provider-02交替出现,能够看出默认是轮询策略。

3、Ribbon负载均衡策略

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

IRule实现类的负载策略含义

RandomRule

随机

RoundRobinRule

轮询

AvailabilityFilteringRule

先过滤掉因为屡次访问故障的服务,以及并发链接数超过阀值的服务,而后对剩下的服务按照轮询策略进行访问

WeightedResponseTimeRule

根据平均响应时间计算全部服务的权重,响应时间越快服务权重就越大被选中的几率即越高,若是服务刚启动时间统计信息不足,,则使用RoundRobinRule策略,待统计信息足够,会切换到该WeightedResponseTimeRule策略

RetryRule

先按照RoundRobinRule策略分发,若是分发到的服务不能访问,则在指定的时间内重试,若是不行的话,则分发到其余可用的服务

BestAvailableRule

先过滤掉因为屡次访问的故障的服务,而后选择一个并发量最小的服务

ZoneAvoidanceRule

综合判断服务节点所在区域的性能和服务节点的可用性,来决定选择哪一个服务

TIP:结合Ribbon负载均衡,默认的是轮询,从新注入IRule能够实现负载均衡的其余策略

4、Rest请求模板类解读

    当咱们从服务消费端去调用服务提供者的服务的时候,使用了一个极其方便的对象叫RestTemplate,当时咱们只使用了 RestTemplate中最简单的一个功能getForEntity发起了一个get请求去调用服务端的数据,同时,咱们还经过配置@Loadbalanced注解开启客户端负载均衡, RestTemplate的功能很是强大, 那么接下来就来详细的看一下RestTemplate中几种常见请求方法的使用。在平常操做中,基于Rest的方式一般是四种状况,它们分表是

  • GET请求-查询数据

  • POST请求-添加数据

  • PUT请求-修改数据

  • DELETE-删除数据

一、RestTemplate的GET请求

Get请求能够有两种方式

第一种:getForEntity(..)

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

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

    以上代码, getForEntity方法第—个参数为要调用的服务的地址,即服务提供者提供的http://SPRINGCLOUD-SERVICE-PROVIDER/provider/hello接口地址,注意这里是经过服务名调用而不是服务地址,若是改成服务地址就没法使用Ribbon实现客户端负载均衡了。getForEntity方法第二个参数String.class表示但愿返回的body类型是 String 类型,若是但愿返回一个对象,也是能够的,好比User对象

/** * 调用get请求,返回一个User对象 * @return
     */ @RequestMapping("/user") public User user(){ //逻辑判断省略
        ResponseEntity<User> forEntity = restTemplate.getForEntity("http://SPRINGCLOUD-SERVICE-PROVIDER/provider/user", User.class); System.out.println(forEntity.getBody().getId()+""+forEntity.getBody().getName()+""+forEntity.getBody().getPhone()); return forEntity.getBody(); }

另外两个重载方法:

 @Override 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)); } @Override 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)); }

好比:

/** * 给服务传参数Get请求 * @return
     */ @RequestMapping("/getUser") public User getUser(){ //逻辑判断省略
        String [] arr={"2","xxx","4545645456"}; Map<String,Object> map=new HashMap<>(); map.put("id",1); map.put("name","wwwwww"); map.put("phone","1213213213123"); //ResponseEntity<User> forEntity = restTemplate.getForEntity("http://SPRINGCLOUD-SERVICE-PROVIDER/provider/getUser?id={0}&name={1}&phone={2}", User.class,arr); //ResponseEntity<User> forEntity = restTemplate.getForEntity("http://SPRINGCLOUD-SERVICE-PROVIDER/provider/getUser?id={id}&name={name}&phone={phone}", User.class,map);
        /* * restTemplate.getForObject在getForObject在getForEntity在次封装,直接获取返回值类型,至关于ResponseEntity中的getBody */ User user1 = restTemplate.getForObject("http://SPRINGCLOUD-SERVICE-PROVIDER/provider/getUser?id={id}&name={name}&phone={phone}", User.class, map); //System.out.println(forEntity.getBody().getId()+""+forEntity.getBody().getName()+""+forEntity.getBody().getPhone());
        System.out.println(user1.getId()+""+user1.getName()+""+user1.getPhone()); return user1; }

    能够用一个数字作占位符,最后是一个可变长度的参数,来来替换前面的占位符也能够前面使用name={name}这种形式,最后一个参数是一个map,map的key即为前边占位符的名字,map的value为参数值

第二种:getForObject(..)

    与getForEntity使用相似,只不过getForobject是在getForEntity基础上进行了再次封装,能够将http的响应体body信息转化成指定的对象,方便咱们的代码开发,当你不须要返回响应中的其余信息,只须要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); }

上面例子已经涉及到了,此处再也不啰嗦。

二、RestTemplate的POST请求

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

例如:

/** * 调用POST请求 * @return
     */ @RequestMapping("/addUser") public User addUser(){ //逻辑判断省略
        String [] arr={"2","xxx","4545645456"}; //不能使用map传递参数
        Map<String,Object> map=new HashMap<>(); map.put("id",1); map.put("name","wwwwww"); map.put("phone","1213213213123"); /** *要传的表单信息,参数数据(很坑人) */ MultiValueMap<String,Object> multiValueMap=new LinkedMultiValueMap<>(); multiValueMap.add("id",1); multiValueMap.add("name","xxxxx"); multiValueMap.add("phone","000000000"); //使用jdk中的map传参数,接收不到
        ResponseEntity<User> userResponseEntity = restTemplate.postForEntity( "http://SPRINGCLOUD-SERVICE-PROVIDER/provider/addUser", multiValueMap, User.class); System.out.println(userResponseEntity.getBody().getId()+""+userResponseEntity.getBody().getName()+""+userResponseEntity.getBody().getPhone()); return userResponseEntity.getBody(); }

三、RestTemplate的PUT请求

restTemplate.put();

例如:

/** * 调用PUT请求 * @return
     */ @RequestMapping("/updateUser") public String updateUser(){ //逻辑判断省略
        String [] arr={"2","xxx","4545645456"}; //不能使用map传递参数
        Map<String,Object> map=new HashMap<>(); map.put("id",1); map.put("name","wwwwww"); map.put("phone","1213213213123"); /** *要传的表单信息,参数数据(很坑人) */ MultiValueMap<String,Object> multiValueMap=new LinkedMultiValueMap<>(); multiValueMap.add("id",1); multiValueMap.add("name","xxxxx"); multiValueMap.add("phone","000000000"); //使用jdk中的map传参数,接收不到
        restTemplate.put("http://SPRINGCLOUD-SERVICE-PROVIDER/provider/updateUser", multiValueMap); return "SUCCESS"; }

四、RestTemplate的DELETE请求

restTemplate.delete();

例如: 

/** * 调用DELETE请求 * @return
     */ @RequestMapping("/deleteUser") public String deleteUser(){ //逻辑判断省略
        String [] arr={"2","xxx","4545645456"}; Map<String,Object> map=new HashMap<>(); map.put("id",1); map.put("name","wwwwww"); map.put("phone","1213213213123"); /** *要传的表单信息,参数数据(很坑人),只有post,PUT请求采用这种map传参数 */
     /* MultiValueMap<String,Object> multiValueMap=new LinkedMultiValueMap<>(); multiValueMap.add("id",1); multiValueMap.add("name","xxxxx"); multiValueMap.add("phone","000000000"); */

        //使用jdk中的map传参数,接收不到,不能使用MultiValueMap,接收不到参数
        restTemplate.delete("http://SPRINGCLOUD-SERVICE-PROVIDER/provider/deleteUser?id={id}&name={name}&phone={phone}", map); return "SUCCESS"; }

 

详细参考案例源码:https://gitee.com/coding-farmer/springcloud-learn

 

 

相关文章
相关标签/搜索