Ribbon整合Eureka组件,以实现负载均衡
1整体框架的说明
在本案例的框架里,我们将配置一个Eureka服务器,搭建三个提供相同服务的Eureka服务提供者,同时在Eureka服务调用者里引入Ribbon组件,这样,当有多个url向服务调用者发起调用请求时,整个框架能按配置在IRule和IPing中的“负载均衡策略“和“判断服务器是否可用的策略”,把这些url请求合理地分摊到多台机器上。
在下图里,我们能看到本系统的结构图,在其中,三个服务提供者向Eureka服务器注册服务,而基于Ribbon的负载均衡器能有效地把请求分摊到不同的服务器上。
为了让大家更方便地跑通这个案例,我们将讲解全部的服务器、服务提供者和服务调用者部分的代码。在下表里,列出了本架构中的所有项目。
项目名 |
说明 |
EurekaRibbonDemo-Server |
Eureka服务器 |
EurekaRibbonDemo-ServiceProviderOne EurekaRibbonDemo-ServiceProviderTwo EurekaRibbonDemo-ServiceProviderThree |
在这三个项目里,分别部署着一个相同的服务提供者 |
EurekaRibbonDemo-ServiceCaller |
服务调用者 |
2 编写Eureka服务器
第一,在pom.xml里编写本项目需要用到的依赖包,在其中,是通过如下的代码引入了Eureka服务器所必需的包,关键代码如下。
1 <dependency>
2 <groupId>org.springframework.cloud</groupId>
3 <artifactId>spring-cloud-starter-eureka-server</artifactId>
4 </dependency>
第二,在application.yml这个文件里,指定了针对Eureka服务器的配置,关键代码如下。
1 server:
2 port: 8888
3 eureka:
4 instance:
5 hostname: localhost
6 client:
7 serviceUrl:
8 defaultZone: http://localhost:8888/eureka/
在第2和第5行里,指定了本服务器所在的主机地址和端口号是localhost:8888,在第8行里,指定了默认的url是http://localhost:8888/eureka/。
第三步,在RegisterCenterApp这个服务启动程序里编写启动代码。
1 //省略必要的package和import代码
2 @EnableEurekaServer
3 @SpringBoot
4 Application
5 public class RegisterCenterApp
6 {
7 public static void main( String[] args )
8 { SpringApplication.run(RegisterCenterApp.class, args); }
9 }
启动该程序后,能在http://localhost:8888/看到该服务器的相关信息。
3 编写Eureka服务提供者
这里有三个服务提供者,它们均是根据之前博文中的案例EurekaBasicDemo-ServiceProvider改写而来。我们就拿EurekaRibbonDemo-ServiceProviderOne来举例,看下其中包含的关键要素。
第一,同样是在pom.xml里,引入了服务提供者程序所需的jar包,不过在其中需要适当地修改项目名。
第二,同样是在ServiceProviderApp.java里,编写了启动程序,代码不变。
第三,在application.yml里,编写了针对这个服务提供者的配置信息,关键代码如下。
1 server:
2 port: 1111
3 spring:
4 application:
5 name: sayHello
6 eureka:
7 client:
8 serviceUrl:
9 defaultZone: http://localhost:8888/eureka/
在第2行里,指定了本服务是运行在1111端口上,在另外的两个服务提供者程序里,我们分别指定了它们的工作端口是2222和3333。
在第5行里,我们指定了服务提供者的名字是sayHello,另外两个服务器提供者的名字同样是sayHello,正因为它们的名字都一样,所以服务调用者在请求服务时,负载均衡组件才能有效地分摊流量。
第四,在Controller这个控制器类里,编写了处理url请求的逻辑,关键代码如下。
1 //省略了必要的package和import的代码
2 @RestController
3 public class Controller {
4 @RequestMapping(value = "/sayHello/{username}", method = RequestMethod.GET )
5 public String hello(@PathVariable("username") String username) {
6 System.out.println("This is ServerProvider1");
7 return "Hello Ribbon, this is Server1, my name is:" + username;
8 }
9 }
在第2行里,我们通过@RestController注解来说明本类承担着“控制器”的角色。在第4行里,我们定义了触发hello方法的url格式和Http请求的方式。在第5到第8行的hello方法里我们返回了一个字符串。请大家注意,在第6行和第7行的代码里,我们能明显看出输出和返回信息是来自于1号服务提供者。
EurekaRibbonDemo-ServiceProviderTwo和EurekaRibbonDemo-ServiceProviderOne项目很相似,改动点有如下三个。
第一,在pom.xml里,把项目名修改成EurekaRibbonDemo-ServiceProviderTwo。
第二,在application.yml里,把端口号修改成2222,关键代码如下所示。
1 server:
2 port: 2222
第三,在Controller.java的hello方法里,在输出和返回信息里,打上出“Server2“的标记,关键代码如下。
1 @RequestMapping(value = "/sayHello/{username}", method = RequestMethod.GET )
2 public String hello(@PathVariable("username") String username) {
3 System.out.println("This is ServerProvider2");
4 return "Hello Ribbon, this is Server2, my name is:" + username;
5 }
在EurekaRibbonDemo-ServiceProviderThree里,同样在EurekaRibbonDemo-ServiceProviderOne基础上做上述3个改动。这里需要在application.yml里,把端口号修改成3333,在Controller类中,同样需要在输出和返回信息中打上“Server3”的标记。
4 在Eureka服务调用者里引入Ribbon
EurekaRibbonDemo-ServiceCaller项目是根据第三章的EurekaBasicDemo-ServiceCaller改写而来,其中的关键代码如下。
第一,在pom.xml里,只是适当地修改项目名字,没有修改其它代码。
第二,没有修改启动类ServiceCallerApp.java里的代码。
第三,在application.yml了, 添加了描述服务器列表的listOfServers属性,代码如下。
1 spring:
2 application:
3 name: callHello
4 server:
5 port: 8080
6 eureka:
7 client:
8 serviceUrl:
9 defaultZone: http://localhost:8888/eureka/
10 sayHello:
11 ribbon:
12 listOfServers:
13 http://localhost:1111/,http://localhost:2222/,http://localhost:3333
在第3行,我们指定了服务调用者本身的服务名是callHello,在第5里,指定了这个微服务的是运行在8080端口上。由于服务调用者本身也能对外界提供服务,所以外部程序能根据这个服务名和端口号,以url的形式调用其中的hello方法。
这里的关键是在第12和13行,我们通过ribbon.listOfServers,指定了该服务调用者能获得服务的三个url地址,请注意,这里的三个地址和上文里服务提供者发布服务的三个地址是一致的。
第四,在控制器类里,用RestTemplate对象,以负载均衡的方式调用服务,代码如下。
1 //省略必要的package和import的代码
2 @RestController
3 @Configuration
4 public class Controller {
5 @Bean
6 @LoadBalanced
7 public RestTemplate getRestTemplate()
8 { return new RestTemplate(); }
9 //提供服务的hello方法
10 @RequestMapping(value = "/hello", method = RequestMethod.GET )
11 public String hello() {
12 RestTemplate template = getRestTemplate();
13 String retVal = template.getForEntity("http://sayHello/sayHello/Eureka", String.class).getBody();
14 return "In Caller, " + retVal;
15 }
16 }
在这个控制器类的第7行里,我们通过getRestTemplate方法返回一个RestTemplate类型对象。
RestTemplate是Spring提供的能以Rest形式访问服务的对象,本身不具备负载均衡的能力,所以我们需要在第6行通过@LoadBalanced注解赋予它这个能力。
在第11行的hello方法里,我们首先在第12行通过getRestTemplate方法得到了template对象,随后通过第13行的代码,用template对象提供的getForEntity方法,访问之前Eureka服务提供者提供的“http://sayHello/sayHello/Eureka“服务,并得到String类型的结果,最后在第14行,根据调用结果返回一个字符串。
由于在框架里,我们模拟了在三台机器上部署服务的场景,而在上述服务调用者的代码里,我们又在template对象上加入了@LoadBalanced注解,所以在上述第13行代码里发起的请求会被均摊到三台服务器上。
需要注意的是,这里我们没有重写IRule和IPing接口,所以这里是采用了默认的RoundRobbin(也就是轮询)的访问策略,同时将默认所有的服务器都处于可用状态。
依次启动本框架中的Eureka服务器,三台服务提供者和服务器调用者的服务之后,在浏览器里输入http://localhost:8888/,我们能看到如下图所示的效果。
在其中,我们能看到有三个提供服务的SAYHELLO应用实例,它们分别运行在1111,2222和3333端口上,同时,服务调用者CALLHELLO则运行在8080端口上。
如果我们不断在浏览器里输入http://localhost:8080/hello,那么能依次看到如下所示的输出。
1 In Caller, Hello Ribbon, this is Server2, my name is:Eureka
2 In Caller, Hello Ribbon, this is Server1, my name is:Eureka
3 In Caller, Hello Ribbon, this is Server3, my name is:Eureka
4 In Caller, Hello Ribbon, this is Server2, my name is:Eureka
5 In Caller, Hello Ribbon, this is Server1, my name is:Eureka
6 In Caller, Hello Ribbon, this is Server3, my name is:Eureka
7 …
从上述输出来看,请求是以Server2,Server1和Server3的次序被均摊到三台服务器上。在每次启动服务后,可能承接请求的服务器次序会有所变化,可能下次是按Server1,Server2和Server3的次序,但每次都能看到“负载均衡”的效果。
5 重写IRule和IPing接口
这里,我们将在上述案例的基础上,重写IRule和IPing接口里的方法,从而实现自定义负载均衡和判断服务器是否可用的规则。
请注意,由于我们是在客户端,也就是EurekaRibbonDemo-ServiceCaller这个项目调用服务,所以本部分的所有代码是写在这个项目里的。
步骤一,编写包含负载均衡规则的MyRule.java,代码如下。
1 package com.controller; //请注意这个package路径
2 省略必要的import语句
3 public class MyRule implements IRule {//实现IRule类
4 private ILoadBalancer lb;
5 //必须要重写这个choose方法
6 public Server choose(Object key) {
7 //得到0到3的一个随机数,但不包括3
8 int number = (int)(Math.random() * 3);
9 System.out.println("Choose the number is:" + number);
10 //得到所有的服务器对象
11 List<Server> servers = lb.getAllServers();
12 //根据随机数,返回一个服务器
13 return servers.get(number);
14 }
15 省略必要的get和set方法
16 }
在上述代码的第3行里,我们实现了IRule类,并在其中得第6行里,重写了choose方法。
在这个方法里,我们在第8行通过Math.random方法,得到了0到3之间的一个随机数,包括0,但不包括3,并用这个随机数在第13行返回了一个Server对象,以此实现随机选择的效果。在实际的项目里,还可以根据具体的业务逻辑choose方法,以实现其它的“选择服务器”的策略。
第二步,编写判断服务器是否可用的MyPing.java,代码如下。
1 package com.controller; //也请注意这个package的路径
2 省略import语句
3 public class MyPing implements IPing { //这里是实现IPing类
4 //重写了判断服务器是否可用的isAlive方法
5 public boolean isAlive(Server server) {
6 //这里是生成一个随机数,以此来判断该服务器是否可用
7 //还可以根据服务器的相应时间等依据,判断服务器是否可用
8 double data = Math.random();
9 if (data > 0.6) {
10 System.out.println("Current Server is available, Name:" + server.getHost() + ", Port is:" + server.getHostPort());
11 return true;
12 } else {
13 System.out.println("Current Server is not available, Name:" + server.getHost() + ", Port is:" + server.getHostPort());
14 return false;
15 }
16 }
17 }
在第3行里,我们是实现了IPing这个接口,并在第5行重写了其中的isAlive方法。
在这个方法里,我们是根据一个随机数,来判断该服务器是否可用,如果可用,则返回true,如反之则返回false。请注意,这仅仅是个演示的案例,在实际项目里,我们基本上是不会重写isAlive方法的。
第三步,改写application.yml,在其中添加关于MyPing和MyRule的配置,代码如下。
1 spring:
2 application:
3 name: callHello
4 server:
5 port: 8080
6 eureka:
7 client:
8 serviceUrl:
9 defaultZone: http://localhost:8888/eureka/
10 sayHello:
11 ribbon:
12 NFLoadBalancerRuleClassName: com.controller.MyRule
13 NFLoadBalancerPingClassName: com.controller.MyPing
14 listOfServers:
15 http://localhost:1111/,http://localhost:2222/,http://localhost:3333
改动点是从第10行到第13行,请注意这里的SayHello需要和服务提供者给出的“服务名”一致,在第12和13行里,分别定义了本程序(也就是服务调用者)所用到的IRule和IPing类,配置时需要包含包名和文件名。
第四步,改写Controller.java和这个控制器类,代码如下。
1 省略必要的package和import代码
2 @RestController
3 @Configuration
4 public class Controller {
5 //以Autowired的方式引入loadBalanceerClient对象
6 @Autowired
7 private LoadBalancerClient loadBalancerClient;
8 //给RestTemplate对象加入@LoadBalanced注解
9 //以此赋予该对象负载均衡的能力
10 @Bean
11 @LoadBalanced
12 public RestTemplate getRestTemplate()
13 { return new RestTemplate(); }
14 @Bean //引入MyRule
15 public IRule ribbonRule()
16 { return new MyRule();}
17 @Bean //引入MyPing
18 public IPing ribbonpIng()
19 { return new MyPing();}
20 //编写提供服务的hello方法
21 @RequestMapping(value = "/hello", method = RequestMethod.GET )
22 public String hello() {
23 //引入策略,这里的sayHello需要和application.yml
24 //第10行的sayHello一致,这样才能引入MyPing和MyRule
25 loadBalancerClient.choose("sayHello");
26 RestTemplate template = getRestTemplate();
27 String retVal = template.getForEntity("http://sayHello/sayHello/Eureka", String.class).getBody();
28 return "In Caller, " + retVal;
29 }
30 }
和之前的代码相比,我们添加了第15行和第18行的两个方法,以此引入自定义的MyRule和MyPing两个方法。
而且,在hello方法的第15行里,我们通过choose方法,为loadBalancerClient这个负载均衡对象选择了MyRule和MyPing这两个规则。
如果依次启动Eureka服务器,注册在Eureka里的三个服务提供者和服务调用者之后,在浏览器里输入http://localhost:8080/hello,那么能在EurekaRibbonDemo-ServiceCaller的控制台里看到类似于如下的输出。
1 Choose the number is:1
2 Choose the number is:0
3 Current Server is not available, Name:192.168.42.1, Port is:192.168.42.1:2222
4 Current Server is available, Name:192.168.42.1, Port is:192.168.42.1:3333
5 Current Server is not available, Name:192.168.42.1, Port is:192.168.42.1:1111
第1和第2行是MyRule里的输出,而第3到第5行是MyPing里的输出,由于这些输出是和随机数有关,所以每次输出的内容未必一致,但至少能说明,我们在MyRule和MyPing里配置的相关策略是生效的,服务调用者(EurekaRibbonDemo-ServiceCaller)的多次请求在以“负载均衡”的方式分发到各服务提供者时,会引入我们定义在上述两个类里的策略。
本文所给出的代码均可运行,此外,本文内容摘自本人所写的专业书籍,转载时请同时引入该版权申明,请勿用于商业用途。
Ribbon整合Eureka组件,以实现负载均衡的更多相关文章
- 客户端负载均衡Ribbon之一:Spring Cloud Netflix负载均衡组件Ribbon介绍
Netflix:['netfliːks] ribbon:英[ˈrɪbən]美[ˈrɪbən]n. 带; 绶带; (打印机的) 色带; 带状物;v. 把…撕成条带; 用缎带装饰; 形成带状; L ...
- Ribbon整合Eureka,出现 No instances available for XXX 异常
请观察这里的片段有没有问题? @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } ...
- springcloud报错-Ribbon整合Eureka,出现 No instances available for XXX 异常
RestTemplate注入有问题 新版的需要这样注入: @Bean @LoadBalanced RestOperations restTemplate(RestTemplateBuilder bui ...
- spring boot 2.0.3+spring cloud (Finchley)2、搭建负载均衡Ribbon (Eureka+Ribbon+RestTemplate)
Ribbon是Netflix公司开源的一个负载均衡组件,将负载均衡逻辑封装在客户端中,运行在客户端的进程里. 本例子是在搭建好eureka的基础上进行的,可参考spring boot 2.0.3+sp ...
- SpringCloud学习系列之二 ----- 服务消费者(Feign)和负载均衡(Ribbon)使用详解
前言 本篇主要介绍的是SpringCloud中的服务消费者(Feign)和负载均衡(Ribbon)功能的实现以及使用Feign结合Ribbon实现负载均衡. SpringCloud Feign Fei ...
- 【四】Ribbon负载均衡
1.概述1.1.是什么 Spring Cloud Ribbon 是基于Netflix Ribbon实现的一套客户端负载均衡的工具. 简单的说, Ribbon是Netflix发布的开源项目,主要功能是提 ...
- SpringCloud Netflix Ribbon(负载均衡)
⒈Ribbon是什么? Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡工具. Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负 ...
- Spring Cloud:使用Ribbon实现负载均衡详解(上)
1. 什么是 Ribbon? Spring Cloud Ribbon 是一套实现客户端负载均衡的工具.注意是客户端,当然也有服务端的负载均衡工具,我们后面再介绍.可以认为 Ribbon 就是一个负载均 ...
- SpringCloud:Ribbon负载均衡
1.概述 Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端 负载均衡的工具. 简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客 ...
随机推荐
- iOS中 动态热修补技术JSPatch 韩俊强的博客
.1.4) JSPatch bridge Objective-C and JavaScript. You can call any Objective-C class and method in Ja ...
- 如何在Cocos2D 1.0 中掩饰一个精灵(六)
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 掩饰一个精灵:实现代码 打开HelloWorldLayer.m并 ...
- mysql5.6升级到5.7后Sequel Pro无法连接解决
因为装ntop,brew自动升级了本地的Mysql,结果升级完成之后,使用Sequel Pro连接总是报错,使用mysql 命令行工具就没有问题. OSX版本 10.11.5 Mysql版本 5.6 ...
- Android5.1设备无法识别exFAT文件系统的64G TF卡问题
64G TF卡刚买回来的时候默认exFAT文件系统,在电脑端(XP和WIN7)可以识别,但在我们Android5.1S设备无法识别,采用guiformat工具格式化为FAT32文件系统后才可以正常识别 ...
- JSP编译成Servlet(三)JSP编译后的Servlet
JSP编译后的Servlet类会是怎样的呢?他们之间有着什么样的映射关系?在探讨JSP与Servlet之间的关系时先看一个简单的HelloWorld.jsp编译成HelloWorld.java后会是什 ...
- BottomSheet底部动作条使用
底部动作条 底部动作条(Bottom Sheets)是一个从屏幕底部边缘向上滑出的一个面板,使用这种方式向用户呈现一组功能.底部动作条呈现了简单.清晰.无需额外解释的一组操作. 使用环境 底部动作条( ...
- Linux Android 多点触摸协议 原文出自【比特网】,转载请保留原文链接:http://soft.chinabyte.com/os/71/12306571.shtml
为了使用功能强大的多点触控设备,就需要一种方案去上报用户层所需的详细的手指触摸数据.这个文档所描述的多点触控协议可以让内核驱动程序向用户层上报任意多指的数据信息. 使用说明 单点触摸信息是以ABS承载 ...
- 2013暑假总结-SB学习
经过暑假的学习,使英语原本基础不好的我找到了英语学习的感觉.方向.信心,暑假的这种团队学习英语的感觉才刚刚开始,即将开学了,我们并将保持着这学习的劲头坚持努力的做下去. 暑假35天英语的全职学习,对于 ...
- leetcode 104 Maximum Depth of Binary Tree二叉树求深度
Maximum Depth of Binary Tree Total Accepted: 63668 Total Submissions: 141121 My Submissions Question ...
- Linux System Programming --Chapter Nine
这一章的标题是 "信号" ,所以本文将对信号的各个方面进行介绍,由于Linux中的信号机制远比想象的要复杂,所以,本文不会讲的很全面... 信号机制是进程之间相互传递消息的一种方法 ...