年后到如今一直很忙,都没什么时间记录东西了,其实以前工做中积累了不少知识点,一直都堆在备忘录里,只是由于近几个月经历了一些事情,没有太多的经从来写了,可是一些重要的东西,我仍是但愿能坚持记录下来。正好最近公司用到了一些本篇文章的知识点,因此就抽空记录一下。html
本文代码github地址:https://github.com/shaweiwei/RibbonTest/tree/masternginx
ribbon 是一个客户端负载均衡器,它和nginx的负载均衡相比,区别是一个是客户端负载均衡,一个是服务端负载均衡。ribbon能够单独使用,也能够配合eureka使用。git
1.首先咱们先在原来的基础上新建一个Ribbon模块,以下图:github
如今咱们单独使用ribbon,在Ribbon模块下添加依赖,以下图所示:算法
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> <version>1.4.0.RELEASE</version> </dependency>
修改application.yml文件,以下所示:spring
server: port: 8082 spring: application: name: Ribbon-Consumer #providers这个是本身命名的,ribbon,listOfServer这两个是规定的 providers: ribbon: listOfServers: localhost:8080,localhost:8081
在Ribbon模块下新建一个测试类以下代码 * Created by cong on 2018/5/8. */浏览器
@RestController public class ConsumerController {
//注入负载均衡客户端 @Autowired
private LoadBalancerClient loadBalancerClient; @RequestMapping("/consumer") public String helloConsumer() throws ExecutionException, InterruptedException {
//这里是根据配置文件的那个providers属性取的 ServiceInstance serviceInstance = loadBalancerClient.choose("providers");
//负载均衡算法默认是轮询,轮询取得服务 URI uri = URI.create(String.format("http://%s:%s", serviceInstance.getHost(), serviceInstance.getPort())); return uri.toString();
}
运行结果以下:并发
会轮询的获取到两个服务的URL 访问第一次,浏览器出现http://localhost:8080 访问第二次就会出现http://localhost:8081app
下面这个例子是在以前这篇文章的例子上改的,Spring Cloud(二):Spring Cloud Eureka Server高可用注册服务中心的配置负载均衡
先看下写好的结构
先介绍下大体功能,EurekaServer提供服务注册功能,RibbonServer里会调用ServiceHello里的接口,ServiceHello和ServiceHello2是一样的服务,只是为了方便分布式部署。
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> </dependencies>
@SpringBootApplication @EnableEurekaServer public class BootApplication { public static void main(String[] args) { SpringApplication.run(BootApplication.class, args); } }
server.port=8760 spring.application.name=eureka-server #eureka.instance.hostname=peer1 eureka.client.serviceUrl.defaultZone=http://localhost:${server.port}/eureka
红色部分代码是关键
@SpringBootApplication @EnableDiscoveryClient @RestController @RibbonClients(value={ @RibbonClient(name="service-hi",configuration=RibbonConfig.class) }) public class BootApplication { public static void main(String[] args) { SpringApplication.run(BootApplication.class, args); } }
@Configuration public class RibbonConfig { @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } @Bean public IRule ribbonRule() { return new RoundRobinRule(); } }
@RestController public class TestController { @Autowired @LoadBalanced private RestTemplate restTemplate; @Autowired SpringClientFactory springClientFactory; @RequestMapping("/consumer") public String helloConsumer() throws ExecutionException, InterruptedException { ILoadBalancer loadBalancer = springClientFactory.getLoadBalancer("service-hi"); List<Server> servers = loadBalancer.getReachableServers(); System.out.println(",......"+servers.size()); return restTemplate.getForEntity("http://service-hi/hi",String.class).getBody(); } }
server.port=8618 spring.application.name=ribbon-service eureka.client.serviceUrl.defaultZone=http://localhost:8760/eureka/
@SpringBootApplication @EnableDiscoveryClient @RestController public class BootApplication { public static void main(String[] args) { SpringApplication.run(BootApplication.class, args); } @RequestMapping(value="/hi",method=RequestMethod.GET) public String hi(){ return "hi"; } }
server.port=8788 spring.application.name=service-hi eureka.client.serviceUrl.defaultZone=http://localhost:8760/eureka/ #service-hi.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RoundRobinRule
和ServiceHello同样,只是端口不一样,另外为了区分,接口hi返回的值也改为不同。
而后就是分别启动各个服务。
查看eureka信息,能够看到服务都启动了。
浏览器里输入http://localhost:8618/consumer,多调用几回,能够看到分别结果是hi和hi2交替出现。
这说明负载均衡实现了,并且我选择的负载均衡策略是轮询,因此hi和hi2确定是交替出现。
Ribbon的核心组件是IRule,是全部负载均衡算法的父接口,其子类有:
每个类就是一种负载均衡算法
RoundRobinRule 轮询
RandomRule 随机
AvailabilityFilteringRule 会先过滤掉因为屡次访问故障而处于断路器跳闸状态的服务,还有并发的链接数超过阈值的服务,而后对剩余的服务列表进行轮询
WeightedResponseTimeRule 权重 根据平均响应时间计算全部服务的权重,响应时间越快服务权重越大被选中的几率越高。刚启动时,若是统计信息不足,则使用轮询策略,等信息足够,切换到 WeightedResponseTimeRule
RetryRule 重试 先按照轮询策略获取服务,若是获取失败则在指定时间内重试,获取可用服务
BestAvailableRule 选过滤掉屡次访问故障而处于断路器跳闸状态的服务,而后选择一个并发量最小的服务
ZoneAvoidanceRule 符合判断server所在区域的性能和server的可用性选择服务
ribbon实现的关键点是为ribbon定制的RestTemplate,ribbon利用了RestTemplate的拦截器机制,在拦截器中实现ribbon的负载均衡。负载均衡的基本实现就是利用applicationName从服务注册中心获取可用的服务地址列表,而后经过必定算法负载,决定使用哪个服务地址来进行http调用。
RestTemplate中有一个属性是List<ClientHttpRequestInterceptor> interceptors,若是interceptors里面的拦截器数据不为空,在RestTemplate进行http请求时,这个请求就会被拦截器拦截进行,拦截器实现接口ClientHttpRequestInterceptor,须要实现方法是
ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException;
也就是说拦截器须要完成http请求,并封装一个标准的response返回。
在Ribbon 中也定义了这样的一个拦截器,而且注入到RestTemplate中,是怎么实现的呢?
在Ribbon实现中,定义了一个LoadBalancerInterceptor,具体的逻辑先不说,ribbon就是经过这个拦截器进行拦截请求,而后实现负载均衡调用。
拦截器定义在org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration.LoadBalancerInterceptorConfig#ribbonInterceptor
@Configuration @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") static class LoadBalancerInterceptorConfig { @Bean //定义ribbon的拦截器 public LoadBalancerInterceptor ribbonInterceptor( LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) { return new LoadBalancerInterceptor(loadBalancerClient, requestFactory); } @Bean @ConditionalOnMissingBean //定义注入器,用来将拦截器注入到RestTemplate中,跟上面配套使用 public RestTemplateCustomizer restTemplateCustomizer( final LoadBalancerInterceptor loadBalancerInterceptor) { return restTemplate -> { List<ClientHttpRequestInterceptor> list = new ArrayList<>( restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); }; } }
定义了拦截器,天然须要把拦截器注入到、RestTemplate才能生效,那么ribbon中是如何实现的?上面说了拦截器的定义与拦截器注入器的定义,那么确定会有个地方使用注入器来注入拦截器的。
在org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration#loadBalancedRestTemplateInitializerDeprecated方法里面,进行注入,代码以下。
@Configuration @ConditionalOnClass(RestTemplate.class) @ConditionalOnBean(LoadBalancerClient.class) @EnableConfigurationProperties(LoadBalancerRetryProperties.class) public class LoadBalancerAutoConfiguration { @LoadBalanced @Autowired(required = false) private List<RestTemplate> restTemplates = Collections.emptyList(); @Bean public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated( final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) { //遍历context中的注入器,调用注入方法。 return () -> restTemplateCustomizers.ifAvailable(customizers -> { for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) { for (RestTemplateCustomizer customizer : customizers) { customizer.customize(restTemplate); } } }); } //...... }
遍历context中的注入器,调用注入方法,为目标RestTemplate注入拦截器,注入器和拦截器都是咱们定义好的。
还有关键的一点是:须要注入拦截器的目标restTemplates究竟是哪一些?由于RestTemplate实例在context中可能存在多个,不可能全部的都注入拦截器,这里就是@LoadBalanced注解发挥做用的时候了。
严格上来讲,这个注解是spring cloud实现的,不是ribbon中的,它的做用是在依赖注入时,只注入实例化时被@LoadBalanced修饰的实例。
例如咱们定义Ribbon的RestTemplate的时候是这样的
@Bean @LoadBalanced public RestTemplate rebbionRestTemplate(){ return new RestTemplate(); }
所以才能为咱们定义的RestTemplate注入拦截器。
那么@LoadBalanced是如何实现这个功能的呢?其实都是spring的原生操做,@LoadBalance的源码以下
/** * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient * @author Spencer Gibb */ @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Qualifier public @interface LoadBalanced { }
很明显,‘继承’了注解@Qualifier,咱们都知道之前在xml定义bean的时候,就是用Qualifier来指定想要依赖某些特征的实例,这里的注解就是相似的实现,restTemplates经过@Autowired注入,同时被@LoadBalanced修饰,因此只会注入@LoadBalanced修饰的RestTemplate,也就是咱们的目标RestTemplate。
拦截器逻辑实现
LoadBalancerInterceptor源码以下。
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor { private LoadBalancerClient loadBalancer; private LoadBalancerRequestFactory requestFactory; public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) { this.loadBalancer = loadBalancer; this.requestFactory = requestFactory; } public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) { // for backwards compatibility this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer)); } @Override public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException { final URI originalUri = request.getURI(); String serviceName = originalUri.getHost(); Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri); return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution)); } }
拦截请求执行
@Override public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException { ILoadBalancer loadBalancer = getLoadBalancer(serviceId); //在这里负载均衡选择服务 Server server = getServer(loadBalancer); if (server == null) { throw new IllegalStateException("No instances available for " + serviceId); } RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server, serviceId), serverIntrospector(serviceId).getMetadata(server)); //执行请求逻辑 return execute(serviceId, ribbonServer, request); }
咱们重点看getServer方法,看看是如何选择服务的
protected Server getServer(ILoadBalancer loadBalancer) { if (loadBalancer == null) { return null; } // return loadBalancer.chooseServer("default"); // TODO: better handling of key }
代码配置随机loadBlancer,进入下面代码
public Server chooseServer(Object key) { if (counter == null) { counter = createCounter(); } counter.increment(); if (rule == null) { return null; } else { try { //使用配置对应负载规则选择服务 return rule.choose(key); } catch (Exception e) { logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e); return null; } } }
这里配置的是RandomRule,因此进入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(); 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); server = upList.get(index); 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; }
随机负载规则很简单,随机整数选择服务,最终达到随机负载均衡。咱们能够配置不一样的Rule来实现不一样的负载方式。