[介绍]
去年(2014),对我们产品中的一个模块,通过使用BlockingQueue,性能提升很多。我觉得有些借鉴意义,这里分享给大家。可以说,此更改是所有java对block q有所了解的人都能够做到的,但是实际工作中确实可能碰到类似的情况。
简而言之:用BlockingQueue替换原有SynchronizeQueue块。更改后,该模块的性能从50msg/second, 提升到700 msg/second!
[代码]
直接看代码比较清楚。看完如下代码,你可能会莞尔一笑,so easy. 那就结束了吧。不过我下面将给出如何发现这个问题,以及自己做的一个小例子来验证性能提升。
修改前
private final Queue<ProtocolMessageEvent> queue = new LinkedList<ProtocolMessageEvent>();
while (true)
{
synchronized (this.queue)
{
final ProtocolMessageEvent event = this.queue.poll();
// TODO does the sync have to be held during message processing?
if (event != null)
{
if (event.getProtocolMessage().getType().equals("5"))
{
break;
}
handleProtocolMessageEvent(event);
}
}
Thread.sleep(1);
}
修改后
private final LinkedBlockingQueue<ProtocolMessageEvent> queue = new LinkedBlockingQueue<ProtocolMessageEvent>();
while (true)
{
ProtocolMessageEvent event = queue.take();
if (event != null)
{
if (event.getProtocolMessage().getType().equals("5"))
{
break;
}
handleProtocolMessageEvent(event);
}
}
[如何发现]
有几种方式都可以发现这个问题
1) 作为一个老程序员,review代码的时候就可以发现这个问题。其实这个实在是太简单直接了
2) 做自动化性能测试时,发现性能一直上不去,在30 msg/second徘徊。通过jvisualvm,看各个线程状态,会发现此线程sleep时间比较长。
注意:应用中有很多线程,要发现bottleneck线程,很多时候比较困难,因为要查看所有线程
[扩展]
1. 学习新知识很重要,尤其是jdk的重要新feature.此类问题就永远不会出现。因为此应用使用的jdk1.6. concurrent framework 是1.5就引入了的。
2. 要有自动化的performance测试,这样修改程序以后,可以很容易知道性能提升有多少。
[简单例子]
为了加深认识,写了一个简化的验证程序。该程序有278行,解释了两种的性能差别。
使用synchronizeQ块的方式,最快不到1000msg/s。而使用blockingQ到了20,000msg/s还完全没问题。
运行结果如下
$$$$ synchronize Q test $$$$ stats result -- acturely duration:9.951 throughput:502.4620641141594 sending speed:500.0 stats result -- acturely duration:10.207 throughput:979.7198001371607 sending speed:999.30048965724 stats result -- acturely duration:20.362 throughput:982.2217856792064 sending speed:1998.201618543311 $$$$ blocking Q test $$$$ stats result -- acturely duration:9.905 throughput:504.7955577990914 sending speed:499.7501249375312 stats result -- acturely duration:9.975 throughput:5012.5313283208025 sending speed:5002.501250625313 stats result -- acturely duration:9.993 throughput:10007.004903432402 sending speed:9997.000899730081 stats result -- acturely duration:9.926 throughput:20149.10336490026 sending speed:20128.824476650563
note:作为测试代码,200+行,略长
note:它引用了我写的一个batch发送的框架程序,极大方便了设定发送速度。此程序非常方便,有空我会分享出来。
代码贴在下面 - 有点冗长:
package baoying.perf.trtnfix;
import java.util.Date;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
import baoying.util.PerfInvoker;
import baoying.util.PerfInvoker.PerfInvokerCallback;
/**
result: 20150711
$$$$ synchronize Q test $$$$
stats result -- acturely duration:9.951 throughput:502.4620641141594 sending speed:500.0
stats result -- acturely duration:10.207 throughput:979.7198001371607 sending speed:999.30048965724
stats result -- acturely duration:20.362 throughput:982.2217856792064 sending speed:1998.201618543311
$$$$ blocking Q test $$$$
stats result -- acturely duration:9.905 throughput:504.7955577990914 sending speed:499.7501249375312
stats result -- acturely duration:9.975 throughput:5012.5313283208025 sending speed:5002.501250625313
stats result -- acturely duration:9.993 throughput:10007.004903432402 sending speed:9997.000899730081
stats result -- acturely duration:9.926 throughput:20149.10336490026 sending speed:20128.824476650563
*
*/
public class PollingVSBlockingQ {
/**
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
PollingVSBlockingQ vs = new PollingVSBlockingQ();
System.out.println("$$$$ synchronize Q test $$$$");
final LogicInterface pollingLogic = new PollingLogic();
vs.runTest(pollingLogic, 500, 10); //500 msg per second, 10 seconds
vs.runTest(pollingLogic, 1000, 10);
vs.runTest(pollingLogic, 2000, 10);
System.out.println("$$$$ blocking Q test $$$$");
final LogicInterface blockingLogic = new BlockingQLogic();
vs.runTest(blockingLogic, 500, 10);
vs.runTest(blockingLogic, 5000, 10);
vs.runTest(blockingLogic, 10000, 10);
vs.runTest(blockingLogic, 20000, 10);
}
public void runTest(final LogicInterface logic, final int ratePerSec, final int duarationInSec) throws InterruptedException{
Thread feedThread = new Thread(new Runnable() {
public void run() {
try {
logic.feedQ(ratePerSec, duarationInSec);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "feedThread");
Thread consumeThread = new Thread(new Runnable() {
public void run() {
try {
logic.consumeQ();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "consumeThread");
feedThread.start();
consumeThread.start();
feedThread.join();
consumeThread.join();
}
}
interface LogicInterface{
public void feedQ(final int ratePerSec, final int duarationInSec) throws InterruptedException;
public void consumeQ() throws InterruptedException;
}
class PollingLogic implements LogicInterface{
int ratePerSec;
int duarationInSec;
Date start = null;
Date endSending = null;
Date recEnd = null;
private final Queue<ProtocolMessageEvent> queue = new LinkedList<ProtocolMessageEvent>();
public void feedQ(final int ratePerSec, final int duarationInSec) throws InterruptedException {
this.ratePerSec = ratePerSec;
this.duarationInSec=duarationInSec;
PerfInvokerCallback c = new PerfInvokerCallback() {
@Override
public void execute(long seq) {
synchronized (queue) {
ProtocolMessageEvent e = new ProtocolMessageEvent(new Type(
"3"));
queue.add(e);
}
}
};
PerfInvoker ferfInvoker = new PerfInvoker(ratePerSec, duarationInSec, c);
start = new java.util.Date();
ferfInvoker.execute();
endSending = new java.util.Date();
}
public void consumeQ() throws InterruptedException {
int iReceived = 0;
while (true) {
synchronized (this.queue) {
final ProtocolMessageEvent event = this.queue.poll();
// TODO does the sync have to be held during message processing?
if (event != null) {
iReceived++;
if (iReceived == ratePerSec * duarationInSec) {
recEnd = new java.util.Date();
if (endSending == null) {
Thread.sleep(1000);
}
SimpleStatsHelper stats = new SimpleStatsHelper(
ratePerSec, duarationInSec, start, endSending,
recEnd);
stats.calcStatsResult();
break;
// System.exit(0);
}
if (event.getProtocolMessage().getType().equals("5")) {
break;
}
handleProtocolMessageEvent(event);
}
}
Thread.sleep(1);
}
}
private void handleProtocolMessageEvent(ProtocolMessageEvent event) {
int x = 6;
if (!(x * System.currentTimeMillis() > 200)) {
System.out.println("impossible");
}
}
}
class BlockingQLogic implements LogicInterface{
private final BlockingQueue<ProtocolMessageEvent> queue = new LinkedBlockingQueue<ProtocolMessageEvent>();
int ratePerSec;
int duarationInSec;
Date start = null;
Date endSending = null;
Date recEnd = null;
public void feedQ(final int ratePerSec, final int duarationInSec) throws InterruptedException {
this.ratePerSec = ratePerSec;
this.duarationInSec=duarationInSec;
PerfInvokerCallback c = new PerfInvokerCallback() {
@Override
public void execute(long seq) {
ProtocolMessageEvent e = new ProtocolMessageEvent(new Type("3"));
queue.add(e);
}
};
PerfInvoker ferfInvoker = new PerfInvoker(ratePerSec, duarationInSec, c);
start = new java.util.Date();
ferfInvoker.execute();
endSending = new java.util.Date();
}
public void consumeQ() throws InterruptedException {
int iReceived = 0;
while (true) {
final ProtocolMessageEvent event = this.queue.take();
// TODO does the sync have to be held during message processing?
if (event != null) {
iReceived++;
if (iReceived == ratePerSec * duarationInSec) {
recEnd = new java.util.Date();
//TODO refactor - bad code to wait endSeding be assigned value in sending thread.
//how to refactor
if (endSending == null) {
Thread.sleep(1000);
}
SimpleStatsHelper stats = new SimpleStatsHelper(ratePerSec,
duarationInSec, start, endSending, recEnd);
stats.calcStatsResult();
//TODO as bad as above, too.
endSending =null;
break;
// System.exit(0);
}
if (event.getProtocolMessage().getType().equals("5")) {
break;
}
handleProtocolMessageEvent(event);
}
}
}
private void handleProtocolMessageEvent(ProtocolMessageEvent event) {
int x = 6;
if (!(x * System.currentTimeMillis() > 200)) {
System.out.println("impossible");
}
}
}
class ProtocolMessageEvent {
Type _t;
ProtocolMessageEvent(Type t) {
_t = t;
}
public Type getProtocolMessage() {
return _t;
}
}
class Type {
String _t;
Type(String t) {
_t = t;
}
String getType() {
return _t;
}
}
本文分享了一次使用BlockingQueue替代SynchronizeQueue的优化案例,使得模块性能从50msg/second提升至700msg/second。通过代码对比、性能测试和自动测试方法的介绍,详细阐述了发现和验证这一改进的过程。此外,文章还讨论了学习新知识、使用自动化性能测试的重要性,以及提供了一个简化验证程序以加深理解。
9万+

被折叠的 条评论
为什么被折叠?



