4.1.1 Ribbon 简介
Ribbon是Netflix下的负载均衡项目,它在集群中为各个客户端的通信提供了支持,它主要实现中间层应用程序的负载均衡。
Ribbon提供以下特性:
同为Netflix项目,Ribbon可以与Eureka整合使用,Ribbon同样被集成到Spring Cloud 中,作为spring-cloud-ne由ix项目中的子模块。
Spring Cloud将Ribbon的API进行了封装, 使用者可以使用封装后的API来实现负载均衡,也可以直接使用Ribbon的原生APL
4.1.2 Ribbon 子模块
Ribbon主要有以下三大子模块。
4.1.3负载均衡器组件
Ribbon的负载均衡器主要与集群中的各个服务器进行通信,负载均衡器需要提供以下基础功能:
为了实现负载均衡的基础功能,Ribbon的负载均衡器有以下三大子模块。
本章关于Ribbon的知识,主要围绕负载均衡器组件进行
本章的4.2节和4.3节,单独使用Ribbon框架,关于整合Spring Cloud的内容,将在 4.4节讲述。本节将以一个简单的Hello World程序来展示Ribbon API的使用。
本例的程序 结构如图所示。
本书所使用的Spring Cloud, 默认集成的Ribbon版本为2.2.2, 因此本书也使用该版本的Ribbon
4.2.1编写服务
为了能查看负载均衡效果,先编写一个简单的REST服务,通过指定不同的端口,让服务可以启动多个实例。本例的请求服务器,仅仅是一个基于Spring Boot的Web应用,
与2.3节中的应用类似,如果读者熟悉建立过程,可跳过部分创建过程,本小节最终目的 是发布两个REST服务。
新建名称为first-ribbon-server的Maven项目,加入以下依赖:
org.springframework.boot
spring-boot-starter-web
l.5.4.RELEASE
建立Spring Boot启动类,如代码所示:
@SpringBootApplication
public class FirstServerApplication {
public static void main(String[] args) {
//读取控制台输入作为端口参数
Scanner scan = new Scanner(System.in);
String port = scan.nextLine();
//设置启动的服务器端口
new SpringApplicationBuiIder(FirstServerApplication.class)
.properties("server.port=" + port)
.run(args);
}
}
运行main方法,并在控制台输入端口号,即可启动Web服务器
接下来编写控制器:
@RestController
public class MyController {
@RequestMapping(value = "/person/{personld}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public Person findPerson(@PathVariable("personld") Integer personld, HttpServletRequest request) {
Person p = new Person ();
p.setld(personld);
p.setName("Crazyit");
p.setAge(30);
p.setMessage(request.getRequestURL().toString());
return p;
}
@RequestMapping(value = "/", method = RequestMethod.GET)
public String hello() {
return "hello";
}
}
在控制器中,发布了两个REST服务。其中,调用地址为/person/personld的服务后, 会返回一个Person实例的JSON字符串,为了看到请求的URL,为Person的message属性设置了请求的URL
4.2.2 编写请求客户端
新建名称为first-ribbon-client的Maven项目,加入以下依赖:
com.netflix.ribbon
ribbon
2.2.2
com.netflix.ribbon
ribbon-httpclient
2.2.2
接下来,使用Ribbon的客户端发送请求, 请见代码:
public class TestRestClient {
public static void main(String[] args) throws Exception {
//设置请求的服务器
ConfigurationManager.getConfiglnstance()
.setProperty("my-client.ribbon.listOfServers", "localhost:8080,localhost:8081");
//获取REST请求客户端
RestClient client = (RestClient) ClientFactory.getNamedClient("my-client");
//创建请求实例
HttpRequest request = HttpRequest.newBuilder()
.uri("/person/l").build();
//发送6次请求到服务器中
for (int i = 0; i < 6; i++) {
HttpResponse response = client.executeWithLoadBalancer(request);
String result = response.getEntity(String.class);
System.out.printIn(result);
}
}
}
启动两次服务器类FirstServerApplication,并在控制台分别输入8080和8081端口。启动服务器后,运行客户端,输出结果如下:
在代码清单中,使用ConfigurationManager类来配置请求的服务器列表,为 localhost:8080 与 localhost:8081, 再使用RestClient 对象,向/person/1 地址发送 6 次请求。
1 2 3 4 5 6 |
|
根据输出结果可知,RestClient轮流向8080与8081端口发送请求,可见在RestClient 中己经帮我们实现了负载均衡的功能
4.2.3 Ribbon 的配置
在编写客户端时,使用了 ConfigurationManager来设置配置项,除了在代码中指定配置项外,还可以将配置放到.properties 文件中。
ConfigurationManager 的 loadPropertiesFromResources 方法可以指定properties文件的位置,配置格式如下:
1 |
|
其中
如果想对全部客户端生效, 可以将客户端名称去掉,直接以
1 |
|
Ribbon的配置同样可以在Spring Cloud的配置文件(即application.yml)中使用。
Ribbon提供了几个负载均衡的组件,其目的就是让请求转给合适的服务器处理。因此, 如何选择合适的服务器便成为负载均衡机制的核心。本节将围绕Ribbon负载均衡器的组件, 向大家展示Ribbon负载均衡的实现机制。
4.3.1负载均衡器
Ribbon的负载均衡器接口定义了服务器的操作,主要是用于进行服务器选择。
在前面 的例子中,客户端使用了 RestClient类,在发送请求时,会使用负载均衡器(ILoadBalancer) 接口,根据特定的逻辑来选择服务器。
服务器列表可使用listOfServers进行配置,也可以 使用动态更新机制。如下代码使用负载均衡器来选择服务器。
public class ChoseServerLest{
//创建负载均衡器
ILoadBalancer lb = new BaseLoadBalancer();
//添加服务器
List servers = new ArrayList();
servers.add(new Server("localhost", 8080));
servers.add(new Server("localhost", 8081));
lb.addServers(servers);
//进行6次服务器选择
for(int i = 0; i < 6; i++) {
Server s = lb.chooseServer(null);
System.out.printIn(s);
}
}
localhost:8081
localhost:8080
localhost:8081
localhost:8080
localhost:8081
localhost:8080
代码中使用了 BaseLoadBalancer这个负载均衡器,将两个服务器对象加入负载均衡器 中,再调用6次chooseServer方法,可以看到输出如下:
根据结果可知,最终选择的服务器与4.2节中介绍的一致,可以判定本例与4.2节的例 子选择服务器的逻辑是一致的,在默认情况下,会使用RoundRobinRule的规则逻辑。
4.3.2 自定义负载规则
根据前一小节的介绍可知,选择哪个服务器进行请求处理,由ILoadBalancer接口的 chooseServer方法决定。
而在BaseLoadBalancer类中,则使用IRule接口的choose方法来 决定选择哪一个服务器对象。
如果想自定义负载均衡规定,可以编写一个IRule接口的实 现类。
如下代码清单实现了自己的负载规定:
public class MyRule implements IRule {
ILoadBalancer lb;
public MyRule() {
}
public MyRule(ILoadBalancer lb) {
this.lb = lb;
}
public Server choose(Object key) {
//获取全部的服务器
List servers = lb.getAHServers ();
//只返回第一个Server对象
return servers.get(0);
}
public void setLoadBalancer(ILoadBalancer lb) {
this.lb = lb;
}
public ILoadBalancer getLoadBalancer () {
return this.lb;
}
}
在自定义规则类中,实现的choose方法调用了 ILoadBalancer的getAHServers方法, 返回全部服务器,为了简单起见,本例只返回第一个服务器。
为了能在负载均衡器中使用 自定义的规则,需要修改选择服务器的代码,请见代码清单:
public class TestMyRule{
//创建负载均衡器
BaseLoadBalancer lb = new BaseLoadBalancer();
//设置自定义的负载规则
lb.setRule(new MyRule(lb));
//添加服务器
List servers = new ArrayList();
servers.add(new Server("localhost", 8080));
servers.add(new Server("localhost", 8081));
lb.addServers(servers);
//进行6次服务器选择
for(int i = 0; i < 6; i++) {
Server s = lb.chooseServer(null);
System.out.printIn(s);
}
}
以上是直 接使用编码方式来设置负载规则,可以使用配置的方式来完成这些工作。修改Ribbon的配 置,让请求的客户端使用我们定义的负载规则,请见代码清单:
运行上面代码清单可以看到,请求6次所得到的服务器均为localhost:8080
public class TestMyRuleConfig{
//设置请求的服务器
ConfigurationManager.getConfiglnstance()
.setProperty("my-client.ribbon.listOfServers","localhost:8080,localhost:8081");
//配置规则处理类
ConfigurationManager.getConfiglnstance()
.setProperty("my-client.ribbon.NFLoadBalancerRuleClassName", MyRule.class.getName());
//获取rest请求客户端
RestClient client = (RestClient) ClientFactory.getNamedClient("my-client");
//创建请求实例
HttpRequest request = HttpRequest.newBuilder().uri("/person/1").build();
//发送6次请求到服务器中
for (int i = 0; i < 6; i++) {
HttpResponse response = client.executeWithLoadBalancer(request);
String result = response.getEntity(String.class);
System.out.printIn(result);
}
}
在请求客户端时,与4.2节中介绍的客户端基本一致,只是加入了 my-client.ribbon.NFLoad- BalancerRuleClassName属性,设置了自定义规则处理类为MyRule,
这个配置项同样可以在配置文件中使用,包括Spring Cloud的配置文件(application.yml等)。
启动4.2节中介绍的服务器端两次,分别设置8080与8081端口,再运行代码清单4-7, 可以看到输出了 6 次 {"id":l,"name":"Crazyit","age":30,"message":"http://localhost:8080/person/l"},
根据结果可知,我们的自定义规则生效了,请求只让8080端口处理。
在实际环境中,如果要实现自定义的负载规则,可能还需要结合各种因素,例如考虑: 具体业务的发生时间、服务器性能等。
实现中可能还涉及使用计算器、数据库等技术,具体情形会更为复杂,本例的负载规则较为简单,目的是让读者了解负载均衡的原理。
4.3.3 Ribbon自带的负载规则
Ribbon提供了若干个内置的负载规则,使用者完全可以直接使用,主要有以下内置的负载规则。
> RoundRobinRule:系统默认的规则,通过简单地轮询服务列表来选择服务器,其他 规则在很多情况下仍然使用RoundRobinRule
> AvailabilityFilteringRule:该规则会忽略以下服务器。
可以通过修改niws.loadbalancer.
> WeightedResponseTimeRule:为每个服务器赋予一个权重值,服务器的响应时间越 长,该权重值就越少,这个规则会随机选择服务器,权重值有可能会决定服务器的 选择。
> ZoneAvoidanceRule:该规则以区域、可用服务器为基础进行服务器选择。使用Zone 对服务器进行分类,可以理解为机架或者机房。
> BestAvailableRule:忽略“短路”的服务器,并选择并发数较低的服务器。
> RandomRule:顾名思义,随机选择可用的服务器。
> RetryRule:含有重试的选择逻辑,如果使用RoundRobinRule选择的服务器无法连接,那么将会重新选择服务器。
以上提供的负载规则基本可以满足大部分的需求,如果有更为复杂的要求,建议实现 自定义负载规则。
4.3.4 Ping 机制
在负载均衡器中,提供了 Ping机制,每隔一段时间,会去Ping服务器,判断服务器 是否存活。
该工作由IPing接口的实现类负责,如果单独使用Ribbon,在默认情况下,不 会激活Ping机制,默认的实现类为DummyPing。
如下代码清单使用了另外一个IPing实现类 PingUrL
public class TestPingUrl{
//创建负载均衡器
BaseLoadBalancer lb = new BaseLoadBalancer();
//添加服务器
List servers = new ArrayList();
// 8080端口连接正常
servers.add(new Server("localhost", 8080));
// 一个不存在的端口
servers.add(new Server("localhost", 8888));
lb.addServers(servers);
//设置IPing实现类
lb.setPing(new PingUrl());
//设置Ping时间间隔为2秒
lb.setPinglnterval(2);
Thread.sleep(6000);
for (Server s : lb.getAHServers()) {
System.out.printin(s.getHostPort() + "状态:"+s.isAlive());
}
}
在以上例子中,实际上会请求 http://localhost:8080 与 http://localhost:8888 这两个地址厂在运行前先以 8080 端口启动4.2 节中介绍的服务器, 上面代码清单使用了代码的方法来设置负载均衡器使用PingUrl,设置了每隔2秒就向 两个服务器发起请求,PingUrl实际使用的是HttpCliento
最终效果为8080的服务器状态正常,而8888的服务器则无法连接, 运行代码清单,可以看到输出如下:
localhost: 8080 状态:true
localhost: 8888 状态:false
除了在代码中配置使用IPing类外,还可以在配置中设置IPing实现类,请见代码:
public class IestPingUrlConfig{
//设置请求的服务器
ConfigurationManager.getConfiglnstance()
.setProperty("my-client.ribbon.1istOfServers", "localhost:8080, localhost:8888");
//配置Ping处理类
ConfigurationManager.getConfiglnstance()
.setProperty("my-client.ribbon.NFLoadBalancerPingClassName",
PingUrl.class.getName());
//配置Ping时间间隔
ConfigurationManager.getConfiglnstance()
.setProperty("my-client.ribbon.NFLoadBalancerPinglnterval", 2);
//获取rest请求客户端
RestClient client = (RestClient) ClientFactory.getNamedClient("my-client");
Thread.sleep (6000);
//获取全部服务器
List servers = client.getLoadBalancer().getAHServers();
System.out.printIn(servers.size());
//输出状态
for(Server s : servers) (
System.out.printin(s.getHostPort() + "状态:" + s.isAlive());
}
}
注意代码中的以下两个配置。
> my-client.ribbon.NFLoadBalancerPingClassName:配置 IPing 的实现类。
> my-client.ribbon.NFLoadBalancerPinglnterval:配置 Ping 操作的时间间隔。
4.3.5 自定义 Ping
以上两个配置同样可以使用在配置文件中。
通过前面章节的案例可知,实现自定义Ping较为简单,先实现IPing接口,然后再通过配置来设定具体的Ping实现类,下面代码清单为自定义的Ping类
public class MyPing implements IPing {
public boolean isAlive(Server server) {
System.out.println("这是自定义Ping实现类:" + server.getHostPort());
return true;
}
}
要使用自定义的 Ping 类,通过修改
4.3.6其他配置
本节主要介绍了 Ribbon负载均衡器的负载规则以及Ping,这两部分可以通过配置来实现逻辑的改变。
除了这两部分外,还可以使用以下配置来改变负载均衡器的其他行为。
Spring Cloud集成了 Ribbon,结合Eureka,可实现客户端的负载均衡。
前面章节中所使 用的RestTemplate (被@LoadBalanced修饰)、还有后面章节中将介绍的Feign,都己经拥有负载均衡功能。
本节将以RestTemplate为基础,讲述及测试Eureka中的Ribbon配置
4.4.1 准备工作
为了本节的测试做准备,按顺序进行以下工作:
>新建Eureka服务器端项目,命名为cloud-server,端口为8761,代码目录为 codes\04\4.4\cloud-server
>新建Eureka服务提供者项目,命名为cloud-provider,代码目录为codes\04\4.4\cloud- provider,该项目主要进行以下工作。
>新建Eureka服务调用者项目,命名为cloud.invoker,对外端口为9000,代码目录 为codes\04\4.4\cloud-invoker;本例的负载均衡配置主要针对服务调用者。
以上项目准备完成并启动后,结构如图
4.4.2 使用代码配置Ribbon
在4.3节中讲述了负载规则以及Ping机制,在Spring Cloud中,可将自定义的负载规 则以及Ping类放到服务调用者中查看效果。新建自定义的IRule与IPing,两个实现类请见如下代码清单:
public class MyRule implements IRule {
private ILoadBalancer lb;
public Server choose(Object key) {
List servers = lb.getAHServers ();
System.out.printin ("这是自定义服务器规则类,输出服务器信息:");
for(Server s : servers) {
System.out.printin("" + s.getHostPort());
}
return servers.get(0);
}
//省略setter和getter方法
}
public class MyPing implements IPing {
public boolean isAlive(Server server) {
System.out.printin("自定义Ping类,服务器信息:"+ server.getHostPort());
return true;
}
}
根据两个自定义的IRule和IPing类可知,实际上跟4.3节中介绍的自定义实现类似, 服务器选择规则只返回集合中的第一个实例,IPing实现仅仅是控制输入服务器信息。
接下来,新建配置类、返回规则与Ping的Bean,请见如下代码清单:
public class MyConfig {
@Bean
public IRule getRule() (
return new MyRule();
}
@Bean
public IPing getPing() (
return new MyPing();
}
}
@RibbonClient(name="cloud-provider", configuration=MyConfig.class)
public class CloudProviderConfig {
}
在代码清单4-12中,CloudProviderConfig配置类使用了 @RibbonClient注解,配置了 RibbonClient的名称为cloud-provider,对应的配置类为MyConfig ,也就是名称为 cloud-provider的客户端将使用MyRule与MyPing两个类。
在服务调用者的控制器中,加入对外服务,服务中调用RestTemplate,如代码清单:
@RestController
@Configuration
public class InvokerController {
@LoadBalanced
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
@RequestMapping(value = "/router", method = RequestMethod.GET,
produces = MediaType.APPLICAT10N_JSON_VALUE)
public String router() {
RestTemplate restTpl = getRestTemplate();
//根据名称调用服务
String json = restTpl.getForObject("http://cloud-provider/person/1", String.class);
return json;
}
}
关于RestTemplate的原理,将在本章后面的章节中讲述。进行以下操作,查看本例效果: 在以上的控制器中,为RestTemplate加入了@LoadBalanced修饰,与第3章类似,在此不再赘述。
4.4.3使用配置文件设置Ribbon
在前面使用Ribbon时,可以通过配置来定义各个属性。在使用Spring Cloud时,这些 属性同样可以配置到application.yml中,以下的配置同样生效:
cloud-provider:
ribbon:
NFLoadBalancerRuleClassName: org.crazyit.cloud.MyRule
NFLoadBalancerPingClassName: org.crazyit,cloud.MyPing
listOfServers: http://localhost:8080/,http://localhost:8081/
4.4.2节使用了代码的方式来设置Ribbon,而4.4.3节则使用配置文件的方式,两种方式的效果一样,但比较起来,明显是配置文件的方式更加简便。
为cloud-provider这个客户端配置了规则处理类、Ping类以及服务器列表,以同样的方式运行本小节的例子,可看到同样的效果,在此不再赘述。
注: 在本案例的cloud-invoker模块中,默认使用了代码的方式来配置Ribbon, 配置文件中的配置已被注释。
4.4.4 Spring 使用 Ribbon 的 API
Spring Cloud对Ribbon进行封装,例如像负载客户端、负载均衡器等,我们可以直接使用Spring的LoadBalancerClient来处理请求以及服务选择。
下面代码清单在服务器调用 者的控制器中使用了 LoadBalancerClient
public class lnvokerController{
@Autowired
private LoadBalancerClient loadBalancer;
@RequestMapping(value = "/uselb", method = RequestMethod.GET,
produces = MediaType.APPLICATI0N_JSON_VALUE)
public Serviceinstance uselb() {
//査找服务器实例
ServiceInstance si = loadBalancer.choose("cloud-provider");
return si;
}
}
public class lnvokerController{
@Autowired
private SpringClientFactory factory;
@RequestMapping(value = "/defaultValue", method = RequestMethod.GET,
produces = MediaType.APPLICATI0N_JSON_VALUE)
public String defaultvalue() {
System.out.printin ("====输出默认配置:");
//获取默认的配置
ZoneAwareLoadBalancer alb =
(ZoneAwareLoadBalancer) factory.getLoadBalancer("default");
System.out.printIn ("IClientConf ig:"+ factory.getLoadBalancer("default") .getClass ().getName());
System.out.printIn("IRule: "+alb.getRule().getClass().getName());
System.out.printin("IPing: "+alb.getPing().getClass().getName());
System.out.printin("ServerList:"+alb.getServerListlmpl().getClass().getName());
System.out.printin("ServerListFilter:"+alb.getFilter().getClass().getName());
System.out.printIn("ILoadBalancer: "+alb.getClass().getName());
System.out.printIn("Pinginterval: "+alb.getPinglnterval());
System.out.printin("====输出 cloud-provider 配置:");
// 获取 cloud-provider 的配置
ZoneAwareLoadBalancer alb2 =
(ZoneAwareLoadBalancer) factory.getLoadBalancer("cloud-provider");
System.out.printIn("IClientConfig:"+
factory.getLoadBalancer("cloud-provider").getClass().getName());
System.out.printIn ("IRule:" + alb2.getRule().getClass().getName());
System.out.printIn ("IPing:" + alb2.getPing().getClass().getName());
System.out.printIn ("ServerList:"+
alb2.getServerListlmpl().getClass().getName());
System.out.printIn("ServerListFilter :"+ alb2.getFilter().getClass().getName());
System.out.printIn ("ILoadBalancer: " + alb2.getClass().getName());
System.out.printIn ("Pinginterval: "+ alb2.getPinglnterval());
return "";
}
}
代码中使用了 SpringClientFactory,通过该实例可获取各个默认的实现类以及配置,分别输出了默认配置以及cloud-provider配置。
除了使用Spring封装的负载客户端外,还可以直接使用Ribbon的API,如代码清单 4-15所示,直接获取Spring Cloud默认环境中各个Ribbon的实现类。
运行上面的代码清单,在浏览器中访问地址 http://localhost:8080/defaultValue,可看到控制台中的输出如下:
====输出默认配置:
IClientConfig: com.netflix.loadbalancer.ZoneAwareLoadBalancer
IRule: com.netflix.loadbalancer.ZoneAvoidanceRule
IPing: com.netflix.niws.loadbalancer.NIWSDiscoveryPing
ServerList: org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingserverList ServerListFilter: org.springframework.cloud.netflix.ribbon.ZonePreferenceServerListFilter ILoadBalancer: com.netflix.loadbalancer.ZoneAwareLoadBalancer
Pinginterval: 30
====输出 cloud-provider 配置:
IClientConfig: com.netflix.loadbalancer.ZoneAwareLoadBalancer
IRule: org.crazyit.cloud.MyRule
IPing: org.crazyit.cloud.MyPing
ServerList: org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList
ServerListFilter: org.springframework.cloud.netflix.ribbon.ZonePreferenceServerListFilter
ILoadBalancer: com.netflix.loadbalancer.ZoneAwareLoadBalancer Pinginterval: 30
一般情况下,Spring已经帮我们封装好了 Ribbon,我们只需直接调用RestTemplate等 API来访问服务即可。
根据输出可知,cloud-provider客户端使用的负载规则类以及Ping类,是我们自定义的实现类。
在接下来的章节中,我们将讲述RestTemplate进行负载均衡的原理。
4.5.1 @LoadBalanced 注解概述
RestTemplate本是spring-web项目中的—个REST客户端,它遵循REST的设计原则, 提供简单的API让我们去调用HTTP服务。
RestTemplate本身不具有负载均衡的功能,该类也与Spring Cloud没有关系,但为何加入@LoadBalanced注解后,一个RestTemplate实例就具有负载均衡的功能了呢?实际上这要得益于RestTemplate的拦截器功能。
在 Spring Cloud 中,使用@LoadBalanced修饰的RestTemplate, 在 Spring 容器启动时, 会为这些被修饰过的RestTemplate添加拦截器,拦截器中使用了 LoadBalancerClient来处理请求,
LoadBalancerClient本来就是Spring封装的负载均衡客户端,通过这样间接处理,使得RestTemplate拥有了负载均衡的功能。
本节将模仿拦截器机制,带领大家实现一个简单的RestTemplate,以便让大家更了解 @LoadBalanced 以及 RestTemplate 的原理。
本节的案例只依赖了 spring-boot-starter-web 模块:
org.springframework.boot
spring-boot-starter-web
l.5.4.RELEASE
4.5.2编写自定义注解以及拦截器
先模仿@LoadBalanced注解,编写一个自定义注解,请见代码清单:
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MyLoadBalanced {
}
public class Mylnterceptor implements ClientHttpRequestInterceptor {
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws lOException {
System.out.printin("这是自定义拦截器实现");
System.out.printin("原来的URI:" + request.getURI());
//换成新的请求对象(更换URI)
MyHttpRequest newRequest = new MyHttpRequest(request);
System.out.printin("拦截后新的URL:"+ request.getURI());
return execution.execute(newRequest, body);
}
}
注意,MyLoadBalanced注解中使用了@Qualifier限定注解,接下来编写自定义的拦截 器,请见代码清单:
在自定义拦截器Mylnterceptor中,实现了 intercept方法,该方法会将原来的HttpRequest 对象转换为我们自定义的MyHttpRequest,
MyHttpRequest是一个自定义的请求类,实现请见代码清单:
public class MyHttpRequest implements HttpRequest {
private HttpRequest sourceRequest;
public MyHttpRequest(HttpRequest sourceRequest) {
this.sourceRequest = sourceRequest;
}
public HttpHeaders getHeaders() {
return sourceRequest.getHeaders ();
}
public HttpMethod getMethod() {
return sourceRequest.getMethod();
}
/**
* 将URI转换
*/
public URI getURI() {
try{
String oldUri = sourceRequest.getURI().toString();
URI newUri = new URI("http://localhost:8080/hello");
return newUri;
} catch (Exception e) {
e.printStackTrace();
}
return sourceRequest.getURI();
}
}
在MyHttpRequest类中,会将原来请求的URI进行改写,只要使用了这个对象,所有 的请求都会被转发到http://localhost:8080/hello 这个地址。
Spring Cloud 在对 RestTemplate 进行拦截的时候也做了同样的事情,只不过并没有像我们这样固定了 URI,而是对“源请 求”进行了更加灵活的处理。
接下来使用自定义注解以及拦截器。
4.5.3使用自定义拦截器以及注解
编写一个Spring的配置类,在初始化的Bean中为容器中的RestTemplate实例设置自 定义拦截器,本例的Spring自动配置类请见代码清单:
@Configuration
public class MyAutoConfiguration {
@Autowired(required=false)
@MyLoadBalanced
private List myTemplates = Collections.emptyList();
@Bean
public SmartInitializingsingleton myLoadBalancedRestTemplatelnitializer() {
System.out.printin("这个Bean将在容器初始化时创建");
return new SmartlnitializingSingleton() {
public void afterSingletonsInstantiated() {
for(RestTemplate tpl : myTemplates) {
//创建一个自定义的拦截器实例
My Interceptor mi = new Mylnterceptor ();
//获取RestTemplate原来的拦截器
List list = new ArrayList(tpl.getlnterceptors());
//添加到拦截器集合
list.add(mi);
//将新的拦截器集合设置到RestTemplate实例
tpl.setinterceptors(list);
}
)};
}
}
在配置类中定义了 RestTemplate实例的集合,并且使用了@MyLoadBalanced以及 @Autowired注解进行修饰,@MyLoadBalanced中含有@Qualifier注解。
简单来说,就是在 Spring容器中使用了@MyLoadBalanced修饰的RestTemplate实例,该实例将会被加入配置类的RestTemplate集合中。
在容器初始化时,Spring 会调用 myLoadBalancedRestTemplatelnitializer 方法来创建 Bean, 该Bean在初始化完成后,会遍历RestTemplate集合并为它们设置“自定义拦截器”,
请见 代码清单4-19中的粗体代码。
下面在控制器中使用@MyLoadBalanced来修饰调用者的 RestTemplate
4.5.4在控制器中使用RestTemplate
控制器代码请见代码清单
@RestController
@Configuration
public class InvokerController {
@Bean
@MyLoadBalanced
public RestTemplate getMyRestTemplate() {
return new RestTemplate();
}
/**
* 浏览器访问的请求
*/
@RequestMapping(value = "/router", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public String router() {
RestTemplate restTpl = getMyRestTemplate();
//根据名称来调用服务,这个URI会被拦截器所置换
String json = restTpl.getForObject("http://my-server/hello", String.class);
return json;
}
/**
* 最终的请求都会转到这个服务
*/
@RequestMapping(value = "/hello", method = RequestMethod.GET)
@ResponseBody
public String hello () {
return "Hello World";
}
}
注意控制器的hello方法,前面实现的拦截器会将全部请求都转到这个服务中。控制器 的RestTemplate使用了@MyLoadBalanced注解进行修饰。
熟悉前面使用RestTemplate的读 者可发现,我们实现的注解与Spring提供的@LoadBalanced注解使用方法一致。
在控制器 的router方法中,使用这个被拦截过的RestTemplate发送请求。
打开浏览器,访问http://localhost:8080/router,可以看到实际上调用了 hello服务。
在访问该地址时,控制台输出如下:
1 2 |
|
Spring Cloud对RestTemplate的拦截实现更加复杂,并且在拦截器中使用 LoadBalancerClient来实现请求的负载均衡功能。
我们在实际环境中,并不需要实现自定义 注解以及拦截器,用Spring提供的现成API即可,
本节的目的是展示RestTemplate的原理。
本章主要讲解了 Spring Cloud中的负载均衡组件Ribbon,对于Ribbon如何进行负载均 衡进行了详细讲解。读者学习完本章后,可以了解Ribbon是如何进行负载均衡的,
如果有 需要,还可以实现自己的负载均衡规则,以满足实际环境中多变的需求。
作为Spring Cloud中的重要组件,Spring Cloud对Ribbon进行了封装,在使用Spring 提供的API时,我们甚至感觉不到Ribbon的存在。
本章的4.4节,重点讲述了在Spring Cloud 中如何配置和使用Ribbon;
在很多章节中,我们都使用了 RestTemplate发送请求,在4.5节,我们讲解了 RestTemplate 如何拥有负载均衡功能。
读者学习完4.5节,可以更加清楚RestTemplate的工作机制,以 及Spring Cloud对其进行的拦截处理。