Performance Tuning-how to create and keep TPS?

性能测试中,我们常使用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更好。

 

001

002