使用Ribbon实现客户端负载均衡
问题:服务消费者调用服务提供者是硬编码的方式,虽然把地址配置到了application.yml中,但是一旦服务端的地址发生改变,那肯定是要修改配置文件的。
如何解决呢?
Ribbon是Nextflix发布的负载均衡器,为Ribbon配置服务提供者地址后,Ribbon就可以基于某种负载均衡的算法,自动帮助服务消费者请求。
Ribbon支持轮询、随机等负载均衡算法,当然也支持实现自定义的负载均衡算法。
在Spring Cloud中,当Ribbon和Eureka配合使用时,Ribbon可自动从Eureka Server获取服务提供者的地址列表,并基于某种负载均衡算法,请求其中一个服务提供者实例。
  
新建服务消费者微服务,配置Ribbon
  
pom.xml引入ribbon依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
为RestTemplate添加@LoadBalanced注解
@SpringBootApplication public class ConsumerMovieRibbonApplication { @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(ConsumerMovieRibbonApplication.class, args); } }
只需要为RestTemplate添加@LoadBalanced注解,就可以为RestTemlate整合Ribbon,使其具备负载均衡的能力
修改Controller层代码,将地址调整为注册在Eureka上的虚拟主机名
package com.smart.consumer_movie_ribbon.controller; import com.smart.consumer_movie_ribbon.model.User; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController @Slf4j public class MovieController { @Autowired private RestTemplate restTemplate; @Autowired private LoadBalancerClient loadBalancerClient; @GetMapping("/movie/{id}") public User findById(@PathVariable Long id){ // 调用注册在Eureka上的服务端的地址 return restTemplate.getForObject("http://provider-user/user/"+id,User.class); } @GetMapping("/callProvider") public String callUserInstance(){ ServiceInstance serviceInstance = loadBalancerClient.choose("provider-user"); // log.info("serviceId: {} , host: {} ,port: {} ,uri: {}" ,serviceInstance.getServiceId() , serviceInstance.getHost(), serviceInstance.getPort(),serviceInstance.getUri()); return serviceInstance.getUri().toString(); } }
把地址修改为http://provider-user/user , 其中provider-user用户微服务的虚拟主机名,是注册在Eureka Server上的名字,也是服务提供者微服务的配置文件中配置的spring.application.name
当Ribbon和Eureka同时使用时,会自动将虚拟主机名映射为微服务的网络地址。
验证Ribbon提供的能力
  1.启动Eureka Server
  2.启动两个 microservice-provider-user实例 。(在STS中启动一个后,修改下application.yml的端口,再次run as spring boot app 即可启动第二个实例,以此类推)
  3.启动microservice-provider-movie-ribbon
多次启动spring boot项目
① 打开运行配置界面
  选择“Edit Configurations”,复制一份UserServiceApplication的运行信息,如图所示步骤进行操作
  

多次访问 http://localhost:7899/movie/1 ,观察控制台每个节点的日志输出情况。
· 同时访问http://localhost:7899/callProvider
注意事项
默认情况下,虚拟主机名和服务名称是一致的,也可以通过eureka.instance.virtual-host-name或者eureka.instance.secure-virtual-host-name指定虚拟主机名
不能将restTemplate.getForObject()和loadBalancerClient写在同一个方法中,两者会冲突,因为RestTemplate实际上是一个Ribbon客户端,本身已经包含choose的行为
虚拟主机名不能包含"_"之类的字符,否则Ribbon再调用的时候会抛出异常
Ribbon自身的负载均衡算法
  RoundRobinRule(轮询算法)
RandomRule(随机算法)
AvailabilityFilteringRule():会先过滤由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问
WeightedResponseTimeRule():根据平均响应的时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越高,刚启动时如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够会切换到WeightedResponseTimeRule
RetryRule():先按照RoundRobinRule的策略获取服务,如果获取失败则在制定时间内进行重试,获取可用的服务。
BestAviableRule():会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
  ZoneAvoidanceRule():默认规则,符合判断server所在区域的性能和server的可用性选择服务器
  自定义的类不能放在@ComponentScan所扫描的当前包以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,也就是达不到特殊化指定的目的。因为主启动类已经有ComponentScan,这个注解,所以说不能放在和主启动类同包下。
  AbstractLoadBalancerRule实现了IRule 中的 setLoadBalancer 和 getLoadBalancer,通过继承 AbstractLoadBalancerRule
/** * 自定义负载均衡算法 */ public class DefineRule extends AbstractLoadBalancerRule { private int total = 0; private int serverIndex = 0; private Server choose(ILoadBalancer loadBalancer, Object key) { if (loadBalancer == null) { return null; } Server server = null; while (server == null) { if (Thread.interrupted()) { return null; } List<Server> reachableServers = loadBalancer.getReachableServers(); List<Server> allServers = loadBalancer.getAllServers(); int serverCount=allServers.size(); if(serverCount==0){ return null; } if(total<5){ total++; server = reachableServers.get(serverIndex); }else { total=0; serverIndex++; if(serverIndex>=reachableServers.size()){ serverIndex=0; } } if(server ==null){ Thread.yield(); continue; } if(server.isAlive()){ return server; } server=null; Thread.yield(); } return server; } @Override public void initWithNiwsConfig(IClientConfig iClientConfig) { } @Override public Server choose(Object key) { return choose(getLoadBalancer(), key); } }
自定义算法DefineRule 必须继承AbstractLoadBalanceRule类
/** * 自定义负载均衡算法 */ @Configuration public class MySelfRule { @Bean public IRule rule(){ // return new RandomRule();//随机算法 return new DefineRule(); } }
在主启动类中添加ribbon注解
@SpringBootApplication @EnableEurekaClient @RibbonClient(name="provider-user",configuration = MySelfRule.class) public class ConsumerMovieRibbonApplication { public static void main(String[] args) { SpringApplication.run(ConsumerMovieRibbonApplication.class, args); } }
name指定针对哪个服务 进行负载均衡,而configuration指定负载均衡的算法具体实现类
通过application.yml中配置的方式,这里就不用使用前面的编程式配置了
格式如下<clientName>.ribbon.
NFLoadBalancerClassName: 配置ILoadBalancer的实现类
NFLoadBalancerRuleClassName: 配置IRule的实现类
NFLoadBalancerPingClassName: 配置IPing的实现类
NFWSServerListClassName: 配置ServerList的实现类
NIWSServerListFilterClassName: 配置ServerListFilter的实现类
直接使用随机规则
provider-user:
ribbon:
NFLoadBalancerRuleClassName: com.smart.ribbonRule.DefineRule
参考:
https://blog.csdn.net/yangshangwei/article/details/84900649 
                    
                
                
            
        
浙公网安备 33010602011771号