SpringCLoud--Ribbon--负载均衡器

Ribbon–负载均衡器

1、什么是Ribbon

​ Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们通过使用简单的RestTemplate来完成客户端负载均衡的服务调用,即Spring Cloud Ribbon = RestTemplate + @LoadBalanced

2、Ribbon负载均衡的特点

​ 负载均衡在系统架构中是一个非常重要,并且是不得不去实施的内容。因为负载均衡是对系统的高可用、网络压力的缓解和处理能力扩容的重要手段之一。负载均衡可以分为两大类:**服务端负载均衡和客户端负载均衡。**Ribbon属于客户端负载均衡的一种技术。

服务端负载均衡

​ 可以分为硬件负载均衡和软件负载均衡。硬件负载均衡主要通过在服务器节点之间按照专门用于负载均衡的设备,比如F5等;而软件负载均衡则是通过在服务器上安装一些用于负载均衡功能或模块等软件来完成请求分发工作,比如Nginx等。不论采用硬件负载均衡还是软件负载均衡,只要是服务端都能以类似下图的架构方式构建起来:
在这里插入图片描述
​ 服务端负载均衡实质上都是维护了一个下挂可用的服务端清单,通过心跳检测来剔除故障的服务端节点以保证清单中都是可以正常访问的服务端节点。当客户端发送请求到负载均衡设备的时候,该设备按某种算法(比如线性轮询、按权重负载、按流量负载等)从维护的可用服务端清单中取出一台服务端端地址,然后进行转发。

客户端负载均衡

​ 客户端负载均衡和服务端负载均衡最大的不同点在于上面所提到服务清单所存储的位置。在客户端负载均衡中,所有客户端节点都维护着自己要访问的服务端清单,而这些服务端端清单来自于服务注册中心,比如Eureka服务端。同服务端负载均衡的架构类似,在客户端负载均衡中也需要心跳去维护服务端清单的健康性,在Eureka中,默认每一次发送心跳时获取最新的服务清单。

​ Ribbon就是一个常用的客户端负载均衡的类库,各种服务注册中心都已经默认自动化整合了它,例如:Eureka中的org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,Consul中的org.springframework.cloud.consul.discovery.RibbonConsulAutoConfiguration。

3、Ribbon的使用

​ 上面说到,Ribbon是被常用的服务注册中心集成并且自动配置的,所以当我们已经在项目中导入了Eureka等的依赖后,就不需要再次导入Ribbon的依赖了。当然,如果非要这么做,可以使用下面的依赖:

<dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

​ 引入依赖之后,我们只需要下面简单的两步就可以完成Ribbon的使用:

​ 第一步: 服务提供者只需要启动多个服务实例并注册到一个注册中心或是多个相关联的服务注册中心。

​ 第二步: 服务消费者直接通过调用被**@LoadBalanced**注解修饰过的RestTemplate来实现面向服务的接口调用。

@Configuration
public class ApplicationContextConfig {
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

​ 这样,我们就可以将服务提供者的高可用以及服务消费者的负载均衡调用一起实现了。

​ 所以,总的来讲,Spring Cloud Ribbon就是使用Netflix Ribbon的机制对RestTemplate进行了配置,我们可以通过调用配置过的RestTemplate来完成服务的调用和负载均衡。

4、RestTemplate的使用

​ 上面提到,Spring Cloud Ribbon = RestTemplate + @LoadBalanced,所以想要更好的使用Ribbon,必须学会使用RestTemplate。下面我们将详细介绍RestTemplate针对几种不同请求类型和参数类型的服务调用实现。

GET请求:

​ 在RestTemplate中,对GET请求可以通过如下两个方法进行调用实现。

  • 第一种:getForEntity函数。

    该方法返回的是ResponseEntity,该对象是Spring对HTTP请求响应的封装,其中主要存储了HTTP的几个重要元素,比如HTTP请求状态码的枚举对象HttpStatus(也就是我们常说的404、500这些错误码)、在它的父类HttpEntity中还存储着HTTP请求的头信息对象HttpHeaders以及泛型类型的请求体对象。比如下面的例子,就是访问USER-SERVER服务端/user请求,同时最后一个参数didi会替换url中的{1}占位符,而返回的ResponseEntity对象的body内容类型会根据第二个参数转换为String类型。
    在这里插入图片描述

上面的例子是比较常用的方法,getForEntity函数实际上提供了以下三种不同的重载实现。

▪️getForEntity(String url, Class responseType,Object… urlVariables);

​ 该方法提供了三个参数,其中url为请求的地址,responseType为请求响应体body的包装类型,urlVariables为url中的参数绑定。GET请求的参数绑定通过使用url中拼接的方式,比如http://USER-SERVICE/user?name=didi,我们可以像这样自己将参数拼接到 url中,但更好的方法是在url中使用占位符并配合urlVariables参数实现GET请求的参数绑定,比如url定义为:getForEntity(“http://USER-SERVICE/user?name={1}”, String.class, “didi”),其中第三个参数didi会替换掉url中的{1}站位符。这里需要注意的是,由于urlVariables参数是一个数组,所以它的顺序会对应url中占位符定义的数字顺序。

▪️getForEntity(String url, Class responseType, Map urlVariables);

​ 该方法提供的参数重,只有urlVariables的参数类型与上面的方法不同。这里使用了Map类型,所以使用该方法进行参数绑定时需要再占位符中指定Map中的参数的key值,比如url定义为http://USER-SERVICE/user?name={name},在Map类型的urlVariables中,我们就需要put一个key为name的参数来绑定url中{name}占位符的值,比如:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jCV5HEdP-1596107291429)(D:\md图片\1596098267483.png)]

▪️getForEntity(URI url, Class responseType)

​ 该方法使用uri对象来代替之前url和urlVariables参数来指定访问地址和参数绑定。URI是JDK java.net包下单一个类,它表示一个统一资源标识符(Uniform Resource Identifier)引用,比如下面的例子:
在这里插入图片描述

  • 第二种:getForObject函数。

    该方法可以理解为对getForEntity的进一步封装,它通过HttpMessageConverterExtractor对

    HTTP的请求响应体body内容进行对象转换,实现请求直接返回包装好的对象内容。比如:

RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject(url, String.class);

当body是一个User对象时,可以直接这样实现:

RestTemplate restTemplate = new RestTemplate();
User result = restTemplate.getForObject(url, User.class);

​ 当不需要关注请求响应除body外的其他内容时,该函数就非常好用,可以少一个从Response中获取body的步骤。它与getForEntity函数类似,也提供了三种不同的重载实现。

▪️getForObject(String url, Class responseType, Object … urlVariables)

▪️getForObject(String url, Class responseType, Map urlVariables)

▪️getForObject(URI url, Class responseType)


POST请求:

​ 在RestTemplate中,对POST请求时可以通过如下三个方法调用实现。

  • 第一种:postForEntity函数

​ 该方法同GET请求中的getForEntity类似,会在调用后返回ResponseEntity对象,其中T为请求响应的body类型。比如下面这个例子,使用postForEntity提交POST请求到USER-SERVICE服务的/user接口,提交的body内容为user对象,请求响应返回的body类型为String。
在这里插入图片描述
▪️postForEntity(String url, Object request, Class responseType, Object… uriVariables)

▪️postForEntity(String url, Object request, Class responseType, Map uriVariables)

▪️postForEntity(URI url, Object request, Class responseType)

​ 这些函数中的参数用法大部分与getForEntity一致,比如,第一个重载函数和第二个重载函数中的uriVariables参数都用来对url中的参数进行绑定使用;responseType参数是对请求响应的body内容的类型定义。这里需要注意的是新增加的request参数,该参数可以是一个普通对象,也可以是一个HttpEntity对象。如果是普通对象,而非HttpEntity对象的时候,RestTemplate会将请求对象转换为一个HttpEntity对象来处理;其中Object就是request的类型,request内容会呗视作完整的body来处理;而如果request是一个HttpEntity对象,那么就会被当作一个完成的HTTP请求对象来处理,这个request中不仅包含了body的内容,也包含了header的内容。

  • 第二种: postForObject函数

    该方法也跟getForObject的类型类似,它的作用就是简化postForEntity的后续处理。通过直接将请求响应的body内容包装成对象来返回使用,比如下面的例子:
    在这里插入图片描述

  • postForObject(String url, Object request, Class responseType, Object… uriVariables)

  • postForObject(String url, Object request, Class responseType, Map uriVariables)

  • postForObject(URI url, Object request, Class responseType)

    这三个函数除了返回的对象类型不同,函数的传入参数均与postForEntity一致,因此可

    参考之前postForEntity的说明。

  • 第三种:postForLocation函数

    该方法实现了以POST请求提交资源,并返回新的资源的URI,比如下面的例子:
    在这里插入图片描述
    postForLocation函数也实现了三种不同的重载方法:

  • postForLocation(String url, Object request, Object… uriVariables)

  • postForLocation(String url, Object request, Map uriVariables)

  • postForLocation(URI url, Object request)

    ​ 由于postForLocation函数会返回新资源的URI,该URI就相当于指定了返回类型,所以此方法实现的POST请求不需要像postForEntity和postForObject那样指定responseType。其他的参数用法相同。

    PUT和DELETE的用法此处就不再叙述,可参见:
    https://docs.spring.io/spring-framework/docs/5.2.2.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html

5、RestTemplate如何实现负载均衡

​ RestTemplate本身只是Spring出品的一个用于发送rest请求的工具类,并不具有负载均衡的能力, 那它跟Ribbon的客户端负载均衡又有什么关系呢?

​ 前面提到过,RestTemplate想要实现Ribbon的负载均衡功能,必须要在RestTemplate的bean上再添加一个@LoadBalanced注解,一切都是从这个注解开始的。

​ 第一步:当RestTemplate的bean被@LoadBalanced修饰后,RestTemplate就可以使用 LoadBalancerClient。此时相当于同时向容器中注入了RestTemplate的bean和LoadBalancerClient的实现bean(RibbonLoadBalancerClient )。

/**
 * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient
 * @author Spencer Gibb
 */
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}

​ 第二步: LoadBalancerClient是一个接口,意为负载均衡器,只有一个实现类RibbonLoadBalancerClient,该接口中有四个方法 :

public interface ServiceInstanceChooser {
    //根据serviceId获取一个服务实例
    ServiceInstance choose(String serviceId);
}

public interface LoadBalancerClient extends ServiceInstanceChooser {
	//根据获取到的服务实例执行请求
    <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;

    <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
	//重构URI,根据服务实例和原生的uri重新拼接得到服务器的真实uri
    //因为服务消费者在调用服务提供者的时候是通过服务名代替ip+port调用的,即:
    //http://serviceId/uri,而不是http://ip:port/uri,所以需要转换
    //ServiceInstance封装这真实服务器的ip和port,再拼接上原生的uri即可得到真实的url
    URI reconstructURI(ServiceInstance instance, URI original);
}

​ 所以,总的来讲:LoadBalancerClient的作用就是:

​ 1、根据serviceId获取一个服务实例

​ 2、根据服务实例,将服务别名转换成真实url

​ 3、根据真实url完成rest请求

​ 第三步:当容器中存在RestTemplate的bean和LoadBalancerClient的实现Bean, LoadBalancerAutoConfiguration就会生效,来自动装配LoadBalancerClient,它的核心作用就是得到一个 LoadBalancerInterceptor,这个拦截器的作用主要是在客户端通过RestTemplate发起请求时进行拦截,进而实现客户端负载均衡功能。

​ 第四步:拦截器拦截到RestTemplate发送的请求后,会调用LoadBalancerClient中的方法完成负载均衡、url转化和真实的请求,LoadBalancerClient只有一个实现类,就是RibbonLoadBalancerClient,实际上RestTemplate发送的请求,最终都是由RibbonLoadBalancerClient完成的,通过这样做,我们就可以使用简单的RestTemplate来完成Ribbon的操作,完成负载均衡服务调用。

​ 总的来讲:

​ @LoadBalanced修饰RestTemplate Bean触发了RestTemplate和RibbonLoadBalancerClient进入容器,进而触发了LoadBalancerAutoConfiguration,获得直观重要的LoadBalancerInterceptor。

​ 最后使用的时候,RestTemplate发起一个请求,这个请求被LoadBalancerInterceptor拦截,拦截后将请求交给RibbonLoadBalancerClient,再由它使用一定的负载均衡策略根据serviceId获得一个服务实例,并且将请求地址中的服务逻辑名转为具体的服务地址,然后继续执行请求,就是这么一个过程。

6、更换Ribbon的默认负载均衡策略

​ 默认情况下,自动装配好的RestTemplate使用的负载均衡策略是轮询,即根据服务清单,一次访问各个服务实例。而Ribbon给我们提供的负载均衡策略不止轮询这一种,还有一些其他的策略:
在这里插入图片描述在这里插入图片描述
​ 我们可以通过下面的步骤修改默认的轮询策略:

​ 第一步:创建配置类MySelfRuler

@Configuration
public class MySelfRuler {
    @Bean
    public IRule mySelfRule(){
        //将默认的轮询算法替换成随机算法
        //此处可以使用其他官方提供的算法,也可以自定义算法
        return new RandomRule();
    }
}

​ 第二步:在服务消费者主配置类上使用@RibbonClient注解

@SpringBootApplication
@EnableEurekaClient
//指明本服务是一个消费者,消费CLOUD-PAYMENT-SERVICE
//使用的配置为自定义配置MySelfRuler
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRuler.class)
public class OrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain80.class,args);
    }
}

​ 但是要注意的是:

​ 官方给出明确警告,自定义的规则配置类不能@ComponentScan注解所在包及其子包下,否则自定义的配置类会被所有的Ribbon客户端共享,不能够起到服务特殊化定制的目的。对于单个的SpringBoot程序来讲,MySelfRuler不能放在主启动类所在包及其子包下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值