想基於Tinyhttp 寫一個服務器,初步設想是這樣的,把我上次寫的那個很low的爬蟲抓取天氣信息的程序優化一下,然後抓取天氣信息,存入mysql,然後再搭一層memcached,緩存天氣信息,提高查詢效率。前端一級頁面有全國城市的連接,點開某個城市,從數據庫裏獲得天氣信息,返回二級頁面。頁面都是最簡單的靜態的 只有兩行HTML這種樣子的(頁面這塊有點玩不來啊。。。),然後考慮,考慮各種優化,提高併發效率,現在想到的除了memcached,還想加入線程池,IO複用。然後此文記錄這一導通數據流並不斷優化的過程,及一些環境搭建,常用工具,指令,還有遇到的問題等等。隨着不斷擴展,本文內容將不斷增加,算是一種記錄。
關於mysql(數據庫有點小白,只查詢學習了一些要用到的東西)
啓動服務 service mysqld start
進入mysql 命令行 mysql -u root -p
mysql -u root -p dbname < /home/root/news.sql//導入腳本news.sql
批量執行語句
通過yum安裝的mysql
要再yum 一個 mysql-de** 否則沒有頭文件
c語言函數接口查詢http://dev.mysql.com/doc/refman/5.1/en/c-api-function-overview.html
<span style="white-space:pre"> </span>char sql[100] = "select page from weather where cityname=";
strcat(sql,"'");
strcat(sql,url+1);
strcat(sql,"'");
MYSQL my_connection;
MYSQL_ROW row;//
MYSQL_RES *res;
<span style="white-space:pre"> </span> int ret;
mysql_init(&my_connection);
if (mysql_real_connect(&my_connection, "localhost","root", "marker", "wea", 0, NULL, 0))
{
//printf("Connection database success\n");
ret = mysql_query(&my_connection, sql);
if (ret != 0)
{
printf("query error %d: %s\n", mysql_errno(&my_connection), mysql_error(&my_connection));
}
res = mysql_store_result(&my_connection);
if(res)
{
row = mysql_fetch_row(res);//return NULL if no more row
if(row == NULL)
{
printf("get data from database error\n");
close(client);
free(page);
mysql_free_result(res);
mysql_close(&my_connection);
memcached_free(memc);
return;
}
printf("get data:<%s> from database success\n",url+1);
rc = memcached_set(memc, url+1, strlen(url)-1, row[0], strlen(row[0]), (time_t)180,(uint32_t)0);
// if(rc)
strcpy(page,row[0]);
主要用了那幾個常用接口,邏輯上是先去memcached查,查不到去數據庫裏查,查到再memcached_set到緩存中,並把查詢結果保存到page中。
關於memcached
程序用c/c++寫的 所以客戶端用的libmemcached
libmemcached 接口查詢http://docs.libmemcached.org/index.html
開始先將服務器搭在了本機上,開上幾個服務,監聽不同端口
./memcached -u root -m 512 -p 11211 -vv (-vv 將信息打印到控制檯,比較方便調試)
關於memcached環境的搭建,好像挺簡單的,記得先裝libevent 然後注意一下共享庫的路徑設置問題。
memcached_st *memc;
memcached_return rc;
memcached_server_st* servers;
memc = memcached_create(NULL);
servers = memcached_server_list_append(NULL, (char*)"localhost", 11211, &rc);
servers = memcached_server_list_append(servers, (char*)"localhost", 11212, &rc);
rc = memcached_server_push(memc, servers);
memcached_server_free(servers);
memcached_behavior_set(memc,MEMCACHED_BEHAVIOR_DISTRIBUTION,MEMCACHED_DISTRIBUTION_CONSISTENT);
memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_RETRY_TIMEOUT, 20);
memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT, 5) ;
size_t value_length = 0;
//char* returned by memcached_get must be free
char *str_get_by_mem = memcached_get(memc,url+1,strlen(url)-1,&value_length,(uint32_t)0,NULL);
if(str_get_by_mem != NULL)
{
strcpy(page,str_get_by_mem);
printf("get data:<%s >from cache successed\n",url+1);
}
else
{
printf("get data:<%s >from cache failed\n",url+1);
}
free(str_get_by_mem);
這裏注意 memcached_get返回的內容需要free掉
遇到的問題
1, 外部機器連接服務器打不開二級頁面
環境搭建好了,基本數據流調通之後,打開本地了瀏覽器試一下,發現沒有問題,在別的機子上的瀏覽器試一下,發現只能打開一級頁面,二級頁面無響應,但在服務器端打出的log上顯示內容發送正常。 然後考慮關掉兩邊防火牆, 無效。 然後用wireshark tcpdump 抓數據包 看看到底數據發送在哪出了問題。最後發現問題在於,服務器收到服務請求後,只讀取了頭部幾行關係的信息,如請求方法 get 資源名等等,讀取完就關閉了連接,對後邊的數據沒有處理。這在tcp中認爲接收端提前關閉,數據並沒有全部 被接收, 不符合可靠性原則,發送reset錯誤 http://my.oschina.net/costaxu/blog/127394
2. 線程創建失敗
Tinyhttp 原本代碼處理是來一個鏈接給一個線程,然後對線程沒有後續的處理,這樣當訪問量多的時候 就無法創建出新的線程了
其實在併發量不是很高的時候 把每個線程分離出來 detach 及時回收線程資源,就能解決上述問題。但創建太多線程,系統效率可能卡在線程切換和頻繁申請釋放上。後邊考慮線程池進行優化。
3. Too many open files
進程打開了太多文件描述符,併發高時,可能同時打開很多文件描述符,都沒有處理完,這是再有新的請求,就超過上限了,這個限制是可以改的,在我的機子上默認的是1024,然後下邊的查到的解決方法,可以臨時更改,也可以在配置文件裏永久更改,雖然上限可以更改, 但代碼邏輯裏還是要儘早釋放掉文件。
4.累計訪問達到1700多以後就開始出現訪問不能響應的問題
這一看應該也是某種資源沒有申請 達到上限了,但在log裏並沒有發現類似錯誤,而且同時和另一個bug摻雜在一起,浪費了好多時間(但最後找出bug之後也格外happy啊)。最後發現是 memcached 的最大連接數達到了上限。查看代碼邏輯發現有一個邏輯分支沒有釋放連接。。。。(自作孽)。由於是memcached的連接限制問題,所以異常信息輸出在memcached的服務器端,但我一開始,是用腳本直接起的多個後臺服務,看不見輸出的信息, 後來啓動時加上 -vv選項,發現了 Too many open file類的錯誤。排查問題過程用到lsof工具,很nice
lsof -n|awk '{print $2}'|sort|uniq -c|sort -nr|more
這樣把各進程使用文件描述符的情況就排行輸出了,發現前幾名 果然是我的程序。。
修改過系統的文件描述符限制,但memcached的沒有修改 默認應該也是1024的 可以在啓動時 加上 -c 選項進行配置。
調好bug來一發試試
調bug之前的
179 fetches/sec >>>>>>>>>>715 fetches /sec
每秒連接數,還是提升不少的,主要是因爲以前沒調bug 時訪問量到1000多之後很多就沒有響應了,所以在一次併發下平均下來 每秒訪問量很少。
工具/其他
1.性能壓力測試用的是http_load,小巧簡單。把要測的連接寫到 urllist.txt中
使用示例:
http_load -p 30 -s 60 urllist.txt
./http_load -rate 5 -seconds 10 urls說明執行了一個持續時間10秒的測試,每秒的頻率爲5。
49 fetches, 2 max parallel, 289884 bytes, in 10.0148 seconds5916 mean bytes/connection4.89274
fetches/sec, 28945.5 bytes/secmsecs/connect: 28.8932 mean, 44.243 max, 24.488 minmsecs/first
-response: 63.5362 mean, 81.624 max, 57.803 minHTTP response codes: code 200 -- 49
結果分析:
1.49 fetches, 2 max parallel, 289884 bytes, in 10.0148 seconds
說明在上面的測試中運行了49個請求,最大的併發進程數是2,總計傳輸的數據是289884bytes,運行的時間是10.0148秒
2.5916 mean bytes/connection說明每一連接平均傳輸的數據量289884/49=5916
3.4.89274 fetches/sec, 28945.5 bytes/sec
說明每秒的響應請求爲4.89274,每秒傳遞的數據爲28945.5 bytes/sec
4.msecs/connect: 28.8932 mean, 44.243 max, 24.488 min說明每連接的平均響應時間是28.8932 msecs
,最大的響應時間44.243 msecs,最小的響應時間24.488 msecs
、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、
2. tcpdump在排除網絡傳輸的錯誤時,實在在邏輯上找不出問題,就抓包分析吧。
對本機的udp
123 端口進行監視 123 爲ntp的服務端口
tcpdump udp port 123
抓取http包
tcpdump -XvvennSs 0 -i eth0 tcp[20:2]=0x4745 or tcp[20:2]=0x4854
0x4745 爲"GET"前兩個字母"GE",0x4854 爲"HTTP"前兩個字母"HT"。