Programming 版 (精华区)

发信人: JJason (C++ Primer), 信区: Programming
标  题: Fast UDP-Based Network Storage(zz)
发信站: 哈工大紫丁香 (2002年11月22日09:47:26 星期五), 站内信件


以下是刊登在CUJ杂志2002年12期上的一篇文章
///////////////////////////////////////////////////////////////////////
                Fast UDP-Based Network Storage
                                    Tim Kientzle
If network performance is critical, you can bypass that database of yours 
with this clever approach. 

-----------------------------------------------------------------------------

The relational database is the hands-down winner for general-purpose data 
storage. But that generality makes it less than ideal for some applications. 
In many cases, especially those where very high performance is critical, a 
custom server will fit your requirements much better. 

Over the last few years, I’ve been examining ways to manage session data for 
clusters of web servers. This particular application needs high-performance, 
low-latency storage of small pieces of data. After examining a number of 
different approaches, I found that a combination of UDP-based transport and 
memory-based storage gave exemplary performance in less than 200 lines of 
straightforward C code. 

In this article, I’ll explain why UDP is often a better fit than TCP for 
performance-driven network applications. I’ll then share the techniques you’l
l need to build your own custom storage service. Full example source code is 
available at <www.cuj.com/code>. 

TCP’s Drawbacks 
TCP is a natural fit for many services. A client opens a TCP connection to 
the server, and the two then exchange commands and results over that 
connection. The TCP connection itself defines a persistent session that the 
server uses to associate various resources with a particular client. 

TCP can be complex to use. Because connections can be long lived, TCP servers 
must support multiple simultaneous connections. Many servers use a separate 
thread or process for each connection, but this approach degrades under high 
load due to task-switching overhead. Single-threaded servers reduce 
task-switching overhead, but must use complex asynchronous I/O to juggle the 
connections.

Latency is also a problem with TCP. TCP’s three-way handshake adds a 
noticeable delay to each new connection. To reduce overhead, the operating 
system deliberately delays short packets in the hope that more data will 
follow, which adds additional latency at the end of each request or response. 
If most requests are short, that additional delay can be added to every 
request. 

UDP to the Rescue 
UDP is less well known than TCP. It provides a mechanism for programs to send 
and receive single-packet messages over IP. Although it has some significant 
limitations, UDP does manage to avoid the latency and connection-management 
problems that dog TCP. 

UDP clients send explicit packets; there is no connection to worry about and 
no delay before a packet is sent. Similarly, the server does not need to 
maintain knowledge of each client. The server simply receives a request, 
builds a response packet, and fires it back. This makes UDP ideal for simple 
request-response protocols such as DNS, NTP, or CLDAP. 

UDP’s Limitations 
UDP’s primary limitations are reliability and security. UDP provides no 
transmission guarantees, which means that a message might not get to its 
intended recipient. Over switched Ethernet, this is not much of a problem, as 
lost packets are rare. However, robust clients do need to be prepared to 
retry requests. 

Security is a more subtle issue. UDP servers tend to be subject to spoofing 
attacks, in which a request packet is sent with a forged source address. As a 
result, UDP is most useful inside a network protected by a good firewall. Any 
authentication information must be provided with each request, which can lead 
to excessive overhead if strong cryptographic authentication is required. Of 
course, if you’re choosing UDP for performance reasons, strong cryptographic 
authentication is probably not a concern. 

The UDP API 
UDP is supported by the standard Unix sockets API. Because there are no 
connections to deal with, only sending and receiving of packets, this portion 
of the API is really quite simple. 

The first step for the server is to create the socket using the socket call 
and bind it to a specific port using the bind call, as shown in Listing 1. 
The server then simply waits for a packet to arrive and responds when it 
does. The socket’s API provides a recvfrom call, which waits for an incoming 
packet and places it into your buffer. The recvfrom call also provides you 
with the address of the sender, which you can then use in a corresponding 
sendto call to send a response. Listing 2 outlines this process. 

The client is only a little different. Because the client doesn’t care what 
port it uses, the bind call is unnecessary. The client needs to build an addr 
structure with the server address and then send the request using sendto. 

A naive client would then wait for a response with recvfrom. Unfortunately, 
this will simply hang if the response packet doesn’t arrive. As a practical 
matter, it’s necessary to enforce some sort of timeout. The standard 
technique on Unix is to use a select call to wait for data on the socket and 
then only call recvfrom when and if data does arrive. Listing 3 provides a 
rough outline of the client logic. 

Session Storage 
The specific application that led me to consider UDP was session storage for 
web server farms. Websites routinely track a certain amount of information 
about each visitor. This involves assigning each visitor a session token, 
which is generally stored in a client-side cookie. The client provides that 
information with each request, and the server can then use it to access 
information about the user. For example, the server might store the user’s 
name, geographic location, or shopping cart information. 

Many websites store this data on each front-end server, often in RAM. This 
creates a number of awkward problems: it is difficult or impossible to mix 
different implementation technologies; the failure of a front-end server will 
lose session data; the load-balancing mechanism must provide “session 
persistence,” in which requests from a single user are consistently 
dispatched to the same server.

Using a shared back-end session server eliminates these problems. A standard 
protocol allows different implementation technologies to be mixed on the same 
site. Front-end servers can come and go without affecting current users. 
Session persistence is not required, since any front-end server can access 
the session data at any time. 

UDP-Based Session Storage 
Session storage requires only a few operations: clients need to be able to 
allocate a session, read a session, or write to a session. To keep things 
simple, I’ve kept the request and response formats identical: 1-byte 
operation/status code, 7-byte session identifier, and up to 1,024 bytes of 
session data. Valid responses always set the status byte to zero, so you can 
simply treat the first eight bytes as a session key. 

To create a new session, the client sends a packet whose first byte is zero. 
The server responds with a newly created session, which includes a newly 
allocated session key. That key will generally be encoded into a cookie for 
the user’s web browser. To read or write the session data, the client 
provides the correct operation code (1 to read, 2 to write) and session key. 
For a write operation, the session data is appended. In either case, the 
server will always respond with the full session (whose first byte is always 
zero) unless there is an error, in which case the first byte of the response 
indicates the nature of the problem. 

Limitations 
This protocol is very simple and should be easy to implement in just about 
any programming language. However, there are a few awkward points that you 
may need to consider. First, there is no explicit support for session 
timeouts; if the web application needs sessions to expire, it will need to 
store an expiration time in the session data. 

If your session data is very large, the resulting UDP packets will be 
transferred as multiple IP fragments. This increases the likelihood of 
dropped packets, so it should be avoided over long-haul networks.

One way to limit the size of your session data is to store only the most 
critical data on the session server. The remaining data can be stored in a 
slower but more flexible relational database. For example, a shopping cart 
might store the total number of items in the session server so that every 
page can display a basic shopping cart status, while the complete list of 
items is kept in a relational database. 

Implementation Notes 
The programs udpserver.c and udpclient.c (available for download at 
<www.cuj.com/code>) provide a sample implementation of this protocol. The 
client exercises the full get/put protocol to ensure that it gets consistent 
results and should be easy to modify for your own requirements. 

The server is more interesting. To keep things as efficient as possible, the 
server is single threaded and stores all session data in memory. By limiting 
each session to 1,024 bytes of data, a server with 1 GB of memory can handle 
just under one million simultaneous sessions without swapping. This is more 
than enough for most websites.

The combination of UDP and simple memory-based storage gives impressive 
performance. This server can easily saturate a 100 Mbps Ethernet — upwards 
of 15,000 requests per second. A full client request, including the network 
round trip, takes a mere 300 microseconds, which is faster than most hard 
disks. I’ve not yet had the opportunity to benchmark this server over 
Gigabit Ethernet.

Recall that the server chooses the session IDs. This is a key feature of the 
protocol, which allows the server to store the array index directly as part 
of the session ID. Incoming requests can then be immediately mapped to a 
particular array slot. Since there are only a limited number of array slots, 
the server keeps a list of the least recently used slots and reuses them as 
necessary. Filling the remainder of the session ID with a random value helps 
guard against any conflicts caused by this reuse. 

Future Directions 
The current implementation does not provide any persistence. While this is 
fine for most e-commerce sites that only need short-term session management, 
it’s less appropriate for portal sites that want to use long-lived cookies 
to provide easy access. Long-lived sessions really require some form of 
disk-based storage. Adding disk-based storage requires care to avoid 
impacting performance. 

One approach would use two threads. One thread is similar to the current 
fast-running server, handling most requests directly from memory. Sessions 
that are not immediately available in memory are placed on a queue to be 
handled by the second thread. That thread moves sessions from disk into 
memory before sending a response. Of course, the simple approach of embedding 
a slot number into the session ID will not work with such a design. Instead, 
use a hash table or balanced tree to map session IDs to sessions. 

About the Author
Tim Kientzle is an independent consultant, instructor, and software developer 
based in Oakland, California. He can be contacted at kientzle@acm.org. 
 
--

     人生,就是一团欲望:
     欲望没有满足的时候就是痛苦,
     欲望被满足的时候就是无聊;
     人生就是在痛苦与无聊之间徘徊。

※ 来源:·哈工大紫丁香 bbs.hit.edu.cn·[FROM: 202.118.229.69]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:211.188毫秒