Ribbon解决了什么问题 当集群中有了注册中心之后,服务消费者可以拿到所有服务的列表,此时就需要有一个组件能够将请求负载均衡到各个实例上去。ribbon就是这样一个客户端侧的负载均衡组件,同出Netflix,与其他组件整合后非常实用。
Ribbon提供了多种不同的负载均衡算法实现,非常实用。
 
快速体验Ribbon(一) 此次我们直接基于原生ribbon体验下他的API,这是由于Spring Cloud Netflix对Ribbon的封装比较彻底,几乎没有使用成本,这也让我们很难了解到Ribbon的架构设计理念、核心概念、API等。
1、开发一个服务提供者 依赖项
1 2 3 4 <dependency >     <groupId > org.springframework.boot</groupId >      <artifactId > spring-boot-starter-web</artifactId >  </dependency > 
 
服务接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Slf 4j@RestController @RequestMapping ("/greeting" )public  class  GreetingController   {    @Autowired      private  Environment env;     @GetMapping ("/sayHello/{name}" )     public  String sayHello (@PathVariable("name" )  String name)  {         log.info("port:{}, name:{}" , env.getProperty("local.server.port" ), name);         return  "hello, "  + name;     } } 
 
启动类
1 2 3 4 5 6 @SpringBootApplication public  class  GreetingServiceApplication   {    public  static  void  main (String[] args)   {         SpringApplication.run(GreetingServiceApplication.class, args);     } } 
 
分别使用如下两个端口启动工程
application.yml文件
 
2、开发一个服务调用者 依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <dependency >     <groupId > com.netflix.ribbon</groupId >      <artifactId > ribbon</artifactId >      <version > 2.2.5</version >  </dependency > <dependency >     <groupId > com.netflix.ribbon</groupId >      <artifactId > ribbon-core</artifactId >      <version > 2.2.5</version >  </dependency > <dependency >     <groupId > com.netflix.ribbon</groupId >      <artifactId > ribbon-httpclient</artifactId >      <version > 2.2.5</version >  </dependency > <dependency >     <groupId > com.netflix.ribbon</groupId >      <artifactId > ribbon-loadbalancer</artifactId >      <version > 2.2.5</version >  </dependency > <dependency >     <groupId > com.netflix.archaius</groupId >      <artifactId > archaius-core</artifactId >      <version > 0.7.7</version >  </dependency > <dependency >     <groupId > com.google.guava</groupId >      <artifactId > guava</artifactId >      <version > 16.0.1</version >  </dependency > <dependency >     <groupId > commons-configuration</groupId >      <artifactId > commons-configuration</artifactId >      <version > 1.6</version >  </dependency > 
 
1 2 3 4 5 6 <properties >     <maven.compiler.source > 8</maven.compiler.source >      <maven.compiler.target > 8</maven.compiler.target >      <ribbon.version > 2.3.0</ribbon.version >      <archaius-core.version > 0.7.6</archaius-core.version >  </properties > 
 
服务消费者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import  com.netflix.client.ClientFactory;import  com.netflix.client.http.HttpRequest;import  com.netflix.client.http.HttpResponse;import  com.netflix.config.ConfigurationManager;import  com.netflix.niws.client.http.RestClient;public  class  GreetingClient   {    public  static  void  main (String[] args)  throws  Exception  {                           ConfigurationManager.getConfigInstance().setProperty(                 "greeting-service.ribbon.listOfServers" , "localhost:8080,localhost:8088" );                  RestClient restClient = (RestClient) ClientFactory.getNamedClient("greeting-service" );                  HttpRequest request = HttpRequest.newBuilder()                 .uri("/greeting/sayHello/leo" )                 .build();                  for (int  i = 0 ; i < 10 ; i++) {             HttpResponse response = restClient.executeWithLoadBalancer(request);             String result = response.getEntity(String.class);             System.out.println(result);         }     } } 
 
3、开始实验 运行GreetingClient,发现10次调用被均匀的分给了两个服务实例,实现了负载均衡。
快速体验Ribbon(二) 1、体验底层的负载均衡器API 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public  class  GreetingClient2   {    public  static  void  main (String[] args)   {         ILoadBalancer loadBalancer = new  BaseLoadBalancer();         ArrayList<Server> servers = new  ArrayList<>();         servers.add(new  Server("localhost" , 8080 ));         servers.add(new  Server("localhost" , 8088 ));         loadBalancer.addServers(servers);                  for  (int  i = 0 ; i < 10 ; i++) {             Server server = loadBalancer.chooseServer(null );             System.out.println("chosen server:"  + server);         }     } } 
 
可以看到,底层默认的负载均衡算法就是轮询算法。RestClient底层也是依赖负载均衡器工作的
2、自定义负载均衡规则 负载均衡器是基于一个IRule接口指定的负载均衡规则,来从服务器列表里获取每次要请求的服务器的,所以可以自定义负载均衡规则。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 static  class  MyRule  implements  IRule   {    ILoadBalancer loadBalancer;     @Override      public  Server choose (Object key)   {         List<Server> servers = loadBalancer.getAllServers();         return  servers.get(0 );     }     @Override      public  void  setLoadBalancer (ILoadBalancer lb)   {         this .loadBalancer = lb;     }     @Override      public  ILoadBalancer getLoadBalancer ()   {         return  this .loadBalancer;     } } 
 
测试方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public  static  void  main (String[] args)   {    BaseLoadBalancer loadBalancer = new  BaseLoadBalancer();     MyRule myRule = new  MyRule();     myRule.setLoadBalancer(loadBalancer);     loadBalancer.setRule(myRule);     List<Server> servers = new  ArrayList<>();     servers.add(new  Server("localhost" , 8080 ));     servers.add(new  Server("localhost" , 8088 ));     loadBalancer.addServers(servers);          for  (int  i = 0 ; i < 10 ; i++) {         Server server = loadBalancer.chooseServer(null );         System.out.println("chosen server:"  + server);     } } 
 
一般情况下,Ribbon内置的负载均衡规则就足够使用了,这里仅仅是为了体验一下底层API的设计。我们通过这个实验了解了ILoadBalancer和IRule是什么,两者有什么关系,以及特定场景下可以通过实现IRule接口实现自己的负载均衡规则(算法)。
3、Ribbon内置负载均衡规则 
RoundRobinRule:默认的负载均衡规则。直接从一堆server list中,不断轮询选择出一个server,每个server收到的请求是均匀的。
 
AvailabilityFilteringRule: 这个rule会检查服务器的可用性 如果连续3次连接失败,就会等待30秒后再次访问; 如果不断失败,那么等待时间会不断变长。 如果某个服务器的并发请求太高了,那么会绕过去,不再访问。
 
WeightedResponseTimeRule: 附带权重的,每个服务器可以指定一个权重,权重越高优先访问,如果魔鬼服务器响应时间比较长,那么权重就会降低,减少访问
 
ZoneAvoidanceRule:根据区域和服务器来进行负载均衡,根据机房划分
 
BestAvailableRule:忽略那些连接失败的服务器,然后尽量找并发比较低的服务器来请求
 
RandomRule:随机选择一个服务器
 
RetryRule:可以重试,先通过round robin找到的服务器,如果请求失败,就会接着会重新找一个服务器
 
 
可以看到,ribbon主打的就是负载均衡、网络通信,别的一些东西都是次要的。本系列文章会分析一些负载均衡算法的实现,以及看ribbon+eureka+spring cloud是如何整合使用的,会从源码角度分析下ribbon里的一些配置参数
快速体验Ribbon(三) 1、体验Ribbon中健康检查组件IPing 负载均衡器ILoadBalancer里,有IRule负责负载均衡的规则,选择一个服务器;还有一个IPing组件负责定时ping每个服务器,判断其是否存活。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public  class  GreetingClient4   {    public  static  void  main (String[] args)  throws  InterruptedException  {         BaseLoadBalancer loadBalancer = new  BaseLoadBalancer();         List<Server> servers = new  ArrayList<>();         servers.add(new  Server("localhost" , 8080 ));         servers.add(new  Server("localhost" , 8088 ));         loadBalancer.addServers(servers);                  loadBalancer.setPing(new  PingUrl(false , "/greeting/ping" ));         loadBalancer.setPingInterval(1 );         Thread.sleep(5000 );         for  (int  i = 0 ; i < 10 ; i++) {             Server server = loadBalancer.chooseServer(null );             System.out.println(server);         }     } } 
 
通过给负载均衡器LoadBalancer设置Ping组件,比如要ping的url,间隔时间等,就可以使用ping功能了。为了实验效果,笔者在原来的greeting服务里新加了一个ping接口,这个接口是个GET接口,没有逻辑和返回值。在启动实验时,可以等1~2秒,停掉其中一个greeting服务实例,可以观察到有趣的现象
笔者停掉了8088端口的实例,可以看到此时选取的服务器已经都是8080了,这在生产环境是比较实用的。
在生产环境,如果我们想要自定义更为强大的健康检查接口,可以通过实现IPing这个接口,覆盖里面的isAlive方法来实现。
 
快速体验Ribbon(四) 1、改造eureka入门里的实验代码 在eureka入门里,我们其实已经体验过了,这里为了更明显,对之前的代码稍加改造即可。
服务A-服务提供者
1 2 3 4 5 6 7 8 9 10 11 12 @RestController public  class  ServiceAController   {    @Resource      private  Environment environment;     @GetMapping ("/sayHello/{name}" )     public  String sayHello (@PathVariable("name" )  String name)  {         Integer port = environment.getProperty("local.server.port" , Integer.class);         return  "hello,"  + name + ", came from "  + port;     } } 
 
服务B-服务消费者
使用@LoadBalanced注解启用Ribbon
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Slf 4j@EnableEurekaClient @SpringBootApplication public  class  ServiceBApplication   {    @Bean      @LoadBalanced      public  RestTemplate getRestTemplate ()   {         return  new  RestTemplate();     }     public  static  void  main (String[] args)   {         SpringApplication.run(ServiceBApplication.class, args);         log.info("ServiceB 启动成功" );     } } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Slf 4j@RestController @Configuration public  class  ServiceBController   {    @Resource      private  RestTemplate restTemplate;     @GetMapping ("/greeting/{name}" )     public  String greeting (@PathVariable("name" )  String name)  {         String response = restTemplate.getForObject("http://serviceA/sayHello/"  + name, String.class);         log.info(response);         return  response;     } } 
 
2、开始实验 
分别用8081、8082两个端口启动服务A 
用9000端口启动服务B 
启动eureka集群 
打开浏览器,访问http://localhost:9000/greeting/sam多次刷新 
 
与我们快速体验Ribbon的效果一样,请求被均匀的打到各个实例上去了。
此处不知你是否有疑问,为什么只是简单的加了一个注解,就可以用restTemplate访问不同的实例了呢?
不难猜测,肯定是从eureka那里获取到了服务注册表,然后ribbon拿着这些服务注册表,用自己的IRule组件选出一个server,然后就发送过去请求就可以了。
其实,ribbon的底层原理说白了,就是这些,我们此次源码分析的目的也是围绕这些点剖析一下就可以了,难度比eureka低不少。
 
总结 我们通过几个实验,了解到了ribbon的几个核心组件,ILoadBalancer负载均衡器,IRule负载均衡规则、IPing定时检测服务存活,以及回味了基于Spring Cloud Netflix Ribbon的简洁集成方式。下一步,跟随笔者进入Ribbon的源码世界吧!