SpringCloud-3-负载均衡&服务调用

本文详细介绍了Spring Cloud Ribbon作为客户端负载均衡器的工作原理,包括轮询策略的实现,并探讨了如何替换不同的负载策略。同时,文章还深入讲解了OpenFeign在服务调用中的作用,它简化了REST模板的使用,实现了基于注解的接口调用。此外,还讨论了OpenFeign的超时控制和日志打印配置,帮助优化服务调用的性能和监控。
摘要由CSDN通过智能技术生成

1 Ribbon

1.1 Ribbon概述

SpringCloudRibbon是基于NetflixRibbon实现的一套客户端负载均衡的工具
主要功能是提供客户端的软件负载均衡算法和服务调用,Ribbon会提供一系列完善的配置项如连接超时、重试等。简单的说就是在配合文件中列出Load Balancer后面所有的机器,Ribbon会自动帮你基于某种规则(简单轮询、随机连接等)去连接这些机器

Load Balance负载均衡:
LB将用户的请求分摊到多个服务上,从而达到系统的高可用
常见的负载均衡由软件Nginx、LVS,硬件F5等

RibbonNginx的区别:
Nginx是服务器负载均衡(集中式LB)Ribbon是客户端负载均衡(进程内LB)

Niginx会收到客户端的所有请求,这些请求由Nginx来转发,LB是在服务端实现

Ribbon应用于客户端,当客户端调用微服务解救时,会在注册中心上获取注册表并缓存在本地JVM
从而根据某种策略选择某台机器在本地实现RPC远程服务调用

先前Consumer使用的spring-cloud-starter-netflix-eureka-client依赖中就已经包含了Ribbon
请添加图片描述

1.2 替换不同的负载策略

Ribbon的负载均衡功能起源于接口IRule
请添加图片描述
其实现:
在这里插入图片描述
这些具体实现就是客户端负载均衡策略的不同选择,默认使用轮询

RoundRobinRule: 轮询

RandomRule: 随机

RetryRule: 先按照轮询策略获取服务地址,如失败会在指定时间内重试

WeightedResponseTimeRule: 对轮询的扩展,响应速度越快的实例选择权重越大,越容易被选择

BestAvailableRule: 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务

AvailabilityFilteringRule: 先过滤掉故障实例,再选择并发较小的实例

ZoneAvoidanceRule: 默认规则,复和判断server所在区域的性能和server的可用性来选择服务器

想要使用Ribbon其他的负载均衡策略,只需通过简单的配置即可:
1、新建package,新建配置类配置负载策略Bean
请添加图片描述
2、在主启动类中使用@RibbonClient注解声明将要访问的微服务名和使用的负载策略
请添加图片描述

1.3 轮询策略分析

轮询的原理很简单,通过discoveryClient.getInstances(“CLOUD-PAYMENT-SERVICE”)
获得CLOUD-PAYMENT-SERVICE集群的所有机器地址信息(ip+port)
装在一个List中缓存在客户端本地JVM中

如此时集群中有两台机器,则:
List[0] instanceA = 127.0.0.1:8001
List[1] instanceB = 127.0.0.1:8002

轮询策略选择的机器 = 第n次请求数 % 集群中机器总数 = 数组中实例下标
如请求为1时 instance = List.get(1%2) = instanceB
此时选择了机器127.0.0.1:8002

回到源码,从IRule开始,choose方法就是从集群中选择机器的策略
请添加图片描述
找到IRule的一个实现类RoundRobinRule,即轮询策略:

public class RoundRobinRule extends AbstractLoadBalancerRule {
    private AtomicInteger nextServerCyclicCounter;
    private static final boolean AVAILABLE_ONLY_SERVERS = true;
    private static final boolean ALL_SERVERS = false;
    private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);

    public RoundRobinRule() {
        this.nextServerCyclicCounter = new AtomicInteger(0);
    }

    public RoundRobinRule(ILoadBalancer lb) {
        this();
        this.setLoadBalancer(lb);
    }

    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        } else {
            Server server = null;
            int count = 0;

            while(true) {
                if (server == null && count++ < 10) {
                	// 获取健康的服务器信息list
                    List<Server> reachableServers = lb.getReachableServers();
                    // 获取所有的服务器list
                    List<Server> allServers = lb.getAllServers();
                    int upCount = reachableServers.size();
                    int serverCount = allServers.size();
					
					// 集群中的可用服务器个数和全部服务器个数不为0才能继续选择
                    if (upCount != 0 && serverCount != 0) {
                    	// 根据当前请求数%集群中总机器数获取下标
                        int nextServerIndex = this.incrementAndGetModulo(serverCount);
                        // 根据下标从所有服务器list中获取服务器地址实例
                        server = (Server)allServers.get(nextServerIndex);
                        // 该地址不可用 之后继续重试
                        if (server == null) {
                            Thread.yield();
                        } else {
                        	// 该地址可用 直接返回并使用
                            if (server.isAlive() && server.isReadyToServe()) {
                                return server;
                            }

                            server = null;
                        }
                        continue;
                    }

                    log.warn("No up servers available from load balancer: " + lb);
                    return null;
                }
				
				// 重试10次后仍然失败 返回null
                if (count >= 10) {
                    log.warn("No available alive servers after 10 tries from load balancer: " + lb);
                }

                return server;
            }
        }
    }

    private int incrementAndGetModulo(int modulo) {
        int current;
        int next;
        do {
        	// 获得当前请求次数 这个次数是AtomicInteger
            current = this.nextServerCyclicCounter.get();
            next = (current + 1) % modulo;
          // 通过CAS来保证访问次数的原子性 保证多线程调用同一微服务时也依次轮询 
        } while(!this.nextServerCyclicCounter.compareAndSet(current, next));

        return next;
    }

    public Server choose(Object key) {
        return this.choose(this.getLoadBalancer(), key);
    }

    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

2 OpenFeign

2.1 OpenFeign简介

先前做服务调用时使用RestTemplate+Ribbon,RestTemplate需要指明调用的url
但在实际开发中,Consumer会调用多个Provider来完成业务,直接用RestTemplate显得复杂
为此在Feign的支持下,只需要创建一个接口并使用注解来配置,就可以绑定Provider

Feign已停止维护,目前可以使用OpenFeign代替
OpenFeignSpringCloudFeign的基础上支持了SpringMVC的注解
如@RequestMapping等等 
OpenFeign@FeignClient可以解析SpringMVC@RequestMapping下的接口
并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务

2.2 OpenFeign的使用

OpenFigen用于Consumer侧,通过微服务调用接口+@FeignClient调用Provider提供的Rest接口

新建子模块以Feign客户端形式调用支付服务
1、创建模块Cloud-Consumerfeign-Order80并引入依赖:

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

2、创建yaml文件并配置

server:
  port: 80

spring:
  application:
    name: cloud-order-service

eureka:
  instance:
    instance-id: order80
    prefer-ip-address: true
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka

3、完成主启动类,在主启动类上使用@EnableFeignClients注解
请添加图片描述
4、创建目标Provider的微服务接口并使用@FeignClient配置
Provider提供的Service:
请添加图片描述
在Consumer中创建对应的Feign接口,并使用@FeignClient注明Provider的名称
请添加图片描述
在Controller层调用Feign接口中的方法:
请添加图片描述
调用成功:
请添加图片描述
OpenFeign整合了RestTemplate+Ribbon,在Consumer侧编写对应的微服务接口并配置
就可以通过Rest方式访问到Provider的Controller层中的方法

2.3 OpenFeign补充

2.3.1 超时控制

当Consumer调用Provider的服务时,存在Provider业务复杂时可能导致超时的现象
OpenFeign客户端默认等待1s,超过1s后就会报TimeOut异常
为此可以在yaml中配置超时时间,这个超时时间由Ribbon决定

ribbon:
  # 建立连接所用的时间
  ReadTimeout: 5000
  # 建立连接后从服务端读取可用资源所用时间
  ConnectTimeout: 5000

2.3.2 日志打印

OpenFeign提供了日志打印功能,可以通过配置来调整日志级别
从而了解OpenFeign中Http请求的细节,即对OpenFeign接口的调用情况进行监控和输出

日志级别:

NONE: 默认,不显示任何日志

BASIC: 仅记录请求方法、URL、响应状态码及执行时间

HEADERS: 除了BASIC中定义的信息外,还有请求和响应的头信息

FULL: 除了HEADERS中定义的信息外,还有请求和响应的正文及元数据

开启日志功能需要配置相关的Bean:

@Configuration
public class FeignConfig {

    @Bean
    public Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }

}

在yaml中配置日志监控级别和接口:

logging:
  level:
    # 配置监控的接口 及监控级别
    com.coisini.springcloud.service.PaymentFeignService: debug

通过Feign提供的日志系统可以看到详细的Http调用细节:
请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值