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的源码世界吧!