为程序增加处理速度的限制
博客专区 > NoahX 的博客 > 博客详情
为程序增加处理速度的限制
NoahX 发表于4年前
为程序增加处理速度的限制
  • 发表于 4年前
  • 阅读 3455
  • 收藏 140
  • 点赞 9
  • 评论 3

腾讯云 新注册用户 域名抢购1元起>>>   

一、背景

有些人可能不太明白为什么要限制,原因也很直白“因为程序处理不过来”。编写数据处理的程序时,程序写的再好也会有处理速度的瓶颈。所以当压力持续增加时,会导致数据处理程序直接崩溃,这个结果是我们不想看到的。我们希望看到的是可以处理不了并丢弃,但程序不能死。

二、分析

从何种角度去限制?多很多种方式。可以设置一个消息队列,并为队列设置最大值。也可以按速度,每秒只可以处理2000条。显然按速度的方式比队列大小的方式更容易表达给客户。也更容易体现我们产品本身的数据处理能力。甚至可以用于License对数据规模进行有效的控制。

如何获得处理速度并进行限制呢?

原理比较简单:设置一个全局计数器,每秒自动清零。新增处理时会自动比较该计数器是否已经超过限制值,如果超出则不执行。

三、编码

先执行init初始化,然后就可以调用handle模式处理数据,最后调用destroy销毁。ExampleHandler类通过调用setHandleLimitEps来设置限制值
代码中有注释,不就做过多说明了。

1、ExampleHandler.java

package org.noahx.slimit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Created with IntelliJ IDEA.
 * User: noah
 * Date: 2/3/14
 * Time: 8:38 AM
 * To change this template use File | Settings | File Templates.
 */
public abstract class ExampleHandler<T> {

    private Logger logger = LoggerFactory.getLogger(this.getClass());
    private long handleLimitEps = 0;
    private long handleSpeedEps = 0;
    private AtomicLong handleSpeedCounter = new AtomicLong(0);
    private Timer timer;

    /**
     * 初始化
     */
    public void init() {
        timer = new Timer();
        timer.schedule(new TimerTask() { //定时速度计算器(EPS)1秒种执行一次,handle速度

            @Override
            public void run() {
                handleSpeedEps = handleSpeedCounter.getAndSet(0); //获得1s内中已处理的数量,并重置counter为0

                if (logger.isTraceEnabled()) {
                    logger.trace("Speed>  " + handleSpeedEps + " EPS");
                }
            }
        }, 1000, 1000);
    }

    /**
     * 销毁
     */
    public void destroy() {
        if (timer != null) {
            timer.cancel();
        }
    }

    /**
     * 限制计算
     *
     * @return 如果受限返回false
     */
    private boolean limit() {
        if (handleLimitEps <= 0) {  //小于等于宇0不做速度限制
            return false;
        }

        if (handleSpeedCounter.get() >= handleLimitEps) { //达到每s限制值返回true
            return true;
        } else {
            return false;
        }
    }

    /**
     * 处理数据外部方法(速度限制)
     *
     * @param data
     * @return 已处理返回true,未处理返回false
     */
    public boolean handle(final T data) {

        if (limit()) {   //限制检测
            return false;
        }

        handleSpeedCounter.incrementAndGet(); //增加计数
        doHandle(data);

        return true;
    }

    /**
     * 待实现处理数据方法
     *
     * @param data
     */
    protected abstract void doHandle(T data);

    /**
     * 获得限制EPS
     *
     * @return
     */
    public long getHandleLimitEps() {
        return handleLimitEps;
    }

    /**
     * 设置EPS限制
     *
     * @param handleLimitEps
     */
    public void setHandleLimitEps(long handleLimitEps) {
        this.handleLimitEps = handleLimitEps;
    }

    /**
     * 获得当前处理速度
     *
     * @return
     */
    public long getHandleSpeedEps() {
        return handleSpeedEps;
    }


}


2、ExampleHandlerTest.java(单元测试类)

package org.noahx.slimit;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Created with IntelliJ IDEA.
 * User: noah
 * Date: 2/3/14
 * Time: 9:05 AM
 * To change this template use File | Settings | File Templates.
 */
public class ExampleHandlerTest {

    static {
        System.setProperty(org.slf4j.impl.SimpleLogger.DEFAULT_LOG_LEVEL_KEY, "TRACE");
    }

    public static final long EVENT_AMOUNT = 10000000;
    private final AtomicLong countLimit = new AtomicLong(0);
    private final AtomicLong countHandle = new AtomicLong(0);
    private final CountDownLatch countDownLatch = new CountDownLatch((int) EVENT_AMOUNT);
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    private ExampleHandler<Object> handler;

    @Before
    public void before() {

        handler = new ExampleHandler<Object>() {

            @Override
            protected void doHandle(Object data) {
                countHandle.incrementAndGet();
                countDownLatch.countDown();
                //处理数据
            }
        };
        handler.init();
    }

    @Test
    public void test1() throws Exception {
        testHandle();        //无速度限制测试
    }

    @Test
    public void test2() throws Exception {
        handler.setHandleLimitEps(2000);  //设置2000的限制
        testHandle();
    }

    protected void testHandle() throws Exception {
        final ExecutorService executorService = Executors.newFixedThreadPool(20);

        for (int c = 0; c < EVENT_AMOUNT; c++) {
            final Runnable runnable = new Runnable() {

                @Override
                public void run() {
                    if (!handler.handle(null)) {
                        countLimit.incrementAndGet();    //受限制数++
                        countDownLatch.countDown();
                    }
                }
            };
            executorService.execute(runnable);
        }

        countDownLatch.await();  //等待多线程处理完毕

        logger.info("Handle: " + countHandle.get());   //处理数
        logger.info("Limit: " + countLimit.get());     //受限制数

    }

    @After
    public void after() {
        handler.destroy();
    }
}


3、test1()测试,不限制执行结果

[Timer-0] TRACE org.noahx.slimit.ExampleHandlerTest$1 - Speed>  975601 EPS
[Timer-0] TRACE org.noahx.slimit.ExampleHandlerTest$1 - Speed>  273132 EPS
[Timer-0] TRACE org.noahx.slimit.ExampleHandlerTest$1 - Speed>  575888 EPS
[Timer-0] TRACE org.noahx.slimit.ExampleHandlerTest$1 - Speed>  1116810 EPS
[Timer-0] TRACE org.noahx.slimit.ExampleHandlerTest$1 - Speed>  1254701 EPS
[Timer-0] TRACE org.noahx.slimit.ExampleHandlerTest$1 - Speed>  700775 EPS
[Timer-0] TRACE org.noahx.slimit.ExampleHandlerTest$1 - Speed>  3367754 EPS
[main] INFO org.noahx.slimit.ExampleHandlerTest - Handle: 10000000
[main] INFO org.noahx.slimit.ExampleHandlerTest - Limit: 0

没有任何请求受到限制,limit为0。


4、test2()测试,受限执行结果

[Timer-0] TRACE org.noahx.slimit.ExampleHandlerTest$1 - Speed>  2000 EPS
[Timer-0] TRACE org.noahx.slimit.ExampleHandlerTest$1 - Speed>  2000 EPS
[Timer-0] TRACE org.noahx.slimit.ExampleHandlerTest$1 - Speed>  2000 EPS
[Timer-0] TRACE org.noahx.slimit.ExampleHandlerTest$1 - Speed>  2000 EPS
[Timer-0] TRACE org.noahx.slimit.ExampleHandlerTest$1 - Speed>  2000 EPS
[Timer-0] TRACE org.noahx.slimit.ExampleHandlerTest$1 - Speed>  2000 EPS
[Timer-0] TRACE org.noahx.slimit.ExampleHandlerTest$1 - Speed>  2000 EPS
[main] INFO org.noahx.slimit.ExampleHandlerTest - Handle: 16000
[main] INFO org.noahx.slimit.ExampleHandlerTest - Limit: 9984000

可以看到受限的情况下,只处理了16000。其余请求全部受限。


5、pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.noahx</groupId>
    <artifactId>speed-limit</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.5 </version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <scope>runtime</scope>
            <version>1.7.5 </version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
        </dependency>

    </dependencies>
</project>


四、总结

EPS名词解释,EPS是Event Per Second的缩写,一般用于表示处理能力(每秒可以处理的事件数)。

我在使用Dispatcher框架时需要进行速度限制,所以开发了速度限制与批处理功能。
以上的样例代码就是从项目中提取出来的,逻辑比较简单基本表达出了限制的意思。
大家如果使用可能需要再次进行完善改造。

注:该限制速度采用最基本的先到先得的方式,并没有采用复杂等待算法。

源码下载:http://sdrv.ms/1dkDqVT

共有 人打赏支持
粉丝 140
博文 59
码字总数 48419
评论 (3)
超级大富
觉厉!
ilxlf
用一个计数器和一个时间戳应该就可以实现限流的功能把?感觉用一个timer有点浪费了。
黄亿华
这种做法对于EPS的计算是不平滑的,博主可以参考guava的RateLimiter,或者这篇文章:https://github.com/springside/springside4/wiki/Rate-Limiter
×
NoahX
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: