负载均衡
一、负载均衡Ribbon
1.1Spring Cloud Ribbon
它是一套基于 Netflix Ribbon 实现的客户端负载均衡和服务调用工具。
Netflix Ribbon 是 Netflix 公司发布的开源组件,其主要功能是提供客户端的负载均衡算法和服务调用。
Spring Cloud 将其与 Netflix 中的其他开源服务组件(例如 Eureka、Feign 以及 Hystrix 等)一起整合进 Spring Cloud Netflix 模块中,整合后全称为Spring Cloud Netflix Ribbon。
Ribbon 是 Spring Cloud Netflix 模块的子模块,它是 Spring Cloud 对 Netflix Ribbon 的二次封装。通过它,我们可以将面向服务的 REST 模板(RestTemplate)请求转换为客户端负载均衡的服务调用。
Ribbon 是 Spring Cloud 体系中最核心、最重要的组件之一。它虽然只是一个工具类型的框架,并不像Eureka Server(服务注册中心)那样需要独立部署,但它几乎存在于每一个使用 Spring Cloud 构建的微服务中。
Spring Cloud 微服务之间的调用,API 网关的请求转发等内容,实际上都是通过 Spring Cloud Ribbon来实现的,包括后续我们要介绍的 OpenFeign 也是基于它实现的。
1.2 什么是负载均衡
在任何一个系统中,负载均衡都是一个十分重要且不得不去实施的内容,它是系统处理高并发、缓解网络压力和服务端扩容的重要手段之一。
负载均衡(Load Balance) ,简单点说就是将用户的请求平摊分配到多个服务器上运行,以达到扩展服务器带宽、增强数据处理能力、增加吞吐量、提高网络的可用性和灵活性的目的。
常见的负载均衡方式有两种:
- 服务端负载均衡
- 客户端负载均衡
服务端负载均衡
服务端负载均衡是最常见的负载均衡方式,其工作原理如下图。
服务端负载均衡是在客户端和服务端之间建立一个独立的负载均衡服务器,该服务器既可以是硬件设备(例如 F5),也可以是软件(例如 Nginx)。
这个负载均衡服务器维护了一份可用服务端清单,然后通过心跳机制来删除故障的服务端节点,以保证清单中的所有服务节点都是可以正常访问的。
当客户端发送请求时,该请求不会直接发送到服务端进行处理,而是全部交给负载均衡服务器,由负载均衡服务器按照某种算法(例如轮询、随机等),从其维护的可用服务清单中选择一个服务端,然后进行转发。
服务端负载均衡具有以下特点:
- 需要建立一个独立的负载均衡服务器。
- 负载均衡是在客户端发送请求后进行的,因此客户端并不知道到底是哪个服务端提供的服务。
- 可用服务端清单存储在负载均衡服务器上。
客户端负载均衡
相较于服务端负载均衡,客户端服务在均衡则是一个比较小众的概念。
客户端负载均衡的工作原理如下图。
客户端负载均衡是将负载均衡逻辑以代码的形式封装到客户端上,即负载均衡器位于客户端。客户端通过服务注册中心(例如 Eureka Server)获取到一份服务端提供的可用服务清单。有了服务清单后,负载均衡器会在客户端发送请求前通过负载均衡算法选择一个服务端实例再进行访问,以达到负载均衡的目的;
客户端负载均衡也需要心跳机制去维护服务端清单的有效性,这个过程需要配合服务注册中心一起完成。
客户端负载均衡具有以下特点:
- 负载均衡器位于客户端,不需要单独搭建一个负载均衡服务器。
- 负载均衡是在客户端发送请求前进行的,因此客户端清楚地知道是哪个服务端提供的服务。
- 客户端都维护了一份可用服务清单,而这份清单都是从服务注册中心获取的。
Ribbon 就是一个基于 HTTP 和 TCP 的客户端负载均衡器,当我们将 Ribbon 和 Eureka 一起使用时,Ribbon 会从 Eureka Server(服务注册中心)中获取服务端列表,然后通过负载均衡策略将请求分摊给多个服务提供者,从而达到负载均衡的目的。
服务端负载均衡 VS 客户端负载均衡
下面我们就来对比下,服务端负载均衡和客户端负载均衡到底有什么区别,如下表。
不同点 | 服务端负载均衡 | 客户端负载均衡 |
---|---|---|
是否需要建立负载均衡服务器 | 需要在客户端和服务端之间建立一个独立的负载均衡服务器。 | 将负载均衡的逻辑以代码的形式封装到客户端上,因此不需要单独建立负载均衡服务器。 |
是否需要服务注册中心 | 不需要服务注册中心。 | 需要服务注册中心。 在客户端负载均衡中,所有的客户端和服务端都需要将其提供的服务注册到服务注册中心上。 |
可用服务清单存储的位置 | 可用服务清单存储在位于客户端与服务器之间的负载均衡服务器上。 | 所有的客户端都维护了一份可用服务清单,这些清单都是从服务注册中心获取的。 |
负载均衡的时机 | 先将请求发送到负载均衡服务器,然后由负载均衡服务器通过负载均衡算法,在多个服务端之间选择一个进行访问;即在服务器端再进行负载均衡算法分配。 简单点说就是,先发送请求,再进行负载均衡。 | 在发送请求前,由位于客户端的服务负载均衡器(例如 Ribbon)通过负载均衡算法选择一个服务器,然后进行访问。 简单点说就是,先进行负载均衡,再发送请求。 |
客户端是否了解服务提供方信息 | 由于负载均衡是在客户端发送请求后进行的,因此客户端并不知道到底是哪个服务端提供的服务。 | 负载均衡是在客户端发送请求前进行的,因此客户端清楚的知道是哪个服务端提供的服务。 |
1.3 Ribbon架构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
Ribbon在工作时分成两步
第一步先选择 EurekaServer ,它优先选择在同一个区域内负载较少的server.
第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。
其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。
1.4 Ribbon负载均衡使用
因为Eureka中已经集成了Ribbon,所以我们无需引入新的依赖。如果不放心,可以单独引入
Ribbon!
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
1.4.1 RestTemplate的使用
RestTemplate是远程访问的客户端工具,在RestTemplate中,提供了xxxForObject和
xxxForEntity方法。
xxxForObject
返回对象为响应体中数据转化成的对象,基本上可以理解为Json
xxxForEntity
返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等
RestTemplate相关方法
GET请求方法 |
---|
T getForObject(String url, Class responseType, Object… uriVariables); |
T getForObject(String url, Class responseType, Map<String, ?> uriVariables); |
T getForObject(URI url, Class responseType); |
ResponseEntity getForEntity(String url, Class responseType, Object… uriVariables); |
ResponseEntity getForEntity(String url, Class responseType, Map<String, ?>uriVariables); |
ResponseEntity getForEntity(URI var1, Class responseType); |
POST请求方法 |
T postForObject(String url, @Nullable Object request, Class responseType, Object…uriVariables); |
T postForObject(String url, @Nullable Object request, Class responseType, Map<String, ?> uriVariables); |
T postForObject(URI url, @Nullable Object request, Class responseType); |
ResponseEntity postForEntity(String url, @Nullable Object request, Class responseType,Object… uriVariables); |
ResponseEntity postForEntity(String url, @Nullable Object request, Class responseType,Map<String, ?> uriVariables); |
ResponseEntity postForEntity(URI url, @Nullable Object request, Class responseType); |
1.4.2 修改配置Bean
@Configuration
public class ConfigBean {
@Bean
@LoadBalanced // 没有LoadBalanced不能按服务名访问 已经负载均衡了
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
1.4.3复制EasyProductPart7081为新模块EasyProductPart7082
新建数据库easy_productdb2 执行sql,改一下数据加以区分
easy_productdb数据库里的数据:
easy_productdb2数据库里的数据:
pom:
<artifactId>EasyProductPart7082</artifactId>
application.yml:
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/easy_productdb2?
serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8 # ##改##
username: root
password: mysql
application:
name: easybuyproduct # 这里不变
server:
port: 7082 # ####
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
type-aliases-package: domain.mapper.productdb
eureka:
client:
service-url:
defaultZone: http://host1.com:7001/eureka
#,http://host3.com:7003/eureka
instance:
instance-id: easybuyproduct7082 # ####
prefer-ip-address: true
info:
app:
name: easybuyproduct
company:
name: this is it
build:
artifactId: EasyProductPart7082 # ####
version: 1.0-SNAPSHOT
#放开监控端点
management:
endpoints:
web:
exposure:
include: "*"
父工程添加模块EasyProductPart7082
<module>EasyProductPart7082</module>
访问:localhost:9081/products
第二次刷新:
再次刷新(又变回来了):
由上面结果可以看出,实现了负载均衡,默认使用的是轮询算法,故每次刷新访问的服务器不同!
1.4.4 IRule
根据特定算法中从服务列表中选取一个要访问的服务,在Ribbon中,默认自带有七种负载均衡算法
IRule实现类 | 名称 |
---|---|
com.netflix.loadbalancer.RoundRobinRule | 轮询算法 |
com.netflix.loadbalancer.RandomRule | 随机算法 |
com.netflix.loadbalancer.RetryRule | 先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务 |
WeightedResponseTimeRule | 对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择 |
BestAvailableRule | 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务 |
AvailabilityFilteringRule | 先过滤掉故障实例,再选择并发较小的实例 |
ZoneAvoidanceRule | 默认规则,复合判断server所在区域的性能和server的可用性选择服务器 |
1.5 替换默认算法
官方文档明确给出了警告:这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及
子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。
在客户端新建包:
定义类:新建MyRuleConfig规则类,用于替换默认的规则
package balance;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyRuleConfig {
@Bean
public IRule generateRule(){
return new RandomRule();
}
}
修改主启动类:在主启动类添加@RibbonClient
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) //不连数据库
@RibbonClient(name = "EASYBUYPRODUCT",configuration = MyRuleConfig.class)
public class EasyClientApplication {
public static void main(String[] args) {
SpringApplication.run(EasyClientApplication.class, args);
}
}
注意点:这些配置和修改都是在客户端上进行的!!!
进行测试(访问http://localhost:9081/products):
将EurakaServer7001服务打开,然后将两台商品服务端7082和7081都打开,再打开客户端9081.
随机访问两个服务器!没有规律。这就是随机算法进行访问!