参考资料:
《Spring Microservices in Action》
《Spring Cloud Alibaba 微服务原理与实战》
《B站 尚硅谷 SpringCloud 框架开发教程 周阳》
Spring Cloud Ribbon 是基于 Netflix Ribbon 实现的一套客户端负载均衡的工具;提供客户端的软件负载均衡算法和服务调用;


<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId></dependency>在服务消费者的 client 包下;
@Componentpublic class ProviderDiscoveryClient { //自动注入 DiscoveryClient 类,该类用于与 Ribbon 交互 @Autowired private DiscoveryClient discoveryClient; public Provide getProvide(String providerId) { RestTemplate restTemplate = new RestTemplate(); //获取服务提供者的所有实例列表,ServiceInstance 用于保存关于服务的特定实例(包括主机名、端口荷 URL) List<ServiceInstance> instances = discoveryClient.getInstances("provider-instance-name"); if (instances.size()==0) return null; //检索要调用的服务端点 String serviceUri = String.format("%s/providers/%s",instances.get(0).getUri().toString(), providerId); //使用标准的 Spring REST 模板类去调用服务 ResponseEntity< provider > restExchange = restTemplate.exchange( serviceUri, HttpMethod.GET, null, Provider.class, providerId); return restExchange.getBody(); }}这种方法存在以下问题:
结合本篇《5. 本地负载均衡器的实现(消费者)》即可用到客户端负载均衡,即:开发人员定义了本地负载均衡器来实现了负载均衡;
@SpringBootApplication //只需要这个注解即可public class Application { @LoadBalanced //告诉 Spring Cloud 创建一个支持 Ribbon 的 RestTemplate @Bean public RestTemplate getRestTemplate(){ return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(Application.class, args); }}@LoadBalanced 注解显式标注,才能将 Ribbon 和 RestTemplate 一起使用;@Configurationpublic class ApplicationContextConfig{ @Bean @LoadBalanced public RestTemplate getRestTemplate(){ return new RestTemplate(); }}在服务消费者的 client 包下;
@Componentpublic class ProviderRestTemplateClient { //自动注入即可,不用实例化 @Autowired RestTemplate restTemplate; public Provider getProvider(String providerId){ ResponseEntity<Provider> restExchange = restTemplate.exchange( //使用 Eureka 服务 ID 来构建目标 URL "http://provider-instance-name/providers/{providerId}", HttpMethod.GET, null, Provider.class, providerId); return restExchange.getBody(); }}Feign 相关知识将在下篇《4.2 基于 Feign 与 OpenFeign 的服务接口调用》中说明,这里仅把重点放在与上述两种调用提供者服务的区别与对比;
@EnableFeignClients:表示启用 Feign 客户端;
@FeignClient("provider-instance-name") //标识服务为 feign 的客户端public interface ProviderFeignClient { //定义端点的路径和动作 @RequestMapping( method= RequestMethod.GET, value="/providers/{providerId}", consumes="application/json") //定义传入端点的参数,该方法可以由客户端调用以触发组织服务 Provider getProvider(@PathVariable("providerId") String providerId);}指切换默认的负载均衡算法,切换后的仍为现成的(与本地负载均衡器有所区别,本地负载均衡器要自己实现);
@ComponentScan 所扫描的当前包下以及子包下,否则自定义的配置类会被所有的Ribbon客户端所共享,达不到自定义的目的;@ComponentScan 注解被封装到主启动类上的 @SpringBootApplication 注解。其默认扫描主启动类所在包及其子包,因此我们要返回上一级目录新建一个 myRule 目录存放我们自定义的负载均衡配置类;
@Configurationpublic class MySelfRule { @Bean public IRule myRule(){ return new RandomRule();//定义为随机 }}name:指定服务提供者的实例名称;configuration:指定需要使用哪个配置类的负载均衡;指切换默认的负载均衡算法,切换后的仍为现成的(与本地负载均衡器有所区别,本地负载均衡器要自己实现);
#服务提供者的实例名称provider-instance-name: ribbon: #代表 Ribbon 使用的负载均衡策略,属性的值为:IRule 的实现类 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #其他可用的配置属性 # NFLoadBalancerClassName : 配置 ILoadBalancer 的实现类 # NFLoadBalancerPingClassName : 配置 IPing 的实现类 # NIWSServerListClassName: 配置 ServerList 的实现类 # NIWSServerListFilterClassName: 配置 ServerListtFilter 的实现类本地负载均衡器不同于自定义负载均衡算法;前者的负载均衡算法需要自己手动实现,后者只是切换成另一种现成的负载均衡算法;
可以新建一个包,专门存放我们自己写的负载均衡算法;
public interface LoadBalancer{ ServiceInstance instances(List<ServiceInstance> serviceInstances);}@Componentpublic class MyLB implements LoadBalancer{ private AtomicInteger atomicInteger = new AtomicInteger(0); public final int getAndIncrement(){ int current; int next; do { current = this.atomicInteger.get(); next = current >= 2147483647 ? 0 : current + 1; }while(!this.atomicInteger.compareAndSet(current,next)); System.out.println("*****第几次访问,次数next: "+next); return next; } //负载均衡算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标 ,每次服务重启动后rest接口计数从1开始。 @Override public ServiceInstance instances(List<ServiceInstance> serviceInstances){ int index = getAndIncrement() % serviceInstances.size(); return serviceInstances.get(index); }}@RestControllerpublic class OrderController{ //服务提供者示例的名字 public static final String PAYMENT_URL = "http://provider-instance-name"; @Resource private RestTemplate restTemplate; @Resource private LoadBalancer loadBalancer; @Resource private DiscoveryClient discoveryClient; @GetMapping(value = "/provider/mylb") public String getProviderLB(){ //获取服务提供者的所有实例列表 List<ServiceInstance> instances = discoveryClient.getInstances("provider-instance-name"); if(instances == null || instances.size() <= 0){ return null; } //使用本地负载均衡器选出提供者服务 ServiceInstance serviceInstance = loadBalancer.instances(instances); URI uri = serviceInstance.getUri(); return restTemplate.getForObject(uri+"/provider/mylb",String.class); }}