Spring Cloud学习笔记(5)- 在分布式应用中使用客户端负载均衡(Ribbon)

159 阅读8分钟

负载均衡

负载均衡是指当出现大量访问请求时,服务器集群通过负载均衡机制将访问请求均衡的分配至单台服务器,保障服务访问的可用性的机制。负载均衡机制一般做法分为两种:

1.服务端负载均衡

一般来说常见的服务端负载均衡机制有以下一些常见的实现方式:

  • DNS域名解析负载均衡;假设我们的域名指向了多个IP地址,当一个域名请求来时,DNS服务器机进行域名解析将域名转换为IP地址是,在1:N的映射转换中实现负载均衡。DNS服务器提供简单的负载均衡算法,但当其中某台服务器出现故障时,通知DNS服务器移除当前故障IP。
  • 反向代理负载均衡;反向代理只值对服务器的代理,代理服务器接受请求,通过负载均衡算法,将请求转发给后端服务器,后端服务返回给代理服务器然后代理服务器返回到客户端。反向代理服务器的优点是隔离后端服务器和客户端,使用双网卡屏蔽真实服务器网络,安全性更好,相比较于DNS域名解决负载均衡,反向代理在故障处理方面更灵活,支持负载均衡算法的横向扩展。目前使用非常广泛。当然反向代理也需要考虑很多问题,比如单点故障,集群部署等。
  • IP负载均衡;我们都知道反向代理工作到HTTP层,本身开销相对大一些,对性能有一定影响,LVS-NAT是一种卫浴传输层的负载均衡,它通过修改接受的数据包目标地址的方式实现负载均衡。Linux2.6.x以后版本内置了IPVS,专注用于实现IP负载均衡,故而在Linux上IP负载均衡使用非常广泛。LVS-DR工作在数据链路层,比LVS-NAT更霸道的时候它直接修改数据包的MAC地址。LVS-TUN——基于IP隧道的请求转发机制,将调度器收到的IP数据包进行封装,转交给服务器,然后服务器返回数据,通过调度器实现负载均衡。这种方式支持跨网段调度。总结一下,LVS-DR和LVS-TUN都适合响应和请求不对称的Web服务器,如何从它们中做出选择,取决于你的网络部署需要,因为LVS-TUN可具有跨地域性,有类似这种需求的,就应该选择LVS-TUN。

由于服务器端负载均衡与当前讨论主题不一致,本章将不在赘述服务端负载均衡机制。

2.客户端负载均衡

客户端负载均衡一般是指客户端全面了解当前服务器集群每台服务器的部署情况,通过不同的配置策略通过API的方式将访问均衡的分配至每一台独立服务器的过程。

一般来说负载均衡策略有以下一些常用算法:

静态负载均衡算法

轮询(Round Robin):服务器按照顺序循环接受请求。
随机(Random):随机选择一台服务器接受请求。
权重(Weight):给每个服务器分配一个权重值,根据权重来分发请求到不同的机器中。
IP哈希(IP Hash):根据客户端IP计算Hash值取模访问对应服务器。
URL哈希(URL Hash):根据请求的URL地址计算Hash值取模访问对应服务器。
一致性哈希(Consistent Hash ):采用一致性Hash算法,相同IP或URL请求总是发送到同一服务器。

动态负载均衡算法

最少连接数(Least Connection):将请求分配给最少连接处理的服务器。
最快响应(Fastest Response):将请求分配给响应时间最快的服务器。
观察(Observed):以连接数和响应时间的平衡为依据请求服务器。
预测(Predictive):收集分析当前服务器性能指标,预测下个时间段内性能最佳服务器。
动态性能分配(Dynamic Ratio-APM):收集服务器各项性能参数,动态调整流量分配。
服务质量(QoS):根据服务质量选择服务器。
服务类型(ToS): 根据服务类型选择服务器。

一般来说Ribbon使用的算法如下所示:

Ribbon中内置的负载均衡策略

RoundRobinRule:轮询。
RandomRule:随机。
WeightedResponseTimeRule:根据响应时间来分配权重的方式,响应的越快,分配的值越大。
BestAvailableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务。
RetryRule:先按照轮询策略获取服务,如果获取服务失败则在指定时间内进行重试,获取可用的服务。
ZoneAvoidanceRule:根据性能和可用性选择服务。
AvailabilityFilteringRule:会先过滤掉由于多次访问故障而处于断路器状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问。

Ribbon的基础用法

Ribbon是Netfix发布的负载均衡组件,实际上是集成在Eureka Server组件中。Ribbon负责帮助开发人员控制HTTP、TCP访问。在日常开发中只需要为Ribbon配置好服务提供列表,Ribbon将基于负载均衡策略来计算出需要访问的目标服务地址。

Ribbon内置的负载均衡策略已经基本满足了当前大多数业务场景中负载均衡的需求,如果需要自定义负载均衡策略进仅仅需要实现IRule接口。

image-20210713152423372

上图清晰的展示了一个基本的请求流程,请注意:

1.Ribbon将在服务消费者端发现服务列表后,更新本地的服务列表信息。

2.服务消费者发出相关请求后,Ribbon将根据负载均衡算法计算出最终请求的特定服务提供者。请求将指向Ribbon计算出的最终服务提供者。

新建一个Ribbon工程

依据前几章的项目,复制microservice-provider-user项目至空白文件夹,然后重命名为microservice-consumer-movie-ribbon。

添加MovieController,其代码如下所示:

@RequestMapping("/movies")
@RestController
public class MovieController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/users/{id}")
    public User findById(@PathVariable Long id) {
        // 这里用到了RestTemplate的占位符能力
        User user = this.restTemplate.getForObject(
                "http://microservice-provider-user/users/{id}",
                User.class,
                id
        );
        // ...电影微服务的业务...
        return user;
    }
}

以上代码传递除了以下一些信息:

  1. MovieController并未添加任何引入诸如Ribbon组件的代码
  2. MovieController提供一个通过ID查询用户的接口方法
  3. MovieController的接口方法并不是直接通过数据库的方式来获取用户数据,而是访问了一个注册的微服务地址,通过RestTemplate来获取接口数据,而后返回。

在ProviderUserApplication中添加如下代码:

@SpringBootApplication
public class ProviderUserApplication {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }


    public static void main(String[] args) {
        SpringApplication.run(ProviderUserApplication.class, args);
    }

}

上述代码传递了如下信息:

  1. 在Spring Boot 启动时,通过注入的方式获取到RestTemplate.
  2. 在Bean注入方法上,添加了LoadBalanced注解。使用了这个注解以后 ,会在 restTemplate 里面 通过 restTemplate.setInterceptors 放入 LoadBalancerInterceptor ,这个过滤器会在 请求远程成接口的时候 动态判断请求的域是不是 负载 负载均衡支付的服务的地址,如果是,那么就会代理使用 这个负载均衡器来调用。

注意:

  1. 由于spring-cloud-starter-netflix-eureka-client 已经包含spring-cloud-starter-netfilx-ribbon ,故而无需额外添加依赖。
  2. 通过上述代码实现客户端负载均衡。
  3. 在MovieController代码中,将请求的目标服务改成了http://microservice-provider-user/users/{id} ,也就是http://{目标服务名称}/{目标服务端点} 的形式,Ribbon会自动在实际调用时,将目标服务名替换为该服务的IP和端口

启动方式 参见:Spring Cloud学习笔记(4)- 构建一个Eureka集群中的启动集群方式,然后运行microservice-provider-user,当microservice-provider-user已被注册到Eureka集群后,运行当前microservice-consumer-movie-ribbon项目,在浏览器中访问:

localhost:8010/movies/users/1

image-20210714155611793
我们已经通过调用服务来获取到了用户数据。

特别注意

事实上,这里的目标服务名称,在Ribbon里叫虚拟主机名 ,主机名是不能包含_ 等特殊字符的

这意味着,一般不建议配置spring.application.name = xxx_xxx ,如果你的应用名称一定带有下划线这种字符,那么请额外配置eureka.instance.virtual-host-name = 一个合法的主机名 ,否则Ribbon将会提示虚拟主机名不合法的异常(在早期的版本则是报空指针)!

代码参见:
Bruce.hong的阿里云

\