BBS水木清华站∶精华区

发信人: cybergene (活泼的基因), 信区: Linux        
标  题: The C10K problem 
发信站: BBS 水木清华站 (Mon Dec 13 14:03:09 1999) 
 
 
The C10K problem 
 
It's time for web servers to handle ten thousand clients simultaneously, 
 don't you think? After all, the web is a big place now.  
 
And computers are big, too. You can buy a 500MHz machine with 1 gigabyte 
 of RAM and six 100Mbit/sec Ethernet card for 
$3000 or so. Let's see - at 10000 clients, that's 50KHz, 100Kbytes,  
and 60Kbits/sec per client. It shouldn't take any more 
horsepower than that to take four kilobytes from the disk and send  
them to the network once a second for each of ten thousand 
clients. (That works out to $0.30 per client, by the way. Those  
$100/client licensing fees some operating systems charge are 
starting to look a little heavy!) So hardware is no longer the  
bottleneck.  
 
One of the busiest ftp sites, cdrom.com, actually can handle 10000  
clients simultaneously through a Gigabit Ethernet pipe to its 
ISP. ( Here's a somewhat dated page about their configuration.) Pipes  
this fast aren't common yet, but technology is improving 
rapidly.  
 
There's interest in benchmarking this kind of configuration; see the  
discussion on June 28th 1999 on linux-kernel, in which people 
propose setting up a testbed that can be used for ongoing benchmarks.  
 
There appears to be interest in this direction from the NSF, too; see  
the Web100 project.  
 
With that in mind, here are a few notes on how to configure operating  
systems and write code to support thousands of clients. The 
discussion centers around Unix-like operating systems, for obvious  
reasons.  
 
Book to Read First 
 
If you haven't read it already, go out and get a copy of Unix Network  
Programming : Networking Apis: Sockets and Xti (Volume 
1) by the late W. Richard Stevens. It describes many of the I/O  
strategies and pitfalls related to writing high-performance servers. 
It even talks about the 'thundering herd' problem.  
 
I/O Strategies 
 
There seem to be four ways of writing a fast web server to handle many  
clients:  
 
   1.Build the server code into the kernel. Novell and Microsoft are  
both said to have done this at various times, at least one 
     NFS implementation does this, and khttpd does this for Linux and  
static web pages.  
     The linux-kernel list has been discussing the pros and cons of this 
 approach, and the consensus seems to be instead of 
     moving web servers into the kernel, the kernel should have the  
smallest possible hooks added to improve web server 
     performance. That way, other kinds of servers can benefit. See e.g. 
 Zach Brown's remarks about userland vs. kernel http 
     servers.  
 
   2.serve one client with each server thread, and let read() and  
write() block. (This is the only model supported by Java.)  
 
   3.serve many clients with each server process or thread, set  
nonblocking mode on all network handles, and use select() or 
     poll() to tell which network handle has data waiting. This is the  
traditional favorite. It's still being improved; see e.g. Niels 
     Provos' benchmarks with hinting poll and thttpd.  
     An important bottleneck in this method is that read() or sendfile() 
 from disk blocks if the page is not in core at the moment; 
     setting nonblocking mode on a disk file handle has no effect.  
Same thing goes for memory-mapped disk files. The first time 
     a server needs disk I/O, its process blocks, all clients must wait, 
 and that raw nonthreaded performance goes to waste.  
     Worker threads or processes that do the disk I/O can get around  
this bottleneck. One approach is to use memory-mapped 
     files, and if mincore() indicates I/O is needed, ask a worker to do 
 the I/O, and continue handling network traffic. Jef 
     Poskanzer mentions that Pai, Druschel, and Zwaenepoel's Flash web  
server uses this trick; they gave a talk at Usenix '99 
     on it. It looks like mincore() is available in BSD-derived Unixes  
like FreeBSD and Solaris, but is not part of the Single Unix 
     Specification.  
 
   4.serve many clients with each server process or thread, and use  
asynchronous I/O to avoid blocking. This has not yet 
     become popular, possibly because of poorly designed asynchronous  
I/O interfaces. Zach Brown (author of HoserFTPD) 
     thinks this might now be the way to go for highest performance; see 
 his 14 April 1999 post to hftpd-users.  
     There are several flavors of asynchronous I/O:  
          the aio_ interface (scroll down from that link to  
"Asynchronous input and output"), which associates a signal and 
          value with each I/O operation. Signals and their values are  
queued and delivered efficiently to the user process. This 
          is from the POSIX 1003.1b realtime extensions, and is also  
in the Single Unix Specification, version 2, and in glibc 
          2.1. However, the generic glibc 2.1 implementation may have  
been written for standards compliance rather than 
          performance.  
 
          Much of the overhead of calling poll() can be eliminated by  
using fcntl(fd, F_SETSIG, signum), which associates a 
          signal with each file descriptor, and raises that signal  
when a normal I/O function like read() or write() completes. 
          Available in Linux 2.3.21 or higher.  
          To use this, you choose a realtime signal number, mask that  
signal and SIGIO with sigprocmask(), use fcntl(fd, 
          F_SETSIG, signum) on each fd, write a normal poll() outer  
loop, and inside it, after you've handled all the fd's 
          noticed by poll(), you loop calling sigwaitinfo(). 
          If sigwaitinfo returns your realtime signal, siginfo.si_fd and 
 siginfo.si_band give the same information as pollfd.fd and 
          pollfd.revents would after a call to poll(), so you handle the 
 i/o, and continue calling sigwaitinfo(). 
          If sigwaitinfo returns a traditional SIGIO, the signal queue  
overflowed, so you flush the signal queue by temporarily 
          changing the signal handler to SIG_DFL, and break back to  
the outer poll() loop.  
          You can support older kernels by surrounding the sigwaitinfo  
code with #if defined(LINUX_VERSION_CODE) 
          && (LINUX_VERSION_CODE >= KERNEL_VERSION(2.3.21))  
          See Zach Brown's phhttpd for example code that uses this  
feature, and deals with some of the gotchas, e.g. events 
          queued before an fd is dealt with or closed, but arriving  
afterwards.  
 
          SIGIO (see glibc doc or BSD Sockets doc) -- doesn't tell you  
which handle needs servicing, so it seems kind of 
          coarse. Used by the Linux F_SETSIG/aio_ implementation as a  
fallback when the realtime signal queue overflows. 
          Here's an example of its use. (Was partly broken in Linux  
kernels 2.2.0 - 2.2.7, fixed in 2.2.8.)  
 
Richard Gooch has written a paper discussing these options.  
Interesting reading.  
 
The Apache mailing lists have some interesting posts about why they  
prefer not to use select() (basically, they think that makes 
plugins harder). Still, they're planning to use  
select()/poll()/sendfile() for static requests in Apache 2.0.  
 
Mark Russinovich wrote an editorial and an article discussing I/O  
strategy issues in the 2.2 Linux kernel. Worth reading, even he 
seems misinformed on some points. In particular, he seems to think  
that Linux 2.2's asyncrhonous I/O (see F_SETSIG above) 
doesn't notify the user process when data is ready, only when new  
connections arrive. This seems like a bizarre misunderstanding. 
See also comments on an earlier draft, a rebuttal from Mingo,  
Russinovich's comments of 2 May 1999, a rebuttal from Alan Cox, 
and various posts to linux-kernel. I suspect he was trying to say that  
Linux doesn't support asynchronous disk I/O, which is 
somewhat true.  
 
There was an interesting discussion on linux-kernel in September 1999  
titled "> 15,000 Simultaneous Connections". (second 
week, third week) Highlights:  
 
     Ed Hall posted a few notes on his experiences; he's achieved  
1000 connects/second on a UP P2/333 running Solaris. His 
     code used a small pool of threads (1 or 2 per CPU) each managing  
a large number of clients using "an event-based model". 
     Mike Jagdis posted an analysis of poll/select overhead, and said  
"The current select/poll implementation can be improved 
     significantly, especially in the blocking case, but the overhead  
will still increase with the number of descriptors because 
     select/poll does not, and cannot, remember what descriptors are  
interesting. This would be easy to fix with a new API. 
     Suggestions are welcome..."  
     Mike posted about his work on improving select() and poll().  
     Mike posted a bit about a possible API to replace poll()/select(): 
 "How about a 'device like' API where you write 'pollfd 
     like' structs, the 'device' listens for events and delivers 'pollfd 
 like' structs representing them when you read it? ... "  
     Rogier Wolff suggested using "the API that the digital guys  
suggested", http://www.cs.rice.edu/~gaurav/papers/usenix99.ps  
     Joerg Pommnitz pointed out that any new API along these lines  
should be able to wait for not just file descriptor events, but 
     also signals and maybe SYSV-IPC. Our synchronization primitives  
should certainly be able to do what Win32's 
     WaitForMultipleObjects can, at least.  
     Stephen Tweedie asserted that the combination of F_SETSIG, queued  
realtime signals, and sigwaitinfo() was a superset of 
     the API proposed in http://www.cs.rice. 
edu/~gaurav/papers/usenix99.ps. He also mentions that you keep the  
signal 
     blocked at all times if you're interested in performance; instead  
of the signal being delivered asynchronously, the process 
     grabs the next one from the queue with sigwaitinfo().  
     Jayson Nordwick compared completion ports with the F_SETSIG  
synchronous event model, and concluded they're pretty 
     similar.  
     Alan Cox noted that an older rev of SCT's SIGIO patch is included  
in 2.3.18ac.  
     Jordan Mendelson posted some example code showing how to use  
F_SETSIG.  
     Stephen C. Tweedie continued the comparison of completion ports and 
 F_SETSIG, and noted: "With a signal dequeuing 
     mechanism, your application is going to get signals destined for  
various library components if libraries are using the same 
     mechanism," but the library can set up its own signal handler, so  
this shouldn't affect the program (much).  
     Doug Royer noted that he'd gotten 100,000 connections on Solaris  
2.6 while he was working on the Sun calendar server. 
     Others chimed in with estimates of how much RAM that would  
require on Linux, and what bottlenecks would be hit.  
 
Interesting reading!  
 
Limits on open filehandles 
 
     Any Unix: the limits set by ulimit or setrlimit.  
     Solaris: see the Solaris FAQ, question 3.45.  
     FreeBSD: use sysctl -w kern.maxfiles=nnnn to raise limit  
     Linux: See Bodo Bauer's /proc documentation. On current 2.2.x  
kernels,  
 
     echo 32768 > /proc/sys/fs/file-max 
     echo 65536 > /proc/sys/fs/inode-max 
 
     increases the system limit on open files, and  
 
     ulimit -n 32768 
 
     increases the current process' limit. I verified that a process  
on Red Hat 6.0 (2.2.5 or so plus patches) can open at least 
     31000 file descriptors this way. Another fellow has verified that a 
 process on 2.2.12 can open at least 90000 file 
     descriptors this way (with appropriate limits). The upper bound  
seems to be available memory.  
     Stephen C. Tweedie posted about how to set ulimit limits globally  
or per-user at boot time using initscript and pam_limit.  
     In older 2.2 kernels, though, the number of open files per  
process is still limited to 1024, even with the above changes.  
     See also Oskar's 1998 post, which talks about the per-process and  
system-wide limits on file descriptors in the 2.0.36 
     kernel.  
 
Limits on threads 
 
On any architecture, you may need to reduce the amount of stack space  
allocated for each thread to avoid running out of virtual 
memory. You can set this at runtime with pthread_attr_init() if you're  
using pthreads.  
 
     Solaris: it supports as many threads as will fit in memory, I hear. 
  
     FreeBSD: ?  
     Linux: Even the 2.2.13 kernel limits the number of threads, at  
least on Intel. I don't know what the limits are on other 
     architectures. Mingo posted a patch for 2.1.131 on Intel that  
removed this limit. It appears to be integrated into 2.3.20.  
 
     See also Volano's detailed instructions for raising file, thread,  
and FD_SET limits in the 2.2 kernel. Wow. This stunning little 
     document steps you through a lot of stuff that would be hard to  
figure out yourself. This is a must-read, even though some 
     of its advice is already out of date.  
     Java: See Volano's detailed benchmark info, plus their info on  
how to tune various systems to handle lots of threads.  
 
Other limits/tips 
 
     select() is limited to FD_SETSIZE handles. This limit is compiled  
in to the standard library and user programs. The similar 
     call poll() does not have a comparable limit, and can have less  
overhead than select().  
     Old system libraries might use 16 bit variables to hold file  
handles, which causes trouble above 32767 handles. glibc2.1 
     should be ok.  
     Many systems use 16 bit variables to hold process or thread id's.  
It would be interesting to port the Volano scalability 
     benchmark to C, and see what the upper limit on number of threads  
is for the various operating systems.  
     Too much thread-local memory is preallocated by some operating  
systems; if each thread gets 1MB, and total VM space 
     is 2GB, that creates an upper limit of 2000 threads.  
     Normally, data gets copied many times on its way from here to  
there. mmap() and sendfile() can be used to reduce this 
     overhead in some cases. IO-Lite is a proposal (already  
implemented on FreeBSD) for a set of I/O primitives that gets rid 
     of the need for many copies. It's sexy; go read it. But see also  
Alan Cox's opinion of zero-copy.  
     The sendfile() function in Linux and FreeBSD lets you tell the  
kernel to send part or all of a file. This lets the OS do it as 
     efficiently as possible. It can be used equally well in servers  
using threads or servers using nonblocking I/O. (In Linux, It's 
     poorly documented at the moment; use _syscall4 to call it. Andi  
Kleen is writing new man pages that cover this.) Rumor 
     has it, ftp.cdrom.com benefitted noticably from sendfile().  
     A new socket option under Linux, TCP_CORK, tells the kernel to  
avoid sending partial frames, which helps a bit e.g. 
     when there are lots of little write() calls you can't bundle  
together for some reason. Unsetting the option flushes the buffer.  
     Not all threads are created equal. The clone() function in Linux  
(and its friends in other operating systems) lets you create a 
     thread that has its own current working directory, for instance,  
which can be very helpful when implementing an ftp server. 
     See Hoser FTPd for an example of the use of native threads rather  
than pthreads.  
     To keep the number of filehandles per process down, servers can  
fork() once they reach the desired maximum; the child 
     finishes serving the existing clients, and the parent accepts and  
services new clients. (If the desired maximum is 1, this 
     degenerates to the classical one-process-per-client model.)  
     One developer using sendfile() with Freebsd reports that using  
POLLWRBAND instead of POLLOUT makes a big 
     difference.  
     Look at the performance comparison graph at the bottom of http: 
//www.acme.com/software/thttpd/benchmarks.html. 
     Notice how various servers have trouble above 128 connections, even 
 on Solaris 2.6? Anyone who figures out why, let me 
     know.  
     Note: if the TCP stack has a bug that causes a short (200ms)  
delay at SYN or FIN time, as Linux 2.2.0-2.2.6 had, and 
     the OS or http daemon has a hard limit on the number of connections 
 open, you would expect exactly this behavior. There 
     may be other causes.  
     "Re: fix for hybrid server problems" by Vivek Sadananda Pai  
(vivek@cs.rice.edu) on new-httpd, May 9th, notes:  
 
          "I've compared the raw performance of a select-based server  
with a multiple-process server on both 
          FreeBSD and Solaris/x86. On microbenchmarks, there's only a  
marginal difference in performance stemming 
          from the software architecture. The big performance win for  
select-based servers stems from doing 
          application-level caching. While multiple-process servers  
can do it at a higher cost, it's harder to get the same 
          benefits on real workloads (vs microbenchmarks). I'll be  
presenting those measurements as part of a paper 
          that'll appear at the next Usenix conference. If you've got  
postscript, the paper is available at 
          http://www.cs.rice.edu/~vivek/flash99/"  
 
Kernel Issues 
 
For Linux, it looks like kernel bottlenecks are being fixed constantly. 
 See Linux HQ, Kernel Traffic, and the Linux-Kernel mailing 
list (Example interesting posts by a user asking how to tune, and Dean  
Gaudet)  
 
In March 1999, Microsoft sponsored a benchmark comparing NT to Linux  
at serving large numbers of http and smb clients, in 
which they failed to see good results from Linux. See also my article on 
 Mindcraft's April 1999 Benchmarks for more info.  
 
See also The Linux Scalability Project. They're doing interesting work, 
 including Niels Provos' hinting poll patch.  
 
See also Mike Jagdis' work on improving select() and poll(); here's  
Mike's post about it.  
 
Solaris 7 supposedly supports an even more efficient poll() scheme which 
 involves /dev/poll; apparantly, if you like, you can avoid 
having to tell the OS constantly what files you're interested in,  
which should help throughput.  
 
Mohit Aron (aron@cs.rice.edu) writes that rate-based clocking in TCP can 
 improve HTTP response time over 'slow' connections 
by 80%.  
 
Measuring Server Performance 
 
Two tests in particular are simple, interesting, and hard:  
 
   1.raw connections per second (how many 512 byte files per second  
can you serve?)  
   2.total transfer rate on large files with many slow clients (how many 
 28.8k modem clients can simultaneously download from 
     your server before performance goes to pot?)  
 
Jef Poskanzer has published benchmarks comparing many web servers. See 
http://www.acme.com/software/thttpd/benchmarks.html for his results.  
 
I also have a few old notes about comparing thttpd to Apache that may be 
 of interest to beginners.  
 
Chuck Lever keeps reminding us about Banga and Druschel's paper on web  
server benchmarking. It's worth a read.  
 
Interesting sigio/siginfo-based servers 
 
     Zach Brown's phhttpd - "a quick web server that was written to  
showcase the sigio/siginfo event model. consider this code 
     highly experimental and yourself highly mental if you try and use  
it in a production environment." Uses the siginfo features of 
     2.3.21 or later, and includes the needed patches for earlier  
kernels. Rumored to be even faster than khttpd. See his post of 
     31 May 1999 for some notes.  
 
Interesting select()-based servers 
 
     thttpd Very simple. Uses a single process. It has good performance, 
 but doesn't scale with the number of CPU's.  
     mathopd. Similar to thttpd.  
     fhttpd  
     boa  
     Roxen  
     Zeus, a commercial server that tries to be the absolute fastest.  
See their tuning guide.  
     The other non-Java servers listed at http://www.acme. 
com/software/thttpd/benchmarks.html  
     BetaFTPd  
     Flash-Lite - web server using IO-Lite.  
     Flash: An efficient and portable Web server -- uses select(),  
mmap(), mincore()  
     xitami - uses select() to implement its own thread abstraction  
for portability to systems without threads.  
     Medusa - a server-writing toolkit in Python that tries to deliver  
very high performance.  
 
Interesting thread-based servers 
 
     Hoser FTPD. See their benchmark page.  
     Peter Eriksson's phttpd and  
     pftpd  
     The Java-based servers listed at http://www.acme. 
com/software/thttpd/benchmarks.html  
     Sun's Java Web Server (which has been reported to handle 500  
simultaneous clients)  
 
Interesting in-kernel servers 
 
     khttpd  
 
Other interesting links 
 
     Novell's FastCache -- claims 10000 hits per second. Quite the  
pretty performance graph.  
     Rik van Riel's Linux Performance Tuning site  
 
 
Copyright 1999 Dan Kegel 
dank@alumni.caltech.edu 
Last updated: 24 October 1999 
[Return to www.kegel.com]  
 
-- 
Windows: 32 bit extensions and a graphical shell for a 16 bit patch to 
an 8 bit operating system originally coded for a 4 bit microprocessor, 
written by a 2 bit company, that can't stand 1 bit of competition. 
 
Welcome to DNA Studio: http://dnastudio.dhs.org 
new software, navupdate, wallpapers, mp3z, linux, forums...... 
 
※ 来源:·BBS 水木清华站 bbs.net.tsinghua.edu.cn·[FROM: 202.112.85.250] 

BBS水木清华站∶精华区