Ribbon客户端负载均衡策略测试及其改进

news2024/10/20 18:30:21

文章目录

  • 一、目的概述
  • 二、验证步骤
    • 1、源码下载
    • 2、导入IDE
    • 3、运行前修改配置
    • 4、策略说明
    • 5、修改策略
  • 三、最终结论
  • 四、改进措施
    • 1. 思路分析
    • 2. 核心代码
    • 3. 测试页面

一、目的概述

为了验证Ribbon客户端负载均衡策略在负载节点失效的情况下,是否具有故障转移的功能,进行了以下代码验证!

二、验证步骤

1、源码下载

git clone https://gitee.com/00fly/microservice-all-in-one.git

https://gitee.com/00fly/microservice-all-in-one/tree/master/ribbon-demo-simple

2、导入IDE

在这里插入图片描述

3、运行前修改配置

根据调用关系,我们需要启动2个user服务,为了方便调试我们这边分别启动8081、8082端口的user服务,并在movie模块中,设置负载节点地址为:127.0.0.1:8081,127.0.0.1:8082

微服务movie
微服务user
微服务user

eclipse为例简要说明
查看环境配置
在这里插入图片描述
打开Dashboard,选择Duplicate config
在这里插入图片描述
选择open Config
在这里插入图片描述
选择Profile设置为dev

在这里插入图片描述
全部启动
在这里插入图片描述
docker部署相对简单,编排文件为
https://gitee.com/00fly/microservice-all-in-one/blob/master/ribbon-demo-simple/docker/docker-compose.yml

version: '3.8'
services:

  #负载均衡节点
  ribbon-user-simple-0:
    image: registry.cn-shanghai.aliyuncs.com/00fly/ribbon-user-simple:0.0.1
    container_name: ribbon-user-simple-0
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 200M
        reservations:
          memory: 180M
    restart: on-failure
    logging:
      driver: json-file
      options:
        max-size: 5m
        max-file: '1'

  #负载均衡节点
  ribbon-user-simple-1:
    image: registry.cn-shanghai.aliyuncs.com/00fly/ribbon-user-simple:0.0.1
    container_name: ribbon-user-simple-1
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 200M
        reservations:
          memory: 180M
    restart: on-failure
    logging:
      driver: json-file
      options:
        max-size: 5m
        max-file: '1'

  #调用方
  ribbon-movie-simple:
    image: registry.cn-shanghai.aliyuncs.com/00fly/ribbon-movie-simple:0.0.1
    container_name: ribbon-movie-simple
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 200M
        reservations:
          memory: 180M
    ports:
    - 8090:8082
    environment:
      USER_SERVERS: ribbon-user-simple-0:8081,ribbon-user-simple-1:8081
    restart: on-failure
    logging:
      driver: json-file
      options:
        max-size: 5m
        max-file: '1'

4、策略说明

  • RandomRule 实现从服务实例清单中随机选择一个服务实例的功能。
  • RoundRobinRule 实现了按照线性轮询的方式依次选择每个服务实例的功能。
  • RetryRule 实现了一个具备重试机制的实例选择功能。
  • WeightedResponseTimeRule是对 RoundRobinRule 的拓展,增加了根据实例的运行情况来计算权重,并根据权重来挑选实例。
  • ClientConfigEnableRoundRobinRule 通过继承该策略,在子类中做一些高级策略时有可能会存在一些无法实施的情况,那么就可以用父类的实现作为备选(线性轮询机制)。
  • BestAvailableRule 通过遍历负载均衡器中维护的所有服务实例,会过滤掉故障的实例,并找出并发请求数最小的一个,所以该策略的特性是可选出最空闲的实例。
  • PredicateBasedRule 先通过子类实现中的 Predicate 逻辑来过滤一部分服务实例,然后再以线性轮询的方式从过滤后的实例清单中选出一个。
  • AvailabilityFilteringRule 通过线性抽样的方式直接尝试寻找可用且较空闲的实例来使用。
  • ZoneAvoidanceRule 根据负载情况选择可用区

5、修改策略

修改这边的负载均衡策略在这里插入图片描述
打开页面
在这里插入图片描述
停止8081或8082端口服务,重新调试,返回结果如下:
在这里插入图片描述

三、最终结论

RandomRule、RoundRobinRule 策略不具备故障转移能力
RetryRule、WeightedResponseTimeRule等虽然具有故障转移,但是故障转移的时间太长,并且故障恢复后,重新选中该恢复的节点所需时间也较长。

各种策略的表现。大家可以自行研究测试。

四、改进措施

1. 思路分析

  • 采用多线程,多个节点同时检测,返回最快响应的节点
  • 采用多线程,定义超时时间,返回超时时间之内有响应的节点, 后续根据规则选择1个节点

2. 核心代码

NodeController.java


import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;

import com.itmuch.cloud.study.user.entity.User;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;

@Slf4j
@Api(tags = "负载均衡节点")
@RestController
@RequestMapping("/node")
public class NodeController
{
    @Autowired
    private WebClient webClient;
    
    @Value("${microservice-ribbon-user.ribbon.listOfServers}")
    private List<String> listOfServers;
    
    private ExecutorService executorService = Executors.newFixedThreadPool(10);
    
    @ApiOperation("查询用户")
    @GetMapping("/user/{id}")
    public List<User> findById(@PathVariable Long id)
        throws InterruptedException
    {
        // WebClient支持异步
        List<User> users = new CopyOnWriteArrayList<User>();
        listOfServers.stream()
            .forEach(hostWithPort -> webClient.get()
                .uri(String.format("http://%s/%s", hostWithPort, id))// URI
                .acceptCharset(StandardCharsets.UTF_8)
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToMono(User.class)
                .subscribe(resp -> users.add(resp)));
        
        int index = 0;
        while (users.isEmpty() && (index++) < 100)
        {
            TimeUnit.MILLISECONDS.sleep(10);
            log.info("index:{}, waitting......", index);
        }
        if (users.isEmpty())
        {
            throw new RuntimeException("查询超时,无返回值");
        }
        return users;
    }
    
    @ApiOperation("查询用户 by execute")
    @GetMapping("/v0/user/{id}")
    public List<User> findByExecute(@PathVariable Long id)
        throws InterruptedException
    {
        // List<User> users = new ArrayList<User>();
        // TODO ArrayList users一定概率有null值
        // 原因:通过new ArrayList<>()初始化的大小是0,首次插入触发扩容,并发可能导致出现null值
        
        List<User> users = new CopyOnWriteArrayList<User>();
        listOfServers.stream()
            .forEach(hostWithPort -> executorService.execute(() -> webClient.get()
                .uri(String.format("http://%s/%s", hostWithPort, id))// URI
                .acceptCharset(StandardCharsets.UTF_8)
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToMono(User.class)
                .subscribe(resp -> users.add(resp))));
        
        int index = 0;
        while (users.isEmpty() && (index++) < 100)
        {
            TimeUnit.MILLISECONDS.sleep(10);
            log.info("index:{}, waitting......", index);
        }
        if (users.isEmpty())
        {
            throw new RuntimeException("查询超时,无返回值");
        }
        return users;
    }
    
    @ApiOperation("查询用户 by submit")
    @GetMapping("/v1/user/{id}")
    public List<User> findBySubmit(@PathVariable Long id)
        throws InterruptedException
    {
        List<User> users = new CopyOnWriteArrayList<User>();
        listOfServers.stream()
            .forEach(hostWithPort -> executorService.submit(() -> webClient.get()
                .uri(String.format("http://%s/%s", hostWithPort, id))// URI
                .acceptCharset(StandardCharsets.UTF_8)
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToMono(User.class)
                .subscribe(resp -> users.add(resp)), users));
        
        int index = 0;
        while (users.isEmpty() && (index++) < 100)
        {
            TimeUnit.MILLISECONDS.sleep(10);
            log.info("index:{}, waitting......", index);
        }
        if (users.isEmpty())
        {
            throw new RuntimeException("查询超时,无返回值");
        }
        return users;
    }
    
    @ApiOperation("查询用户 by invokeAny")
    @GetMapping("/v2/user/{id}")
    public User findByInvokeAny(@PathVariable Long id)
        throws InterruptedException, ExecutionException, TimeoutException
    {
        return executorService.invokeAny(listOfServers.stream().map(hostWithPort -> new Callable<User>()
        {
            @Override
            public User call()
            {
                Mono<User> mono = webClient.get()
                    .uri(String.format("http://%s/%s", hostWithPort, id))// URI
                    .acceptCharset(StandardCharsets.UTF_8)
                    .accept(MediaType.APPLICATION_JSON)
                    .retrieve()
                    .bodyToMono(User.class);
                return mono.block();
            }
        }).collect(Collectors.toList()), 1000, TimeUnit.MILLISECONDS);
    }
    
    @ApiOperation("查询用户 by invokeAll")
    @GetMapping("/v3/user/{id}")
    public List<User> findByInvokeAll(@PathVariable Long id)
        throws InterruptedException
    {
        List<Future<User>> futures = executorService.invokeAll(listOfServers.stream().map(hostWithPort -> new Callable<User>()
        {
            @Override
            public User call()
            {
                Mono<User> mono = webClient.get()
                    .uri(String.format("http://%s/%s", hostWithPort, id))// URI
                    .acceptCharset(StandardCharsets.UTF_8)
                    .accept(MediaType.APPLICATION_JSON)
                    .retrieve()
                    .bodyToMono(User.class);
                return mono.block();
            }
        }).collect(Collectors.toList()), 1000, TimeUnit.MILLISECONDS);
        
        List<User> users = new ArrayList<User>();
        for (Future<User> future : futures)
        {
            try
            {
                users.add(future.get());
            }
            catch (Exception e)
            {
                log.error(e.getMessage(), e);
            }
        }
        return users;
    }
}

3. 测试页面

在这里插入图片描述


有任何问题和建议,都可以向我提问讨论,大家一起进步,谢谢!

-over-

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

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

相关文章

学习 UE5 的一些前置操作总结

随着 Unity, Godot 这些引擎都玩抽象&#xff0c;主动捅自己一刀后&#xff0c;UE5 的风头不可谓不盛&#xff0c;本着多学一点免得失业的思路方针&#xff0c;咱也研究了一下 UE5 引擎&#xff0c;然后发现想要开始使用 UE5 &#xff0c;包含了很多前置操作&#xff0c;这里总…

Java项目-基于springboot框架的家具商城系统项目实战(附源码+文档)

作者&#xff1a;计算机学长阿伟 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、ElementUI等&#xff0c;“文末源码”。 开发运行环境 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBoot、Vue、Mybaits Plus、ELementUI工具&#xff1a;IDEA/…

使用Yolov10和Ollama增强OCR

1. 训练自定义 Yolov10 数据集 利用物体检测增强 OCR 的第一步是在数据集上训练自定义 YOLO 模型。YOLO&#xff08;只看一遍&#xff09;是一种功能强大的实时对象检测模型&#xff0c;它将图像划分为网格&#xff0c;使其能够在一次前向传递中识别多个对象。这种方法非常适合…

AI大模型开发架构设计(14)——基于LangChain大模型的案例架构实战

文章目录 基于LangChain大模型的案例架构实战1 LangChain 顶层架构设计以及关键技术剖析LangChain 是什么?LangChain的主要功能是什么&#xff1f;LangChain 顶层架构设计LangChain 典型使用场景&#xff1a;QA 问答系统LangChain 顶层架构设计之 Model I/OLangChain 顶层架构…

No.17 笔记 | XXE漏洞:XML外部实体注入攻击

1. XXE漏洞概览 XXE&#xff08;XML External Entity&#xff09;是一种允许攻击者干扰应用程序对XML输入处理的漏洞。 1.1 XXE漏洞比喻 想象XML解析器是一个听话的机器人&#xff0c;而XXE就是利用这个机器人的"过分听话"来获取不应该获取的信息。 1.2 XXE漏洞危…

基于SSM汽车零部件加工系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;员工管理&#xff0c;经理管理&#xff0c;零件材料管理&#xff0c;产品类型管理&#xff0c;产品信息管理&#xff0c;产品出库管理&#xff0c;产品入库管理 员工账号功能包括&#xff1a;系统首页…

spring-cloud-alibaba-nacos-config2023.0.1.*启动打印配置文件内容

**背景&#xff1a;**在开发测试过程中如果可以打印出配置文件的内容&#xff0c;方便确认配置是否准确&#xff1b;那么如何才可以打印出来呢&#xff1b; spring-cloud-alibaba-nacos-config 调整日志级别 logging:level:com.alibaba.cloud.nacos.configdata.NacosConfigD…

Java爬虫:获取商品销量详情API返回值的实战指南

在数字化时代&#xff0c;数据已成为电商运营的核心。商品销量数据不仅反映了市场的需求和趋势&#xff0c;还能为商家提供决策支持。通过Java爬虫技术&#xff0c;我们可以高效地获取这些数据&#xff0c;从而深入分析商品的市场表现。 为何选择Java爬虫获取销量数据 自动化处…

股票与基金资料收集

声明&#xff1a;本内容是网上资料的收集与整理而成&#xff0c;不定时更新。仅供参考&#xff0c;不构成任何投资建议。 目录&#xff1a; 一、股票 1、黄金交叉和死亡交叉 2、技术指标 3、T、TR、THR含义 二、基金 平准基金 一、股票 1、黄金交叉和死亡交叉 “黄金交…

【C++_string类练习】仅仅反转字母

题目链接&#xff1a;仅仅反转字母 解题思路&#xff1a; 这种反转字符的题目我第一个想到的方法就是&#xff1a;双指针 一个指针在前start&#xff0c;一个指针在后back&#xff0c; 如果指针所指向的位置的值是字母&#xff0c;那么两个指针位置的值就进行交换&#xff0…

P2-3与P2-4.【C语言基本数据类型、运算符和表达式】第三节与第四节

讲解视频&#xff1a; P2-3.【基本数据类型、运算符和表达式】第三节 P2-4.【基本数据类型、运算符和表达式】第四节 目录 必备知识与理论 任务实施 必备知识与理论 C语言中把除了控制语句和输入输出以外的几乎所有的基本操作都作为运算符处理。 其运算符和表达式数量之多&a…

以简单组合优化为例讨论计算复杂性

此为课题组所指导本科生和低年级硕士生学习组合优化问题汇报 所用教材&#xff1a;北京大学屈婉玲教授《算法设计与分析》 课程资料&#xff1a;https://www.icourse163.org/course/PKU-1002525003 承诺不用于任何商业用途&#xff0c;仅用于学术交流和分享 更多内容请关注课题…

centOS实用命令

一、查看进程&#xff0c;端口占用 netstat命令(window和linux通用&#xff0c;细节不同) 查看端口占用(linux) netstat -ano |grep 8080查看端口占用(window) netstat -ano |findstr 8080ps命令 可以直接使用ps aux查看所有用户的进程信息 一些参数 参数解释-p根据进程P…

【git】如何快速准确的回退(revert)已经合并(merge)主分支(master)的新提交代码

文章目录 前言一、merge模式二、回滚步骤总结 前言 我们在做一些需求&#xff0c;正常流程经过开发&#xff0c;测试到最后和代码上线。但是有时候就会发生一些小插曲&#xff0c;比如产品说老板说某某某你的代码要延后上线&#xff01;&#xff01;或者你写的不合格预发环境出…

(成功解决)ubuntu22.04不小心更新成了atzlinux12.7.1,右上角出现红色错误符号

文章目录 &#x1f315;问题&#x1f315;查看系统版本&#x1f315;为什么更新更成了atzlinux&#x1f315;通过修复依赖关系尝试解决右上角红色错误符号&#x1f315;把源换成ubuntu的源&#x1f315;删除atzlinux源和自定义的第三方源&#x1f315;重新创建/etc/os-release文…

AJAX——服务端响应 JSON 数据

网页文件中&#xff1a; js 文件中&#xff1a; 本文分享到此结束&#xff0c;欢迎大家评论区相互讨论学习&#xff0c;下一篇继续分享AJAX中AJAX 请求超时与网络异常处理的学习。

吴伟仁《英国文学史及选读》第一二册课后答案PDF

新经典高等学校英语专业系列教材《英国文学史及选读》根据英国文学历史的顺序结合作品选读编写而成&#xff0c;在历史部分&#xff0c;对英国文学史的每个阶段作了简明扼要的概述&#xff0c;而在作品选读部分则尽可能遴选了文学史上的重要作家和重要作品。教材内容丰富&#…

python机器人编程——用python调用API控制wifi小车的实例程序

目录 一、前言二、一个客户端的简单实现2.1 首先定义一个类及属性2.2 其次定义连接方法2.3 定义一些回调函数2.4 定义发送小车指令方法2.5 定义一个正常关闭方法 三、python编程控制小车的demo实现四、小结PS.扩展阅读ps1.六自由度机器人相关文章资源ps2.四轴机器相关文章资源p…

vue elementui table编辑表单时,弹框增加编辑明细数据

需求: 前端进行新增表单时&#xff0c;同时增加表单的明细数据。明细数据部分&#xff0c;通过弹框方式增加或者编辑。 效果图&#xff1a; 代码&#xff1a; <!-- 新增主表弹窗 Begin --><el-dialog:title"titleInfo"top"5vh"centerwidth"…

从零开始学PHP之输出语句变量常量

一、 输出方式 在 PHP 中输出方式&#xff1a; echo&#xff0c;print&#xff0c;print_r&#xff0c;var_dump 1、echo和print为php的输出语句 2、var_dump&#xff0c;print_r为php的输出函数 &#xff08;这里不做介绍&#xff09;echo 和 print 区别 1、echo - 可以输出…