用于调整网络包发送的数据。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。Sentinel 作为一个适配器,可以根据需要将随机的请求调整成合适的形状,如下:

流量控制有两种统计类型 - 统计线程数、统计 QPS。可以通过如下命令查看实时统计信息:
curl http://localhost:8719/cnode?id=resourceName
输出内容的格式如下:
idx id thread pass blocked success total Rt 1m-pass 1m-block 1m-all exeption
2 abc647 0 46 0 46 46 1 2763 0 2763 0
实际代码中对应 FlowRule。
同一个资源可以同时有多个流量规则。
| Field | 说明 | 默认值 |
|---|---|---|
| resource | 资源名称 | |
| count | 限流阈值 | |
| grade | 流量控制的指标(0 - 线程数、1 - QPS) | 1 (即 QPS) |
| limitApp | 流量控制针对的调用来源 | default(不区分调用来源) |
| strategy | 资源的调用关系(0 - 直接、1 - 关联、2 - 链路) | 0(即直接) |
| controlBehavior | 流量控制的效果(0 - 直接拒绝、1 - 冷启动、2 - 匀速器、3 - 冷启动 + 匀速器) | 0(即直接拒绝) |
简单统计当前请求上下文的线程个数,如果超过阈值,则新的请求会被拒绝。用于保护业务线程数不被耗尽。
比如,应用依赖的下游应用由于某种原因导致服务不稳定、响应时间增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下导致线程池耗尽。为应对高线程占用的情况,业内有使用隔离的方案,比如通过不同业务逻辑使用不同的线程池来隔离业务之间的资源争抢(线程池隔离),或者使用信号量来控制同时请求的数量(信号量隔离)。信号量隔离的策略虽然能够控制线程数量,但是无法控制请求的排队时间。当请求过多时,排队也是无益的,直接拒绝能够迅速降低系统压力。Sentinel 线程数限流不负责创建和管理线程池,而是简单统计当前请求上下文的线程个数,如果超过阈值,新的请求会被立即拒绝。
流量控制效果只有直接拒绝。
[
{
"resource": "sayHello",
"limitApp": "default",
"grade": 0,
"count": 4,
"strategy": 0,
"controlBehavior": 0
}
]
当 QPS 超过阈值时,则采取措施进行流量控制。
对应 RuleConstant.CONTROL_BEHAVIOR_DEFAULT。该方式是默认的流量控制方式,当 QPS 超过阈值时,直接拒绝新的请求(拒绝方式是抛出 FlowException)。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。
[
{
"resource": "sayHello",
"limitApp": "default",
"grade": 1,
"count": 4,
"strategy": 0,
"controlBehavior": 0
}
]
对应 RuleConstant.CONTROL_BEHAVIOR_WARM_UP。该方式用于系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位,这种情况可能瞬间把系统压垮。冷启动方式让通过的流量缓慢增加,在一定时间内(warmUpPeriodSec 参数控制预热时长,单位秒)逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮的情况。

[
{
"resource": "sayHello",
"limitApp": "default",
"grade": 1,
"count": 10,
"strategy": 0,
"controlBehavior": 1,
"warmUpPeriodSec": 3
}
]
对应 RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER。严格控制请求通过的间隔时间,也就是让请求以均匀的速度通过,对应的是漏桶算法。这种方式主要用于处理间隔性突发的流量,比如消息队列。想象一下这种的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。

[
{
"resource": "sayHello",
"limitApp": "default",
"grade": 1,
"count": 10,
"strategy": 0,
"controlBehavior": 2,
"maxQueueingTimeMs": 500
}
]
每秒请求数大于 5 时,多余的请求排队等待。如果 500 毫秒内这些请求得不到处理,则请求会被限流导致访问失败。
对应 RuleConstant.CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER。冷启动加匀速器组合的方式。
[
{
"resource": "sayHello",
"limitApp": "default",
"grade": 1,
"count": 10,
"strategy": 0,
"controlBehavior": 3,
"maxQueueingTimeMs": 500,
"warmUpPeriodSec": 5
}
]
调用关系包括调用方、被调用方;方法又可能会调用其它方法,形成一个调用链路的层次关系。
可以通过如下命令查看不同调用方对于同一个资源的调用数据。
curl http://localhost:8719/origin?id=nodeA
调用数据示例如下:
id: nodeA
idx origin threadNum passedQps blockedQps totalQps aRt 1m-passed 1m-blocked 1m-total
1 caller1 0 0 0 0 0 0 0 0
2 caller2 0 0 0 0 0 0 0 0
限流规则中的 limitApp 字段用于根据调用方进行流量控制。该字段的值有如下三种选项,分别对应不同的场景:
同一个资源名可以配置多条规则,规则的生效顺序为:{some_origin_name} > other > default
[
{
"resource": "sayHello",
"limitApp": "spring-cloud-demo-consumer",
"grade": 1,
"count": 5,
"controlBehavior": 2,
"maxQueueingTimeMs": 500
},
{
"resource": "sayHello",
"limitApp": "other",
"grade": 1,
"count": 5,
"controlBehavior": 2,
"maxQueueingTimeMs": 500
}
]
额外需要注册一个 RequestOriginParser 类型的 Bean。
@Component
public class MyRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
String origin = httpServletRequest.getHeader("origin");
if (StringUtils.isBlank(origin)) {
origin = "default";
}
return origin;
}
}
然后请求头携带 origin 属性以及属性值。
一棵典型的调用树如下图所示:
machine-root
/ \
/ \
Entrance1 Entrance2
/ \
/ \
DefaultNode(nodeA) DefaultNode(nodeA)
上图中来自入口 Entrance1 和 Entrance2 的请求都调用到了资源 NodeA,Sentinel 允许只根据某个入口的统计信息对资源限流。比如我们可以设置 FlowRule.strategy 为 RuleConstant.CHAIN,同时设置 refResource 为 Entrance1 来表示只有从入口 Entrance1 的调用才会记录到 NodeA 的限流统计当中,而对来自 Entrance2 的调用漠不关心。
[
{
"resource": "sayHello",
"limitApp": "default",
"strategy": 2,
"refResource": "/hello/say",
"grade": 1,
"count": 3,
"controlBehavior": 2,
"maxQueueingTimeMs": 500
}
]
当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。
比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,read_db 和 write_db 这两个资源分别代表数据库读写,我们可以给 read_db 设置限流规则来达到写优先的目的:设置 FlowRule.strategy 为 RuleConstant.RELATE ,同时设置 refResource 为 write_db。这样当写库操作过于频繁时,读数据的请求会被限流。
[
{
"resource": "/hello/say",
"limitApp": "default",
"strategy": 1,
"refResource": "/hello/say2",
"grade": 1,
"count": 1
}
]
/hello/say2 接口访问达到限流阈值时(不对它限流),对 /hello/say 接口根据配置的限流手段进行限流。
负责限流规则的判断。
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
boolean prioritized, Object... args) throws Throwable {
// 校验限流规则
checkFlow(resourceWrapper, context, node, count, prioritized);
// 交给下一个ProcessorSlot处理
fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
void checkFlow(ResourceWrapper resource, Context context, DefaultNode node, int count, boolean prioritized)
throws BlockException {
// 交给FlowRuleChecker继续处理
checker.checkFlow(ruleProvider, resource, context, node, count, prioritized);
}
checkFlow
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;
}
Collection<FlowRule> rules = ruleProvider.apply(resource.getName());
if (rules != null) {
// 只要有一个流量规则校验失败,则抛出FlowException异常
for (FlowRule rule : rules) {
if (!canPassCheck(rule, context, node, count, prioritized)) {
throw new FlowException(rule.getLimitApp(), rule);
}
}
}
}
canPassCheck
public boolean canPassCheck(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node,
int acquireCount) {
return canPassCheck(rule, context, node, acquireCount, false);
}
public boolean canPassCheck(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node, int acquireCount,
boolean prioritized) {
String limitApp = rule.getLimitApp();
if (limitApp == null) {
return true;
}
// 如果是集群模式
if (rule.isClusterMode()) {
// 进行集群模式下的流量规则校验(该部分在集群流控文章中进行分析)
return passClusterCheck(rule, context, node, acquireCount, prioritized);
}
// 进行本地模式下的流量规则校验
return passLocalCheck(rule, context, node, acquireCount, prioritized);
}
passLocalCheck
private static boolean passLocalCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount,
boolean prioritized) {
// 根据限流规则中的limitApp、strategy参数值,选择合适的节点(DefaultNode、ClusterNode)
Node selectedNode = selectNodeByRequesterAndStrategy(rule, context, node);
// 如果选择的节点为空,则返回true,表示校验成功
if (selectedNode == null) {
return true;
}
// 根据具体的TrafficShapingController接口的实现类进行校验
return rule.getRater().canPass(selectedNode, acquireCount, prioritized);
}
selectNodeByRequesterAndStrategy
static Node selectNodeByRequesterAndStrategy(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node) {
String limitApp = rule.getLimitApp();
int strategy = rule.getStrategy();
String origin = context.getOrigin();
// 如果限流规则中的limitApp参数值等于上下文记录的请求来源,并且上下文记录的请求来源不是DEFAULT、OTHER
if (limitApp.equals(origin) && filterOrigin(origin)) {
// 如果调用关系是根据调用方限流
if (strategy == RuleConstant.STRATEGY_DIRECT) {
// 返回上下文记录的起源节点
return context.getOriginNode();
}
return selectReferenceNode(rule, context, node);
// 如果限流规则中的limitApp参数值等于DEFAULT
} else if (RuleConstant.LIMIT_APP_DEFAULT.equals(limitApp)) {
// 如果调用关系是根据调用方限流
if (strategy == RuleConstant.STRATEGY_DIRECT) {
// 返回对应的集群节点
return node.getClusterNode();
}
return selectReferenceNode(rule, context, node);
// 如果限流规则中的limitApp参数值等于OTHER
} else if (RuleConstant.LIMIT_APP_OTHER.equals(limitApp)
&& FlowRuleManager.isOtherOrigin(origin, rule.getResource())) {
// 如果调用关系是根据调用方限流
if (strategy == RuleConstant.STRATEGY_DIRECT) {
// 返回上下文记录的起源节点
return context.getOriginNode();
}
return selectReferenceNode(rule, context, node);
}
// 其余情况返回空
return null;
}
isOtherOrigin
public static boolean isOtherOrigin(String origin, String resourceName) {
if (StringUtil.isEmpty(origin)) {
return false;
}
List<FlowRule> rules = flowRules.get().get(resourceName);
if (rules != null) {
for (FlowRule rule : rules) {
if (origin.equals(rule.getLimitApp())) {
return false;
}
}
}
return true;
}
selectReferenceNode
static Node selectReferenceNode(FlowRule rule, Context context, DefaultNode node) {
// 获取流量控制规则的refResource参数值
String refResource = rule.getRefResource();
// 获取流量控制规则的strategy参数值
int strategy = rule.getStrategy();
// 如果refResource参数值为空,则返回空
if (StringUtil.isEmpty(refResource)) {
return null;
}
// 如果是关联限流
if (strategy == RuleConstant.STRATEGY_RELATE) {
// 获取refResource参数值对应的集群节点
return ClusterBuilderSlot.getClusterNode(refResource);
}
// 如果是链路限流
if (strategy == RuleConstant.STRATEGY_CHAIN) {
// 如果refResource参数值不等于上下文名称,则返回空
if (!refResource.equals(context.getName())) {
return null;
}
// 返回DefaultNode
return node;
}
// 返回空
return null;
}
根据流量控制的效果,有四种与之对应的 TrafficShapingController 接口的实现类。
TrafficShapingController 接口的实现类的解析。
当我在Rails控制台中按向上或向左箭头时,出现此错误:irb(main):001:0>/Users/me/.rvm/gems/ruby-2.0.0-p247/gems/rb-readline-0.4.2/lib/rbreadline.rb:4269:in`blockin_rl_dispatch_subseq':invalidbytesequenceinUTF-8(ArgumentError)我使用rvm来管理我的ruby安装。我正在使用=>ruby-2.0.0-p247[x86_64]我使用bundle来管理我的gem,并且我有rb-readline(0.4.2)(人们推荐的最少
我正在使用Ruby2.1.1和Rails4.1.0.rc1。当执行railsc时,它被锁定了。使用Ctrl-C停止,我得到以下错误日志:~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`gets':Interruptfrom~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`verify_server_version'from~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.
我将我的Rails应用程序部署到OpenShift,它运行良好,但我无法在生产服务器上运行“Rails控制台”。它给了我这个错误。我该如何解决这个问题?我尝试更新rubygems,但它也给出了权限被拒绝的错误,我也无法做到。railsc错误:Warning:You'reusingRubygems1.8.24withSpring.UpgradetoatleastRubygems2.1.0andrun`gempristine--all`forbetterstartupperformance./opt/rh/ruby193/root/usr/share/rubygems/rubygems
说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时
在我的Character模型中,我添加了:字符.rbbefore_savedoself.profile_picture_url=asset_path('icon.png')end但是,对于数据库中已存在的所有角色,它们的profile_picture_url为nil。因此,我想进入控制台并遍历所有这些并进行设置。在我试过的控制台中:Character.find_eachdo|c|c.profile_picture_url=asset_path('icon.png')end但这给出了错误:NoMethodError:undefinedmethod`asset_path'formain:O
当我进入Rails控制台时,我已将pry设置为加载代替irb。我找不到该页面或不记得如何将其恢复为默认行为,因为它似乎干扰了我的Rubymine调试器。有什么建议吗? 最佳答案 我刚发现问题,pry-railsgem。忘记了它的目的是让“railsconsole”打开pry。 关于ruby-on-rails-带有Pry的Rails控制台,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/question
我正在尝试将$stdout设置为临时写入一个文件,然后返回到一个文件。test.rb:old_stdout=$stdout$stdout.reopen("mytestfile.out",'w+')puts"thisgoesinmytestfile"$stdout=old_stdoutputs"thisshouldbeontheconsole"$stdout.reopen("mytestfile1.out",'w+')puts"thisgoesinmytestfile1:"$stdout=old_stdoutputs"thisshouldbebackontheconsole"这是输出。r
我在思考流量控制的最佳实践。我应该走哪条路?1)不要检查任何东西并让程序失败(更清晰的代码,自然的错误消息):defself.fetch(feed_id)feed=Feed.find(feed_id)feed.fetchend2)通过返回nil静默失败(但是,“CleanCode”说,你永远不应该返回null):defself.fetch(feed_id)returnunlessfeed_idfeed=Feed.find(feed_id)returnunlessfeedfeed.fetchend3)抛出异常(因为不按id查找feed是异常的):defself.fetch(feed_id
我是一名决定学习Ruby和RubyonRails的ASP.NETMVC开发人员。我已经有所了解并在RoR上创建了一个网站。在ASP.NETMVC上开发,我一直使用三层架构:数据层、业务层和UI(或表示)层。尝试在RubyonRails应用程序中使用这种方法,我发现没有关于它的信息(或者也许我只是找不到它?)。也许有人可以建议我如何在RubyonRails上创建或使用三层架构?附言我使用ruby1.9.3和RubyonRails3.2.3。 最佳答案 我建议在制作RoR应用程序时遵循RubyonRails(RoR)风格。Rails
我真的只是不确定这意味着什么或我应该做什么才能让网页在我的本地主机上运行。现在它只是显示一个错误,上面写着“我们很抱歉,但出了点问题。”当我运行railsserver并在chrome中打开localhost:3000时。这是控制台输出:StartedGET"/users/sign_in"for127.0.0.1at2013-07-0512:07:07-0400ProcessingbyDevise::SessionsController#newasHTMLCompleted500InternalServerErrorin55msNoMethodError(undefinedmethod`