首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >3. Sentinel源码分析— QPS流量控制是如何实现的?

3. Sentinel源码分析— QPS流量控制是如何实现的?

作者头像
luozhiyun
发布于 2019-09-10 12:50:13
发布于 2019-09-10 12:50:13
1.6K00
代码可运行
举报
运行总次数:0
代码可运行

终于在这周内写了一篇源码解析,每周一篇即使再忙也不能打破

Sentinel源码解析系列:

1.Sentinel源码分析—FlowRuleManager加载规则做了什么?

2. Sentinel源码分析—Sentinel是如何进行流量统计的?


上回我们用基于并发数来讲了一下Sentinel的整个流程,这篇文章我们来讲一下Sentinel的QPS流量控制是如何实现的。

先上一个极简的demo,我们的代码就从这个demo入手:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) {
    List<FlowRule> rules = new ArrayList<FlowRule>();
    FlowRule rule1 = new FlowRule();
    rule1.setResource("abc"); 
    rule1.setCount(20);
    rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
    rule1.setLimitApp("default");
    rules.add(rule1);
    FlowRuleManager.loadRules(rules);

    Entry entry = null;

    try {
        entry = SphU.entry("abc");
        //dosomething 
    } catch (BlockException e1) {

    } catch (Exception e2) {
        // biz exception
    } finally {
        if (entry != null) {
            entry.exit();
        }
    }
}

在这个例子中我们首先新建了一个FlowRule实例,然后调用了loadRules方法加载规则,这部分的代码都和基于并发数的流量控制的代码是一样的,想要了解的朋友可以去看看我的这一篇文章1.Sentinel源码分析—FlowRuleManager加载规则做了什么?,下面我们说说不一样的地方。

在调用FlowRuleManager的loadRules方法的时候会创建一个rater实例:

FlowRuleUtil#buildFlowRuleMap

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//设置拒绝策略:直接拒绝、Warm Up、匀速排队,默认是DefaultController
TrafficShapingController rater = generateRater(rule);
rule.setRater(rater);

我们进入到generateRater看一下是怎么创建实例的:

FlowRuleUtil#generateRater

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private static TrafficShapingController generateRater(/*@Valid*/ FlowRule rule) {
    if (rule.getGrade() == RuleConstant.FLOW_GRADE_QPS) {
        switch (rule.getControlBehavior()) {
            case RuleConstant.CONTROL_BEHAVIOR_WARM_UP:
                //warmUpPeriodSec默认是10 
                return new WarmUpController(rule.getCount(), rule.getWarmUpPeriodSec(),
                    ColdFactorProperty.coldFactor);
            case RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER:
                //rule.getMaxQueueingTimeMs()默认是500
                return new RateLimiterController(rule.getMaxQueueingTimeMs(), rule.getCount());
            case RuleConstant.CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER:
                return new WarmUpRateLimiterController(rule.getCount(), rule.getWarmUpPeriodSec(),
                    rule.getMaxQueueingTimeMs(), ColdFactorProperty.coldFactor);
            case RuleConstant.CONTROL_BEHAVIOR_DEFAULT:
            default:
                // Default mode or unknown mode: default traffic shaping controller (fast-reject).
        }
    }
    return new DefaultController(rule.getCount(), rule.getGrade());
}

这个方法里面如果设置的是按QPS的方式来限流的话,可以设置一个ControlBehavior属性,用来做流量控制分别是:直接拒绝、Warm Up、匀速排队。

接下来的所有的限流操作全部在FlowSlot中进行,不熟悉Sentinel流程的朋友可以去看看我的这一篇文章:2. Sentinel源码分析—Sentinel是如何进行流量统计的?,这篇文章介绍了Sentinel的全流程分析,本文的其他流程基本都在这篇文章上讲了,只有FlowSlot部分代码不同。

接下来我们来讲一下FlowSlot里面是怎么实现QPS限流的

FlowSlot#entry

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                  boolean prioritized, Object... args) throws Throwable {
    checkFlow(resourceWrapper, context, node, count, prioritized);

    fireEntry(context, resourceWrapper, node, count, prioritized, args);
}

void checkFlow(ResourceWrapper resource, Context context, DefaultNode node, int count, boolean prioritized)
    throws BlockException {
    checker.checkFlow(ruleProvider, resource, context, node, count, prioritized);
}

FlowSlot在实例化的时候会实例化一个FlowRuleChecker实例作为checker。在checkFlow方法里面会继续调用FlowRuleChecker的checkFlow方法,其中ruleProvider实例是用来根据根据resource来从flowRules中获取相应的FlowRule。

我们进入到FlowRuleChecker的checkFlow方法中

FlowRuleChecker#checkFlow

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void checkFlow(Function<String, Collection<FlowRule>> ruleProvider, ResourceWrapper resource,
                      Context context, DefaultNode node, int count, boolean prioritized) throws BlockException {
    if (ruleProvider == null || resource == null) {
        return;
    }
    //返回FlowRuleManager里面注册的所有规则
    Collection<FlowRule> rules = ruleProvider.apply(resource.getName());
    if (rules != null) {
        for (FlowRule rule : rules) {
            //如果当前的请求不能通过,那么就抛出FlowException异常
            if (!canPassCheck(rule, context, node, count, prioritized)) {
                throw new FlowException(rule.getLimitApp(), rule);
            }
        }
    }
}

这里是调用ruleProvider来获取所有FlowRule,然后遍历rule集合通过canPassCheck方法来进行过滤,如果不符合条件则会抛出FlowException异常。

我们跟进去直接来到passLocalCheck方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private static boolean passLocalCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount,
                                      boolean prioritized) {
    //节点选择
    Node selectedNode = selectNodeByRequesterAndStrategy(rule, context, node);
    if (selectedNode == null) {
        return true;
    }
    //根据设置的规则来拦截
    return rule.getRater().canPass(selectedNode, acquireCount, prioritized);
}

这个方法里面会选择好相应的节点后调用rater的canPass方法来判断是否需要阻塞。

Rater有四个,分别是:DefaultController、RateLimiterController、WarmUpController、WarmUpRateLimiterController,我们挨个分析一下。

其中DefaultController是直接拒绝策略,我们在上一篇文章中已经分析过了,这次我们来看看其他三个。

RateLimiterController匀速排队

它的中心思想是,以固定的间隔时间让请求通过。当请求到来的时候,如果当前请求距离上个通过的请求通过的时间间隔不小于预设值,则让当前请求通过;否则,计算当前请求的预期通过时间,如果该请求的预期通过时间小于规则预设的 timeout 时间,则该请求会等待直到预设时间到来通过(排队等待处理);若预期的通过时间超出最大排队时长,则直接拒接这个请求。

这种方式适合用于请求以突刺状来到,这个时候我们不希望一下子把所有的请求都通过,这样可能会把系统压垮;同时我们也期待系统以稳定的速度,逐步处理这些请求,以起到“削峰填谷”的效果,而不是拒绝所有请求。

要想使用这个策略需要在实例化FlowRule的时候设置rule1.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)这样的一句代码。

在实例化Rater的时候会调用FlowRuleUtil#generateRateri创建一个实例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
new RateLimiterController(rule.getMaxQueueingTimeMs(), rule.getCount());

MaxQueueingTimeMs默认是500 ,Count在我们这个例子中传入的是20。

我们看一下具体的canPass方法是怎么实现限流的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
    // Pass when acquire count is less or equal than 0.
    if (acquireCount <= 0) {
        return true;
    }
    // Reject when count is less or equal than 0.
    // Otherwise,the costTime will be max of long and waitTime will overflow in some cases.
    if (count <= 0) {
        return false;
    }

    long currentTime = TimeUtil.currentTimeMillis();
    //两个请求预期通过的时间,也就是说把请求平均分配到1秒上
    // Calculate the interval between every two requests.
    long costTime = Math.round(1.0 * (acquireCount) / count * 1000);

    //latestPassedTime代表的是上一次调用请求的时间
    // Expected pass time of this request.
    long expectedTime = costTime + latestPassedTime.get();
    //如果预期通过的时间加上上次的请求时间小于当前时间,则通过
    if (expectedTime <= currentTime) {
        // Contention may exist here, but it's okay.
        latestPassedTime.set(currentTime);
        return true;
    } else {
        //默认是maxQueueingTimeMs
        // Calculate the time to wait.
        long waitTime = costTime + latestPassedTime.get() - TimeUtil.currentTimeMillis();

        //如果预提时间比当前时间大maxQueueingTimeMs那么多,那么就阻塞
        if (waitTime > maxQueueingTimeMs) {
            return false;
        } else {
            //将上次时间加上这次请求要耗费的时间
            long oldTime = latestPassedTime.addAndGet(costTime);
            try {
                waitTime = oldTime - TimeUtil.currentTimeMillis();
                //再次判断一下是否超过maxQueueingTimeMs设置的时间
                if (waitTime > maxQueueingTimeMs) {
                    //如果是的话就阻塞,并重置上次通过时间
                    latestPassedTime.addAndGet(-costTime);
                    return false;
                }
                //如果需要等待的时间大于零,那么就sleep
                // in race condition waitTime may <= 0
                if (waitTime > 0) {
                    Thread.sleep(waitTime);
                }
                return true;
            } catch (InterruptedException e) {
            }
        }
    }
    return false;
}

这个方法一开始会计算一下costTime这个值,将请求平均分配到一秒中。例如:当 count 设为 10 的时候,则代表一秒匀速的通过 10 个请求,也就是每个请求平均间隔恒定为 1000 / 10 = 100 ms。

但是这里有个小bug,如果count设置的比较大,比如设置成10000,那么costTime永远都会等于0,整个QPS限流将会失效。

然后会将costTime和上次的请求时间相加,如果大于当前时间就表明请求的太频繁了,会将latestPassedTime这个属性加上这次请求的costTime,并调用sleep方法让这个线程先睡眠一会再请求。

这里有个细节,如果多个请求同时一起过来,那么每个请求在设置oldTime的时候都会通过addAndGet这个原子性的方法将latestPassedTime依次相加,并赋值给oldTime,这样每个线程的sleep的时间都不会相同,线程也不会同时醒来。

WarmUpController限流 冷启动

当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//默认为3
private int coldFactor;
//转折点的令牌数
protected int warningToken = 0;
//最大的令牌数
private int maxToken;
//斜线斜率
protected double slope;
//累积的令牌数
protected AtomicLong storedTokens = new AtomicLong(0);
//最后更新令牌的时间
protected AtomicLong lastFilledTime = new AtomicLong(0);

public WarmUpController(double count, int warmUpPeriodInSec, int coldFactor) {
    construct(count, warmUpPeriodInSec, coldFactor);
}

private void construct(double count, int warmUpPeriodInSec, int coldFactor) {

    if (coldFactor <= 1) {
        throw new IllegalArgumentException("Cold factor should be larger than 1");
    }

    this.count = count;
    //默认是3
    this.coldFactor = coldFactor;

    // thresholdPermits = 0.5 * warmupPeriod / stableInterval.
    // 10*20/2 = 100
    // warningToken = 100;
    warningToken = (int) (warmUpPeriodInSec * count) / (coldFactor - 1);
    // / maxPermits = thresholdPermits + 2 * warmupPeriod /
    // (stableInterval + coldInterval)
    // maxToken = 200
    maxToken = warningToken + (int) (2 * warmUpPeriodInSec * count / (1.0 + coldFactor));

    // slope
    // slope = (coldIntervalMicros - stableIntervalMicros) / (maxPermits
    // - thresholdPermits);
    slope = (coldFactor - 1.0) / count / (maxToken - warningToken);
}

这里我拿一张图来说明一下:

X 轴代表 storedPermits 的数量,Y 轴代表获取一个 permits 需要的时间。

假设指定 permitsPerSecond 为 10,那么 stableInterval 为 100ms,而 coldInterval 是 3 倍,也就是 300ms(coldFactor,3 倍 )。也就是说,当达到 maxPermits 时,此时处于系统最冷的时候,获取一个 permit 需要 300ms,而如果 storedPermits 小于 thresholdPermits 的时候,只需要 100ms。

利用 “获取冷的 permits ” 需要等待更多时间,来限制突发请求通过,达到系统预热的目的。

所以在我们的代码中,maxToken代表的就是图中的maxPermits,warningToken代表的就是thresholdPermits,slope就是代表每次获取permit减少的程度。

我们接下来看看WarmUpController的canpass方法:

WarmUpController#canpass

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
    //获取当前时间窗口的流量大小
    long passQps = (long) node.passQps();
    //获取上一个窗口的流量大小
    long previousQps = (long) node.previousPassQps();
    //设置 storedTokens 和 lastFilledTime 到正确的值
    syncToken(previousQps);

    // 开始计算它的斜率
    // 如果进入了警戒线,开始调整他的qps
    long restToken = storedTokens.get();
    if (restToken >= warningToken) {
        //通过计算当前的restToken和警戒线的距离来计算当前的QPS
        //离警戒线越接近,代表这个程序越“热”,从而逐步释放QPS
        long aboveToken = restToken - warningToken;
        //当前状态下能达到的最高 QPS
        // current interval = restToken*slope+1/count
        double warningQps = Math.nextUp(1.0 / (aboveToken * slope + 1.0 / count));

        // 如果不会超过,那么通过,否则不通过
        if (passQps + acquireCount <= warningQps) {
            return true;
        }
    } else {
        // count 是最高能达到的 QPS
        if (passQps + acquireCount <= count) {
            return true;
        }
    }
    return false;
}

这个方法里通过syncToken(previousQps)设置storedTokens的值后,与警戒值做判断,如果没有达到警戒值,那么通过计算和警戒值的距离再加上slope计算出一个当前的QPS值,storedTokens越大当前的QPS越小。

如果当前的storedTokens已经小于警戒值了,说明已经预热完毕了,直接用count判断就好了。

WarmUpController#syncToken

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
protected void syncToken(long passQps) {
    long currentTime = TimeUtil.currentTimeMillis();
    //去掉毫秒的时间
    currentTime = currentTime - currentTime % 1000;
    long oldLastFillTime = lastFilledTime.get();
    if (currentTime <= oldLastFillTime) {
        return;
    }

    // 令牌数量的旧值
    long oldValue = storedTokens.get();
    // 计算新的令牌数量,往下看
    long newValue = coolDownTokens(currentTime, passQps);

    if (storedTokens.compareAndSet(oldValue, newValue)) {
        // 令牌数量上,减去上一分钟的 QPS,然后设置新值
        long currentValue = storedTokens.addAndGet(0 - passQps);
        if (currentValue < 0) {
            storedTokens.set(0L);
        }
        lastFilledTime.set(currentTime);
    } 
}

这个方法通过coolDownTokens方法来获取一个新的value,然后通过CAS设置到storedTokens中,然后将storedTokens减去上一个窗口的QPS值,并为lastFilledTime设置一个新的值。

其实我这里有个疑惑,在用storedTokens减去上一个窗口的QPS的时候并没有做控制,假如处理的速度非常的快,在一个窗口内就减了很多次,直接把当前的storedTokens减到了小于warningToken,那么是不是就没有在一定的时间范围内启动冷启动的效果?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private long coolDownTokens(long currentTime, long passQps) {
    long oldValue = storedTokens.get();
    long newValue = oldValue;

    // 添加令牌的判断前提条件:
    // 当令牌的消耗程度远远低于警戒线的时候
    if (oldValue < warningToken) {
        // 根据count数每秒加上令牌
        newValue = (long) (oldValue + (currentTime - lastFilledTime.get()) * count / 1000);
    } else if (oldValue > warningToken) {
        //如果还在冷启动阶段
        // 如果当前通过的 QPS 大于 count/coldFactor,说明系统消耗令牌的速度,大于冷却速度
        //    那么不需要添加令牌,否则需要添加令牌
        if (passQps < (int) count / coldFactor) {
            newValue = (long) (oldValue + (currentTime - lastFilledTime.get()) * count / 1000);
        }
    }
    return Math.min(newValue, maxToken);
}

这个方法主要是用来做添加令牌的操作,如果是流量比较小或者是已经预热完毕了,那么就需要根据count数每秒加上令牌,如果是在预热阶段那么就不进行令牌添加。

WarmUpRateLimiterController就是结合了冷启动和匀速排队,代码非常的简单,有了上面的分析,相信大家也能看得懂,所以也就不讲解了。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-09-08 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Dubbo配置方式详解
Dubbo 是一个分布式服务框架,致力于提供高性能和透明化的 RPC 远程服务调用方案,是阿里巴巴 SOA 服务化治理方案的核心框架,每天为 2,000+ 个服务提供 3,000,000,000+ 次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。 Dubbo 采用全Spring配置方式,透明化接入应用,对应用没有任何API侵入,只需用Spring加载Dubbo的配置即可,Dubbo基于Spring的Schema扩展进行加载。 根据 DUBBO 官方文档,配置 DUBBO 有 4 种方式,分别是:
陈树义
2018/04/13
1.6K1
Dubbo配置方式详解
【三剑客之一】Dubbo 遇到初恋
很多时候,其实我们使用这个技术的时候,可能都是因为项目需要,所以,我们就用了,但是,至于为什么我们需要用到这个技术,可能自身并不是很了解的,但是,其实了解技术的来由及背景知识,对于理解一项技术还是有帮助的,那么,dubbo是怎么被提上日程的呢?
好好学java
2019/07/29
4170
【三剑客之一】Dubbo 遇到初恋
白话分布式系统
1、单体应用介绍: 所谓单体应用,就是一些小型的应用,一个系统就是eclipse中的一个工程,然后打一个jar包或者war运行,这个jar包或者war就是整个系统服务。这就叫单体应用。
贪挽懒月
2019/07/08
1.1K2
Dubbo入门到实战2
1、为什么需要dubbo 2、dubbo架构简析 3、dubbo入门 4、zookeeper注册中心加入dubbo 5、dubbo多种配置方式(xml、api、注解) 6、常用场景介绍
Vincent-yuan
2021/11/04
3020
dubbo学习之源码创建属于自己的dubbo-demo
上篇博文<一路踩坑构建Dubbo源码>谈论了如何本地构建dubbo源码,最近溪源也在努力的学习dubbo相关知识和机制,学习过程也可以称之苦不堪言吧。dubbo官网是入门学习资源重要之一;故溪源先分享中文官网:dubbo中文手册。 上网文章中也清晰地带着大家成功运行dubbo-demo;对于像溪源这样的新手接触dubbo,学习源码估计都很难找到入手的方法,溪源也是走了不少冤枉路,所以特此写了一篇入手dubbo源码的文章,希望能够帮助伙伴们降低时间浪费,少走弯路。溪源这篇带着大家在dubbo-demo中创建自己的跟踪源码的单测用例。对于官网给与的demo用例,大家可以大胆的修改,溪源就是把demo改的面目全非。
沁溪源
2020/09/02
5680
Dubbo服务暴露步骤详解
Dubbo 是一款高性能的分布式服务框架,能够帮助我们快速实现微服务架构。在 Dubbo 中,服务提供者需要将自己的服务暴露出去,并注册到注册中心,让消费者能够通过注册中心找到并调用该服务。
青山师
2023/05/05
1960
dubbo 服务中间件 的使用
2 在spring的配置文件中添加dubbo的约束,然后使用dubbo:service发布服务。
用户5927264
2019/07/31
6440
Dubbo02【搭建provider和consumer】
  本文来给大家介绍下基于Spring配置的方式来搭建dubbo中的服务提供端和消费端
用户4919348
2019/04/02
6790
Dubbo02【搭建provider和consumer】
Dubbo系列之服务注册与发现
《分布式系统原理与范型》定义: “分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统” 分布式系统(distributed system)是建立在网络之上的软件系统。
SmileNicky
2022/05/07
3310
Dubbo系列之服务注册与发现
Dubbo和Zookeeper
当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。
宋先生
2019/07/18
8140
Dubbo和Zookeeper
SpringBoot2.0 整合 Dubbo框架 ,实现RPC服务远程调用
图例说明: 1)图中小方块 Protocol, Cluster, Proxy, Service, Container, Registry, Monitor 代表层或模块,蓝色的表示与业务有交互,绿色的表示只对 Dubbo 内部交互。 2)图中背景方块 Consumer, Provider, Registry, Monitor 代表部署逻辑拓扑节点。 3)图中蓝色虚线为初始化时调用,红色虚线为运行时异步调用,红色实线为运行时同步调用。 4)图中只包含 RPC 的层,不包含 Remoting 的层,Remoting 整体都隐含在 Protocol 中。
知了一笑
2019/07/19
2.1K0
SpringBoot2.0 整合 Dubbo框架 ,实现RPC服务远程调用
dubbo配置负载均衡、集群环境
再用dubbo作为项目架构的时候,给consumer消费者用nginx提供了负载均衡策略和集群的实现,
Arebirth
2019/10/15
1.2K0
dubbo配置负载均衡、集群环境
Dubbo服务注册与发现
本文链接:https://blog.csdn.net/u014427391/article/details/96754952
SmileNicky
2019/08/29
6140
Dubbo服务注册与发现
Dubbo了解一下
Apache Dubbo是阿里巴巴开源的一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
程序大视界
2020/07/21
4040
Dubbo了解一下
Spring-boot:5分钟整合Dubbo构建分布式服务
概述:   Dubbo是Alibaba开源的分布式服务框架,它最大的特点是按照分层的方式来架构,使用这种方式可以使各个层之间解耦合(或者最大限度地松耦合)。从服务模型的角度来看,Dubbo采用的是一种
九灵
2018/03/09
1.8K0
Spring-boot:5分钟整合Dubbo构建分布式服务
idea使用Dubbo创建提供者消费者(7)
订单服务web模块一般指的是对外HTTP服务,用户服务service模块一般指的是对外提供RPC服务
桑鱼
2020/03/17
5420
分布式服务框架之Dubbo整合Spring项目(二)
applicationContext.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframewo
尚浩宇
2018/08/17
4110
Docker下dubbo开发,三部曲之三:java开发
程序员欣宸
2018/01/04
6950
Docker下dubbo开发,三部曲之三:java开发
Dubbo 高级特性
dubbo-admin 是一个前后端分离的项目。前端使用vue,后端使用springboot,安装 dubbo-admin 其实就是部署该项目。我们将dubbo-admin安装到开发环境上。要保证开发环境有jdk,maven,node.js
用户9615083
2022/12/25
4990
Dubbo 高级特性
基于RPC实现服务的注册、发布和消费
业务层不变, 应用层 设置一个集合存放url地址, 调用ThreadLocalRandom取随机数的方法为index ,定义一个String类型url获取集合中的地址
时间静止不是简史
2020/07/25
4110
相关推荐
Dubbo配置方式详解
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档