一、背景
有些人可能不太明白为什么要限制,原因也很直白“因为程序处理不过来”。编写数据处理的程序时,程序写的再好也会有处理速度的瓶颈。所以当压力持续增加时,会导致数据处理程序直接崩溃,这个结果是我们不想看到的。我们希望看到的是可以处理不了并丢弃,但程序不能死。
二、分析
从何种角度去限制?多很多种方式。可以设置一个消息队列,并为队列设置最大值。也可以按速度,每秒只可以处理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