上周發布的一個系統,出現了一個很詭異的現象。抽象一下描述,問題大概就是這樣的:
需求: 一次http請求,通過url的params來讀取服務器上的一個日志,并將日志內容返回給用戶。
問題表現:存在一定的機率,一次請求返回的內容,與期望的內容不一致。也就是所謂的串日志問題。
這個問題出現后,我們都認為后臺請求應該沒有問題,因為我們是直接用的JDK自帶的HttpServer來起的服務,心中對JDK還是有一些信任的。所以我們還是前端發送請求的時候發亂了,返回的結果和請求是能夠匹配的。
為了定位問題,我們在后臺加了些日志,對比了每次請求和請求的返回內容。在1~2天的監控下,發現真的出現了請求和返回內容之間存在不一致的地方,而前端發送的請求沒有錯,問題直接定位到后臺處理的問題。
簡單說來,大致就類似這樣:前端發送請求 http://127.0.0.1/getlog?id=123&path=testlog&type=r,期望是讀取的是testlog文件,但是實際確是把devlog的內容返回給前端了。由于定位到這個現象,問題也就可以定位到后臺讀文件拿參數是錯的。
我們怎么獲取url中的參數呢,翻下源碼,如下:
Map
JDK上是這么定義這個方法的,getAttribute:
Filter modules may store arbitrary objects with HttpExchange instances as an out-of-band communication mechanism. Other Filters or the exchange handler may then access these objects.
并沒有說他存在線程不安全,至于到底是不是這里出現的問題了,寫了一個測試程序:
1000并發,下發10000個請求,看看出現多少次這樣的問題。結果很明顯,統計下來,發現存在這種問題的數據接近1000條左右,這問題就非常明顯了。
httpExchange.getAttribute()在一定并發的情況下,存在線程不安全的問題。
public class HttpServersPerfTest extends BaseCase {
private static final Log logger = LogFactory.getLog(AlisaNodeHttpServersPerfTest.class);
int maxThread = 1000;
private int totalTask = 1000;
@Test
public void testRequestRunningLog() throws InterruptedException {
execute();
}
private void execute() throws InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(maxThread);
final CountDownLatch countDownLatch = new CountDownLatch(totalTask);
for (int n = 0; n < totalTask; n++) {
pool.execute(new RequestHttpServerThread(countDownLatch));
}
try {
countDownLatch.await();
System.err.println(“==============>>>>> 下發結束” );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private class RequestHttpServerThread implements Runnable {
private CountDownLatch countDownLatch;
public RequestHttpServerThread(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
/**
* 任務執行,這里實際上在服務器上準備好一堆文件,文件內容按行將文件名寫入
* 讀取到內容可以和文件名進行比較
*/
public void run() {
Random rd = new Random();
int index = rd.nextInt(60);
if (index < 10){
index = index + 10;
}
String indexStr = Integer.toString(index);
String path = indexStr+”-”+indexStr+”-”+indexStr+”/”;
LogGetter logGetter = new LogGetter();
logGetter.setPath(path);
String runningLog = logGetter.getRunningLog(0L);
if(runningLog.contains( “T3_000000000″ + indexStr)){
System.out.println(“==============>”+”exit”);
}else
{
String[] lines = runningLog.split(“\n”);
System.out.println(“期望:” + indexStr + “\n” + “實際請求的:” + lines[0] + “\n”);
}
}
}
}
定位到問題之后,我們就決定放棄使用這個方法,自己重寫一個parserQuery的方法,后臺拿到url之后重新對url進行參數的解析。
Map
public static Map
Map
if (query != null) {
String pairs[] = query.split(“[&]“);
for (String pair : pairs) {
String param[] = pair.split(“[=]“);
String key = null;
String value = null;
原文轉自:http://www.kjueaiud.com/deltestingadmindd/