One of the most tricky part in using MINA is configuring your application's thread model, but once understood, it is fairly easy.
Disable the default
ThreadModel setting has been introduced since MINA 1.0. Later, we realized using
ThreadModel increases the complexity of configuration. It is strongly recommended to disable the default
Please note the other parts of this tutorial assume that you disabled
ThreadModel as addressed in this section.
Configure the number of I/O worker threads.
In MINA, there are three kinds of I/O worker threads in the NIO socket implementation.
- Acceptor thread accepts incoming connections, and forwards the connection to the I/O processor thread for read and write operations.
SocketAcceptorcreates one acceptor thread. You can't configure the number of the acceptor threads.
- Connector thread attempts connections to a remote peer, and forwards the succeeded connection to the I/O processor thread for read and write operations.
SocketConnectorcreates one connector thread. You can't configure the number of the connector threads, either.
- I/O processor thread performs the actual read and write operation until the connection is closed.
SocketConnectorcreates its own I/O processor thread(s). You can configure the number of the I/O processor threads. The default maximum number of the I/O processor threads is
the number of CPU cores + 1.
Therefore, the available configuration is the number of the I/O processor threads per
IoService. The following code creates a
SocketAcceptor with 4 I/O processor threads and one acceptor thread.
There's no rule of thumb in determining the number of I/O processor threads yet. You could probably start from adding 1 to the number or CPUs (cores). The following code creates a
nCPU + 1 I/O processor thrads and one acceptor thread.
Please note that you can do the same for
SocketConnector. There's no difference except that a connector thread is created instead of an acceptor thread.
ExecutorFilter to an
ExecutorFilter is an
IoFilter that forwards incoming I/O events to a
java.util.concurrent.Executor implementation. The events are forwarded to the next
IoFilter via the
Executor, which is usually a thread pool. You can add any number of
ExecutorFilter anywhere in the
IoFilterChain to implement any kind of thread model including from a simple thread pool to complex SEDA.
We didn't add any
ExecutorFilter so far. If no
ExecutorFilter is added, events are forwarded to an
IoHandler via method invocations. This means your business logic in your
IoHandler implementation will run in the I/O processor thread. We call this thread model a 'single thread model'. The single thread model is known to be adequate for low-latency network applications with CPU-bound business logic (e.g. game servers).
Typical network applications need an
ExecutorFilter to be inserted into the
IoFilterChain because the business logic has different resource usage pattern from the I/O processor threads. If you didn't add an
ExecutorFilter with an
IoHandler implementation which performs database operations, your entire server can block when a database operation occurs, especially when the database is under load. The following example configures an
IoService to add an
ExecutorFilter when a new
IoSession is created.
Please note that the
ExecutorFilter doesn't manage the life cycle of the specified
Executor at all. You have to shut down all worker threads of the specified
Executor when you are finished with it.
ExecutorFilter doesn't always mean that you use a thread pool though. There's no limitation on what
Executor implementation you specify at all.
Where should I put an
ExecutorFilter in an
It depends on the characteristics of your application. For an application with a
ProtocolCodecFilter implemetation and a usual
IoHandler implementation with database operations, I'd suggest you to add the
ExecutorFilter after the
ProtocolCodecFilter implementation. It is because the performance characteristic of most protocol codec implementations is CPU-bound, which is the same with I/O processor threads.
Be careful when you choose the thread pool type for
Executors.newCachedThreadPool() is always preferred by
IoService. It is because using other thread pool type can lead to unpredictable performance side effect in
IoService. Once all threads in the pool become in use,
IoService will start to block while it tries to acquire a thread from the pool and to start to show weird performance degradation, which is sometimes very hard to trace.
It is not recommended to share one thread pool for
You might want to share one thread pool for
ExecutorFilters instead of using one thread pool for each. It is not prohibited at all, but please note that it can cause a lot of problems unless you use a cached thread pool for
IoServices in such a case.