手写Ribbon基本原理

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

目录

  • 概念说明
    • 什么是Ribbon
    • Ribbon和Nginx负载均衡的区别
  • 工作流程
  • 代码实现
    • RibbonSDK
    • 发送请求端
      • 引入RibbonSDK和Nacos的依赖
      • 配置文件中填写负载均衡策略
      • 调用代码
    • 接收请求端
    • 执行效果
      • 发送请求端
      • 接收请求端
  • 总结提升

概念说明

什么是Ribbon

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

Ribbon和Nginx负载均衡的区别

手写Ribbon基本原理_第1张图片

工作流程

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

代码实现

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();
        //获取服务名
        int startIndex = request.getURI().getPath().indexOf("/") + 1;
        int endIndex = request.getURI().getPath().indexOf("/", startIndex);
        String serveName = request.getURI().getPath().substring(startIndex, endIndex);
        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; // 没有找到合适的服务器
    }

}

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

发送请求端

引入RibbonSDK和Nacos的依赖

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

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

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

ribbon:
  loadBalanceName: polling

调用代码

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服务成功!");
        }
    }
}
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();
    }
}

接收请求端

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;
    }
}

执行效果

发送请求端

手写Ribbon基本原理_第3张图片

接收请求端

手写Ribbon基本原理_第4张图片

总结提升

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


此文章对你有用的话记得留言+点赞+收藏哦

你可能感兴趣的:(中间件合集,ribbon,spring,cloud,后端)