SpringCloud(四)Ribbon自定义负载均衡

上篇文章我们已经完成了Ribbon负载均衡的功能。做法很简单,只需要在RestTemplate添加@LoanBalanced 的注解。默认情况下,Ribbon的负载均衡策略是RoundRobbin(轮训)的方式,可很多时候在特定场景下需要不同的策略,这个时候就需要自定义Ribbon策略了。看下面代码:

package com.zhuyang.cloud.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import com.zhuyang.cloud.entity.User;
import com.zhuyang.config.RibbonConfiguration;

@RestController
@RibbonClient(name = "microservice-provider", configuration = RibbonConfiguration.class)//name是provider的服务名  RibbonConfiguration为自定义配置
public class MovieController {
	@Bean
	@LoadBalanced
	public RestTemplate restTemplate() { // equals to RestTemplate
		// restTemplate=new RestTemplate();
		return new RestTemplate();
	}

	@Autowired
	private RestTemplate restTemplate;

	@RequestMapping(value = "/movie/{id}", method = RequestMethod.GET)
	public User findById(@PathVariable Long id) {
		// return restTemplate.getForEntity("http://localhost:8000/service/"+id,
		// User.class).getBody();
		return restTemplate.getForEntity("http://microservice-provider/provider/service/" + id, User.class).getBody();
	}
}

server: 
  port: 8001 
eureka: 
  client: 
    serviceUrl: 
      defaultZone: http://user:password123@localhost:8761/eureka/    # 指定注册中心的地址 
  instance: 
    preferIpAddress: true 
spring: 
  application: 
    name: microservice-consumer 
microservice-provider:  ##config ribbon 
  ribbon: 
    eureka: 
      enabled: false 
    listOfServers: localhost:8000, localhost:8002,localhost:8003 ##假设provider有3台instance端口分别是8000 8002 8003
    ServerListRefreshInterval: 15000


package com.zhuyang.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.IPing;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.PingUrl;
import com.netflix.loadbalancer.ZoneAvoidanceRule;

/**
 * 
 * Here, we override the IPing and IRule used by the default load balancer. The
 * default IPing is a NoOpPing (which doesn’t actually ping server instances,
 * instead always reporting that they’re stable), and the default IRule is a
 * ZoneAvoidanceRule (which avoids the Amazon EC2 zone that has the most
 * malfunctioning servers, and might thus be a bit difficult to try out in our
 * local environment).
 * 
 */
public class RibbonConfiguration {
	@Autowired
	private IClientConfig ribbonClientConfig;

	/**
	 * Our IPing is a PingUrl, which will ping a URL to check the status of each
	 * server.provider has, as you’ll recall, a method mapped to the / path;
	 * that means that Ribbon will get an HTTP 200 response when it pings a
	 * running provider server.
	 * 
	 * server list defined in application.yml :listOfServers: localhost:8000,
	 * localhost:8002,localhost:8003
	 * 
	 */
	@Bean
	public IPing ribbonPing(IClientConfig config) {
		// ping url will try to access http://microservice-provider/provider/ to
		// see if reponse code is 200 . check PingUrl.isAlive()
		// param /provider/ is the context-path of provider service
		return new PingUrl(false, "/provider/");
	}

	/**
	 * The IRule we set up, the AvailabilityFilteringRule, will use Ribbon’s
	 * built-in circuit breaker functionality to filter out any servers in an
	 * “open-circuit” state: if a ping fails to connect to a given server, or if
	 * it gets a read failure for the server, Ribbon will consider that server
	 * “dead” until it begins to respond normally.
	 * 
	 * AvailabilityFilteringRule | 过滤掉那些因为一直连接失败的被标记为circuit tripped的后端server,并过滤掉那些高并发的的后端server(active connections 超过配置的阈值) | 使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查status里记录的各个server的运行状态
	 * RandomRule  | 随机选择一个server
	 * BestAvailabl eRule | 选择一个最小的并发请求的server | 逐个考察Server,如果Server被tripped了,则忽略,在选择其中
	 * RoundRobinRule  |  roundRobin方式轮询选择  |  轮询index,选择index对应位置的server
	 * WeightedResponseTimeRule  |  根据响应时间分配一个weight,响应时间越长,weight越小,被选中的可能性越低。  |  一 个后台线程定期的从status里面读取评价响应时间,为每个server计算一个weight。Weight的计算也比较简单responsetime 减去每个server自己平均的responsetime是server的权重。当刚开始运行,没有形成statas时,使用roubine策略选择 server。
	 * RetryRule  |  对选定的负载均衡策略机上重试机制。 |  在一个配置时间段内当选择server不成功,则一直尝试使用subRule的方式选择一个可用的server
	 * ZoneAvoidanceRule  |  复合判断server所在区域的性能和server的可用性选择server  |  使 用ZoneAvoidancePredicate和AvailabilityPredicate来判断是否选择某个server,前一个判断判定一个 zone的运行性能是否可用,剔除不可用的zone(的所有server),AvailabilityPredicate用于过滤掉连接数过多的 Server。
	 * @param config
	 * @return
	 */
	@Bean
	public IRule ribbonRule(IClientConfig config) {
		// return new AvailabilityFilteringRule();
		 return new RandomRule();//
		// return new BestAvailableRule();
		// return new RoundRobinRule();//轮询
		// return new WeightedResponseTimeRule();
		// return new RetryRule();
		// return new ZoneAvoidanceRule();
	}
}

在RibbonConfiguration中的ribbonRule方法就是用来定义不用的策略,每种策略所对应的实现类和描述 都已经添加了注释。例如我们返回的是RandomRule策略,那么我们在多次请求provider的时候就不再是轮训的方式进行命中,而是随机方式。。下面是RandomRule的代码实现

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();//get all reachable server .list  listOfServers: localhost:8000, localhost:8002,localhost:8003
            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);//get random index
            server = upList.get(index);//get specified server eg:localhost:8000

            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;//return selected server

    }




阅读更多
换一批

没有更多推荐了,返回首页