上周發(fā)布的一個(gè)系統,出現了一個(gè)很詭異的現象。抽象一下描述,問(wèn)題大概就是這樣的:
需求: 一次http請求,通過(guò)url的params來(lái)讀取服務(wù)器上的一個(gè)日志,并將日志內容返回給用戶(hù)。
問(wèn)題表現:存在一定的機率,一次請求返回的內容,與期望的內容不一致。也就是所謂的串日志問(wèn)題。
這個(gè)問(wèn)題出現后,我們都認為后臺請求應該沒(méi)有問(wèn)題,因為我們是直接用的JDK自帶的HttpServer來(lái)起的服務(wù),心中對JDK還是有一些信任的。所以我們還是前端發(fā)送請求的時(shí)候發(fā)亂了,返回的結果和請求是能夠匹配的。
為了定位問(wèn)題,我們在后臺加了些日志,對比了每次請求和請求的返回內容。在1~2天的監控下,發(fā)現真的出現了請求和返回內容之間存在不一致的地方,而前端發(fā)送的請求沒(méi)有錯,問(wèn)題直接定位到后臺處理的問(wèn)題。
簡(jiǎn)單說(shuō)來(lái),大致就類(lèi)似這樣:前端發(fā)送請求 http://127.0.0.1/getlog?id=123&path=testlog&type=r,期望是讀取的是testlog文件,但是實(shí)際確是把devlog的內容返回給前端了。由于定位到這個(gè)現象,問(wèn)題也就可以定位到后臺讀文件拿參數是錯的。
我們怎么獲取url中的參數呢,翻下源碼,如下:
Map
JDK上是這么定義這個(gè)方法的,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.
并沒(méi)有說(shuō)他存在線(xiàn)程不安全,至于到底是不是這里出現的問(wèn)題了,寫(xiě)了一個(gè)測試程序:
1000并發(fā),下發(fā)10000個(gè)請求,看看出現多少次這樣的問(wèn)題。結果很明顯,統計下來(lái),發(fā)現存在這種問(wèn)題的數據接近1000條左右,這問(wèn)題就非常明顯了。
httpExchange.getAttribute()在一定并發(fā)的情況下,存在線(xiàn)程不安全的問(wèn)題。
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(“==============>>>>> 下發(fā)結束” );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private class RequestHttpServerThread implements Runnable {
private CountDownLatch countDownLatch;
public RequestHttpServerThread(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
/**
* 任務(wù)執行,這里實(shí)際上在服務(wù)器上準備好一堆文件,文件內容按行將文件名寫(xiě)入
* 讀取到內容可以和文件名進(jìn)行比較
*/
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” + “實(shí)際請求的:” + lines[0] + “\n”);
}
}
}
}
定位到問(wèn)題之后,我們就決定放棄使用這個(gè)方法,自己重寫(xiě)一個(gè)parserQuery的方法,后臺拿到url之后重新對url進(jìn)行參數的解析。
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://kjueaiud.com/deltestingadmindd/