使用Ribbon实现客户端负载均衡

       问题:服务消费者调用服务提供者是硬编码的方式,虽然把地址配置到了application.yml中,但是一旦服务端的地址发生改变,那肯定是要修改配置文件的。

  如何解决呢? 

  Ribbon是Nextflix发布的负载均衡器,为Ribbon配置服务提供者地址后,Ribbon就可以基于某种负载均衡的算法,自动帮助服务消费者请求。

  Ribbon支持轮询、随机等负载均衡算法,当然也支持实现自定义的负载均衡算法。

  在Spring Cloud中,当Ribbon和Eureka配合使用时,Ribbon可自动从Eureka Server获取服务提供者的地址列表,并基于某种负载均衡算法,请求其中一个服务提供者实例。

  

  新建服务消费者微服务,配置Ribbon

  

  pom.xml引入ribbon依赖

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

  为RestTemplate添加@LoadBalanced注解

@SpringBootApplication
public class ConsumerMovieRibbonApplication {

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

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

}

  只需要为RestTemplate添加@LoadBalanced注解,就可以为RestTemlate整合Ribbon,使其具备负载均衡的能力

  修改Controller层代码,将地址调整为注册在Eureka上的虚拟主机名

package com.smart.consumer_movie_ribbon.controller;

import com.smart.consumer_movie_ribbon.model.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@Slf4j
public class MovieController {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @GetMapping("/movie/{id}")
    public User findById(@PathVariable Long id){
        // 调用注册在Eureka上的服务端的地址
        return restTemplate.getForObject("http://provider-user/user/"+id,User.class);
    }

    @GetMapping("/callProvider")
    public String callUserInstance(){
        ServiceInstance serviceInstance = loadBalancerClient.choose("provider-user");
//        log.info("serviceId: {} , host: {} ,port: {} ,uri: {}" ,serviceInstance.getServiceId() , serviceInstance.getHost(), serviceInstance.getPort(),serviceInstance.getUri());
        return serviceInstance.getUri().toString();
    }
}

  把地址修改为http://provider-user/user , 其中provider-user用户微服务的虚拟主机名,是注册在Eureka Server上的名字,也是服务提供者微服务的配置文件中配置的spring.application.name

  当Ribbon和Eureka同时使用时,会自动将虚拟主机名映射为微服务的网络地址。

  验证Ribbon提供的能力

  1.启动Eureka Server
  2.启动两个 microservice-provider-user实例 。(在STS中启动一个后,修改下application.yml的端口,再次run as spring boot app 即可启动第二个实例,以此类推)
  3.启动microservice-provider-movie-ribbon

  多次启动spring boot项目

  ① 打开运行配置界面

  选择“Edit Configurations”,复制一份UserServiceApplication的运行信息,如图所示步骤进行操作

  

     多次访问 http://localhost:7899/movie/1 ,观察控制台每个节点的日志输出情况。

·   同时访问http://localhost:7899/callProvider

注意事项

  默认情况下,虚拟主机名和服务名称是一致的,也可以通过eureka.instance.virtual-host-name或者eureka.instance.secure-virtual-host-name指定虚拟主机名

  不能将restTemplate.getForObject()和loadBalancerClient写在同一个方法中,两者会冲突,因为RestTemplate实际上是一个Ribbon客户端,本身已经包含choose的行为

  虚拟主机名不能包含"_"之类的字符,否则Ribbon再调用的时候会抛出异常

Ribbon自身的负载均衡算法
  RoundRobinRule(轮询算法)

  RandomRule(随机算法)

  AvailabilityFilteringRule():会先过滤由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问

  WeightedResponseTimeRule():根据平均响应的时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越高,刚启动时如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够会切换到WeightedResponseTimeRule

  RetryRule():先按照RoundRobinRule的策略获取服务,如果获取失败则在制定时间内进行重试,获取可用的服务。

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

  ZoneAvoidanceRule():默认规则,符合判断server所在区域的性能和server的可用性选择服务器
  自定义的类不能放在@ComponentScan所扫描的当前包以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,也就是达不到特殊化指定的目的。因为主启动类已经有ComponentScan,这个注解,所以说不能放在和主启动类同包下。

  AbstractLoadBalancerRule实现了IRule 中的 setLoadBalancergetLoadBalancer,通过继承 AbstractLoadBalancerRule

/**
 * 自定义负载均衡算法
 */
public class DefineRule extends AbstractLoadBalancerRule {
    private int total = 0;
    private int serverIndex = 0;

    private Server choose(ILoadBalancer loadBalancer, Object key) {
        if (loadBalancer == null) {
            return null;
        }
        Server server = null;
        while (server == null) {
            if (Thread.interrupted()) {
                return null;
            }
            List<Server> reachableServers = loadBalancer.getReachableServers();
            List<Server> allServers = loadBalancer.getAllServers();
            int serverCount=allServers.size();
            if(serverCount==0){
                return null;
            }
            if(total<5){
                total++;
                server = reachableServers.get(serverIndex);
            }else {
                total=0;
                serverIndex++;
                if(serverIndex>=reachableServers.size()){
                    serverIndex=0;
                }
            }
            if(server ==null){
                Thread.yield();
                continue;
            }
            if(server.isAlive()){
                return server;
            }
            server=null;
            Thread.yield();
        }
        return server;
    }

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {

    }

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

}

  自定义算法DefineRule 必须继承AbstractLoadBalanceRule类

/**
 * 自定义负载均衡算法
 */
@Configuration
public class MySelfRule {
    @Bean
    public IRule rule(){
//        return new RandomRule();//随机算法
        return new DefineRule();
    }
}

  在主启动类中添加ribbon注解

@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name="provider-user",configuration = MySelfRule.class)
public class ConsumerMovieRibbonApplication {

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

  name指定针对哪个服务 进行负载均衡,而configuration指定负载均衡的算法具体实现类

  通过application.yml中配置的方式,这里就不用使用前面的编程式配置了

  格式如下<clientName>.ribbon.

NFLoadBalancerClassName: 配置ILoadBalancer的实现类
NFLoadBalancerRuleClassName: 配置IRule的实现类
NFLoadBalancerPingClassName: 配置IPing的实现类
NFWSServerListClassName: 配置ServerList的实现类
NIWSServerListFilterClassName: 配置ServerListFilter的实现类

  直接使用随机规则

provider-user:
ribbon:
NFLoadBalancerRuleClassName: com.smart.ribbonRule.DefineRule

 

       

参考

https://blog.csdn.net/yangshangwei/article/details/84900649

posted on 2019-04-21 22:53  溪水静幽  阅读(270)  评论(0)    收藏  举报