基於Tinyhttp改寫的http服務器(memcached,mysql)

想基於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"。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章