在 mclee 那邊看到 Apache 2.2 出版了,翻 Overview of new features in Apache 2.2 的時候看到 Apache MPM event,回頭看記錄發現 Apache 2.1 就有了。
不知道我是不是有猜錯,這應該是 Event-driven 的 module 吧?hmmm...
Friday, December 02, 2005
Monday, November 28, 2005
Linux epoll() performance
在找一些資料的時候發現 zmx 的舊文章 (也沒有很舊,半年前的文章),談 Linux
這篇文章裡提到了
對於 Connection 數目很大的 HTTP server (所謂的 The C10K problem),BSD 上的
epoll()
performance 的改善:Comparing and Evaluating epoll, select, and poll。這篇文章裡提到了
epoll()
在 idle connection 很少的情況下,與 poll()
或 select()
改善的空間不會太多,大概就只有省下從 kernel 要修改 userland space 的時間吧。對於 Connection 數目很大的 HTTP server (所謂的 The C10K problem),BSD 上的
ACCEPT_FILTER_HTTP
還是很重要,可以省下 accept()
進來卻慢慢送 Header 的情況 :p
Web Performance Tools - Siege
剛剛在 ijliao 那邊看到 O3 Magazine,看了 Lighttpd 的測試報告,發現測試報告裡面不是用 ab,而是用 Siege。看了一下 ab 與 Siege 的差別:
這樣看說明可能看不太出來,Siege 與 ab 主要的差別在於同時發出的 Request 不一樣 (於是會比較逼近真實的情況)。
查了一下 FreeBSD Ports 裡面,發現在 benchmarks/siege 下面有,邊裝邊找中文的說明文件,在 Siege:压力模拟/测试工具 這篇裡面有很簡單的說明,等下來用 WWW::Mechanize 寫個小程式抓網頁 url 測試看看。
Siege is an http regression testing and benchmarking utility. It was designed to let web developers measure the performance of their code under duress, to see how it will stand up to load on the internet. Siege supports basic authentication, cookies, HTTP and HTTPS protocols. It allows the user hit a web server with a configurable number of concurrent simulated users. Those users place the webserver "under siege."
這樣看說明可能看不太出來,Siege 與 ab 主要的差別在於同時發出的 Request 不一樣 (於是會比較逼近真實的情況)。
查了一下 FreeBSD Ports 裡面,發現在 benchmarks/siege 下面有,邊裝邊找中文的說明文件,在 Siege:压力模拟/测试工具 這篇裡面有很簡單的說明,等下來用 WWW::Mechanize 寫個小程式抓網頁 url 測試看看。
Sunday, November 27, 2005
Thread-safe gethostbyname() on FreeBSD 6.0
前陣子在我個人板上有寫,FreeBSD 6.0 的
gethost*()
已經 Thread-safe 了:gethostbyname
manpage on FreeBSD 6.0gethostbyname
manpage on FreeBSD 5.4
gethostbyname()
,看傳回來的位置是什麼:
#include <netdb.h>
#include <pthread.h>
#include <stdio.h>
#define THREAD_NUM (3)
void *thread_start()
{
const pthread_t tid = pthread_self();
int i;
for (i = 0; i < 5; i++) {
struct hostent *p = gethostbyname("ccca.nctu.edu.tw");
printf("tid = %d, gethostbyname() = %p\n", tid, p);
sleep(1);
}
return;
}
int main(void)
{
pthread_t tid[THREAD_NUM];
int i;
for (i = 0; i < THREAD_NUM; i++)
pthread_create(tid + i, NULL, thread_start, NULL);
sleep(10);
}
Saturday, November 26, 2005
Squid 3 的設定
Squid 3 網站上的 20051125 版 是有問題的,後來透過 cvs 抓到的版本就 okay 了。本來是要在 www.cs.nctu.edu.tw 上面測試的,後來被提醒 11/25 碩班甄試放榜,那我們就換回舊的版本了。
我後來在 netnews.nctu.edu.tw 上面架了一個,bind 在 127.0.0.1:3128 上面給自己用 (putty tunnel,3128:127.0.0.1:3128,然後把我 Laptop 與 Desktop 的 Firefox 都設定 127.0.0.1:3128 為 Proxy Server),因為 netnews.nctu.edu.tw 是 FreeBSD 6.0,所以我在安裝的時候特地把 Thread 有關的選項打開 (像是 aufs),先測看看 stability 如何,晚點再來測試看看 performance。
這是我 compile 時的參數:
我後來在 netnews.nctu.edu.tw 上面架了一個,bind 在 127.0.0.1:3128 上面給自己用 (putty tunnel,3128:127.0.0.1:3128,然後把我 Laptop 與 Desktop 的 Firefox 都設定 127.0.0.1:3128 為 Proxy Server),因為 netnews.nctu.edu.tw 是 FreeBSD 6.0,所以我在安裝的時候特地把 Thread 有關的選項打開 (像是 aufs),先測看看 stability 如何,晚點再來測試看看 performance。
這是我 compile 時的參數:
--prefix=/home/squid --enable-storeio="ufs aufs diskd" --enable-disk-io="Blocking" --enable-removal-policies="heap lru" --disable-wccp --enable-cache-digests --disable-poll --disable-select --enable-kqueue --disable-epoll --disable-ident-lookups --enable-underscores --disable-unlinkd --enable-x-accelerator-vary --with-aufs-threads=8 --with-pthreads --with-aio
會同時把這麼多東西 compile 進去主要是要測試各種 algorithm 的效能,實際在跑的時候可能會再拔掉一些 :p
Friday, November 25, 2005
調整 Reverse Proxy 的 Performance
早上想測試看看 www.cs.nctu.edu.tw 的 Reverse Proxy,就把某些東西導過去測試了,跟 ericlin 測試了一下,馬上就發現了一些問題:
- Squid 的實際吃的記憶體量大約是
cache_mem
所設定的兩倍多,如果 FreeBSD kernel 沒有調整過,則單一 Process 可使用記憶體最大上限應該是 512MB。 - Squid 2.x 只支援
select()
或poll()
這類 Function,在 3.0 才有支援kqueue()
,這可能是造成 Squid 使用大量的 CPU resource 的原因。
Network Programming Using Libevent - (III)
這次要談的跟 Network Programming 沒有直接的關係。
在寫 Nonblocking Network Program 通常要處理 Buffering 的問題,但並不好寫,主要是因為
在 libevent 裡面提供相當不錯的 Buffer Library 可以用,完整的說明在
下面直接提供
在寫 Nonblocking Network Program 通常要處理 Buffering 的問題,但並不好寫,主要是因為
read()
或 recv()
不保證可以一次讀到一行的份量進來。在 libevent 裡面提供相當不錯的 Buffer Library 可以用,完整的說明在
man event
的時候可以看到,最常用的應該就是以 evbuffer_add()
、evbuffer_readline()
這兩個 Function,其他的知道存在就可以了,需要的時候再去看詳細的用法。下面直接提供
libevent-buff.c
當作範例,編譯後看執行結果,再回頭來看 source code 應該就有感覺了:
#include <sys/time.h>
#include <event.h>
#include <stdio.h>
void printbuf(struct evbuffer *evbuf)
{
for (;;) {
char *buf = evbuffer_readline(evbuf);
printf("* buf = %p, the string = \"\e[1;33m%s\e[m\"\n", buf, buf);
if (buf == NULL)
break;
free(buf);
}
}
int main(void)
{
struct evbuffer *evbuf;
evbuf = evbuffer_new();
if (evbuf == NULL) {
fprintf(stderr, "%s(): evbuffer_new() failed.\n", __func__);
exit(1);
}
/* Add "gslin" into buffer. */
u_char *buf1 = "gslin";
printf("* Add \"\e[1;33m%s\e[m\".\n", buf1);
evbuffer_add(evbuf, buf1, strlen(buf1));
printbuf(evbuf);
u_char *buf2 = " is reading.\nAnd he is at home.\nLast.";
printf("* Add \"\e[1;33m%s\e[m\".\n", buf2);
evbuffer_add(evbuf, buf2, strlen(buf2));
printbuf(evbuf);
evbuffer_free(evbuf);
}
Network Programming Using Libevent - (II)
接下來要談的是 libevent 要如何使用,不過為了方便起見,我們直接寫一個很簡單的 Time Server 來當作例子:當你連上去以後 Server 端直接提供時間,然後結束連線。
在這些例子裡面我以 FreeBSD 6.0 當作測試的平台,另外使用 libevent 1.1a 當作 Event-based Library,Compile 時請使用
原始程式碼在文章的最後頭。
其中的
而
最後的
這隻程式非常粗糙,有很多地方沒有注意到 Blocking 的問題,這點我們就先不管了。當跑起來以後你就可以連到 port 7000,就會出現類似下面的結果:gslin@netnews [~] [3:14/W5] t 0 7000
最基本的使用就是這樣了,你可以
這是
在這些例子裡面我以 FreeBSD 6.0 當作測試的平台,另外使用 libevent 1.1a 當作 Event-based Library,Compile 時請使用
gcc -I/usr/local/include -o timeserver timeserver.c -L/usr/local/lib -levent
(如果 libevent 的 Header 與 Library 放在 /usr/include
與 /usr/lib
下的話可以省略這兩個參數)。原始程式碼在文章的最後頭。
event_init()
表示初始化 libevent 所使用到的變數。event_set(&ev, s, EV_READ | EV_PERSIST, connection_accept, &ev)
把 s
這個 File Description 放入 ev
(第一個參數與第二個參數),並且告知當事件 (第三個參數的 EV_READ
) 發生時要呼叫 connection_accept()
(第四個參數),呼叫時要把 ev
當作參數丟進去 (第五個參數)。其中的
EV_PERSIST
表示當呼叫進去的時候不要把這個 event 拿掉 (繼續保留在 Event Queue 裡面),這點可以跟 connection_accept()
內在註冊 connection_time()
的程式碼做比較。而
event_add(&ev, NULL)
就是把 ev
註冊到 event queue 裡面,第二個參數指定的是 Timeout 時間,設定成 NULL
表示忽略這項設定。最後的
event_dispatch()
表示進入 event loop,當 Queue 裡面的任何一個 File Description 發生事件的時候就會進入 callback function 執行。這隻程式非常粗糙,有很多地方沒有注意到 Blocking 的問題,這點我們就先不管了。當跑起來以後你就可以連到 port 7000,就會出現類似下面的結果:gslin@netnews [~] [3:14/W5] t 0 7000
gslin@netnews [~/work/C] [3:15/W3] t 0 7000
Trying 0.0.0.0...
Connected to 0.
Escape character is '^]'.
Fri Nov 25 03:15:10 2005
Connection closed by foreign host.
最基本的使用就是這樣了,你可以
man event
看到完整的說明。這是
timeserver.c
:
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <event.h>
#include <stdio.h>
#include <time.h>
void connection_time(int fd, short event, struct event *arg)
{
char buf[32];
struct tm t;
time_t now;
time(&now);
localtime_r(&now, &t);
asctime_r(&t, buf);
write(fd, buf, strlen(buf));
shutdown(fd, SHUT_RDWR);
free(arg);
}
void connection_accept(int fd, short event, void *arg)
{
/* for debugging */
fprintf(stderr, "%s(): fd = %d, event = %d.\n", __func__, fd, event);
/* Accept a new connection. */
struct sockaddr_in s_in;
socklen_t len = sizeof(s_in);
int ns = accept(fd, (struct sockaddr *) &s_in, &len);
if (ns < 0) {
perror("accept");
return;
}
/* Install time server. */
struct event *ev = malloc(sizeof(struct event));
event_set(ev, ns, EV_WRITE, (void *) connection_time, ev);
event_add(ev, NULL);
}
int main(void)
{
/* Request socket. */
int s = socket(PF_INET, SOCK_STREAM, 0);
if (s < 0) {
perror("socket");
exit(1);
}
/* bind() */
struct sockaddr_in s_in;
bzero(&s_in, sizeof(s_in));
s_in.sin_family = AF_INET;
s_in.sin_port = htons(7000);
s_in.sin_addr.s_addr = INADDR_ANY;
if (bind(s, (struct sockaddr *) &s_in, sizeof(s_in)) < 0) {
perror("bind");
exit(1);
}
/* listen() */
if (listen(s, 5) < 0) {
perror("listen");
exit(1);
}
/* Initial libevent. */
event_init();
/* Create event. */
struct event ev;
event_set(&ev, s, EV_READ | EV_PERSIST, connection_accept, &ev);
/* Add event. */
event_add(&ev, NULL);
event_dispatch();
return 0;
}
Thursday, November 24, 2005
Network Programming Using Libevent - (I)
在課堂上學過 Unix Network Programming 後,我們知道在處理多 User 時會有幾種方法解決:
然而,Event-based 在實做上的幾個複雜的地方在於:
另外值得注意的一點在於 libevent 使用的是 3-clause BSD license 而非 GPL,所以在不想公開程式碼 (像是商業用途) 的情況下會比其他的 Library 適合。
- 一個新的 Connection 進來,用
fork()
產生一個 Process 處理。 - 一個新的 Connection 進來,用
pthread_create()
產生一個 Thread 處理。 - 一個新的 Connection 進來,丟入 Event-based Array,由 Main Process 以 Nonblocking 的方式處理所有的 I/O。
- 用
fork()
的問題在於每一個 Connection 進來時的成本太高。 - 用 Multi-thread 的問題在於 Thread-safe 與 Deadlock 問題難以解決,另外有 Memory-leak 的問題要處理。
- 用 Event-based 的方式在於實做上不好寫,尤其是要注意到事件產生時必須 Nonblocking,於是會需要實做 Buffering 的問題,而 Multi-thread 所會遇到的 Memory-leak 問題在這邊會更嚴重。而在多 CPU 的系統上沒有辦法使用到所有的 CPU resource。
- 以 Poll 的方式解決:當一個 Process 處理完一個 Connection 後,不直接死掉,而繼續回到 accept() 的狀態繼續處理,但這樣會遇到 Memory-leak 的問題,於是採用這種方式的人通常會再加上「處理過 N 個 Connection 後死掉,由 Parent Process 再
fork()
一隻新的」。最有名的例子是 Apache 1.3。 - Thread-safe 的問題可以透過自己撰寫,或是尋找其他 Thread-safe Library 直接使用。Memory-leak 的問題可以試著透過 Garbage Collection Library 分析出來。Apache 2.0 的 Thread MPM 就是使用這個模式。
然而,Event-based 在實做上的幾個複雜的地方在於:
select()
與poll()
的效率過慢,造成每次要判斷「有哪些 Event 發生」這件事情的成本很高,這在 BSD 支援kqueue()
、Linux 支援epoll()
、Solaris 支援/dev/poll
後就解決了,但這兩組 Function 都不是 Standard,於是在不同的平台上就必須再改一次。- 因為 Nonblocking,所以在
write()
或是send()
時滿了需要自己 Buffering。 - 因為 Nonblocking,所以不能使用
fgets()
或是其他類似的 function,於是需要自己刻一個 Nonblocking 的fgets()
。但是使用者所丟過來的資料又不能保證在一次read()
或recv()
就有一行,於是要自己做 Buffering。
另外值得注意的一點在於 libevent 使用的是 3-clause BSD license 而非 GPL,所以在不想公開程式碼 (像是商業用途) 的情況下會比其他的 Library 適合。
Subscribe to:
Posts (Atom)