dubbo 源码系列之-集群三板斧---负载均衡(二)

在上一课时我们了解了 LoadBalance 接口定义以及 AbstractLoadBalance 抽象类的内容,还详细介绍了 ConsistentHashLoadBalance 以及 RandomLoadBalance 这两个实现类的核心原理和大致实现。本课时我们将继续介绍 LoadBalance 的剩余三个实现。

LeastActiveLoadBalance 最小活跃数

LeastActiveLoadBalance 使用的是最小活跃数负载均衡算法。它认为当前活跃请求数越小的 Provider 节点,剩余的处理能力越多,处理请求的效率也就越高,那么该 Provider 在单位时间内就可以处理更多的请求,所以我们应该优先将请求分配给该 Provider 节点。

LeastActiveLoadBalance 需要配合 ActiveLimitFilter 使用,ActiveLimitFilter 会记录每个接口方法的活跃请求数,在 LeastActiveLoadBalance 进行负载均衡时,只会从活跃请求数最少的 Invoker 集合里挑选 Invoker。

在 LeastActiveLoadBalance 的实现中,首先会选出所有活跃请求数最小的 Invoker 对象,之后的逻辑与 RandomLoadBalance 完全一样,即按照这些 Invoker 对象的权重挑选最终的 Invoker 对象。下面是 LeastActiveLoadBalance.doSelect() 方法的具体实现:

protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
  // 初始化Invoker数量
  int length = invokers.size();
  // 记录最小的活跃请求数
  int leastActive = -1;
  // 记录活跃请求数最小的Invoker集合的个数
  int leastCount = 0;
  // 记录活跃请求数最小的Invoker在invokers数组中的下标位置 
  int[] leastIndexes = new int[length];
  // 记录活跃请求数最小的Invoker集合中,每个Invoker的权重值
  int[] weights = new int[length];
  // 记录活跃请求数最小的Invoker集合中,所有Invoker的权重值之和
  int totalWeight = 0;
  // 记录活跃请求数最小的Invoker集合中,第一个Invoker的权重值
  int firstWeight = 0;
  // 活跃请求数最小的集合中,所有Invoker的权重值是否相同
  boolean sameWeight = true;
  for (int i = 0; i < length; i++) { // 遍历所有Invoker,获取活跃请求数最小的Invoker集合
      Invoker<T> invoker = invokers.get(i);
      // 获取该Invoker的活跃请求数
      int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive();
      // 获取该Invoker的权重
      int afterWarmup = getWeight(invoker, invocation);
      weights[i] = afterWarmup;
      // 比较活跃请求数
      if (leastActive == -1 || active < leastActive) {
          // 当前的Invoker是第一个活跃请求数最小的Invoker,则记录如下信息
          leastActive = active; // 重新记录最小的活跃请求数
          leastCount = 1; // 重新记录活跃请求数最小的Invoker集合个数
          leastIndexes[0] = i; // 重新记录Invoker
          totalWeight = afterWarmup; // 重新记录总权重值
          firstWeight = afterWarmup; // 该Invoker作为第一个Invoker,记录其权重值
          sameWeight = true; // 重新记录是否权重值相等
      } else if (active == leastActive) { 
          // 当前Invoker属于活跃请求数最小的Invoker集合
          leastIndexes[leastCount++] = i; // 记录该Invoker的下标
          totalWeight += afterWarmup; // 更新总权重
          if (sameWeight && afterWarmup != firstWeight) {
              sameWeight = false; // 更新权重值是否相等
          }
      }
  }
  // 如果只有一个活跃请求数最小的Invoker对象,直接返回即可
  if (leastCount == 1) {
      return invokers.get(leastIndexes[0]);
  }
  // 下面按照RandomLoadBalance的逻辑,从活跃请求数最小的Invoker集合中,随机选择一个Invoker对象返回
  if (!sameWeight && totalWeight > 0) {
      int offsetWeight = ThreadLocalRandom.current().nextInt(totalWeight);
      for (int i = 0; i < leastCount; i++) {
          int leastIndex = leastIndexes[i];
          offsetWeight -= weights[leastIndex];
          if (offsetWeight < 0) {
              return invokers.get(leastIndex);
          }
      }
  }
  return invokers.get(leastIndexes[ThreadLocalRandom.current().nextInt(leastCount)]);
}

ActiveLimitFilter 以及底层的 RpcStatus 记录活跃请求数的具体原理,在前面的[第 30 课时]中我们已经详细分析过了,这里不再重复,如果有不清楚的地方,你可以回顾之前课时相关的内容。

RoundRobinLoadBalance 加权轮询

RoundRobinLoadBalance 实现的是加权轮询负载均衡算法。

轮询指的是将请求轮流分配给每个 Provider。例如,有 A、B、C 三个 Provider 节点,按照普通轮询的方式,我们会将第一个请求分配给 Provider A,将第二个请求分配给 Provider B,第三个请求分配给 Provider C,第四个请求再次分配给 Provider A……如此循环往复。

轮询是一种无状态负载均衡算法,实现简单,适用于集群中所有 Provider 节点性能相近的场景。 但现实情况中就很难保证这一点了,因为很容易出现集群中性能最好和最差的 Provider 节点处理同样流量的情况,这就可能导致性能差的 Provider 节点各方面资源非常紧张,甚至无法及时响应了,但是性能好的 Provider 节点的各方面资源使用还较为空闲。这时我们可以通过加权轮询的方式,降低分配到性能较差的 Provider 节点的流量。

加权之后,分配给每个 Provider 节点的流量比会接近或等于它们的权重比。例如,Provider 节点 A、B、C 权重比为 5:1:1,那么在 7 次请求中,节点 A 将收到 5 次请求,节点 B 会收到 1 次请求,节点 C 则会收到 1 次请求。

在 Dubbo 2.6.4 版本及之前,RoundRobinLoadBalance 的实现存在一些问题,例如,选择 Invoker 的性能问题、负载均衡时不够平滑等。在 Dubbo 2.6.5 版本之后,这些问题都得到了修复,所以这里我们就来介绍最新的 RoundRobinLoadBalance 实现。

每个 Provider 节点有两个权重:一个权重是配置的 weight,该值在负载均衡的过程中不会变化;另一个权重是 currentWeight,该值会在负载均衡的过程中动态调整,初始值为 0。

当有新的请求进来时,

  1. RoundRobinLoadBalance 会遍历 Invoker 列表,并用对应的 currentWeight 加上其配置的权重。
  2. 遍历完成后,再找到最大的 currentWeight,将其减去权重总和,然后返回相应的 Invoker 对象。

下面我们通过一个示例说明 RoundRobinLoadBalance 的执行流程,这里我们依旧假设 A、B、C 三个节点的权重比例为 5:1:1。

在这里插入图片描述

  1. 处理第一个请求,currentWeight 数组中的权重与配置的 weight 相加,即从 [0, 0, 0] 变为 [5, 1, 1]。接下来,从中选择权重最大的 Invoker 作为结果,即节点 A。最后,将节点 A 的 currentWeight 值减去 totalWeight 值,最终得到 currentWeight 数组为 [-2, 1, 1]。
  2. 处理第二个请求,currentWeight 数组中的权重与配置的 weight 相加,即从 [-2, 1, 1] 变为 [3, 2, 2]。接下来,从中选择权重最大的 Invoker 作为结果,即节点 A。最后,将节点 A 的 currentWeight 值减去 totalWeight 值,最终得到 currentWeight 数组为 [-4, 2, 2]。
  3. 处理第三个请求,currentWeight 数组中的权重与配置的 weight 相加,即从 [-4, 2, 2] 变为 [1, 3, 3]。接下来,从中选择权重最大的 Invoker 作为结果,即节点 B。最后,将节点 B 的 currentWeight 值减去 totalWeight 值,最终得到 currentWeight 数组为 [1, -4, 3]。
  4. 处理第四个请求,currentWeight 数组中的权重与配置的 weight 相加,即从 [1, -4, 3] 变为 [6, -3, 4]。接下来,从中选择权重最大的 Invoker 作为结果,即节点 A。最后,将节点 A 的 currentWeight 值减去 totalWeight 值,最终得到 currentWeight 数组为 [-1, -3, 4]。
  5. 处理第五个请求,currentWeight 数组中的权重与配置的 weight 相加,即从 [-1, -3, 4] 变为 [4, -2, 5]。接下来,从中选择权重最大的 Invoker 作为结果,即节点 C。最后,将节点 C 的 currentWeight 值减去 totalWeight 值,最终得到 currentWeight 数组为 [4, -2, -2]。
  6. 处理第六个请求,currentWeight 数组中的权重与配置的 weight 相加,即从 [4, -2, -2] 变为 [9, -1, -1]。接下来,从中选择权重最大的 Invoker 作为结果,即节点 A。最后,将节点 A 的 currentWeight 值减去 totalWeight 值,最终得到 currentWeight 数组为 [2, -1, -1]。
  7. 处理第七个请求,currentWeight 数组中的权重与配置的 weight 相加,即从 [2, -1, -1] 变为 [7, 0, 0]。接下来,从中选择权重最大的 Invoker 作为结果,即节点 A。最后,将节点 A 的 currentWeight 值减去 totalWeight 值,最终得到 currentWeight 数组为 [0, 0, 0]。

到此为止,一个轮询的周期就结束了。

而在 Dubbo 2.6.4 版本中,上面示例的一次轮询结果是 [A, A, A, A, A, B, C],也就是说前 5 个请求会全部都落到 A 这个节点上。这将会使节点 A 在短时间内接收大量的请求,压力陡增,而节点 B 和节点 C 此时没有收到任何请求,处于完全空闲的状态,这种“瞬间分配不平衡”的情况也就是前面提到的“不平滑问题”。

在 RoundRobinLoadBalance 中,我们为每个 Invoker 对象创建了一个对应的 WeightedRoundRobin 对象,用来记录配置的权重(weight 字段)以及随每次负载均衡算法执行变化的 current 权重(current 字段)。

了解了 WeightedRoundRobin 这个内部类后,我们再来看 RoundRobinLoadBalance.doSelect() 方法的具体实现:

protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
  String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
  // 获取整个Invoker列表对应的WeightedRoundRobin映射表,如果为空,则创建一个新的WeightedRoundRobin映射表
  ConcurrentMap<String, WeightedRoundRobin> map = methodWeightMap.computeIfAbsent(key, k -> new ConcurrentHashMap<>());
  int totalWeight = 0;
  long maxCurrent = Long.MIN_VALUE;
  long now = System.currentTimeMillis(); // 获取当前时间
  Invoker<T> selectedInvoker = null;
  WeightedRoundRobin selectedWRR = null;
  for (Invoker<T> invoker : invokers) {
      String identifyString = invoker.getUrl().toIdentityString();
      int weight = getWeight(invoker, invocation);
      // 检测当前Invoker是否有相应的WeightedRoundRobin对象,没有则进行创建
      WeightedRoundRobin weightedRoundRobin = map.computeIfAbsent(identifyString, k -> {
          WeightedRoundRobin wrr = new WeightedRoundRobin();
          wrr.setWeight(weight);
          return wrr;
      });
      // 检测Invoker权重是否发生了变化,若发生变化,则更新WeightedRoundRobin的weight字段
      if (weight != weightedRoundRobin.getWeight()) {
          weightedRoundRobin.setWeight(weight);
      }
      // 让currentWeight加上配置的Weight
      long cur = weightedRoundRobin.increaseCurrent();
      //  设置lastUpdate字段
      weightedRoundRobin.setLastUpdate(now);
      // 寻找具有最大currentWeight的Invoker,以及Invoker对应的WeightedRoundRobin
      if (cur > maxCurrent) {
          maxCurrent = cur;
          selectedInvoker = invoker;
          selectedWRR = weightedRoundRobin;
      }
      totalWeight += weight; // 计算权重总和
  }
  if (invokers.size() != map.size()) {
      map.entrySet().removeIf(item -> now - item.getValue().getLastUpdate() > RECYCLE_PERIOD);
  }
  if (selectedInvoker != null) {
      // 用currentWeight减去totalWeight
      selectedWRR.sel(totalWeight);
      // 返回选中的Invoker对象
      return selectedInvoker;
  }
  return invokers.get(0);
}

ShortestResponseLoadBalance 最短响应时间

ShortestResponseLoadBalance 是Dubbo 2.7 版本之后新增加的一个 LoadBalance 实现类。它实现了最短响应时间的负载均衡算法,也就是从多个 Provider 节点中选出调用成功的且响应时间最短的 Provider 节点,不过满足该条件的 Provider 节点可能有多个,所以还要再使用随机算法进行一次选择,得到最终要调用的 Provider 节点。

了解了 ShortestResponseLoadBalance 的核心原理之后,我们一起来看 ShortestResponseLoadBalance.doSelect() 方法的核心实现,如下所示:

protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
  // 记录Invoker集合的数量
  int length = invokers.size();
  // 用于记录所有Invoker集合中最短响应时间
  long shortestResponse = Long.MAX_VALUE;
  // 具有相同最短响应时间的Invoker个数
  int shortestCount = 0;
  // 存放所有最短响应时间的Invoker的下标
  int[] shortestIndexes = new int[length];
  // 存储每个Invoker的权重
  int[] weights = new int[length];
  // 存储权重总和
  int totalWeight = 0;
  // 记录第一个Invoker对象的权重
  int firstWeight = 0;
  // 最短响应时间Invoker集合中的Invoker权重是否相同
  boolean sameWeight = true;
  for (int i = 0; i < length; i++) {
      Invoker<T> invoker = invokers.get(i);
      RpcStatus rpcStatus = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName());
      // 获取调用成功的平均时间,具体计算方式是:调用成功的请求数总数对应的总耗时 / 调用成功的请求数总数 = 成功调用的平均时间
      // RpcStatus 的内容在前面课时已经介绍过了,这里不再重复
      long succeededAverageElapsed = rpcStatus.getSucceededAverageElapsed();
      // 获取的是该Provider当前的活跃请求数,也就是当前正在处理的请求数
      int active = rpcStatus.getActive();
      // 计算一个处理新请求的预估值,也就是如果当前请求发给这个Provider,大概耗时多久处理完成
      long estimateResponse = succeededAverageElapsed * active;
      // 计算该Invoker的权重(主要是处理预热)
      int afterWarmup = getWeight(invoker, invocation);
      weights[i] = afterWarmup;
      if (estimateResponse < shortestResponse) { 
          // 第一次找到Invoker集合中最短响应耗时的Invoker对象,记录其相关信息
          shortestResponse = estimateResponse;
          shortestCount = 1;
          shortestIndexes[0] = i;
          totalWeight = afterWarmup;
          firstWeight = afterWarmup;
          sameWeight = true;
      } else if (estimateResponse == shortestResponse) {
          // 出现多个耗时最短的Invoker对象
          shortestIndexes[shortestCount++] = i;
          totalWeight += afterWarmup;
          if (sameWeight && i > 0
                  && afterWarmup != firstWeight) {
              sameWeight = false;
          }
      }
  }
  if (shortestCount == 1) {
      return invokers.get(shortestIndexes[0]);
  }
  // 如果耗时最短的所有Invoker对象的权重不相同,则通过加权随机负载均衡的方式选择一个Invoker返回
  if (!sameWeight && totalWeight > 0) {
      int offsetWeight = ThreadLocalRandom.current().nextInt(totalWeight);
      for (int i = 0; i < shortestCount; i++) {
          int shortestIndex = shortestIndexes[i];
          offsetWeight -= weights[shortestIndex];
          if (offsetWeight < 0) {
              return invokers.get(shortestIndex);
          }
      }
  }
  // 如果耗时最短的所有Invoker对象的权重相同,则随机返回一个
  return invokers.get(shortestIndexes[ThreadLocalRandom.current().nextInt(shortestCount)]);
}

总结

我们紧接上一课时介绍了 LoadBalance 接口的剩余三个实现。

我们首先介绍了

  • LeastActiveLoadBalance 实现,它使用最小活跃数负载均衡算法,选择当前请求最少的 Provider 节点处理最新的请求;
  • 接下来介绍了 RoundRobinLoadBalance 实现,它使用加权轮询负载均衡算法,弥补了单纯的轮询负载均衡算法导致的问题,同时随着 Dubbo 版本的升级,也将其自身不够平滑的问题优化掉了;
  • 最后介绍了 ShortestResponseLoadBalance 实现,它会从响应时间最短的 Provider 节点中选择一个 Provider 节点来处理新请求。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用 Flink + Faker Connector 生成测试数据压测 MySQL

博主历时三年精心创作的《大数据平台架构与原型实现&#xff1a;数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行&#xff0c;点击《重磅推荐&#xff1a;建大数据平台太难了&#xff01;给我发个工程原型吧&#xff01;》了解图书详情&#xff0c;…

【数据结构】顺序表习题之移除元素和合并两个有效数组

&#x1f451;个人主页&#xff1a;啊Q闻 &#x1f387;收录专栏&#xff1a;《数据结构》 &#x1f389;道阻且长&#xff0c;行则将至 前言 嗨呀&#xff0c;今天的博客是关于顺序表的两道题目&#xff0c;是力扣的移除元素和合并有序数组的题目。 一.移除…

基于springboot和vue的旅游资源网站的设计与实现

环境以及简介 基于vue, springboot旅游资源网站的设计与实现&#xff0c;Java项目&#xff0c;SpringBoot项目&#xff0c;含开发文档&#xff0c;源码&#xff0c;数据库以及ppt 环境配置&#xff1a; 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xf…

力扣题库88题:合并两个有序数组(c语言)

解法&#xff1a; void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n) {int l1m-1;int l2n-1;int l3mn-1;while(l1>0&&l2>0){if(nums1[l1]>nums2[l2]){nums1[l3--]nums1[l1--];}else{nums1[l3--]nums2[l2--];}}while(l2>0)…

LinuxYUMVimg++/gccgdbGit使用

前言 大家好&#xff0c;我是jiantaoyab&#xff0c;前面的文章给大家介绍了Linux的基础命令和权限&#xff0c;学会了命令行的模式使用Linux&#xff0c;今后要开始在Linux上写代码了&#xff0c;在这篇文章将介绍YUM、vim、gdb、git等常用的工具。 先来看看Linux如何安装软…

【C++算法】二分算法、二分模板详解,四道例题带详细注释

文章目录 [toc]1&#xff09;整数二分2&#xff09;解二分题步骤AcWing 789.数的范围洛谷 P1873.EKO/砍树洛谷 P1678.烦恼的高考志愿 2&#xff09;浮点二分AcWing 790. 数的三次方根 1&#xff09;整数二分 有单调性的题目一定可以二分&#xff0c;但是用二分做的题目不一定拥…

【物联网开源平台】tingsboard二次开发环境搭建+编译

文章目录 一&#xff0c;需要准备的环境二&#xff0c;获取tingsboard源码1.git拉取源码2.下载源码压缩包 三.新建仓库存放依赖文件四&#xff0c;编译五&#xff0c;遇到的错误 提示&#xff1a; 1.这篇只要准备两个环境&#xff0c;方法更简单&#xff01; 2.基于tingsboard …

动态路由协议——OSPF

目录 一.OSPF来源 二.OSPF术语 1.area id——区域的划分 2.cost——路径开销值 3.route id 4.LSDB表 5.邻居表 6.OSPF路由表 三.OSPF工作过程 1.交互hello报文建立邻居关系 2.选举主从 3.交互LSDB摘要信息 4.LSR,LSU,LSACK同步LSDB表项 5.各自计算路由 四.OSPF交…

【Linux命令】查看内存占用情况(mem, swap)

1. 方法1&#xff08;top&#xff09; # top2.方法2&#xff08;free&#xff09; # free -h3. 方法3&#xff08;swapon&#xff09; # swapon -s

Spring Boot1

SpringBoot概述 Spring Boot是Spring提供的一个子项目&#xff0c;用于快速构建Spring应用程序 SpringBoot特性 起步依赖 本质上就是一个Maven坐标&#xff0c;整合了完成一个功能所需要的所有坐标 自动配置 遵循约定大于配置的原则&#xff0c;再boot程序启动后&#xff0…

【MySQL】深入解析事务与MVCC

文章目录 1、事务四大特性1.1、原子性1.2、一致性1.3、隔离性1.4、持久性 2、并发事务带来问题2.1、脏读2.2、不可重复读2.3、幻读 3、事务隔离级别3.1、读未提交3.2、读已提交3.3、可重复读3.4、串行化 4、MVCC4.1、InnoDB隐藏字段4.2、undo log版本链4.3、ReadView4.4、MVCC工…

fiddler过滤器使用,隐藏图片、js、css请求

如果抓包过程中不想查看图片、js、css请求&#xff0c;或者只想抓某个ip或者某个网页下的请求&#xff0c;可以在过滤器中设置。 &#xff08;1&#xff09;没有开启过滤器 可以看出所有的请求都会抓取&#xff0c;cs、js、图片请求都有 &#xff08;2&#xff09;开启过滤器 …

波奇学Linux:网络套接字

domain:ipv4 还是ipv6 type:面向字节流还是... 虚拟机 云服务器禁止直接bind公网ip 服务器可以有多个ip&#xff0c;如果只绑定一个ip&#xff0c;只能收到来自一个ip的信息 任意地址绑定 关于port的问题 [0,1024]&#xff1a;系统内定的端口号&#xff0c;一般要用固定的应…

基于SpringBoot+MyBatis框架的智慧生活商城系统的设计与实现(源码+LW+部署+讲解)

目录 前言 需求分析 可行性分析 技术实现 后端框架&#xff1a;Spring Boot 持久层框架&#xff1a;MyBatis 前端框架&#xff1a;Vue.js 数据库&#xff1a;MySQL 功能介绍 前台功能拓展 商品详情单管理 个人中心 秒杀活动 推荐系统 评论与评分系统 后台功能拓…

基于Matlab的眼底图像血管分割,Matlab实现

博主简介&#xff1a; 专注、专一于Matlab图像处理学习、交流&#xff0c;matlab图像代码代做/项目合作可以联系&#xff08;QQ:3249726188&#xff09; 个人主页&#xff1a;Matlab_ImagePro-CSDN博客 原则&#xff1a;代码均由本人编写完成&#xff0c;非中介&#xff0c;提供…

光速论文能用吗 #媒体#知识分享#学习方法

光速论文是一个非常有效的论文写作、查重降重工具&#xff0c;它的使用非常简单方便&#xff0c;而且功能强大&#xff0c;是每个写作者必备的利器。 首先&#xff0c;光速论文具有强大的查重降重功能&#xff0c;能够快速检测论文中的抄袭部分&#xff0c;帮助作者避免不必要的…

2021年12月更新千言万语汇聚成一张图

今年的双十二几乎不复存在。 11月11日之后&#xff0c;有读者问我要不要等双十二。 现在看来&#xff0c;房东已经没有多余的粮食了&#xff0c;互联网的冬天比以前来得早得多。 进入12月份&#xff0c;公司同事和项目组的合伙人最近讨论的不再是年终奖&#xff0c;而是项目能…

解决mysql问题: this is incompatible with sql_mode=only_full_group_by

今天在部署一趟测试环境的服务&#xff0c;各种配置文件都配好了&#xff0c;启动服务后台报错&#xff0c;解决后记录一下&#xff0c;小伙伴们也可以看看&#xff01; ### Cause: java.sql.SQLSyntaxErrorException: Expression #1 of SELECT list is not in GROUP BY clause…

004——内存映射(基于鸿蒙和I.MAX6ULL)

目录 一、 ARM架构内存映射模型 1.1 页表项 1.2 一级页表映射过程 1.3 二级页表映射过程 1.4 cache 和 buffer 二、 鸿蒙内存映射代码学习 三、 为板子编写内存映射代码 3.1 内存地址范围 3.2 设备地址范围 一、 ARM架构内存映射模型 &#xff08;以前我以为页表机制…

力扣● 84.柱状图中最大的矩形

84.柱状图中最大的矩形 需要找到元素i的上一个更小的元素leftmin和下一个更小的元素rightmin&#xff0c;这样leftmin和rightmin之间的元素都比当前元素i更大&#xff0c;那么矩形的宽就是中间的这些元素&#xff1a;可以从leftmin1延伸到rightmin-1&#xff0c;长即为height[i…