手写Ribbon基本原理

news2024/11/15 19:53:45
本文已收录于专栏
《中间件合集》

目录

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

概念说明

什么是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();
        //获取服务名
        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 是一个强大的客户端负载均衡器,可以帮助构建可靠和高性能的分布式系统。它通过负载均衡策略将请求分发到多个服务实例上,提供了灵活的配置选项和额外的功能。


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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/973248.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

揭秘企业标准化作业:提升效率、降低成本、保障质量!为什么需要推行?

企业标准化作业是现今生产车间中出现频率非常高的一个词&#xff0c;那么什么是企业标准化作业&#xff1f;企业为什么推行标准化作业&#xff1f;标准化作业的实施有哪些好处&#xff1f;实施过程又有哪些难点呢&#xff1f;今天就来说一说&#xff01; 企业标准化作业是对生产…

智能制造效率与创新:RFID智能设备的引领作用

在现代制造业中&#xff0c;如何提升生产效率、降低成本、实现创新已经成为制造企业持续追求的目标。随着科技的不断进步&#xff0c;智能制造RFID智能设备正逐渐成为实现这些目标的得力工具。本文将探讨智能制造RFID智能设备在提升制造效率和创新方面的引领作用。 实时生产监控…

如何使用ArcGIS去除卫星影像上的云

虽然目前发布的地图都是对云量进行过筛选&#xff08;一般低于20%&#xff09;&#xff0c;但是还是有可能会遇到有云的情况&#xff08;特别是下载历史影像的时候&#xff09;&#xff0c;那么这些云应该怎么去除呢&#xff0c;我们可以尝试使用ArcGIS进行处理。 识别像素 将…

通过使用过硫酸铵溶液轻松预处理铜催化剂基底具有独特底部轮廓的剥离光刻胶的开发

引言 石墨烯是sp2杂化碳原子的二维蜂窝晶格&#xff0c;自首次成功分离和表征单层石墨烯以来就引起了广泛关注。载流子迁移率、稳健的机械公差和高光学透明度为未来的超大规模器件的应用提供了巨大的利用机会。因此&#xff0c;英思特提出了化学剥离、外延生长、热解和化学气相…

FreeRTOS中断与任务之间同步(Error:..\..\FreeRTOS\portable\RVDS\ARM_CM4F\port.c,422 )

前言&#xff1a; FreeRTOS中&#xff0c;中断需要注意几点&#xff1a; 何时使用中断&#xff1b;中断服务函数&#xff08;ISR&#xff09;要处理的数据量有多大&#xff0c;通常我们希望中断的切换越快越好&#xff0c;也就是说&#xff0c;ISR尽量采用耗时较少的处理方式…

2000-2022年上市公司融资约束SA指数(含原始数据+计算方法+计算结果)

2000-2022年上市企业的融资约束指数&#xff08;含原始数据计算方法计算结果&#xff09; 1、时间&#xff1a;2000-2022年 2、范围&#xff1a;沪深A股上市公司 3、指标&#xff1a; 证券代码、证券简称、统计截止日期、是否发生ST或*ST或PT、是否发生暂停上市、行业代码、…

【Java 基础篇】Java多态:让你的代码更灵活而强大

多态是面向对象编程中的一个重要概念&#xff0c;它允许我们在不同的对象上调用相同的方法&#xff0c;但根据对象的不同&#xff0c;可以产生不同的行为。在 Java 中&#xff0c;多态性是一个强大的特性&#xff0c;它有助于代码的可扩展性和可维护性。本篇博客将深入探讨 Jav…

javaee spring aop实现事务 项目结构

spring配置文件 <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xmlns:context"http://www.springframewo…

搭建hadoop集群的常见问题及解决办法

问题一: namenode -format重复初始化 出现问题的原因是重复初始化时会重新生成集群ID&#xff0c;而dn还是原先的集群ID&#xff0c;两者不匹配时无法启动相应的dn进程。 怎么查找问题原因&#xff1a;在logs目录下找到对应节点的.log文件&#xff0c;使用tail -200 文件名来查…

(vue)Vue项目中使用jsPDF和html2canvas生成PDF

(vue)Vue项目中使用jsPDF和html2canvas生成PDF 效果&#xff1a; 安装与使用 1.&#xff1a;安装jsPDF和html2canvas npm install jspdf html2canvas2.在需要生成PDF文档的组件中引入jsPDF和html2canvas <template><div><el-button type"primary"…

Android平台GB28181历史视音频文件检索规范探讨及技术实现

技术背景 我们在做Android平台GB28181设备接入侧模块的时候&#xff0c;特别是执法记录仪或类似场景&#xff0c;系统除了对常规的录像有要求&#xff0c;还需要能和GB28181平台侧交互&#xff0c;比如实现设备侧视音频文件检索、下载或回放。本文假定记录仪或相关设备已经完成…

Gin项目实战

Gin项目实战 Gin博客项目-项目架构Gin博客项目-集成gormGin博客项目-集成Bootstrap创建用户表单Gin 博客项目-实现控制器和路由Gin 博客项目-设计静态页面Gin 博客项目-用户注册Gin 博客项目-用户登录Gin 博客项目-集成markdown编辑器Gin 博客项目-创建博客模型和DAOGin 博客项…

MediaBox助力企业一站式获取音视频能力

以一只音视频百宝箱&#xff0c;应对「千行千面」。 洪炳峰、楚佩斯&#xff5c;作者 大家好&#xff0c;今天我分享的主题是MediaBox——行业音视频数字化再加速。 根据权威数据表明&#xff0c;65%的行业数字化信息来自视频&#xff0c;基于此&#xff0c;音视频技术对于行…

长胜证券:三大拐点共振 看好智能驾驶新一轮行情

摘要 【长胜证券&#xff1a;三大拐点共振 看好智能驾驭新一轮行情】长胜证券研报指出&#xff0c;全球共振&#xff0c;国内智驾商场正迎来三大拐点&#xff1a;1&#xff09;技能上&#xff0c;“BEV Transformer数据闭环”新架构2023年开端上车&#xff0c;使得不依靠高精地…

高并发-ExecutorCompletionService

目录 1 为什么要引入高并发 2 ExecutorCompletionService分析 2.1 原理 2.2 api调用分析 3 实操 1 为什么要引入高并发 众所周知&#xff0c;程序中的代码是从下往下顺序执行的&#xff0c;当我们需要在一个方法中同时执行多个耗时的任务时所消耗时间就会大于等于这些任务消…

港陆证券:五日线破位怎么看?

在股票交易中&#xff0c;五日线是个重要的技术指标之一&#xff0c;它能够反映出最近的商场趋势。假如五日线破位&#xff0c;这意味着商场呈现了趋势反转&#xff0c;出资者需求注重趋势改动&#xff0c;并采取相应的出资战略。 首先&#xff0c;咱们来看看五日线破位的原因…

修改PX4飞控的imu频率

QGroundControl 连接上飞控后&#xff0c;打开 Analyze Tools 下的 MAVLink Inspector 界面 可以看到当前的 IMU 频率为50 HZ&#xff0c;或者在终端启动 mavros&#xff0c;终端输入 sudo chmod 777 /dev/ttyACM0 roslaunch mavros px4.launch 然后查看频率 rostopic hz /m…

备份StarRocks数据到对象存储minio中/外表查minio中的数据

1.部署minio环境 docker pull minio/minio宿主机与容器挂在映射 宿主机位置容器位置/data/minio/config/data/data/minio/data/root/.minio 拉起环境&#xff1a; docker run -p 9000:9000 -p 9090:9090 --name minio \ -d --restartalways \ -e "MINIO_ACCESS_KEYadm…

uniapp的小程序中使用web-view进行相互传参,并监听web-view的返回键

uniapp的小程序中使用web-view进行相互传参&#xff0c;并监听web-view的返回键 一、unaipp给webview传参 //uniapp页面中 <web-view :src"src" message"getMessage" onPostMessage"getPostMessage"></web-view>data() {return …

Mybatis学习|Mybatis缓存:一级缓存、二级缓存

Mybatis缓存 MyBatis包含一个非常强大的查询缓存特性&#xff0c;它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。 MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存 默认情况下&#xff0c;只有一级缓存开启。(SqlSession级别的缓存&#xff0c;也称为本地…