Running Aeron
Before looking at how to run Aeron, it's worth understanding how it is built at a high level. The four major Aeron components are built as Agrona Agents: the Client Conductor, Driver Conductor, Sender and Receiver.
Agrona
Agrona is another open source project by Real Logic, sitting alongside Aeron. It is a treasure trove of "High Performance data structures and utility methods for Java", including:
- Long2LongHashMap - a hash map specialised for primitive key and value pairs (the link is to Int2IntHashMap, which this is generated from)
- ManyToOneRingBuffer - a ring-buffer that supports the exchange of messages from many producers to a single consumer
- DirectBuffer - an abstraction over a range of buffer types that allows fields to be read in native typed fashion
- Agent - defines a unit of work that fulfills a role within a system
- IdleStrategy - for use by threads when they do not have work to do
Agrona Agents
As above, an Agent defines a unit of work that fulfills a role within a system. It implements the Agent interface, which essentially has one method:
int doWork();
A unit of work, known as a duty cycle, should be defined in doWork()
. It should return either 0 to
indicate no work was necessary (it was idle), or a positive integer if it did some work.
An Agent does not have a thread of its own, but is invoked repeatedly by an outside thread. Decoupling the work from the threading model means the threading model can be changed easily. A thread could be created per Agent, or a single thread could service several Agents, or some other configuration.
An Agent should perform its work quickly. Long-running operations should be broken up and performed over multiple duty cycles. I/O should be asynchronous. One duty cycle might send a message, and a later duty cycle receive the response. Agents should not create threads of their own. If an Agent wants to communicate with another Agent, it should do so by passing a message, e.g. by injecting a RingBuffer into both Agents, which one writes to and the other reads from.
Idle Strategies
Agents are used hand-in-hand with an IdleStrategy, which defines what to do when the Agent is idle (when doWork()
returns 0). There are many implementations of IdleStrategy, including:
- NoOpIdleStrategy - Low-latency idle strategy to be employed in loops that do significant work on each iteration such that any work in the idle strategy would be wasteful
- BackoffIdleStrategy - backs off more and more while the Agent continues to be idle
- YieldingIdleStrategy -
calls
Thread.yield()
when the Agent is idle
It is typical to configure the IdleStrategy per environment, for example using a NoOpIdleStrategy in production to provide the lowest latency at the cost of continuously high CPU load, and BackoffIdleStrategy on a developer machine.
Agent Runners
An AgentRunner is a container for an Agent and an IdleStrategy. It creates a thread to run the Agent in a loop that looks like:
while (isRunning)
{
idleStrategy.idle(agent.doWork());
}
A
CompositeAgent
is an Agent that is composed of other Agents. When doWork()
is called on it, it calls doWork()
on each of the
composed Agents, then returns the sum of their results. A CompositeAgent can be used with an AgentRunner to service
several Agents with one thread.
Now that we've looked at Agents, let's look at how Aeron uses them.
Running the Media Driver
The Media Driver can be run in its own JVM (the usual case) or embedded within an application. There is also a C Media Driver that can be used when running standalone for the best performance.
The Media Driver contains three Agents: the Driver Conductor, Sender and Receiver. Aeron has a threading mode configuration that controls how the Agents are invoked. It can utilise 0, 1, 2 or 3 threads:
- INVOKER mode (0 threads): no threads are created, the Media Driver has to be embedded within an application, which is responsible for invoking the duty cycle of the Agents. Useful for low CPU resource systems
- SHARED mode (1 thread): one thread is created, which uses a CompositeAgent to drive all 3 Agent duty cycles
- SHARED_NETWORK mode (2 threads): one thread is created to run the Driver Conductor and another thread to run a CompositeAgent containing the Sender and Receiver
- DEDICATED mode (3 threads): the default, which creates one thread per Agent. This provides the lowest latency, when enough CPU cores are available
Running the Aeron Client
Each Aeron Client has a Client Conductor. It doesn't have many responsibilities, but they include checking it is still connected to the Driver Conductor and processing messages from it, such as a new Image becoming available, or confirmation of a Publication being created.
An Aeron Client is created by creating an instance of the Aeron, class. It is a facade through which an application talks to the Aeron Client API. By default, when an instance of Aeron is created, it creates a thread to run the Client Conductor duty cycle. This can be configured to run in INVOKER mode, which requires the application to invoke the Client Conductor's duty cycle, which does not require the additional thread.