六.客户端负载均衡Ribbon+RestTemplate
1.基本概念
1.1.为什么要Ribbon
我们知道,为了防止应用出现单节点故障问题,同时为了提高应用的作业能力,我们需要对应用做集群 ,如果我们对user-server(用户服务)做了集群 ,那么这个时候回衍生出一些问题:现在有两个user-server(用户服务)就意味着有两个user-server(用户服务)的通信地址,我的order-server(订单服务)在向user-server(用户服务)发起调用的时候该访问哪个?如何访问?这个时候就需要有一个组件帮我们做请求的分发,即:负载均衡器,而Ribbon就是一个客户端负载均衡器。
1.2.什么是Ribbon
Ribbon是Netflix发布的云中间层服务开源项目,主要功能是提供客户端负载均衡算法。Ribbon客户端组件提供一系列完善的配置项,如,连接超时,重试等。简单的说,Ribbon是一个客户端负载均衡器,Ribbon可以按照负载均衡算法(如简单轮询,随机连接等)向多个服务发起调用(正好可以解决上面的问题),我们也很容易使用Ribbon实现自定义的负载均衡算法。
1.3.Ribbon的工作机制
如下图,我们将user-server(用户服务)做集群处理,增加到2个节点(注意:两个user-server(用户服务)的服务名要一样,ip和端口不一样),在注册中心的服务通信地址清单中user-server(用户服务)这个服务下面会挂载两个通信地址 。 order-server(订单服务)会定时把服务通信地址清单拉取到本地进行缓存, 那么当order-server(订单服务)在向user-server(用户服务)发起调用时,需要指定服务名为 user-server(用户服务);那么这个时候,ribbon会根据user-server(用户服务)这个服务名找到两个order-server的通信地址 , 然后ribbon会按照负载均衡算法(默认轮询)选择其中的某一个通信地址,发起http请求实现服务的调用,如下图:
在理解了Ribbon工作机制之后,我们就来编码实战上图描述的场景。
2.提供者user-server(用户服务)集群
2.1.服务集群方案
使用SpringBoot多环境配置方式集群,一个配置文件配置多个order-server环境 ,需要注意的是集群中的多个服务名(spring.application.name)应该一样,我们把相同的东西提取到最上面,不同的东西配置在各自的环境中
2.2.用户服务集群配置
修改 application.yml 如下:
#注册到EurekaServereureka client serviceUrl defaultZone http //peer1 1010/eureka/,http //peer2 1011/eureka/,http //peer3 1012/eureka/ #使用ip地址进行注册 instance prefer-ip-addresstruespring application name user-server #服务名都叫user-server profiles active user-server1 ---server port1020eureka instance instance-id user-server1020spring profiles user-server1 ---server port1021eureka instance instance-id user-server1021spring profiles user-server2
用户服务集群配置成功,启动方式同注册中心集群一样。需要配置服务运行多实例启动,启动时需要注意修改spring.profiles.active的值来切换不同的实例。
2.3.修改Controller
为了后续测试的时候方便区分不同的用户服务实例,这里我们在Controller中读取应用的端口随User返回,在启动不同的用户服务实例的时候端口是不同的,修改后的代码如下:
//用户服务:暴露接口给订单访问publicclassUserController { //加载端口"${server.port}") (privateintport; //订单服务来调用这个方法 http://localhost:1020/user/10// @GetMapping(value = "/user/{id}" )value="/user/{id}",method=RequestMethod.GET) (publicUsergetById( ("id")Longid){ //根据id去数据库查询UserSystem.out.println(port); returnnewUser(id,"zs:"+id,"我是zs,port:"+port); //端口随User返回 } }
3.消费者Order-server集成Ribbon
Ribbon集成官方文档:https://cloud.spring.io/spring-cloud-static/Greenwich.SR5/multi/multi_spring-cloud-ribbon.html#netflix-ribbon-starter
修改springcloud-order-server-1030工程,集成Ribbon
3.1.导入依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-ribbon</artifactId></dependency>
3.2.开启负载均衡
修改RestTemplate的Bean的定义方法,加上Ribbon的负载均衡注解@LoadBalanced赋予RestTemplate有负债均衡的能力。
/*** 订单的启动类*/publicclassOrderServerApplication1030{ //配置一个RestTemplate ,Spring封装的一个机遇Restful风格的http客户端 工具//@LoadBalanced :让RestTemplate有负载均衡的功能publicRestTemplaterestTemplate(){ returnnewRestTemplate(); } //省略...
3.3.修改Controller调用方式
在之前的案例中,我们调用用户服务是通过用户服务的主机加上端口“localhost:1020”的调用方式,现在我们把 “localhost:1020” 修改为 用户服务的服务名 。底层会通过服务发现的方式使用Ribbin进行负载均衡调用。
publicclassOrderController { //需要配置成BeanprivateRestTemplaterestTemplate ; //浏览器调用该方法value="/order/{id}",method=RequestMethod.GET) (publicUsergetById( ("id")Longid){ //发送http请求调用 user的服务,获取user对象 : RestTemplate//user的ip,user的端口,user的Controller路径//String url = "http://localhost:1020/user/"+id;Stringurl="http://user-server/user/"+id; //发送http请求returnrestTemplate.getForObject(url, User.class); } }
3.4.测试Ribbon
分别启动EurekaServer注册中心 ,启动两个UserServer用户服务,启动OrderServer订单消费者服务,浏览器访问订单服务:http://localhost:1030/order/1 ,发送多次请求。
观察响应的结果中的端口变化 - 端口会交替出现1020,,1021我们可以推断出Ribbon默认使用的是轮询策略。
4.负载均衡算法
4.1.Ribbon内置算法
Ribbon内置7种负载均衡算法,每种算法对应了一个算法类如下:
内置负载均衡规则类 | 规则描述 |
RoundRobinRule(默认) | 简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。 |
AvailabilityFilteringRule | 对以下两种服务器进行忽略:(1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。可以通过修改配置loadbalancer..connectionFailureCountThreshold来修改连接失败多少次之后被设置为短路状态。默认是3次。(2)并发数过高的服务器。并发连接数的上线,可以由客户端的..ActiveConnectionsLimit属性进行配置。 |
WeightedResponseTimeRule | 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。 |
ZoneAvoidanceRule | 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。 |
BestAvailableRule | 忽略哪些短路的服务器,并选择并发数较低的服务器。 |
RandomRule | 随机选择一个可用的服务器。 |
Retry | 重试机制的选择逻辑 |
4.2.配置负载均衡算法
Ribbon可以进行全局负载均衡算法配置,也可以针对于具体的服务做不同的算法配置。同时可以使用注解方式和yml配置方式来实现上面两种情况。
1.注解全局配置
随机算法的效果最好演示,我们把负载均衡算法修改成随机算法,只需要RandomRule配置成Bean即可,修改主配置类如下:
/*** 订单的启动类*/publicclassOrderServerApplication1030{ //配置一个RestTemplate ,Spring封装的一个机遇Restful风格的http客户端 工具//@LoadBalanced :让RestTemplate有负载均衡的功能publicRestTemplaterestTemplate(){ returnnewRestTemplate(); } //负载均衡算法publicRandomRulerandomRule(){ returnnewRandomRule(); } //省略...
测试
重启订单服务,访问http://localhost:1030/order/1 ,发送多次请求应该可以看到结果中的端口随机变动。
2.配置具体服务的负载均衡
如果要针对某一服务配置负载均衡算法可以通过@RibbonClient注解来实现。
先来定义一个注解,用来排除配置类不被扫描到(后面有解释):
ElementType.TYPE}) ({RetentionPolicy.RUNTIME) (public@interfaceNoScan { }
然后定义一个配置类,在配置类中定义随机算法策略:
//这是一个空标签,是用来防止@SpringBootApplication 扫描到该类publicclassRibbonConfig { publicIRulerandomRule(){ returnnewRandomRule(); } }
提示:需要注意的是这个类我们是要交给RibbonClient作为配置,而不应该被主配置类上的@SpringBootApplication(这个标签里面有个@ComponentScan)所扫描到,不然会不起作用(官方文档上有明确的解释),这里又两种方法可以不让@SpringBootApplication扫描到,1是调整 RibbonConfig 所在包的位置 (@MapperScan扫描当前包及其子包), 2是通过@ComponentScan排除注解的方式,所以我们在配置类上贴了一个注解@NoScan ,后面我们需要根据这个注解排除配置不被扫描。
主配置类通过@RibbonClient指定服务的负载均衡配置,同时排除@NoScan 注解不被扫描
/*** 订单的启动类*///排除 NoScan标签所在的类excludeFilters= .Filter(type=FilterType.ANNOTATION,value=NoScan.class)) (value="user-server",configuration=RibbonConfig.class) (publicclassOrderServerApplication1030{ //配置一个RestTemplate ,Spring封装的一个机遇Restful风格的http客户端 工具//@LoadBalanced :让RestTemplate有负载均衡的功能publicRestTemplaterestTemplate(){ returnnewRestTemplate(); } //负载均衡算法// @Bean// public RandomRule randomRule(){// return new RandomRule();// }publicstaticvoidmain( String[] args ) { SpringApplication.run(OrderServerApplication1030.class); } }
除了 @RibbonClient 注解以外还可以通过@RibbonClients来对多个服务进行策略配置
({ value="服务名",configuration=RibbonConfig.class), (value="服务名",configuration=RibbonConfig.class) (})
3.yml方式配置负载均衡算法
配置全局Ribbon算法
ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
配置某个服务的Ribbon算法
user-server ribbon NFLoadBalancerRuleClassName com.netflix.loadbalancer.RandomRule
4.做个总结
上面我们介绍了通过注解方式进行负载均衡策略全局配置和针对某个服务的负载均衡配置,也介绍了通过yml方式配置,我们可以根据项目情况选择其中一种配置方式即可。
5.Ribbon调优配置
5.1.超时配置
使用Ribbon进行服务通信时为了防止网络波动造成服务调用超时,我们可以针对Ribbon配置超时时间以及重试机制
ribbon ReadTimeout 3000 #读取超时时间 ConnectTimeout 3000 #链接超时时间 MaxAutoRetries 1 #重试机制:同一台实例最大重试次数 MaxAutoRetriesNextServer 1 #重试负载均衡其他的实例最大重试次数 OkToRetryOnAllOperations false #是否所有操作都重试,因为针对post请求如果没做幂等处理可能会造成数据多次添加/修改
当然也可以针对具体的服务进行超时配置:如"<服务名>.ribbon…"
5.2.饥饿加载
我们在启动服务使用Ribbon发起服务调用的时候往往会出现找不到目标服务的情况,这是因为Ribbon在进行客户端负载均衡的时候并不是启动时就创建好的,而是在实际请求的时候才会去创建,所以往往我们在发起第一次调用的时候会出现超时导致服务调用失败,我们可以通过设置Ribbon的饥饿加载来改善此情况,即在服务启动时就把Ribbon相关内容创建好。
ribbon eager-load enabled true #开启饥饿加载 clients user-server #针对于哪些服务需要饥饿加载
6.做个小结
Ribbon的使用相对比较简单,配合RestTemplate使用注解@LoadBalanced即可完成负载均衡配置,但是再我们的订单服务Controller中向用户服务发起请求的代码就显得不简单了。我们需要手动去拼接目标服务的URL,以及参数,可能参数比较简单的时候你没什么感觉,当地址比较复杂,参数比较多的时候,拼接URL就会得特别麻烦,而且显得好傻,在下一章节我们会学习另外一个客户端负载均衡Feign,它在Ribbon的基础上进行了封装,让服务的调用方式显得更简答和高级。