Scaleable Server architecture?

Started by
5 comments, last by hplus0603 19 years, 6 months ago
I've been working on a semi-scalable server for a current project and while it's great I was curious what would be the best way to do a large scaleable server. On my current server: Uses WSAAsyncSocket and all network is handled through the windows message pump via the network messages. Problems with this: - Network messages can be blocked if you are handling a message while you recieve another message. - Relys on the Windows message pump. Using a non-blocking server via the select() function: Good: - Still non-blocking Bad: - Not asynchronous - Still no way to handle messages that come while you are busy with a current message or if there is a message already waiting to be handled. What is the best way to handle events as soon as they are generated and also allow the server to go through and update all clients on it's connection list? Is it best to have a seperate connection server and then pass off connected clients to an "update server" that does nothing but process the requests for the X amount of players it has? Thoughts?
Advertisement
I tend to just use select, read however many of bytes are available then post them to a worker thread that does the actual computation. Depending on how complex the processing is, I tend to just use a thread pool with a round-robin dispatch.

Writes back to the clients work in a similar manor. The threads dump a serialized bytestream back into the writer fifo to be written out.

I haven't measured my actual capacity with this scheme, but it seems okay from a theoretical point of view. If I was restricting myself to Windows only, I'd probably use IO completion ports (I think that is what it is called) to bypass the fancy pants threading that I've already written...


I'd use a FreeBSD or OpenBSD server and use the kqueue system call.
On Windows NT and above(this includes Windows 2K and XP) for the best performance one should use completion ports.
What do you mean by "scalable" in this context?

Is it an FPS, and you want to scale to more players with more RAM?

Is it an RPG, and you want to scale the world to more square kilometers with faster CPUs?

Is it a trading card server, and you want to scale to more simultaneous connections with fatter networking pipes?

I e:

- What is the general category of server (and thus type of load)?
- What aspect or aspects of the game server do you want to scale?
- What are the inputs that you wish to take advantage of when scaling?

To answer a specific question:

Quote:What is the best way to handle events as soon as they are generated


The best way to handle each event exactly as it occurs is to have one CPU in your machine per possible event that can occur simultaneously.

However, I think you're staring yourself blind on the fact that there's queuing. Queuing is necessary, healthy, and a requirement for good throughput. What you should be worrying about is the possibility that more events and network traffic may be generated per time unit, than you are able to complete during that same time unit -- that's the point at which your server has exceeded its capacity. Before then, the server will have some idle capacity, and adding more users just means "adding more users," not actually "scaling."
enum Bool { True, False, FileNotFound };
Quote:Original post by hplus0603
What do you mean by "scalable" in this context?

Is it an FPS, and you want to scale to more players with more RAM?

Is it an RPG, and you want to scale the world to more square kilometers with faster CPUs?

Is it a trading card server, and you want to scale to more simultaneous connections with fatter networking pipes?

I e:

- What is the general category of server (and thus type of load)?
- What aspect or aspects of the game server do you want to scale?
- What are the inputs that you wish to take advantage of when scaling?

To answer a specific question:

Quote:What is the best way to handle events as soon as they are generated


The best way to handle each event exactly as it occurs is to have one CPU in your machine per possible event that can occur simultaneously.

However, I think you're staring yourself blind on the fact that there's queuing. Queuing is necessary, healthy, and a requirement for good throughput. What you should be worrying about is the possibility that more events and network traffic may be generated per time unit, than you are able to complete during that same time unit -- that's the point at which your server has exceeded its capacity. Before then, the server will have some idle capacity, and adding more users just means "adding more users," not actually "scaling."


You are right, I was talking about scaling as in adding more players to the current load a server can handle and the best(most effecient) way to deal with their requests.

The idea of a dedicated CPU per event is nice. I also like the idea of a worker thread to handle events and just have another thread that processes the event queue and handle updating the various clients.

Very interesting ways to approach the problem.
Quote:The idea of a dedicated CPU per event is nice.


Too bad I was being sarcastic :-)

You don't need to process events EXACTLY when they are generated/delivered. You need to process them within some short time after they arrive. As long as that short delay is shorter than the duration of a single time step of your world, you're fine, because the outcome of the simulation wouldn't have changed anyway (assuming your server steps everyone at the same time).

Thus, when you step the world, read out all the available data on all sockets, and dispatch to all the internal data recipients. Then update each object by increasing the world time. Then send out all the state updates that the objects may have generated while stepping. Then wait for the next world step time to arrive, and repeat.

Threading for network I/O usually hurts more than helps, because the extra overhead of locking in your threading queues costs more than the gain of running threading on one CPU and world update on another CPU. The actual time spent inside the kernel for the networking is usually negligible. Meanwhile, any world update needs to "lock" the world to make sure it makes consistent changes to the world, so if you had more than one thread trying to affect the world, you'd end up serializing anyway. Thus, threading means lots of work for usually negative gain.
enum Bool { True, False, FileNotFound };

This topic is closed to new replies.

Advertisement