微服务6:Ribbon负载均衡和服务调用

80 阅读6分钟
  • Spring Cloud Ribbon 是一套基于 Netflix Ribbon 实现的客户端负载均衡和服务调用工具。
  • Netflix Ribbon 是 Netflix 公司发布的开源组件,其主要功能是提供客户端的负载均衡算法和服务调用。Spring Cloud 将其与 Netflix 中的其他开源服务组件(例如 Eureka、Feign 以及 Hystrix 等)一起整合进 Spring Cloud Netflix 模块中,整合后全称为 Spring Cloud Netflix Ribbon。
  • Ribbon客户端组件提供一系列完善的配置项如来连接超时,重试等。简单来说即是在配置文件中列出Load Balance(负载均衡简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如轮询策略,随机连接)去连接这些机器,从而很容易使用Ribbon实现自定义的负载均衡算法

负载均衡:

服务端负载均衡


图1:服务端负载均衡工作原理

服务端负载均衡是在客户端和服务端之间建立一个独立的负载均衡服务器,该服务器既可以是硬件设备(例如 F5),也可以是软件(例如 Nginx)。这个负载均衡服务器维护了一份可用服务端清单,然后通过心跳机制来删除故障的服务端节点,以保证清单中的所有服务节点都是可以正常访问的。
当客户端发送请求时,该请求不会直接发送到服务端进行处理,而是全部交给负载均衡服务器,由负载均衡服务器按照某种算法(例如轮询、随机等),从其维护的可用服务清单中选择一个服务端,然后进行转发。

服务端负载均衡具有以下特点:

  • 需要建立一个独立的负载均衡服务器。

  • 负载均衡是在客户端发送请求后进行的,因此客户端并不知道到底是哪个服务端提供的服务。

  • 可用服务端清单存储在负载均衡服务器上。

客户端负载均衡

图2:客户端负载均衡工作原理

客户端负载均衡是将负载均衡逻辑以代码的形式封装到客户端上,即负载均衡器位于客户端。客户端通过服务注册中心(例如 Eureka Server)获取到一份服务端提供的可用服务清单。有了服务清单后,负载均衡器会在客户端发送请求前通过负载均衡算法选择一个服务端实例再进行访问,以达到负载均衡的目的;
客户端负载均衡也需要心跳机制去维护服务端清单的有效性,这个过程需要配合服务注册中心一起完成。

客户端负载均衡具有以下特点:

  • 负载均衡器位于客户端,不需要单独搭建一个负载均衡服务器。

  • 负载均衡是在客户端发送请求前进行的,因此客户端清楚地知道是哪个服务端提供的服务。

  • 客户端都维护了一份可用服务清单,而这份清单都是从服务注册中心获取的。

Ribbon 就是一个基于 HTTP 和 TCP 的客户端负载均衡器,当我们将 Ribbon 和 Eureka 一起使用时,Ribbon 会从 Eureka Server(服务注册中心)中获取服务端列表,然后通过负载均衡策略将请求分摊给多个服务提供者,从而达到负载均衡的目的。

服务端负载均衡 VS 客户端负载均衡

下面我们就来对比下,服务端负载均衡和客户端负载均衡到底有什么区别,如下表。

不同点服务端负载均衡客户端负载均衡
是否需要建立负载均衡服务器需要在客户端和服务端之间建立一个独立的负载均衡服务器。将负载均衡的逻辑以代码的形式封装到客户端上,因此不需要单独建立负载均衡服务器。
是否需要服务注册中心不需要服务注册中心。需要服务注册中心。 在客户端负载均衡中,所有的客户端和服务端都需要将其提供的服务注册到服务注册中心上。
可用服务清单存储的位置可用服务清单存储在位于客户端与服务器之间的负载均衡服务器上。所有的客户端都维护了一份可用服务清单,这些清单都是从服务注册中心获取的。
负载均衡的时机先将请求发送到负载均衡服务器,然后由负载均衡服务器通过负载均衡算法,在多个服务端之间选择一个进行访问;即在服务器端再进行负载均衡算法分配。 简单点说就是,先发送请求,再进行负载均衡。在发送请求前,由位于客户端的服务负载均衡器(例如 Ribbon)通过负载均衡算法选择一个服务器,然后进行访问。 简单点说就是,先进行负载均衡,再发送请求。
客户端是否了解服务提供方信息由于负载均衡是在客户端发送请求后进行的,因此客户端并不知道到底是哪个服务端提供的服务。负载均衡是在客户端发送请求前进行的,因此客户端清楚的知道是哪个服务端提供的服务。

服务调用:

所谓Ribbon即是 负载均衡+RestTemplate(Rest 模板)配合使用,以实现微服务之间的调用

注意:有些spring-cloud-starter-netflix-eureka-clientpom依赖(如2.2.1),其Eureka自主携带者Ribbon依赖,可不用再次单独引入。我此处测试环境版本为3.1.1经了解官方已经移除,但是我的负载均衡轮询确实在没有引入Ribbon依赖时生效(参见微服务6),所以建议主动引入最为保险

  1. 在需要进行负载均衡的集群式客户端模块pom中添加依赖
<!-- spring-cloud-starter-netflix-ribbon:ribbon负载均衡和服务调用组件 -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
  <version>2.2.10.RELEASE</version>
</dependency>

2. 再此深入RestTemplate

  • RestTemplate是一个同步的web http客户端请求模板工具
  • 官方文档

docs.spring.io/spring-fram…

  • 常用请求方法:
    • GET请求:
      • getForObject():返回对象为响应体中数据转化成的对象,基本上可以理解为Json
      • getForEntity():返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等
    • PSOT请求:
      • postForObject():返回对象为响应体中数据转化成的对象,基本上可以理解为Json
      • postForEntity():返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等
    • 通用请求:exchange():可以用get也可以用post
  • 微服务中的Rest调用见微服务6,不在赘述
  • 封装请求拼装辅助工具类见《HeavenEarth》httpUtil.java,用法举例
@Resource
private RestTemplate restTemplate;


/**
* 递归协助部门查询岗位
*
* @param dingDeptId
* @param page
* @param postResultDTOList
* @return
*/
public List<PostResultDTO> postPageRecursion(List<String> dingDeptId, int page, List<PostResultDTO> postResultDTOList) {
    //        String url = "http://192.168.1.200:8920/post/getPostListByDeptIds";
    
    Map<String, Object> jsonMap = new HashMap<>();
    jsonMap.put("deptIds", dingDeptId);
    jsonMap.put("source", dingTalkConfig.getSource());
    jsonMap.put("page", page);
    jsonMap.put("rows", 200);
    ResponseEntity<PostAccessDTO> postAccessDTOResponseEntity = restTemplate.postForEntity(dingTalkConfig.getPostUrl(), httpUtil.generatePostJson(jsonMap), PostAccessDTO.class);
    List<PostResultDTO> obj = postAccessDTOResponseEntity.getBody().getObj();
    
    postResultDTOList.addAll(obj);
    if (ObjectUtil.isNotEmpty(obj)) {
        
        page += 1;
        postPageRecursion(dingDeptId, page, postResultDTOList);
    }
    
    return postResultDTOList;
}


/**
* 递归协助岗位查询人员
*
* @param postId
* @param page
* @param userResultDTOList
* @return
*/
public List<UserResultDTO> UserPageRecursion(String postId, int page, List<UserResultDTO> userResultDTOList) {
    //        String url = "192.168.1.200:8920/post/getPersonListByPostId";
    
    Map<String, Object> urlMap = new HashMap<>();
    urlMap.put("postId", postId);
    urlMap.put("source", dingTalkConfig.getSource());
    urlMap.put("page", page);
    urlMap.put("rows", 200);
    ResponseEntity<PostUserAccessDTO> postUserAccessDTOResponseEntity = restTemplate.getForEntity(httpUtil.generateRequestParameters("http", dingTalkConfig.getPostUserUrl(), urlMap), PostUserAccessDTO.class);
    List<UserResultDTO> obj = postUserAccessDTOResponseEntity.getBody().getObj();
    
    userResultDTOList.addAll(obj);
    if (ObjectUtil.isNotEmpty(obj)) {
        
        page += 1;
        UserPageRecursion(postId, page, userResultDTOList);
    }
    
    return userResultDTOList;
}