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