<返回更多

你要的负载均衡Ribbon,来了

2020-08-26    
加入收藏

大家好,我是阿七。

在上一篇文章中,我们已经实现了内容中心总能够调用用户中心,那如何实现负载均衡呢?请听阿七为你娓娓道来。(没看到上篇文章的同学请戳这里:实战(一)nacos注册与发现

一、负载均衡的两种方式

众所周知,在负载均衡领域一般有两种方式去实现,分别是:

1、服务器端负载均衡;

2、客户端侧负载均衡;

在单体架构时代,我们一般会部署多个实例在服务器上,然后使用Nginx做负载均衡(nginx也是部署在服务器上的)。请求全部打在nginx上,nginx根据负载均衡策略将请求转发到不同的实例上。如下面这幅图所示:

你要的负载均衡Ribbon,来了

 

有同学会疑问了,为什么nginx可以抗住这么多的请求呢?因为它异步,非阻塞,使用了epoll 和大量的底层代码优化,哈哈跑题了,阿七后面再专门写文章说说nginx。

说完了服务器端负载均衡,那么什么是客户端负载均衡呢?且看下图:

你要的负载均衡Ribbon,来了

 

在这幅图中,内容中心是要调用用户中心的,那么内容中心相对于用户中心就是客户端,对吧,这个应该可以理解。现在我们已经可以通过DiscoveryClient来获取用户中心的多个实例,如果我们在内容中心自己写一个负载均衡规则,然后交给RestTemplate来请求一个用户中心实例,这样就实现了客户端负载均衡了。说干就干,咱这就来写一个负载均衡策略。

二、手写一个客户端负载均衡器

第一步,修改代码:之前是调用findFirst()默认返回第一个用户中心实例,现在注释掉了,改成随机获取一个用户中心实例。

public ShareDTO findById(Integer id) {

        Share share = this.shareMApper.selectByPrimaryKey(id);

        Integer userId = share.getUserId();

        //用户中心的所有实例信息

        List<ServiceInstance> instances = discoveryClient.getInstances("user-center");

//        String targetUrl = instances

//                .stream()

//                .map(instance -> instance.getUri().toString() + "/users/{id}")

//                .findFirst()

//                .orElseThrow(() -> new IllegalArgumentException("当前没有实例对象"));

        List<String> targetUrls = instances

                .stream()

                .map(instance -> instance.getUri().toString() + "/users/{id}")

                .collect(Collectors.toList());

        //随机算法

        int i = ThreadLocalRandom.current().nextInt(targetUrls.size());

        String targetUrl = targetUrls.get(i);

        log.info("请求的目标地址:{}",targetUrl);

        //根据userId查询用户信息

        UserDTO userDTO = this.restTemplate.getForObject(targetUrl, UserDTO.class, userId);

        ShareDTO shareDTO = new ShareDTO();

        BeanUtils.copyProperties(share, shareDTO);

        shareDTO.setWxNickname(userDTO.getWxNickname());

        return shareDTO;

    }

第二步,测试:

1、我们先启动一个用户中心实例,端口为8888;然后修改端口号为8887,点击Edit Configurations,勾选Allow parallel run,保存,再启动一次项目,这样就可以启动两个用户中心实例。如下图:

你要的负载均衡Ribbon,来了

 

2、启动内容中心实例,打开nacos看看

你要的负载均衡Ribbon,来了

 

3、接口测试,访问http://localhost:8889/shares/1

你要的负载均衡Ribbon,来了

 

这时,我们去控制台看看:

你要的负载均衡Ribbon,来了

 

哎,我们会发现,内容中心在随机调用用户中心的实例。说白了,负载均衡器就是给你一个list,你随机从中获取一个实例来调用。 但是我们这个代码还是比较简单的,只是模拟一下。如果我们每次进行服务调用都写这么一堆代码肯定也是不现实的。

那么下面我们就要ribbon进行重构咯,同学们继续往下看。

三、整合ribbon

在使用ribbon进行重构之前,我们肯定得了解什么是ribbon对吧。一句话阐述ribbon是Netflix开源的客户端侧负载均衡器。 其实,ribbon就是我们刚刚写的代码的一个组件,但是它封装的更好,提供了更多的负载均衡策略。

下面我们就来整合ribbon到我们的项目中。还记得三步骤吗?

第一步加依赖:因为nacos-discovery中已经集成了netflix-ribbon,所以这里就不要在单独集成了。

你要的负载均衡Ribbon,来了

 

第二步加注解:在RestTemplate上加注解@LoadBalanced

@MapperScan("com.seven")

@SpringBootApplication

public class ContentCenterApplication {

    public static void main(String[] args) {

        SpringApplication.run(ContentCenterApplication.class, args);

    }

    @Bean

    @LoadBalanced

    public RestTemplate restTemplate(){

        return new RestTemplate();

    }

}

第三步写配置:在yml文件中写配置,这里不需要写。

第四步修改代码:ribbon会根据user-center去nacos中找到请求的真是路径。

public ShareDTO findById(Integer id) {

        Share share = this.shareMapper.selectByPrimaryKey(id);

        Integer userId = share.getUserId();

        //根据userId查询用户信息

        UserDTO userDTO = this.restTemplate

                .getForObject("http://user-center/users/{userId}", UserDTO.class, userId);

        ShareDTO shareDTO = new ShareDTO();

        BeanUtils.copyProperties(share, shareDTO);

        shareDTO.setWxNickname(userDTO.getWxNickname());

        return shareDTO;

    }

ok,到这里ribbon就整合完了。那有人会问,到这就结束了吗?那显然不是,阿七不是浮于表面的人,咱学习一个东西就要刨根问底。

四、ribbon组成

ribbon组件虽小,但是五脏俱全,它的组成都有哪些呢?贴心的我已经为大家准备好了。

你要的负载均衡Ribbon,来了

 

同时,ribbon支持8种负载均衡策略,如下所示:

你要的负载均衡Ribbon,来了

 

这里的Zone本意为地区、地带的意思,这里作机房的架子,也就是放服务器的机架。那我们实际上没有Zone的,也就是说默认采用的负载均衡策略是RoundRibonRule(轮训策略)。我们看一下源码,找到RoundRobinRule.JAVA类,其中最主要的就是这一段,实际上就是取余得到访问的server的index。

private int incrementAndGetModulo(int modulo) {

        int current;

        int next;

        do {

            current = this.nextServerCyclicCounter.get();

            next = (current + 1) % modulo;

        } while(!this.nextServerCyclicCounter.compareAndSet(current, next));

        return next;

}

compareAndSet是AtomicInteger的方法,其实类似参数列表compareAndSwapInt(var1, var2, var5, var4),作为base的var1加上偏移量var2之后和var5比较是不是值相同,相同就update为var4. valueOffset是native的C的方法指针找到的地址。

public final boolean compareAndSet(int expect, int update) {

    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);

}

五、细粒度配置ribbon

那我们如何细粒度、自定义配置负载均衡策略呢?比如,内容中心想指定一种负载均衡策略来获取用户中心的实例。很简单,我们只需要加一下yml配置文件即可。

#指定服务名

user-center:

  ribbon:

    #负载均衡策略的全路径

    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

这样我们就修改ribbon负载均衡策略为RandomRule,也就是随机获取一个实例。

六、ribbon解饿加载

在实际代码运行中发现,当我们第一次请求http://localhost:8889/shares/1这个接口时,运行结果是很慢的,为什么呢?因为ribbon默认是懒加载。只有在下面代码第一次执行的时候,才会创建一个名叫user-center的ribbon client。

//根据userId查询用户信息

        UserDTO userDTO = this.restTemplate

                .getForObject("http://user-center/users/{userId}", UserDTO.class, userId);

我们可以通过修改yml配置文件来解决这个问题。

ribbon:

  eager-load:

    enabled: true

    #多个用逗号隔开 user-center,xxx,yyy

    clients: user-center

这样就可以第一次请求也变得很快啦。

好了ribbon就学习到这里了。但是,我们的代码还是存在很多问题的,那么下一篇我们将一起学习声明式http客户端--feign,一起将代码优化的更好吧。

喜欢的朋友记得点个关注吧,一起探讨,共同进步。

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>