【SpringCloud】Ribbon实现负载均衡

目前Spring Cloud中服务之间通过Rest方式调用有两种方式:

  1. feign
  2. RestTemplate+Ribbon

feign的方式更优雅,使用更简单(内部也使用的ribbon做的负载均衡),但是我们这使用的第二种方案,因为Ribbon可以负载均衡并且可以自定义负载均衡算法

1. 是什么

  是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。在配置文件中列出Load Balancer(简称LB)后面的所有的机器,Ribbon后自动帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器,也可实现自定义的负载均衡算法。

  Dubbo和SpringCloud中均提供了负载均衡,但SpringCloud的负载均衡算法可以自定义

2. LB 相关知识

  LB负载均衡 (Load Balance),在微服务或分布式集群中经常用到的一种应用,将用户的请求平摊到多个服务上,从而达到系统的高可用HA(High Available),常用的负载均衡有软件NGINX,LVS,硬件F5等。
LB分为:

  1. 集中式LB
    即在服务的消费方和提供方之间使用独立的LB设施(硬件如F5,软件如nginx),由该设施负责把访问请求通过某种策略转发至服务提供方
  2. 进程内LB
    即将LB逻辑继承到服务消费方,消费方从服务注册中心获取哪些地址可用,然后自己在从这些地址中选择出一个合适的服务器。Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取服务提供方的地址

3. Ribbon中LB的思路

  1. 先选择EurekaServer,优先选择在同一区域负载较少的EurekaServer
  2. 再根据用户指定的策略,从EurekaServer取到的服务注册列表中选择一个地址

4. 核心组件IRule

IRule:根据特定算法中从服务列表中选取一个要访问的服务,提供了7种策略:

  1. RoundRobinRule: 轮询
  2. RandomRule: 随机
  3. AvailabilityFilteringRule: 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问
  4. WeightedResponseTimeRule: 根据平均响应时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越高。刚启动时如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够时,会切换到WeightedResponseTimeRule、
  5. RetryRule: 先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内进行重试,获取可用服务
  6. BestAvailableRule: 会先过滤掉由于多次访问故障而处于短路器跳闸状态的服务,然后选择一个并发量最小的服务
  7. ZoneAvoidanceRule: 复合判断server所在区域的性能和server的可用性选择服务器

也可以自定义策略,步骤为:

  1. 自定义返回类型为IRule的Bean的配置类

    @Configuration
    public class MySelfRule
    {
    	@Bean
    	public IRule myRule()
    	{
    		//return new RandomRule();// Ribbon默认是轮询,我自定义为随机
    		//return new RoundRobinRule();// Ribbon默认是轮询,我自定义为随机
    		
    		return new RandomRule_ZY();// 我自定义为每台机器5次
    	}
    }
    //----------------------自定义路由策略类---------------
    public class RandomRule_ZY extends AbstractLoadBalancerRule
    {
    // total = 0 // 当total==5以后,我们指针才能往下走,
    // index = 0 // 当前对外提供服务的服务器地址,
    // total需要重新置为零,但是已经达到过一个5次,我们的index = 1
    // 分析:我们5次,但是微服务只有8001 8002 8003 三台,OK?
    // 
    
    
    private int total = 0; 			// 总共被调用的次数,目前要求每台被调用5次
    private int currentIndex = 0;	// 当前提供服务的机器号
    
    public Server choose(ILoadBalancer lb, Object key)
    {
    	if (lb == null) {
    		return null;
    	}
    	Server server = null;
    
    while (server == null) {
    	if (Thread.interrupted()) {
    		return null;
    	}
    	List<Server> upList = lb.getReachableServers();
    	List<Server> allList = lb.getAllServers();
    
    	int serverCount = allList.size();
    	if (serverCount == 0) {
    		/*
    		 * No servers. End regardless of pass, because subsequent passes only get more
    		 * restrictive.
    		 */
    		return null;
    	}
    //			int index = rand.nextInt(serverCount);// java.util.Random().nextInt(3);
    //			server = upList.get(index);
    
    			
    //			private int total = 0; 			// 总共被调用的次数,目前要求每台被调用5次
    //			private int currentIndex = 0;	// 当前提供服务的机器号
                if(total < 5)
                {
    	            server = upList.get(currentIndex);
    	            total++;
                }else {
    	            total = 0;
    	            currentIndex++;
    	            if(currentIndex >= upList.size())
    	            {
    	              currentIndex = 0;
    	            }
                }					
    			if (server == null) {
    				/*
    				 * The only time this should happen is if the server list were somehow trimmed.
    				 * This is a transient condition. Retry after yielding.
    				 */
    				Thread.yield();
    				continue;
    			}
    
    	if (server.isAlive()) {
    		return (server);
    	}
    
    	// Shouldn't actually happen.. but must be transient or a bug.
    	server = null;
    	Thread.yield();
    }
    
    return server;
    
    }
    
    @Override
    public Server choose(Object key)
    {
    	return choose(getLoadBalancer(), key);
    }
    
    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig)
    {
    	// TODO Auto-generated method stub
    
    }
    

    }

  2. 在启动类上配置自定义的路由策略@RibbonClient,使其启动时就加载配置

    @SpringBootApplication
    @EnableEurekaClient
    //在启动该微服务的时候就能去加载我们的自定义Ribbon配置类,从而使配置生效
    //@RibbonClient(name="MICROSERVICECLOUD-DEPT",configuration=MySelfRule.class)
    @RibbonClient(name="MICROSERVICECLOUD-DEPT",configuration=MySelfRule.class)
    public class DeptConsumer80_App
    {
    	public static void main(String[] args)
    	{
    		SpringApplication.run(DeptConsumer80_App.class, args);
    	}
    }
    

注:自定义配置类不能放在@ComponentScan所扫描的当前包下及子包下,否则我们自定义的配置类就会被所有的Ribbon客户端锁共享,也就是说达不到特殊化定制的目的了

5.实现方式

5.1 配置文件方法

配置格式为:<client>.<nameSpace>.<property>=<value>

  • client为客户端名称:服务提供者对外暴露的服务名
  • nameSpace为名称空间:默认就是ribbon
  • property为属性名:我们要配置负载均衡策略就是NFLoadBalancerRuleClassName cloud-provider.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
### 针对单个服务的 Ribbon 配置
MICROSERVICECLOUD-DEPT:
  ribbon: 
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

配置全局负载均衡策略吧前面的服务名去掉就可以

5.2 注解方式

示例如下6

6. 项目实战

6.1 引依赖

<!-- Ribbon相关 -->
<!-- Eureka -->
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!-- Ribbon -->
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<!-- Eureka相关 -->
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>

6.2 配置文件

eureka:
  client:
    register-with-eureka: false
    service-url: 
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/  

6.3 在消费者(客户端)的启动项配置

@SpringBootApplication
@EnableEurekaClient
// name:要调用服务(提供者)的名称,针对单个该服务使用的LB策略,默认是轮询
@RibbonClient(name="MICROSERVICECLOUD-DEPT",configuration=RetryRule.class)
public class DeptConsumer80_App
{
	public static void main(String[] args)
	{
		SpringApplication.run(DeptConsumer80_App.class, args);
	}

    @Bean
   	@LoadBalanced//Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端 , 负载均衡的工具。
   	public RestTemplate getRestTemplate()
   	{
   		return new RestTemplate();
   	}

}

RestTemplate 也可以写在统一配置中心上,这样不用再在每个客户端都写了

    @Bean
   	@LoadBalanced//Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端 , 负载均衡的工具。
   	public RestTemplate getRestTemplate()
   	{
   		return new RestTemplate();
   	}

6.4 service层调用服务


// 注意,这些的是注册到Eureka上服务的名称地址,不是通过ip调用的。
// 直接调用服务而不用在关心地址和端口号
//private static final String REST_URL_PREFIX = "http://localhost:8001";
private static final String REST_URL_PREFIX = "http://MICROSERVICECLOUD-DEPT";

	/**
	 * 使用 使用restTemplate访问restful接口非常的简单粗暴无脑。
	 *  (url, requestMap, ResponseBean.class)这三个参数分别代表 REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。
	 */
	@Autowired
	private RestTemplate restTemplate;

	@RequestMapping(value = "/consumer/dept/add")
	public boolean add(Dept dept)
	{
		。。。逻辑代码
		// 请求地址:服务地址+方法路径,参数,返回类型,这样就实现了调用别的服务
		// 完成真正的通过微服务名字从Eureka萨汗找到服务并访问
		return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class);
		。。。逻辑代码
	}

猜你喜欢

转载自blog.csdn.net/wrs120/article/details/91573056