Ribbon-负载均衡

已Ribbon为例了解负载均衡

什么是负载均衡

负载均衡建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。

简单来说就是当一个服务部署在了多个节点时,客户端可以通过负载均衡来获取单个可以请求的节点。

为什么需要负载均衡

一台服务器所能承受的请求有限,并发场景下,为了能够承载更多请求,满足更多的用户,需要对服务器节点进行扩容,将用户流量分摊到多个节点,从而达到承接更大流量的目的。

常见的负载均衡算法

  1. 随机
  2. 轮询
  3. 权重 (响应时间、同机房策略、灰度策略)

怎么实现负载均衡

引入Spring Cloud Ribbon jar包,可以单独使用,需要在application.yml中配置服务节点。然后通过在RestTemplate实例上增加注解的方式,拦截RestTemplate发出的http请求,通过Ribbon内置的负载均衡算法,选择需要请求的服务节点,向选中的节点发送请求。

@Configuration
public class RestTemplateConfig {

    @LoadBalanced
    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder){
        return restTemplateBuilder.build();
    }

}
@RestController
public class LoadBlanceTest {

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/getUser")
    public String getUser(){
        return restTemplate.getForObject("http://spring-cloud-test-service/users", String.class);
    }


}
# 应用名称
spring.application.name=spring-cloud-ribbon
# 应用服务 WEB 访问端口
server.port=8080

spring-cloud-test-service.ribbon.listOfServers=\
  http://localhost:8081,http://localhost:8082,http://localhost:8083

自定义负载均衡策略

image.png

可以通过实现IRule接口来自定义负载均衡算法实现。上图右侧是Spring Cloud目前以支持的实现算法。

spring-cloud-ribbon-service.ribbon.NFLoadBalancerRuleClassName=\
            com.fishbone.example.springcloudprovidercosumer.FishBoneLoadBalance
public class FishBoneLoadBalance extends AbstractLoadBalancerRule {

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {

    }

    @Override
    public Server choose(Object key) {
        return chooseServer(getLoadBalancer(),key);
    }

    public Server chooseServer(ILoadBalancer lb, Object key){
        if (lb == null) {
            return null;
        }
        Server server = null;
        List<Server> upList = lb.getReachableServers();
        List<Server> allList = lb.getAllServers();

        int serverCount = allList.size();
        if (serverCount == 0) {
            /*
             * No servers. End regardless of pass, because subsequent passes
             * only get more restrictive.
             */
            return null;
        }

        int index = chooseIpInt(serverCount);
        server = upList.get(index);
        return server;
    }

    private int chooseIpInt(int serverCount) {
        //获取请求IP地址 
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        String remoteAddre = requestAttributes.getRequest().getRemoteAddr();
        int code = Math.abs(remoteAddre.hashCode());
        return code%serverCount;
    }

}

负载均衡原理

通过上方的内容,我们已经对负载均有了一个大概的理解。要实现负载均衡有以下几点需要考虑。

  1. 如何拦截客户端请求?
  2. 如何获取服务列表?
  3. 如何选择负载均衡规则?
  4. 如何请求服务端并拿到结果给到客户端?

以上只是一些基础的问题,肯定还会有很多细节需要考虑,比如:

http请求拦截到之后,只需要修改地址、端口么?
获取服务列表后,如果服务挂了怎么办?
如果调用不成功,比如超时要怎么解决?
如何根据业务选择最优的节点?

拦截请求

可以这么说,Spring Cloud 相关的starter 组件都用到了 Spring Boot 的自动装配机制。Ribbon 也不例外。所以我们需要找到 Ribbon 相关的自动装配入口。

在第一时间搜索 RibbonAutoConfiguration 后发现 一个类

@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
@AutoConfigureAfter(
        name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
// 在看这里依赖 LoadBalancerAutoConfiguration 我们前边 就是通过 @LoadBalanced 注解实现负载
// 所以先去看一下 LoadBalancerAutoConfiguration
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,
        AsyncLoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties({ RibbonEagerLoadProperties.class,
        ServerIntrospectorProperties.class })
public class RibbonAutoConfiguration {
    // ...
}

@Conditional 注解

看源码之前先简单介绍几个 @Conditional 注解。源码中大量使用,如果搞不清楚,会容易晕~

@ConditionalOnBean 
// 当给定的在 bean 存在时,则实例化当前Bean,这个bean可能由于某种原因而没有注册到ioc里。
// 这时 @ConditionalOnBean 可以让当前bean也不进行注册,当前bean 指使用了这个注解的 bean 
@ConditionalOnMissingBean 
// 当给定的在bean不存在时,则实例化当前Bean,感觉这个是在多态环境下使用,
// 当一个接口有多个实现类时,如果只希望它有一个实现类,那就在各个实现类上加上这个注解
@ConditionalOnClass // 当给定的类名在类路径上存在,则实例化当前Bean
@ConditionalOnMissingClass // 当给定的类名在类路径上不存在,则实例化当前Bean

这几个注解,包括其他类似的注解使用时后边一般会跟上条件类。比如:

@Configuration
public class ConditioanTeset {
    
    // 对于这种 后边加了注释的 条件意思为 如果 CTest 不存在 则创建 ATest  
    @Bean
    @ConditionalOnMissingBean(CTest.class)
    public ATest getATest(){
        return new ATest();
    }

    // 对于这种释没有跟条件值的看返回值类型,这里表示:如果BTest不存在则创建 BTest 
    @Bean
    @ConditionalOnMissingBean
    public BTest getBTest(){
        System.out.println("getCTest");
        return new BTest();
    }

    // 对于这种有参数条件的 条件意思为 如果 ATest 已存在 则用此方法 创建 ATest  但是 BTest 必须要存在
    // 如果BTest 不存在 会查看是否有 BTest 实例化方法(本类或者其他 Spring 托管类),
    // 如果没有则不满足条件
    // 注意 如果方法名字一样 只会走一个,无参的优先级最低 (只有当有参的不满足条件时 才会走无参)
    // 可能有点绕。可以理解一下,因为Spring 默认是单例, 所以 在初始化一个 bean 时 如果 一个bean 有多种初始化方式, 那就会选用其中的一种。
    // 比如这里 getAtest 方法有两个,所以只会走有参数的(@ConditionalOnBean 满足条件,如果删除上边的无参,不满足条件就不会走)。 
    // 如果方法名字 不一样,则两个方法都会走。  
    @Bean
    @ConditionalOnBean
    public ATest getATest(BTest bTest) {
        System.out.println(bTest);
        return new ATest();
    }

}

记住几点:

  1. 对于这种释没有跟条件值的看返回值类型,返回值类型就是条件类型。
  2. 如果方法名字一样 只会走一个,无参的优先级最低。
  3. 如果定义了参数,则参数必须要先实例化。会从 IOC容器中找参数对应的实例化类,先完成初始化,依赖注入。
  4. 在使用过程中,一定要谨慎,避免写一些不会触发的定义。比如:如果 A不存在,实例化A,并且需要一个参数A。

好了下面继续看 LoadBalancerAutoConfiguration 方法

LoadBalancerAutoConfiguration

这里需要先看类上的定义注解, LoadBalancerAutoConfiguration 依赖与 RestTemplate 和 LoadBlancerClient 两个类。

  1. 其实这里就是一个简单的 @Bean 对象的配置,可以按照顺序看一下,注意根据依赖关系决定加载的先后顺序,比如第一个方法,参数类型是 RestTemplateCustomizer 所以可以直接去看返回值为 RestTemplateCustomizer 的类。

  2. 这时会发现有两个方法 两个方法的区别就是参数不同,以及 方法所属的类加载条件不同 (RetryTemplate 是否存在)。 这里 我们并没有使用 RetryTemplate,因此看 LoadBalancerInterceptorConfig#restTemplateCustomizer 方法即可。

  3. 这时发现该方法又依赖 LoadBalancerInterceptor 参数, 接着找,又发现 LoadBalancerInterceptorConfig#loadBalancerInterceptor 方法,然后又有参数,继续找...

  4. 其实在 LoadBalancerInterceptor 这里就不难发现,这里注册了一个 LoadBalancerInterceptor 拦截器,同时用 SmartInitializingSingleton 做了一层封装。

  5. 简单来说,这个类就是用来 添加拦截器。 需要注意的就是 两个函数式 接口,在客户端初始化对象时(SmartInitializingSingleton )将会用到。

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

    @LoadBalanced
    @Autowired(required = false)
    private List<RestTemplate> restTemplates = Collections.emptyList();

    @Autowired(required = false)
    private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

    @Bean
    public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
            final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
        return () -> restTemplateCustomizers.ifAvailable(customizers -> {
            for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                for (RestTemplateCustomizer customizer : customizers) {
                    customizer.customize(restTemplate);
                }
            }
        });
    }

    @Bean
    // 当 bean 不存在时 则实例化当前 bean  对于这里来说这个 bean 就是 LoadBalancerRequestFactory 
    @ConditionalOnMissingBean
    public LoadBalancerRequestFactory loadBalancerRequestFactory(
            LoadBalancerClient loadBalancerClient) {
        return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
    static class LoadBalancerInterceptorConfig {

        @Bean
        public LoadBalancerInterceptor loadBalancerInterceptor(
                LoadBalancerClient loadBalancerClient,
                LoadBalancerRequestFactory requestFactory) {
            return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
        }

        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(
                final LoadBalancerInterceptor loadBalancerInterceptor) {
            return restTemplate -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                        restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
        }

    }

    /**
     * Auto configuration for retry mechanism.
     */
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(RetryTemplate.class)
    public static class RetryAutoConfiguration {

        @Bean
        @ConditionalOnMissingBean
        public LoadBalancedRetryFactory loadBalancedRetryFactory() {
            return new LoadBalancedRetryFactory() {
            };
        }

    }

    /**
     * Auto configuration for retry intercepting mechanism.
     */
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(RetryTemplate.class)
    public static class RetryInterceptorAutoConfiguration {

        @Bean
        @ConditionalOnMissingBean
        public RetryLoadBalancerInterceptor loadBalancerRetryInterceptor(
                LoadBalancerClient loadBalancerClient,
                LoadBalancerRetryProperties properties,
                LoadBalancerRequestFactory requestFactory,
                List<LoadBalancedRetryFactory> loadBalancedRetryFactories) {
            AnnotationAwareOrderComparator.sort(loadBalancedRetryFactories);
            return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
                    requestFactory, loadBalancedRetryFactories.get(0));
        }

        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(
                final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
            return restTemplate -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                        restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
        }

    }

}

来一波分析:

首先,起点就是 各个 AutoConfiguration ,需要关注的就是两个,第一个,LoadBalancerAutoConfiguration 这个的主要功能就是向 RestTemplate 注册拦截器。第二个,

负载均衡算法解析

自定义健康检查

负载均衡原理

怎么实现嵌入http请求

获取服务列表

负载均衡算法配置

调用以及结果获取

负载均衡算法解析

Ribbon 服务感知

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容