मेरे शोध के एक हिस्से के रूप में मैं जावा में एक उच्च लोड टीसीपी/आईपी इको सर्वर लिख रहा हूं। मैं लगभग 3-4k ग्राहकों की सेवा करना चाहता हूं और प्रति सेकंड अधिकतम संभव संदेश देख सकता हूं कि मैं इससे बाहर निकल सकता हूं। संदेश का आकार काफी छोटा है - 100 बाइट तक। इस काम में कोई व्यावहारिक उद्देश्य नहीं है - केवल एक शोध।जावा हाई-लोड एनआईओ टीसीपी सर्वर
मैंने देखा है कि कई प्रस्तुतियों (हॉर्नेटक बेंचमार्क, एलएमएक्स विघटन वार्ता, आदि) के अनुसार, असली दुनिया उच्च लोड सिस्टम प्रति सेकंड लाखों लेनदेन की सेवा करते हैं (मुझे विश्वास है कि विघटनकर्ता ने लगभग 6 मिलियन और हॉर्नेट का उल्लेख किया है 8.5)। उदाहरण के लिए, this post बताता है कि 40 एम एमपीएस तक हासिल करना संभव है। इसलिए मैंने इसे आधुनिक हार्डवेयर के सक्षम होने के बारे में अनुमान लगाया।
मैंने सरल एकल थ्रेडेड एनआईओ सर्वर लिखा और एक लोड टेस्ट लॉन्च किया। मुझे आश्चर्य नहीं हुआ कि मैं स्थानीयहोस्ट पर केवल 100k एमपीएस और वास्तविक नेटवर्किंग के साथ 25k प्राप्त कर सकता हूं। संख्या काफी छोटी दिखती है। मैं Win7 x64, कोर i7 पर परीक्षण कर रहा था। सीपीयू लोड को देखते हुए - केवल एक कोर व्यस्त है (जिसे एकल-थ्रेडेड ऐप पर अपेक्षित किया जाता है), जबकि शेष निष्क्रिय रहते हैं। हालांकि अगर मैं सभी 8 कोर (वर्चुअल समेत) लोड करता हूं, तो मेरे पास 800k से अधिक एमपीएस नहीं होंगे - 0 मिलियन के करीब भी नहीं :)
मेरा प्रश्न है: ग्राहकों को भारी मात्रा में संदेश देने के लिए एक सामान्य पैटर्न क्या है ? क्या मुझे एक एकल जेवीएम के अंदर कई अलग-अलग सॉकेट पर नेटवर्किंग लोड वितरित करना चाहिए और कई कोरों को लोड वितरित करने के लिए हैप्रोक्सी जैसे कुछ प्रकार के लोड बैलेंसर का उपयोग करना चाहिए? या मुझे अपने एनआईओ कोड में एकाधिक चयनकर्ताओं का उपयोग करने की ओर देखना चाहिए? या हो सकता है कि कई जेवीएम के बीच लोड भी वितरित करें और उनके बीच एक इंटर-प्रोसेस संचार बनाने के लिए क्रॉनिकल का उपयोग करें? CentOS जैसे उचित सर्वरसाइड ओएस पर परीक्षण करना एक बड़ा अंतर बनाता है (शायद यह विंडोज़ है जो चीजों को धीमा कर देता है)?
नीचे मेरे सर्वर का नमूना कोड है। यह किसी भी आने वाले डेटा के लिए हमेशा "ठीक" के साथ जवाब देता है। मुझे पता है कि असली दुनिया में मुझे संदेश के आकार को ट्रैक करने की आवश्यकता होगी और तैयार रहें कि एक संदेश कई पाठों के बीच विभाजित हो सकता है, हालांकि मैं अब चीजों को सुपर-सरल रखना चाहता हूं।
public class EchoServer {
private static final int BUFFER_SIZE = 1024;
private final static int DEFAULT_PORT = 9090;
// The buffer into which we'll read data when it's available
private ByteBuffer readBuffer = ByteBuffer.allocate(BUFFER_SIZE);
private InetAddress hostAddress = null;
private int port;
private Selector selector;
private long loopTime;
private long numMessages = 0;
public EchoServer() throws IOException {
this(DEFAULT_PORT);
}
public EchoServer(int port) throws IOException {
this.port = port;
selector = initSelector();
loop();
}
private void loop() {
while (true) {
try{
selector.select();
Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();
while (selectedKeys.hasNext()) {
SelectionKey key = selectedKeys.next();
selectedKeys.remove();
if (!key.isValid()) {
continue;
}
// Check what event is available and deal with it
if (key.isAcceptable()) {
accept(key);
} else if (key.isReadable()) {
read(key);
} else if (key.isWritable()) {
write(key);
}
}
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
}
private void accept(SelectionKey key) throws IOException {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.setOption(StandardSocketOptions.SO_KEEPALIVE, true);
socketChannel.setOption(StandardSocketOptions.TCP_NODELAY, true);
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("Client is connected");
}
private void read(SelectionKey key) throws IOException {
SocketChannel socketChannel = (SocketChannel) key.channel();
// Clear out our read buffer so it's ready for new data
readBuffer.clear();
// Attempt to read off the channel
int numRead;
try {
numRead = socketChannel.read(readBuffer);
} catch (IOException e) {
key.cancel();
socketChannel.close();
System.out.println("Forceful shutdown");
return;
}
if (numRead == -1) {
System.out.println("Graceful shutdown");
key.channel().close();
key.cancel();
return;
}
socketChannel.register(selector, SelectionKey.OP_WRITE);
numMessages++;
if (numMessages%100000 == 0) {
long elapsed = System.currentTimeMillis() - loopTime;
loopTime = System.currentTimeMillis();
System.out.println(elapsed);
}
}
private void write(SelectionKey key) throws IOException {
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer dummyResponse = ByteBuffer.wrap("ok".getBytes("UTF-8"));
socketChannel.write(dummyResponse);
if (dummyResponse.remaining() > 0) {
System.err.print("Filled UP");
}
key.interestOps(SelectionKey.OP_READ);
}
private Selector initSelector() throws IOException {
Selector socketSelector = SelectorProvider.provider().openSelector();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
InetSocketAddress isa = new InetSocketAddress(hostAddress, port);
serverChannel.socket().bind(isa);
serverChannel.register(socketSelector, SelectionKey.OP_ACCEPT);
return socketSelector;
}
public static void main(String[] args) throws IOException {
System.out.println("Starting echo server");
new EchoServer();
}
}
40 मिलियन लेनदेन प्रति सेकंड ** प्रति सर्वर ** ?! वे एक बाइट के साथ जवाब देना चाहिए। –
मुझे विश्वास है कि व्यापार तर्क के बिना था - केवल संदेशों का एक roundtrips। लेकिन हाँ, मैंने उस पोस्ट में जो देखा है। अद्भुत संख्या – Juriy
लिखने से पहले आपको OP_WRITE की प्रतीक्षा करने की आवश्यकता नहीं है। शून्य लंबाई लिखने के बाद आपको केवल ऐसा करने की आवश्यकता है। चैनल बंद करने से पहले या बाद में आपको कुंजी को रद्द करने की आवश्यकता नहीं है। – EJP