• 手写Ribbon基本原理


    本文已收录于专栏
    《中间件合集》

    概念说明

    什么是Ribbon

      Ribbon 是一个客户端负载均衡器,它是Spring Cloud Netflix开源的一个组件,用于在分布式系统中实现对服务实例的负载均衡。它可以作为一个独立的组件使用,也可以与 Spring Cloud 等微服务框架集成使用。
      Ribbon 的主要功能是根据一定的负载均衡策略,将客户端请求分配到可用的服务实例上,以提高系统的可用性和性能。它通过周期性地从服务注册中心(如 Eureka)获取可用的服务实例列表,并根据配置的负载均衡策略选择合适的实例来处理请求。Ribbon 支持多种负载均衡策略,如轮询、随机、加权随机、加权轮询等。

    Ribbon和Nginx负载均衡的区别

    在这里插入图片描述

    工作流程

    1. 客户端发起请求到 Ribbon。
    2. Ribbon 从服务注册中心获取可用的服务实例列表。
    3. 根据配置的负载均衡策略,选择一个合适的服务实例。
    4. 将请求转发给选中的服务实例进行处理。
    5. 如果请求失败或超时,Ribbon 会尝试选择其他的服务实例进行重试。
      在这里插入图片描述

    代码实现

    RibbonSDK

    sdk是每个使用ribbon的服务中需要引入的jar包,需要借助jar包中的功能来完成ribbon的使用。

    package com.example.ribbonsdk.config.test;
    
    
    import com.example.client.Controller.SDKController;
    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    import org.springframework.context.annotation.Bean;
    import org.springframework.core.env.Environment;
    import org.springframework.http.*;
    import org.springframework.http.client.ClientHttpRequestExecution;
    import org.springframework.http.client.ClientHttpRequestInterceptor;
    import org.springframework.http.client.ClientHttpResponse;
    import org.springframework.stereotype.Component;
    import org.springframework.web.client.RestTemplate;
    import org.springframework.web.util.UriComponentsBuilder;
    
    import java.io.ByteArrayInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.URI;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Random;
    
    
    /**
     * @BelongsProject: ribbonDemo
     * @BelongsPackage: com.example.ribbonsdk.config
     * @Author: Wuzilong
     * @Description: RibbonSDK
     * @CreateTime: 2023-07-31 22:47
     * @Version: 1.0
     */
    @Component
    public class RequestInterceptor implements ClientHttpRequestInterceptor, ApplicationContextAware {
    
        public static ApplicationContext applicationContext;
    
        int index = 0;
    
        // 目前是写死的,应该放到注册中心中去,动态的添加注册服务和权重
        public Map<String,Integer> serverList = new HashMap<>(){{
            put("localhost:9002",7); // 权重值为7
            put("localhost:9005",3); // 权重值为3
        }};
    
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            if (this.applicationContext == null) {
                this.applicationContext = applicationContext;
            }
        }
    
        /**
         * @Author:Wuzilong
         * @Description: 手动注入AnnotationConfigApplicationContext用于判断
         * @CreateTime: 2023/6/19 17:36
         * @param:
         * @return:
         **/
        @Bean
        public AnnotationConfigApplicationContext annotationConfigApplicationContext() {
            return new AnnotationConfigApplicationContext();
        }
    
    
        @Override
        public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
            System.out.println("拦截器拦截进来了,拦截的地址是:"+request.getURI());
            RestTemplate restTemplate = new RestTemplate();
            //获取服务名
    		String serveName = request.getURI().getAuthority();
            String newAuthority = null;
            Environment environment = applicationContext.getBean(Environment.class);
            String loadBalanceName = environment.getProperty("ribbon.loadBalanceName");
            if (loadBalanceName.equals("polling")){
                 newAuthority = this.polling(serveName);
                System.out.println("采用的是负载均衡策略————轮询");
            }else if (loadBalanceName.equals("weight")){
                newAuthority = this.weight();
                System.out.println("采用的是负载均衡策略————权重");
            }
            
            String newHost= newAuthority.split(":")[0];
            String newPort= newAuthority.split(":")[1];
            URI newUri = UriComponentsBuilder.fromUri(request.getURI())
                    .host(newHost)
                    .port(newPort)
                    .build()
                    .toUri();
    
            RequestEntity tRequestEntity = new RequestEntity(HttpMethod.GET, newUri);
            ResponseEntity<String> exchange = restTemplate.exchange(tRequestEntity, String.class);
            System.out.println("请求的服务是"+exchange.getBody());
    
    
            // 创建一个ClientHttpResponse对象,并将实际的响应内容传递给它
            ClientHttpResponse response = new ClientHttpResponse() {
                @Override
                public HttpStatus getStatusCode() {
                    return exchange.getStatusCode();
                }
    
                @Override
                public int getRawStatusCode() {
                    return exchange.getStatusCodeValue();
                }
    
                @Override
                public String getStatusText() {
                    return exchange.getBody();
                }
    
                @Override
                public void close() {
    
                }
    
                @Override
                public InputStream getBody() {
                    return new ByteArrayInputStream(exchange.getBody().getBytes());
                }
    
                @Override
                public HttpHeaders getHeaders() {
                    return exchange.getHeaders();
                }
            };
            return response;
        }
    
    
    
        //轮询获取服务的IP地址
        public  String polling(String serverName){
            List<String> pollingList = applicationContext.getBean(SDKController.class).getList(serverName);
            String ipContext = pollingList.get(index);
            index=(index+1)%pollingList.size();
            return ipContext;
        }
    
    
    
        //权重获取服务的IP地址
        public String weight() {
            int totalWeight = serverList.values().stream().mapToInt(Integer::intValue).sum();
            int randomWeight = new Random().nextInt(totalWeight); // 生成一个随机权重值
            int cumulativeWeight = 0; // 累计权重值
            for (Map.Entry<String,Integer> server : serverList.entrySet()) {
                cumulativeWeight += server.getValue();
                if (randomWeight < cumulativeWeight) {
                    return server.getKey();
                }
            }
            return null; // 没有找到合适的服务器
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163

      RequestInterceptor 类实现了两个接口,一个是ClientHttpRequestInterceptor用来重写intercept方法,也就是说重写了拦截器中的业务逻辑,我们可以把拦截到的请求进行处理,处理的过程可以写到intercept方法中,另一个是ApplicationContextAware这个接口是用来获取bean容器中对象的。

    发送请求端

    引入RibbonSDK和Nacos的依赖

            <dependency>
                <groupId>com.example</groupId>
                <artifactId>RibbonSDK</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
            <!-- 手写nacos的sdk,用来获取注册列表-->
            <dependency>
                <groupId>com.example</groupId>
                <artifactId>Client</artifactId>
                <version>2.5-20230615.123611-1</version>
            </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Nacos的其他配置可参考:手写Naocs注册中心基本原理  手写Nacos配置中心基本原理

    配置文件中填写负载均衡策略

    ribbon:
      loadBalanceName: polling
    
    • 1
    • 2

    调用代码

    import com.example.ribbonsdk.config.test.RequestInterceptor;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.web.client.RestTemplateBuilder;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.stereotype.Service;
    import org.springframework.web.client.RestTemplate;
    
    /**
     * @BelongsProject: ribbonDemo
     * @BelongsPackage: com.example.ribbonsdk.service
     * @Author: Wuzilong
     * @Description: 请求端
     * @CreateTime: 2023-08-28 08:20
     * @Version: 1.0
     */
    @Service
    public class ServiceA {
        @Autowired
        private RequestInterceptor requestInterceptor;
    
        public void getServiceInfo(){
            String url = "http://"+"localhost"+"/B/receiveMessage/";
            RestTemplate restTemplate=new RestTemplateBuilder().build();
            restTemplate.getInterceptors().add(requestInterceptor);
            ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
            if (forEntity.getStatusCode() == HttpStatus.OK) {
                System.out.println("调用B服务成功!");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    import com.example.ribbonsdk.service.ServiceA;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @BelongsProject: ribbonDemo
     * @BelongsPackage: com.example.ribbonsdk.Controller
     * @Author: Wuzilong
     * @Description: 描述什么人干什么事儿
     * @CreateTime: 2023-07-31 22:54
     * @Version: 1.0
     */
    @RestController
    @RequestMapping("/ribbonsdk")
    public class ServiceAController {
    
        @Autowired
        private ServiceA serviceA;
    
        @RequestMapping(value="getInfo",method= RequestMethod.GET)
        public void getInfo(){
            serviceA.getServiceInfo();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    接收请求端

    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.net.InetAddress;
    import java.net.UnknownHostException;
    
    /**
     * @BelongsProject: ServiceB
     * @BelongsPackage: com.example.serviceb.Controller
     * @Author: Wuzilong
     * @Description: B服务
     * @CreateTime: 2023-06-07 19:08
     * @Version: 1.0
     */
    @RestController
    @RequestMapping("/B")
    public class ServiceBController {
    
        @Value("${server.port}")
        private String serverPort;
    
        @GetMapping("/receiveMessage")
        public String receiveMessage() throws UnknownHostException {
    
            System.out.println("B:我被调用了");
            //返回的内容是ip地址和端口号
            return InetAddress.getLocalHost().getHostAddress()+":"+serverPort;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    执行效果

    发送请求端

    在这里插入图片描述

    接收请求端

    在这里插入图片描述

    总结提升

      Ribbon 是一个强大的客户端负载均衡器,可以帮助构建可靠和高性能的分布式系统。它通过负载均衡策略将请求分发到多个服务实例上,提供了灵活的配置选项和额外的功能。


    🎯 此文章对你有用的话记得留言+点赞+收藏哦🎯
  • 相关阅读:
    MySQL多表查询面试题一
    day34(arguments 原型对象prototype apply和call 以及 柯里化函数)
    JavaScript DOM中的基本事件介绍(详细文章请看后期)
    交换机、IP地址、ARP协议
    React报错之Parameter 'event' implicitly has an 'any' type
    VPP以太网VLAN子接口
    godot引擎学习2
    前端面试的话术集锦第 17 篇博文——高频考点(TCP知识点)
    河南分销商城小程序开发跟分销系统区别是什么?
    CSP-S 2022 题解
  • 原文地址:https://blog.csdn.net/weixin_45490198/article/details/132679247