性能测试中,我们常使用TPS来衡量服务的性能,这就需要我们在S内均匀的做到N个业务,而这里所要求的均匀,即保持固定频率,不是指间隔相同的时间,去做一些请求,因为实际处理请求的时间可能稍有不同,如果用固定的间隔时间去发送请求,那么所达到效果不见得是均匀,这里所述的固定频率或者说均匀实际上包含了本身请求处理的时间。
所以这里提供2种简单的方式:
(1)频率>=MS的:
采用JDK自带的Timer去处理:
jdk的timer核心源码:
private void mainLoop() { while (true) { try { TimerTask task; boolean taskFired; synchronized(queue) { // Wait for queue to become non-empty while (queue.isEmpty() && newTasksMayBeScheduled) queue.wait(); if (queue.isEmpty()) break; // Queue is empty and will forever remain; die // Queue nonempty; look at first evt and do the right thing long currentTime, executionTime; task = queue.getMin(); synchronized(task.lock) { if (task.state == TimerTask.CANCELLED) { queue.removeMin(); continue; // No action required, poll queue again } currentTime = System.currentTimeMillis(); executionTime = task.nextExecutionTime; if (taskFired = (executionTime<=currentTime)) { if (task.period == 0) { // Non-repeating, remove queue.removeMin(); task.state = TimerTask.EXECUTED; } else { // Repeating task, reschedule queue.rescheduleMin( task.period<0 ? currentTime - task.period : executionTime + task.period); } } } if (!taskFired) // Task hasn't yet fired; wait queue.wait(executionTime - currentTime); } if (taskFired) // Task fired; run it, holding no locks task.run(); } catch(InterruptedException e) { } } }
timer用法:
timer.scheduleAtFixedRate(task, 4, 50); timer.cancel(); //达到自己需要发送的总时间可以去取消
(2)频率<MS:
由于JDK自带的Timer只能精确到1MS,所以如果发送间隔要求精确到微妙或者纳秒,可以使用JDK的另外一个类,例如控制到微妙:
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(1); newScheduledThreadPool.scheduleAtFixedRate(task, 0, 1000, TimeUnit.MICROSECONDS);
之前笔者不知道这个方式,所以提供一个简单的实现:
long sendIntervalInNano = 100 * 1000; long totalExecuteTimeInSecond = 10; //控制最长发多久 long lastSendTime = System.nanoTime(); long endTime = lastSendTime + totalExecuteTimeInSecond*1000*1000*1000; long offsite = 0; //补偿一些请求占用太长时间 while (lastSendTime < endTime) { doThing(); long interval = sendIntervalInNano - (System.nanoTime() - lastSendTime) + offsite; if (interval > 0) { TimeUnit.NANOSECONDS.sleep(interval); offsite = 0; } else { offsite = interval; } lastSendTime = System.nanoTime(); }
总结: 比较和JDK timer内部实现,核心的区别在于timer使用的是Object.wait来实现等待,而后者使用了sleep的方式。
具体区别:
(1)除了都可以被线程中断打断外,在等待的过程中,如果需要停止,前者可以优美的使用配套的Object.notify(Timer的cancel实现),而使用sleep方式就没有中断之外的方法了。(Timer使用它的一个因素,更重要的是为了保持线程安全,用了同步块,顺其自然用了同步块锁的wait方法)
换句网友的说法比较贴切: 区别在于”(wait)同时又“积极”地等待条件发生改变”。
(2)sleep不会释放任何锁资源,而wait会释放对象锁资源,让其他线程获取这个锁,以便通知它可以继续执行,而
(3)sleep写在任何地方都可以,但是wait不行,wait必须在synchronized锁的锁范围内书写。
(4)sleep来源thread方法或者timeunit,wait来源于所有object,因为所有对象都有锁以提供支持。
题外话:
ScheduledExecutorService在task耗时大于调度间隔时间时,并不会使用更多的线程来并发,所以使用这种方式来达到预期的TPS不见得生效,所以最好直接使用thread pool来算好间隔时间,直接提交,同时不固定线程数,来弹性增加或者减少,虽然有线程大多挂掉的风险,但是还是比较准确的。需要结合具体运行情况来调整。
在保持一定的TPS的基础上,我们如果需要海量的TPS压力,就不能仅仅使用单台机器多线程去完成,且不说操作系统对线程数有限制,CPU本身提供的并发能力也有限。所以在单台机器创建的TPS已经无法满足需求时,我们要协同多台机器去做。
这里提供一种方法是使用持续集成平台jenkin的multiconfigure job来完成。构建多配置项目,选择N台结点,这样构建时,会同时并发N台结点做任务,当然这种方式创建的TPS和预想的会有细微差距:虽然是同时启动N台机器上的任务去做,但是难免因为机器不同等原因导致并发时间有差距。但是即使自己去实现一套多机器协同系统,也很难实现的比jenkin更好。