手写Ribbon基本原理

这篇具有很好参考价值的文章主要介绍了手写Ribbon基本原理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

概念说明

什么是Ribbon

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

Ribbon和Nginx负载均衡的区别

手写Ribbon基本原理,中间件合集,ribbon,spring cloud,后端

工作流程

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

代码实现

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; // 没有找到合适的服务器
    }

}

  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基本原理,中间件合集,ribbon,spring cloud,后端

接收请求端

手写Ribbon基本原理,中间件合集,ribbon,spring cloud,后端

总结提升

  Ribbon 是一个强大的客户端负载均衡器,可以帮助构建可靠和高性能的分布式系统。它通过负载均衡策略将请求分发到多个服务实例上,提供了灵活的配置选项和额外的功能。文章来源地址https://www.toymoban.com/news/detail-702058.html


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

到了这里,关于手写Ribbon基本原理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 消息中间件 —— ActiveMQ 使用及原理详解

    目录 一. 前言 二. JMS 规范 2.1. 基本概念 2.2. JMS 体系结构 三. ActiveMQ 使用 3.1. ActiveMQ Classic 和 ActiveMQ Artemis 3.2. Queue 模式(P2P) 3.3. Topic 模式(Pub/Sub) 3.4. 持久订阅 3.5. 消息传递的可靠性 3.5.1. 事务型会话与非事务型会话 3.5.2. 持久化与非持久化消息的存储策略 3.6. 消息发

    2024年02月03日
    浏览(31)
  • Flask 中间件的原理及使用详解

    Flask 中间件是 Flask Web 应用的核心组件之一,它在处理 HTTP 请求和响应的生命周期中扮演着关键角色。利用中间件,开发者可以在不改变现有视图逻辑的前提下,为应用添加额外的处理逻辑,如身份验证、日志记录、请求预处理等。本文旨在提供一个全面的指南,讲解 Flask 中

    2024年02月04日
    浏览(30)
  • MQ(消息中间件)概述及 RabbitMQ 的基本介绍

    消息队列中间件是分布式系统中重要的组件,主要解决 应用解耦,异步消息,流量削锋等 问题,实现高性能,高可用,可伸缩和最终一致性架构。流量削锋 : 削减峰值压力(秒杀,抢购) MQ(Message Queue,消息队列)是典型的生产者、消费者模型。生产者不断向消息队列中

    2024年02月12日
    浏览(33)
  • 中间件系列 - Redis入门到实战(原理篇)

    学习视频: 黑马程序员Redis入门到实战教程,深度透析redis底层原理+redis分布式锁+企业解决方案+黑马点评实战项目 中间件系列 - Redis入门到实战 本内容仅用于个人学习笔记,如有侵扰,联系删除 学习目标 Redis数据结构 Redis网络模型 Redis通信协议-RESP协议 我们都知道Redis中保

    2024年02月03日
    浏览(31)
  • 由浅入深:Django中间件原理及编程步骤

    什么是 Django 中的中间件? 中间件如何工作? 中间件有哪些类型? 如何在 Django 中编写自定义中间件? 1.1 什么是 Django 的中间件?** 通俗地说👨 ,中间件是充当程序或系统两个部分之间的桥梁,使它们之间的通信成为可能。在技术术语👨 💻中,中间件是Django的请求/响应

    2024年02月16日
    浏览(30)
  • 数据库访问中间件--springdata-jpa的基本使用

    回顾 示例 JPQL 片段 And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2 Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2 Is,Equals findByFirstnameIs,findByFirstnameEquals … where x.firstname = ?1 Between findByStartDateBetween … where x.startDate between ?1 and ?2 LessThan findByAgeLessT

    2024年02月14日
    浏览(31)
  • 【送书福利-第七期】《分布式中间件核心原理与RocketMQ最佳实践》

    大家好,我是洲洲,欢迎关注,一个爱听周杰伦的程序员。关注公众号【程序员洲洲】即可获得10G学习资料、面试笔记、大厂独家学习体系路线等…还可以加入技术交流群欢迎大家在CSDN后台私信我! 分布式中间件核心原理与RocketMQ实战技术一本通:实战案例+操作步骤+执行效

    2024年02月08日
    浏览(89)
  • 一篇文章带你从入门都入土 Kafka 消息中间件(原理+代码)

    目录 一、Kafka定义 二、消息队列 三、Kafka基础架构图 四、安装Kafka 4.1 为每台服务器下载Kafka并解压 4.2 查看目录结构 4.3 为每台服务器修改配置文件server.properties 4.4 为每台服务器配置Kafka环境变量 4.5 启动zookeeper集群 4.6 启动Kafka集群 4.7 关闭Kafka集群的注意事项 五、Topic命令

    2024年02月04日
    浏览(30)
  • 【云原生进阶之PaaS中间件】第四章RabbitMQ-4.1-原理机制与进阶特性

    1.客户端连接到消息队列服务器,打开一个Channel。 2.客户端声明一个Exchange,并设置相关属性。 3.客户端声明一个Queue,并设置相关属性。 4.客户端使用Routing key,在Exchange和Queue之间建立好绑定关系。 5.客户端投递消息到Exchange。 6.Exchange接收到消息后,就根据消息的key和已经

    2024年02月21日
    浏览(37)
  • 【Alibaba中间件技术系列】「RocketMQ技术专题」RocketMQ消息发送的全部流程和落盘原理分析

    RocketMQ目前在国内应该是比较流行的MQ 了,目前本人也在公司的项目中进行使用和研究,借着这个机会,分析一下RocketMQ 发送一条消息到存储一条消息的过程,这样会对以后大家分析和研究RocketMQ相关的问题有一定的帮助。 分析的总体技术范围发送到存储,本文的主要目的是

    2024年02月10日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包