DNS 壓力測試
2010-01-01
網域名稱服務〈Domain Name Service〉效率決定網路服務器名稱與 IP 解析之速度,如果網域名稱服務效率差,使得用戶在使用網路應用系統時,會因過多等待而放棄瀏覽,進而影響企業整個電子商務之推廣。
因此建置一套高效率網域名稱服務便顯得重要,然而在選擇網域名稱服務系統前,應該針對該系統實施壓力測試,據以選擇適當網域名稱服務系統。大型電子商務企業,為服務更多用戶其網域名稱服務器是成群集式架構如下:
- 網路負載平衡器:為群集式網路服務器,利用演算法將用戶網路服務請求,分配給群集服務器之一,避免多個用戶集中請求某一台服務器,造成負載過重不均。為簡化用戶請求網路負載平衡器提供公共 IP 給服務請求者,網路負載平衡器接受用戶請求後再分配給適當服務器提供網路服務。
- DNS 緩存器〈DNS Cache〉:大多數網路服務系統其網域名稱與 IP 不甚變動,每當用戶提出名稱解析服務,就由網域名稱服務器自其資料庫查找,再將 IP 解析資訊傳回用戶。
此種架構當請求量大時,服務便易被拖垮。因此需要建置 DNS 緩存器,將已解析過資訊加以緩存,當其他用戶已請求解析過之資訊,直接傳回用戶而無需再向網域名稱服務器請求解析,大大地降低網域名稱服務器負載。
Python 是一種物件導向直譯式電腦語言,語法簡明易懂,並提供豐富程式庫,使得 Python 應用系統更多樣性,選用 Python 做為撰寫壓力測試程式語言。
在測試網域名稱服務系統前,需要先下載大量測試資料。
測試資料檔內容範例如下:
www.ibm.com. A www.hinet.net. A 168.95.1.1.in-addr.apra. PTR
由於測試資料檔過大,為有效實施壓力測試,找出現有網域名稱服務能力,故拆分為每檔 10,000 筆測試資料,存放於各壓力測試器中。
在 *NIX 作業系統中,執行 split -l 10000 dns 拆分原測試資料檔成許多 dns 起頭之檔案群。
在原測試資料檔中存在許多錯誤資料,必須先撰寫過濾程式將錯誤筆錄剔除,過濾程式如下:
過濾程式 | |
#include <iostream> #include <fstream> #include <string> #include <vector> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> int GetIP(char *dnsRecord, std::vector<std::string> *ipList) { struct hostent *host; if ((host = gethostbyname(dnsRecord)) == NULL) return (-1); for (int i = 0;; i++) { struct in_addr ip; if (host->h_addr_list[i] == NULL) break; ip.s_addr = *(ulong *) host->h_addr_list[i]; ipList->push_back(std::string(inet_ntoa(ip))); } return (0); } int GetName(char *ip, std::string &hostName) { struct sockaddr_in iip; struct hostent *host; inet_aton(ip, &iip.sin_addr); if ((host = gethostbyaddr((char *) & iip.sin_addr, 4, AF_INET)) == NULL) return (-1); hostName.append(host->h_name); return (0); } int main(int argc, char** argv) { std::string dnsFile = ""; if (argc == 1) return (1); dnsFile = argv[1]; std::ifstream dnsFileStream; std::string dnsRecord; char name_ip[64], flag[8]; dnsFileStream.open(dnsFile.c_str(), std::ios::in); if (dnsFileStream.is_open()) { while (!dnsFileStream.eof()) { std::getline(dnsFileStream, dnsRecord); memset(name_ip, 0, sizeof (name_ip)); memset(flag, 0, sizeof (flag)); sscanf(dnsRecord.c_str(), "%s %s", name_ip, flag); if (name_ip[strlen(name_ip) - 1] == '.') name_ip[strlen(name_ip) - 1] = 0; if (!strcmp(flag, "A")) { std::vector<std::string> ipList; if (!GetIP(name_ip, &ipList)) { std::cout << dnsRecord << std::endl; } } else if (!strcmp(flag, "PTR")) { std::string hostName = ""; char *mark = strstr(name_ip, ".in-addr.arpa"); if (mark != NULL) { *mark = 0; } if (!GetName(name_ip, hostName)) { std::cout << dnsRecord << std::endl; } } } dnsFileStream.close(); } return (0); } |
|
編譯 | g++ -o DNSFilter dnsFilter.cpp |
# 找出所有測試資料小檔,過濾後產生新檔,其附檔名為 .dat for dnsf in dns* ; do ./DNSFilter $dnsf > $dnsf.dat & done
為找出 DNS 整體機制瓶頸,撰寫一個簡單命令稿〈run.sh〉,記錄測試單一資料檔耗時,內容如下:
start=`date +%H%M%S` # 使用說明:dnsperf -h dnsperf -d $1 stop=`date +%H%M%S` echo $1:$stop-$start
每 10,000 筆收集 run.sh {測試資料小檔} 輸出至一個文字檔,編輯成內容如下:
10K 140204 140200 20K 140234 140230 30K 140304 140300 40K 140334 140330 ...
將此文字檔匯入至 Microsoft Excel 再繪製成折線圖,觀察 DNS 整體機制效能。
在壓力測試過程中,要隨時觀察測試機資源狀態〈top〉,如果已達滿載則不需要再測試。
最後撰寫一個簡單命令稿,〈runAll.sh〉,記錄測試所有資料檔耗時,內容如下:
for dnsf in dns*.dat ; do sh run.sh $dnsf & done
收集 runAll.sh 輸出至一個文字檔〈如前述〉,再將此文字檔匯入至 Microsoft Excel 再繪製成折線圖,觀察 DNS 整體機制效能。
如果不使用 dnsperf 也可以使用 Python 撰寫各式壓力測試程式如下:
讀取標準輸入管道解析 | |
#!/usr/bin/python import os import re import time import sys import string import dns.resolver import fileinput def main(): addressType = 'A' if len(sys.argv) > 1: dnsFile = sys.argv[1:][0] startTime = time.clock() for line in sys.stdin.readlines(): myLine = line.rstrip() myParams = myLine.split() myDomain = myParams[0].rstrip('.') # 過濾 網域名稱 錯誤資料 if myDomain.find('.') == -1: continue # 過濾 網域名稱反解 資料 if myDomain.endswith('.in-addr.arpa'): continue try: myResolved = dns.resolver.query(myDomain, addressType) for myIPObject in myResolved: myIP = str(myIPObject) print "%s.\t%s\t%s" % (myDomain, addressType, myIP) except: myIP = '0.0.0.0' stopTime = time.clock() if __name__ == "__main__": sys.exit(main()) |
|
讀取網域資料檔解析 | |
#!/usr/bin/python import os import re import time import sys import string import dns.resolver import fileinput def main(): addressType = 'A' dnsFile = 'dnsTest.txt' if len(sys.argv) > 1: dnsFile = sys.argv[1:][0] startTime = time.clock() for line in fileinput.input(dnsFile): myLine = line.rstrip() myParams = myLine.split() myDomain = myParams[0].rstrip('.') # 過濾 網域名稱 錯誤資料 if myDomain.find('.') == -1: continue # 過濾 網域名稱反解 資料 if myDomain.endswith('.in-addr.arpa'): continue try: myResolved = dns.resolver.query(myDomain, addressType) for myIPObject in myResolved: myIP = str(myIPObject) print "%s.\t%s\t%s" % (myDomain, addressType, myIP) except: myIP = '0.0.0.0' stopTime = time.clock() if __name__ == "__main__": sys.exit(main()) |
|
讀取網域資料檔多執行緒解析 | |
#!/usr/bin/python |