sentinel规则问题

为什么sentinel的流量控制不准,而且处理模式为 均速排队时不生效?

业务代码:

@Service
public class SentinelServiceImpl implements SentinelService {

    private final AtomicInteger NUM = new AtomicInteger(0);

    @PostConstruct
    private  void initFlowRules(){
        System.out.println("init sentinel rules");
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        rule.setResource("name");
        //0线程,1QPS
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        //处理模式
        //直接拒绝:(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)方式是默认的
        //Warm Up:(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动
        //均速排队:(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)
        rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER);
        // 阈值
        rule.setCount(2);

        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }

    @SentinelResource(value = "name",blockHandler = "reject")
    @Override
    public String getMsg() {
        int andIncrement = NUM.getAndIncrement();
        System.out.println(new Date() + ":get msg success :" + andIncrement);
        return "success" + andIncrement;
    }

    public String reject(BlockException ex){
        int andIncrement = NUM.getAndIncrement();
        System.out.println(new Date() + ":blockHandler:" + andIncrement);
        return "reject" + andIncrement;
    }
}

设置QPS为2,模式为 匀速排队,看看调用方的同时发生10个请求的结果:

1682674982090--> result:success1
1682674982090--> result:success0
1682674982090--> result:reject3
1682674982090--> result:success2
1682674982093--> result:reject4
1682674982095--> result:reject5
1682674982095--> result:reject7
1682674982096--> result:reject6
1682674982098--> result:reject8
1682674982552--> result:success9

首先一秒了通过了4个请求,其次超流量的请求没有排队等待处理,而是直接走了拒绝了

  • 关于该问题,我找了一篇非常好的博客,你可以看看是否有帮助,链接:sentinel 控制台讲解-流控规则 QPS和线程数的区别
  • 除此之外, 这篇博客: Sentinel的基本使用(1)-流量控制及其源码调用分析中的 1、QPS方式 部分也许能够解决你的问题, 你可以仔细阅读以下内容或跳转源博客中阅读:
  • public class SimpleEntryMain {
    
        private static String resourceName = "simpleEntry";
    
        public static void main(String[] args) throws InterruptedException {
            initFlowRule();
            for (int i = 0; i < 10; i++) {
                Thread t = new Thread(new RunTask());
                t.setName("simulate-traffic-Task");
                t.start();
                Thread.sleep(50);
            }
        }
    
        private static void initFlowRule() {
            List<FlowRule> rules = new ArrayList<FlowRule>();
            FlowRule rule1 = new FlowRule();
            rule1.setResource(resourceName);
            rule1.setCount(2);
            rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
            rule1.setLimitApp("default");
            rules.add(rule1);
            FlowRuleManager.loadRules(rules);
        }
    
        static class RunTask implements Runnable {
            @Override
            public void run() {
                    Entry entry = null;
                    try {
                        entry = SphU.entry(resourceName);
                        doSomeThing();
                    } catch (BlockException e1) {
                        System.out.println("Block   ----------");
                    } finally {
                        if (entry != null) {
                            entry.exit();
                        }
                    }
            }
        }
    
        public static void doSomeThing(){
            System.out.println("++++++++++++++++++");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                // ignore
            }
        }
    }
    

    ​ 这个demo首先是通过initFlowRule()方法设置了规则,然后我们通过多线程来模拟多个调用规则,方法的调用我们简单的模拟花费Thread.sleep(3000)也就是3秒。然后线程中运行的就是Sentinel的使用:

    Entry entry = null;
    try {
        entry = SphU.entry(resourceName);
        doSomeThing();
    } catch (BlockException e1) {
        System.out.println("Block   ----------");
    } finally {
        if (entry != null) {
            entry.exit();
        }
    }
    

    ​ 这里就是try-catch-finally的标准使用流程,其主要是通过SphU.entry(resourceName)的调用在里面解析规则的配置校验,如果能通过的话就不会抛出异常,如果不能通过就抛出BlockException异常。我们上面是直接使用,当我们在SpringBoot中直接使用的话,其本身的封装可能使用动态代理或者AOP的方式来将这里的doSomeThingSphU.entry(resourceName)的获取做解耦,我们加了注解后就可以直接专注于我们的doSomeThing()的方法逻辑。