温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

SpringCloud中Ribbon怎么调用

发布时间:2022-02-24 17:20:57 来源:亿速云 阅读:128 作者:iii 栏目:开发技术

这篇文章主要介绍“SpringCloud中Ribbon怎么调用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“SpringCloud中Ribbon怎么调用”文章能帮助大家解决问题。

    一、简介

    1. 是什么

    • Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。

    • 简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。

    • 官方文档

    • 目前已进入维护状态,以后可以通过Open Feign作为替代方案

    • 负载均衡+RestTemplate,实现负载均衡调用

    2. 负载均衡

    • 负载均衡(Load Balance,LB),即将用户的请求平摊到多个服务上,从而达到系统的高可用(HA)

    • 负载均衡分为两种方案:集中式LB、进程内LB

    2.1 集中式LB
    • 即服务方和消费方之间使用独立的LB设施,由该设备负责把访问请求通过某种策略转发至服务提供方。

    • 比如说Nginx、Gateway、zuul等

    2.2 进程内LB
    • 负载均衡的算法集成到消费方,消费方在注册中心中获取可用地址,然后通过LB算法选择出一个合适的服务器

    • Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务方提供的地址。

    二、实验

    Ribbon集成在spring-cloud-starter-netflix-eureka-client中,可以参考eureka的使用。在此基础上简单修改一下,就可以完成服务调用及负载均衡

    1. RestTemplate

    • 官网

    • 通过RestTemplate,可以实现HttpClient的功能,只需要给它提供一个url及返回类型,即可实现远程方法调用。

    1.1 加入到IOC容器

    首先,将其加入到IOC容器中。@LoadBalanced表示开启负载均衡。

    @Configuration
    public class ApplicationContextConfig {
      @Bean
      @LoadBalanced
      public RestTemplate restTemplate() {
        return new RestTemplate();
      }
    }
    1.2 RestTemplate 远程调用
    @Slf4j
    @RestController
    @RequestMapping("/order")
    public class OrderController {
      @Autowired
      RestTemplate restTemplate;  // 在ioc容器中获取
      @Value("${payment.url}")
      String paymentUrl;  // 远程调用的URL,保存在配置文件中,解耦
    
      @GetMapping("/payment/get/{id}")
      public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
        CommonResult<Payment> result = restTemplate.getForObject(paymentUrl + "/payment/get/" + id, CommonResult.class);  // get方法调用,并且返回封装成 CommonResult 类型
        log.info("Order 查询 Payment,id:" + id);
        return result;
      }
    }

    也可以使用getForEntity()方法,获取整个响应,自己在响应中获取想要的内容。

      @GetMapping("/payment/getEntity/{id}")
      public CommonResult<Payment> getPaymentEntityById(@PathVariable("id") Long id) {
        ResponseEntity<CommonResult> entity = restTemplate.getForEntity(paymentUrl + "/payment/get/" + id, CommonResult.class);
        log.info("获取到的信息是:" + entity.toString());
        log.info("获取到的StatusCode是:" + entity.getStatusCode());
        log.info("获取到的StatusCodeValue是:" + entity.getStatusCodeValue());
        log.info("获取到的Headers是:" + entity.getHeaders());
    
        if (entity.getStatusCode().is2xxSuccessful()) {
          log.info("查询成功:" + id);
          return entity.getBody();
        } else {
          log.info("查询失败:" + id);
          return new CommonResult<>(CommonResult.FAIlURE, "查询失败");
        }
      }

    如果使用post方法,就将get改成post就好了。

    1.3 配置文件

    url,可以写具体的地址,表示直接调用该地址;也可以写在eureka的服务名,首先在eureka中获取该服务的所有地址,再通过LB选择一个。

    payment:
      url: "http://CLOUD-PAYMENT-SERVICE"

    2. LoadBalancer

    上面通过@LoadBalanced开启了负载均衡。默认使用轮询算法,也可以修改成其他算法。

    Class算法
    com.netflix.loadbalancer.RoundRobinRule轮询,默认算法
    com.netflix.loadbalancer.RandomRule随机算法,通过产生随机数选择服务器
    com.netflix.loadbalancer.RetryRule先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务
    WeightedResponseTimeRule对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
    BestAvailableRule会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
    AvailabilityFilteringRule先过滤掉故障实例,再选择并发较小的实例
    ZoneAvoidanceRule默认规则,复合判断server所在区域的性能和server的可用性选择服务器
    2.1 修改负载均衡算法

    如果想让该算法只针对某个服务,则不能将其放在ComponentScan够得到的地方,否则会修改所有服务的负载均衡算法。因此,最好在外面再新建一个package,用来放这个LB

    @Configuration
    public class MyRule {
      @Bean
      public IRule rule() {
        return new RandomRule();
      }
    }

    在主启动类上,标识一下服务与算法直接的映射关系

    @SpringBootApplication
    @EnableEurekaClient
    @RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MyRule.class)
    public class OrderApplication80 {
      public static void main(String[] args) {
        SpringApplication.run(OrderApplication80.class, args);
      }
    }

    如果嫌这种方法麻烦,也可以使用配置文件的方法

    CLOUD-PAYMENT-SERVICE:  # 服务名称
      ribbon:
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule  # 算法选择

    3. 负载均衡算法源码

    以默认的RoundRobinRule作为阅读的源码,其他的源码基本上很类似,只是修改的选择服务器的代码。

    • RoundRobinRule父类为AbstractLoadBalancerRule,AbstractLoadBalancerRule实现了接口IRule

    3.1 IRule
    public interface IRule {
      Server choose(Object var1);  // 选择服务器,最重要的方法
    
      void setLoadBalancer(ILoadBalancer var1);
    
      ILoadBalancer getLoadBalancer();
    }
    3.2 AbstractLoadBalancerRule

    基本没什么作用,只是将公共的部分提取了出来进行实现。

    public abstract class AbstractLoadBalancerRule implements IRule, IClientConfigAware {
      private ILoadBalancer lb;  // ILoadBalancer接口,主要的功能就是获取当前服务器的状态、数量等,为负载均衡算法提供计算的参数
    
      public AbstractLoadBalancerRule() {
      }
    
      public void setLoadBalancer(ILoadBalancer lb) {
        this.lb = lb;
      }
    
      public ILoadBalancer getLoadBalancer() {
        return this.lb;
      }
    }
    3.3 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) {  // 设置LoadBalancer
        this();
        this.setLoadBalancer(lb);
      }
    
      public Server choose(ILoadBalancer lb, Object key) {  // 最重要的方法,选择服务器并返回
      // 下面贴出来
      }
     
      private int incrementAndGetModulo(int modulo) {  // 对计数器进行修改,并返回一个选择值,是轮询算法的实现
      // 下面贴出来
      }
    
      public Server choose(Object key) {   // 接口的方法,在该类中调用了另一个方法实现
        return this.choose(this.getLoadBalancer(), key);
      }
    
      public void initWithNiwsConfig(IClientConfig clientConfig) {}
    }

    简单来说,该方法就是根据目前的状态,选择一个服务器返回。

    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {  // 如果没有LoadBalancer,那就不白费功夫了
          log.warn("no load balancer");
          return null;
        } else {
          Server server = null;
          int count = 0;
    
          while(true) {  
            if (server == null && count++ < 10) {  // 尝试十次,如果还找不到server就放弃了
              List<Server> reachableServers = lb.getReachableServers();  // 通过LB获取目前所有可获取的服务器
              List<Server> allServers = lb.getAllServers();  // 获取实际上的所有服务器
              int upCount = reachableServers.size();  // 获取目前可获得的服务器数量
              int serverCount = allServers.size();  // 所有服务器的数量,这是取余的除数
              if (upCount != 0 && serverCount != 0) {  // 如果目前有服务器且服务器可用
                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;
          }
        }
      }

    简单来说,就是将目前的计数器+1取余,获取一个下标,并返回。为了避免高并发的危险,采用CAS的方法进行设置。

      private int incrementAndGetModulo(int modulo) {
        int current;
        int next;
        do {
          current = this.nextServerCyclicCounter.get();  // 获取当前值
          next = (current + 1) % modulo;  // +1取余
        } while(!this.nextServerCyclicCounter.compareAndSet(current, next));  // CAS,如果成功就返回,失败就再来
    
        return next;
      }

    关于“SpringCloud中Ribbon怎么调用”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注亿速云行业资讯频道,小编每天都会为大家更新不同的知识点。

    向AI问一下细节

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    AI