用注解和配置文件的方式引用Spring Cloud整合的Ribbon
1.准备工作
为了实现负载均衡的效果,我们使用Spring Cloud服务管理框架Eureka简单示例(三)底部的源代码稍加修改使用(如果下面的内容看不懂,可以先查看上面这篇博文),eureka-provider需要启动两个实例,修改启动类ProviderApp的main()方法,通过在控制台输入不同的端口(8082和8083)来启动实例:
package com.init.springCloud; import java.util.Scanner; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient public class ProviderApp { public static void main(String[] args) { @SuppressWarnings("resource") Scanner scan = new Scanner(System.in); String port = scan.nextLine(); new SpringApplicationBuilder(ProviderApp.class).properties("server.port=" + port).run(args); } }
为了区分服务调用者使用的是哪一个服务,为Person类添加info属性:
package com.init.springCloud; import lombok.Data; @Data public class Person { private Integer id; //主键ID private String name; //姓名 private String info; //信息,根据URL地址查看服务的来源 }
同时修改控制器ProviderController,把请求的参数传输到Person类的info属性里:
package com.init.springCloud; import javax.servlet.http.HttpServletRequest; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; @RestController public class ProviderController { @RequestMapping(value = "/search/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public Person searchPerson(@PathVariable Integer id, HttpServletRequest request){ Person person = new Person(); person.setId(id); person.setName("Spirit"); person.setInfo(request.getRequestURL().toString()); return person; } }
之后,依次启动euraka-server,两个eureka-provider服务(8082和8083端口),eureka-consumer,访问:http://localhost:8081/router,验证我们的配置是否成功,成功的标志是轮询调用两个服务返回结果。
2.使用注解引用Ribbon
在eureka-consumer的com.init.springCloud包下新建MyRule类,来自Ribbon负载均衡器详细介绍(七)中创建的规则,新增一段打印文字,用来显示是否使用了我们的规则,完整代码如下:
package com.init.springCloud; import java.util.List; import java.util.Random; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.Server; public class MyRule implements IRule { private ILoadBalancer lb; @Override public Server choose(Object key) { System.out.println("这是自定义的规则"); Random random = new Random(); Integer num = random.nextInt(10);//在0-9这10个随机数里取值 //获取传输负载均衡器里所有的服务 List<Server> servers = lb.getAllServers(); if(num>7){//返回8082端口服务 return chooseServerByPort(servers,8082); } //返回8083端口服务 return chooseServerByPort(servers,8083); } private Server chooseServerByPort(List<Server> servers,Integer port){ for (Server server : servers) { if(server.getPort() == port){ return server; } } return null; } @Override public void setLoadBalancer(ILoadBalancer lb) { this.lb = lb; } @Override public ILoadBalancer getLoadBalancer() { return lb; } }
之后创建一个MyConfig类,用于返回我们自定义的负载均衡规则:
package com.init.springCloud; import org.springframework.context.annotation.Bean; import com.netflix.loadbalancer.IRule; public class MyConfig { @Bean public IRule getMyRule(){ return new MyRule(); } }
再新建MyLoadBalanceClient类,使用我们的配置去调用自定义规则:
package com.init.springCloud; import org.springframework.cloud.netflix.ribbon.RibbonClient; @RibbonClient(name = "eureka-provider", configuration = MyConfig.class) public class MyLoadBalanceClient { }
这个时候重启eureka-consumer项目,多次访问:http://localhost:8081/router,我们就会看到调用8083服务明显比8082服务的次数多,控制台也显示调用了我们的自定义规则:
3.使用配置文件引用Ribbon
server: port: 8081 spring: application: name: eureka-consumer eureka-provider: ribbon: NFLoadBalancerRuleClassName: com.init.springCloud.MyRule eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/重新启动eureka-consumer项目,多次访问:http://localhost:8081/router,我们就会看到调用8083服务明显比8082服务的次数多,控制台同样也显示调用了我们的自定义规则。
使用Spring Cloud封装好的Ribbon
Spring Cloud将Ribbon整合之后,使用了一个叫做LoadBalancerClient的客户端来做服务调用,这个客户端有一个choose的方法,传入服务的ID就可以依据规则从对应的服务实例中获取到服务,在com.init.springCloud下的ConsumerController控制器新建getMyDefinedService方法并访问http://localhost:8081/service:
@Autowired private LoadBalancerClient client; @GetMapping(value = "/service") @ResponseBody public void getMyDefinedService(){ ServiceInstance instance = client.choose("eureka-provider"); System.out.println("地址:"+instance.getHost()+",端口:"+instance.getPort()); }
可以看到LoadBalancerClient使用了我们自定义的负载均衡规则:
我们也可以去探寻一下LoadBalancerClient底层默认使用了什么样的负载均衡器,又使用了什么样的负载规则。引入SpringClientFactory,获取默认的负载均衡器类和对应的规则方法,同样在ConsumerController控制器里添加方法:
@Autowired private SpringClientFactory factory; @GetMapping(value = "/rule") @ResponseBody public void getMyDefinedRule(){ ILoadBalancer balancer = factory.getLoadBalancer("default"); System.out.println("Spring Cloud默认使用的均衡器:"+balancer); DynamicServerListLoadBalancer balancer2 = (DynamicServerListLoadBalancer)factory.getLoadBalancer("default"); System.out.println("默认使用的规则:"+balancer2.getRule().getClass().getName()); DynamicServerListLoadBalancer balancer3 = (DynamicServerListLoadBalancer)factory.getLoadBalancer("eureka-provider"); System.out.println("eureka-provider使用的规则:"+balancer3.getRule().getClass().getName()); }
访问:http://localhost:8081/rule,控制台输出了Spring Cloud整合Ribbon默认使用的负载均衡器和负载均衡规则,以及eureka-provider使用的我们自定义负载均衡规则。
这里Spring Cloud默认使用的ZoneAvoidanceRule规则,在上一篇博客中也提到了,它是指使用ZoneAvoidancePredicate和AvailabilityPredicate来判断是否选择某个server,前一个判断判定一个zone的运行性能是否可用,剔除不可用的zone的所有server,AvailabilityPredicate用于过滤掉连接数过多的Server。
如果我们要覆盖默认的负载均衡规则,只需要在配置文件中将Ribbon配置的客户端名称改为“default”就OK了:
default: ribbon: NFLoadBalancerRuleClassName: com.init.springCloud.MyRule
Spring Cloud系列:
Spring Cloud服务管理框架Eureka简单示例(三)
Spring Cloud服务管理框架Eureka项目集群(四)