欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

[SpringCloud学习笔记3]SpringCloud服务调用(ribbon,openFeign)

程序员文章站 2022-07-12 23:26:27
...

SpringCloud服务调用

这次学习的服务调用主要是ribbon和openFeign的应用

一、ribbon的负载均衡和RestTemplate

1.负载均衡

在调用eurake的时候,其pom文件默认帮我们调用了ribbon,所以能实现默认的轮询方式的负载均衡功能.

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-netflix-ribbon</artifactId>
  <version>2.2.1.RELEASE</version>
  <scope>compile</scope>
  <optional>true</optional>
</dependency>

查询ribbon源码,其调用规则是IRule

package com.netflix.loadbalancer;

public interface IRule {
    Server choose(Object var1);

    void setLoadBalancer(ILoadBalancer var1);

    ILoadBalancer getLoadBalancer();
}

查看其源码可以发现其实现类

[SpringCloud学习笔记3]SpringCloud服务调用(ribbon,openFeign)

  • com.netflix.loadbalancer.RoundRobinRule 实现轮询选择

  • com.netflix.loadbalancer.RandomRule 实现随机选择

  • com.netflix.loadbalancer.RetryRule 先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试

  • com.netflix.loadbalancer.WeightedResponseTimeRule 对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择

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

  • com.netflix.loadbalancer.AvailabilityFilteringRule 先过滤掉故障实例,再选择并发较小的实例

  • com.netflix.loadbalancer.ZoneAvoidanceRule 默认规则,复合判断server所在区域的性能和server的可用性选择服务器

默认调用的就是轮询选择

2.RestTemplate的方法区别

模板获取对象方法有以下几种

getForObject方法/getForEntity方法

postForObject/postForEntity方法

其实区别不大,就是object的返回值是json串,entitity返回的是一个带有很多头信息等等信息的对象

3.rule切换

1.新建子包

默认规则是轮询,现在可以把它改成随机,根据官网所述,不能将新的rule放入到@componetScan注解能够扫描到的地方,所以先新建一个包.

2.新建一个Myrule类

@Component
public class MyRule {
    @Bean
    public IRule randomRule() {
        return new RandomRule();
    }
}

3.在主方法上声明调用

//必须指定name和configuration
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MyRule.class)

4.轮询算法源码探究

轮询算法底层还是挺简单的,就是用CAS和一个循环.

源码分析如下:

public class RoundRobinRule extends AbstractLoadBalancerRule {
    //声明一个AtomicInteger类
    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);
	//初始化AtomicInteger参数为0
    public RoundRobinRule() {
        this.nextServerCyclicCounter = new AtomicInteger(0);
    }
	//初始化ILoadBalancer
    public RoundRobinRule(ILoadBalancer lb) {
        this();
        this.setLoadBalancer(lb);
    }

    public Server choose(ILoadBalancer lb, Object key) {
        //如果为空,那就提示没有ILoadBalancer
        if (lb == null) {
            log.warn("no load balancer");
            return null;
            
        } else {
            //如果ILoadBalancer非空,就开始计数
            Server server = null;
            int count = 0;
			//开启一个循环
            while(true) {
                //默认是10次为上限,每次重启都会重置count
                if (server == null && count++ < 10) {
                    //获取所有可用的服务器
                    List<Server> reachableServers = lb.getReachableServers();
                    //获取所有服务器
                    List<Server> allServers = lb.getAllServers();
                    //获取所有可用的服务器数量
                    int upCount = reachableServers.size();
                    //获取所有服务器数量
                    int serverCount = allServers.size();
                    if (upCount != 0 && serverCount != 0) {
                        //调用下面的incrementAndGetModulo函数来选择几号服务器
                        int nextServerIndex = this.incrementAndGetModulo(serverCount);
                        //调用对应编号服务器
                        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;
                }

                if (count >= 10) {
                    log.warn("No available alive servers after 10 tries from load balancer: " + lb);
                }

                return server;
            }
        }
    }
	//核心算法
    private int incrementAndGetModulo(int modulo) {
        //初始化参数,传入modulo为总数
        int current;
        int next;
        //每次用一次就+1%总数
        do {
            current = this.nextServerCyclicCounter.get();
            next = (current + 1) % modulo;
        } while(!this.nextServerCyclicCounter.compareAndSet(current, next));
		//返回服务器编号
        return next;
    }

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

    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

二、openFeign的入门

源码及说明文档

Declarative REST Client: Feign creates a dynamic implementation of an interface decorated with JAX-RS or Spring MVC annotations

Feign是一个声明式的web服务客户端,让编写web服务客户端变得非常容易,只需创建一个接口并在接口上添加注解即可

1.openFeign服务调用

1.修改pom

<!--openfeign里面引入了ribbon-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--eureka client-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

2.编写appliciation.yml

server:
  port: 80
eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/

3.编写主方法

@SpringBootApplication
//开启openfeign
@EnableFeignClients
public class FeignOrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(FeignOrderMain80.class,args);
    }
}

4.开启feign注解的接口服务

@Component
@FeignClient(name = "CLOUD-PAYMENT-SERVICE")
public interface FeignOrderService {
    @GetMapping("/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) ;
}

5.控制层编写

@RestController
@Slf4j
public class FeignPaymentController {
    @Resource
    FeignOrderService feignOrderService;
    @GetMapping("/consumer/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
        return feignOrderService.getPaymentById(id);
    }
}

2.openFeign的超时控制

默认情况下,如果业务不在一秒内完成就会报出timeout的错误

There was an unexpected error (type=Internal Server Error, status=500).

Read timed out executing GET http://CLOUD-PAYMENT-SERVICE/payment/timeout

feign.RetryableException: Read timed out executing GET http://CLOUD-PAYMENT-SERVICE/payment/timeout

由于整合了ribbon,可以直接在yaml文件中进行修改

ribbon:
  ReadTimeout: 5000
  ConnectTimeout: 5000

3.日志增强

1.新建配置类

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

2.改写Yml文件

logging:
  level:
    #以什么级别监视哪个接口
    com.xiaoxiao.springcloud.service.FeignOrderService: debug