天天看点

SpringCloud学习记录 | 第五篇:Ribbon负载均衡

1.负载均衡

Nginx和Ribbon都能实现负载均衡。Nginx是集中式负载均衡而Ribbon(负载均衡+RestTemplet远程RPC)是进程内的负载均衡。我们实际使用中Nginx负载的通常是我们api模块,Ribbon(负载均衡+RestTemplet远程RPC)的话是给api调用的服务做负载均衡用的。通俗点的说,Nginx负载的客户端的负载,而Ribbon负载的是我们服务间的负载。

2. EurekaClient默认引入Ribbon的依赖

2.1 我们点开spring-cloud-starter-netflix-eureka-client的依赖的时候就会发现其实EurekaClient默认是引入Ribbon的,也就是说EurekaClient其实已经默认实现了负载均衡。具体的使用的话我们结合RestTemplate就可以了,在RestTemplate的配置信息上注解上@LoadBalanced即可:

@Configuration
public class ApplicationContextConfig {

    @Bean
    @LoadBalanced //赋予RestTemplate负载均衡能力
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}
           

接下来我们使用RestTemplate结合Eureka使用就好了:

private static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";//服务注册到Eureka时候的名称

    @Resource
    private RestTemplate restTemplate;

    @PostMapping("/consumer/payment/create")
    public CommonResult<Payment> create(Payment payment) {
        return restTemplate.postForObject(PAYMENT_URL + "/payment/create", payment, CommonResult.class);
    }

    @GetMapping("/consumer/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
        return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
    }
           

3. 替换RestTemplet默认的负载均衡算法

3.1 IRule接口默认实现的负载均衡算法的实现,见下图:

SpringCloud学习记录 | 第五篇:Ribbon负载均衡

3.2 替换Ribbon默认的负载均衡算法:

3.2.1 配置替换新的负载均衡算法,我这里选择的是RandomRule。这里需要注意是这个Configuration不能配置在@ComponentSacn能扫描到的包下。通常@ComponentSacn在启动类上的@SpringBootApplication注解上面,所以不能在启动类能扫描到的包里面。

@Configuration
public class MyRule {
    @Bean
    public IRule getMyRule() {
        return new RandomRule();
    }
}
           

3.2.2 在启动类的@SpringBootApplication中引入上一步(3.2.1)的配置:

@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MyRule.class)//CLOUD-PAYMENT-SERVICE为你需要负载的目标服务注册到Eureka的名字,MyRule为你配置文件
public class OrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain80.class,args);
    }
}
           

4. 仿照系统手写Ribbon默认负载均衡策略(轮询策略)

4.1 RoundRobinRule和DiscoveryClient

原理就是利用DiscoveryClient+服务注册的名字可以从EurekaService中获取对应服务的全部信息,包括服务的地址、端口等等。在Eureka做注册中心这一节时有提到(https://blog.csdn.net/qq_27062249/article/details/107432371)。接下来我们通过客户端调用的次数就能从List<ServiceInstance> 中获取对应的服务:接口请求次数%服务集群的数量=实际调用服务在Eureka中的下标位置。

4.2 仿照系统手写

4.2.1 把RestTemplate配置上的@LoadBalanced注释掉,让其失去负载均衡的能力:

@Configuration
public class ApplicationContextConfig {
    @Bean
    //@LoadBalanced //赋予RestTemplate负载均衡能力
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}
           

这里一定要记得把@LoadBalanced注释掉,不然会后面自定义负载均衡策略之后会报错:

java.lang.IllegalStateException: No instances available for xxxxx
           

(很容易就想明白,你在最后通过RestTemplate去调用目标服务的时候它是到EurekaService的注册信息中那服务的名字去查找服务的调用地址,这个时候你传给RestTemplate是一个服务的实际地址[我这里的是 192.168.0.170:8002/8001]是所以在EurekaService中就找不到对应的服务的事例了)

4.2.2 自定义LoadBalanced 接口(这里名字可以随意):

public interface LoadBalanced {

    ServiceInstance serviceInstance(List<ServiceInstance> serviceInstances);

}
           

4.2.3 实现上一步定义的接口(4.2.2):

@Component
@Slf4j
public class MyLB implements LoadBalanced {

    private AtomicInteger atomicInteger = new AtomicInteger(0);//原子整型类

    public final int getAndIncrement() {
        int current;
        int next;
        //自旋锁
        for (; ; ) {
            current = atomicInteger.get();
            next = current >= Integer.MAX_VALUE ? 0 : current+1;
            if (atomicInteger.compareAndSet(current, next))
                break;
        }
        return next;
    }

    @Override
    public ServiceInstance serviceInstance(List<ServiceInstance> serviceInstances) {
        //获得即将调用的服务在EurekaService注册List中的下标
        int index = getAndIncrement() % serviceInstances.size();
        log.info("index:" + index);
        return serviceInstances.get(index);
    }

}
           

4.2.4 调用

@RestController
@Slf4j
public class OrderController {

    private static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";//CLOUD-PAYMENT-SERVICE为目标服务注册到EurekaService中的名字

    @Autowired
    private LoadBalanced loadBalanced;

    @Autowired
    private DiscoveryClient discoveryClient;

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/payment/lb")
    public String getPaymentLB() {
        List<ServiceInstance> ServiceInstanceList = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");//通过服务注册到EurekaService的名字从discoveryClient中过去服务的事例列表
        ServiceInstance serviceInstance = loadBalanced.serviceInstance(ServiceInstanceList);
        URI uri = serviceInstance.getUri();//拿到服务的调用地址
        String url=uri+"/payment/lb";
        log.info("url:"+url);
        return restTemplate.getForObject(url, String.class);//调用
    }
}
           

5. 代码地址

https://github.com/TianLuhua/springCloud2020

继续阅读