一篇文章学会SpringCloud服务调用与负载均衡

一篇文章解决ribbon和OpenFegin负载均衡

ribbon入门介绍

什么是ribbon?

springcloud ribbon是Netflix发布的开源项目,主要的功能是提供客户端软件的负载均衡算法和服务的调用。是实现客户端负载均衡的一套工具。我们可以很容易的通过ribbon实现自定义的负载均衡算法。客户端组件提供了一系列的组件。

是现在的主流的客户端工具之一。

负载均衡?

简单的来说就是将用户的请求平摊的分配到多个服务上,从而达到系统的高可用。

常见的负载均衡的软件有Nginx等

ribbon与nginx负载均衡的区别?

nginx是服务器负载均衡,客户端会把请求都交给nginx然后由nginx来进行转发,即负载均衡是由服务端实现的。

ribbon本地负载均衡,在调用服务接口的时候,会在配置中心上获取注册信息服务列表之后缓存到jvm本地,从而在本地实现RPC远程服务调用技术。

ribbon能用来做什么?

简单的一句话,负载均衡+RestTemplate调用

总结:ribbon其实是一个软负载均衡的客户端组件。他可以和eureka结合只是其中的一个实例。

ribbon负载均衡和rest调用

整体项目架构图,Rest项目的搭建和之前使用的springcloud eureka服务注册与发现的项目相同。8001,8002是两个服务提供者,80是客户端,也是服务的消费者。7001,7002两个微服务搭建看Eureka集群环境。

一篇文章学会SpringCloud服务调用与负载均衡_第1张图片

在之前写案例的时候并没有引入ribbon的pom依赖,但同样可以使用ribbon

  
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
        dependency>

原因是在引入了eureka的客户端依赖时里面自动包含了ribbon的相关信息

一篇文章学会SpringCloud服务调用与负载均衡_第2张图片

RestTemplate的使用

getforobject和getforentity的区别?

getfor object返回的对象信息为响应体中数据转换的对象。基本上可以理解为json.

getforentity返回的信息包含响应中的一些重要的信息。比如响应头,响应状态码,响应体等。

@RequestMapping("/consumer/payment/get/{id}")
    @ResponseBody
    public CommonResult<Payment> getPayment(@PathVariable("id")Long id){
        return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
    }

ribbon默认自带的负载规则

ribbon的核心组件IRule接口

Irule:根据特定的算法从服务列表中选取一个要访问的服务。

IRule接口的具体实现类

  • RoundRobinRule:轮询
  • RandomRule:随机
  • RetryRule
  • weightedResponseTimeRule:权重(高响应比)
  • BestAvailableRule

ribbon如何进行替换

修改cloud-consumer-order80的同时注意配置的细节。新建一个package com.dzu.myrule。然后在包下面新建MySelfRule规则类。最后在主启动类上添加注解@RibbonClient后进行测试。

一篇文章学会SpringCloud服务调用与负载均衡_第3张图片

package com.dzu.myrule;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author chenruxu
 * @date 1.5
 */
@Configuration
public class MySelfRule {
    @Bean
    public IRule myRule(){
        return new RandomRule(); //定义方式为随机
    }
}

在主启动类上加入注解之后就可以进行使用了

package com.dzu.springcloud;

import com.dzu.myrule.MySelfRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;

/**
 * @author chenruxu
 * @date 2022 01/03
 */
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class)
public class OrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain80.class,args);
    }
}

之后启动五个微服务,在浏览器端不断的刷新测试,观察负载均衡算法改变之后程序的变化

ribbion默认轮询算法的实现原理

采用轮询方式的负载均衡算法:rest接口第几次请求数%服务器集群总数量=实际调用服务器的下标。

每次服务器启动之后rest接口的计数从一开始。

对于轮询算法可以类比于操作系统中的时间片轮转算法,两者之间有相似之处

roundRobbionRule源码分析

package com.netflix.loadbalancer;

public interface IRule {
    Server choose(Object var1);

    void setLoadBalancer(ILoadBalancer var1);

    ILoadBalancer getLoadBalancer();
}
 RoundRobinRule extends AbstractLoadBalancerRule
/*
 *
 * Copyright 2013 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package com.netflix.loadbalancer;

import com.netflix.client.config.IClientConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * The most well known and basic load balancing strategy, i.e. Round Robin Rule.
 *
 * @author stonse
 * @author Nikos Michalakis 
 *
 */
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() {
        nextServerCyclicCounter = new AtomicInteger(0);
    }

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

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

        Server server = null;
        int count = 0;
        while (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)) {
                log.warn("No up servers available from load balancer: " + lb);
                return null;
            }

            int nextServerIndex = incrementAndGetModulo(serverCount);
            server = allServers.get(nextServerIndex);

            if (server == null) {
                /* Transient. */
                Thread.yield();
                continue;
            }

            if (server.isAlive() && (server.isReadyToServe())) {
                return (server);
            }

            // Next.
            server = null;
        }

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

    /**
     * Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
     *
     * @param modulo The modulo to bound the value of the counter.
     * @return The next value.
     */
    private int incrementAndGetModulo(int modulo) {
        for (;;) {
            int current = nextServerCyclicCounter.get();
            int next = (current + 1) % modulo;
            if (nextServerCyclicCounter.compareAndSet(current, next))
                return next;
        }
    }

    @Override
    public Server choose(Object key) {
        return choose(getLoadBalancer(), key);
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

OpenFeign服务接口调用

什么是fegin?

fegin是一个可以与Eureka和ribbon组合使用的一支持负载均衡。

是一个声明式的web客户端,集成了ribbon。只需要创建一个接口,并在接口上添加一个微服务注解,就可以使用。

使用OpenFegin替代Rest+ribbon

openfegin的使用步骤

  • 接口加注解:微服务调用接口+@FeginClient注解
  • 创建服务消费者的微服务。
  • 添加pom类中的依赖
  • yaml配置文件修改
  • 业务类编写,启动与测试

新建一个微服务模块cloud-consumer-feign-order80。并引入需要用到的pom依赖信息

 <dependencies>
        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-openfeignartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <scope>runtimescope>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
        <dependency>
            <groupId>com.atguigu.springcloudgroupId>
            <artifactId>cloud-api-commonsartifactId>
            <version>${project.version}version>
        dependency>
    dependencies>

OpenFegin中整合了ribbon中相关的组件

一篇文章学会SpringCloud服务调用与负载均衡_第4张图片

创建主启动类并使用注解@EnableFeginClients激活相对应的组件。

package com.dzu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 * openFegin测试使用的微服务
 * @author chenruxu
 * @date 1.7
 */
@SpringBootApplication
@EnableFeignClients
public class OrderFeignMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderFeignMain80.class,args);
    }
}

编写配置文件完成Eureka的配置信息

server:
  port: 80

eureka:
  client:
    #表示是否将自己注册进eurekaServer 默认为true
    register-with-eureka: false
    service-url:
      #defaultZone: http://localhost:7001/eureka/
      defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/

业务类的编写是重点的环节 编写一个业务逻辑接口+@FeginClient配置调用provider服务

创建一个微服务调用接口

找到CLOUD-PAYMENT-SERVICE下面的微服务进行调用

package com.dzu.springcloud.service;

import com.dzu.springcloud.entities.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 *微服务调用接口
 * @author chenruxu
 */
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
    @RequestMapping(value = "/payment/get/{id}")
    public CommonResult getPaymentbyid(@PathVariable("id")Long id);
}

创建controller层的控制器类调用刚刚创建的微服务接口的实现类

package com.dzu.springcloud.controller;

import com.dzu.springcloud.entities.CommonResult;
import com.dzu.springcloud.entities.Payment;
import com.dzu.springcloud.service.PaymentFeignService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@Slf4j
public class OrderFeignController {

    @Resource
    private PaymentFeignService paymentFeignService;

    /**
     *通过该控制器调用Feign中的微服务接口
     * @param id
     * @return
     */
    @RequestMapping(value = "/consumer/payment/get/{id}")
    public CommonResult<Payment> getPaymentbyid(@PathVariable("id")Long id){
        return paymentFeignService.getPaymentbyid(id);
    }
}

启动需要的微服务进行测试。openFegin中集成了ribbon自带了负载均衡的配置项。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cRCNm8LO-1641541720401)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220107120614774.png)]

从测试中可以看出采用的是轮询方式的负载均衡算法。

openFegin的超时管理

为了方便进行测试服务提供方8001,故意的写一个服务暂停的程序。

先在8001微服务的端口上增加添加一个超时的方法

@RequestMapping("/payment/feign/timeout")
    public String paymentFeignTimeout(){
        //暂停几秒钟线程
        try{
            TimeUnit.SECONDS.sleep(3);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return serverPort;
    }

服务消费方80添加超时方法PaymentFeignService

    @GetMapping("/payment/feign/timeout")
    public String paymentFeignTimeout();


服务消费方80的controller添加超时方法

package com.dzu.springcloud.controller;

import com.dzu.springcloud.entities.CommonResult;
import com.dzu.springcloud.entities.Payment;
import com.dzu.springcloud.service.PaymentFeignService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@Slf4j
public class OrderFeignController {

    @Resource
    private PaymentFeignService paymentFeignService;

    /**
     *通过该控制器调用Feign中的微服务接口
     * @param id
     * @return
     */
    @RequestMapping(value = "/consumer/payment/get/{id}")
    public CommonResult<Payment> getPaymentbyid(@PathVariable("id")Long id){
        return paymentFeignService.getPaymentbyid(id);
    }
    
    @RequestMapping("/consumer/payment/feign/timeout")
    public String paymentFeignTimeout(){
        //OpenFeign-ribbon 客户端一般默认等待1秒钟
        return paymentFeignService.paymentFeignTimeout();
    }
}

完成之后开启各个微服务进行测试,先直接的访问8001微服务进程中超时的方法。测试通过之后。使用客户端访问观察openFegin的超时报错页面。

http://localhost:8001/payment/feign/timeout

一篇文章学会SpringCloud服务调用与负载均衡_第5张图片

在通过客户端访问的时候,由于默认的时间为1s所以会报超时的错误。

超时管理的配置文件修改

默认Feign客户端只等待秒钟, 但是服务端处理需要超过1秒钟,导致Feign客户端不想等待了,直接返回报错。为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制。
Yaml文件里需要开启OpenFeign客户端超时控制

#没置feign客户端超时时间(openFeign默认支持ribbon)
ribbon:
  #指的是建立连接所用的时间,适用于网络状况iE常的情况下两端连接所用的时间
  Readtimeout: 5000
  #指的是建立连接后从服务器读取到可用资源所用的时间
  ConnectTimeout: 5000

你可能感兴趣的:(微服务,负载均衡,spring,cloud)