AsyncDispatcher Architecture
AsyncDispatcher Example
Enum AgentEventType
public enum AgentEventType {
REGISTER,
UNREGISTER,
EXPIRED,
HEARTBEAT,
JOBLAUNCH,
JOBKILL
}
AgentEvent
class AgentEvent extends AbstractEvent<AgentEventType> {
private Agent agent;
public AgentEvent(AgentEventType type, Agent agent){
super(type);
this.agent = agent;
}
public Agent getAgent() {
return agent;
}
}
JobEvent
class JobEvent extends AgentEvent{
private int jobId;
JobEvent(AgentEventType type, Agent agent, int jobId){
super(type, agent);
this.jobId = jobId;
}
public int getJobId() {
return jobId;
}
}
class Agent implements EventHandler<AgentEvent>{
private final int id;
private Set<Long> runningJobs = new HashSet<Long>();
public Agent(int id) {
this.id = id;
}
// hashCode and equals method must be implemented if the object is used in HashMap as key or HashSet.
@Override
public int hashCode() {
return id;
}
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (obj == null) {
return false;
} else if (this.getClass() != obj.getClass()) {
return false;
} else {
Agent other = (Agent) obj;
return this.id == other.id;
}
}
public int getId() {
return id;
}
@Override
public String toString() {
return "Agent: " + id;
}
@Override
public void handle(AgentEvent agentEvent) {
switch (agentEvent.getType()) {
case REGISTER:
System.out.println("receive register event in agent");
break;
case UNREGISTER:
break;
case EXPIRED:
break;
case JOBLAUNCH:
JobEvent jobEvent = (JobEvent)agentEvent;
System.out.println("agent:" + agentEvent.getAgent()
+" receive JOBLAUNCH event, jobId :" + jobEvent.getJobId());
break;
case JOBKILL:
break;
}
}
}
AgentManager
class AgentManager extends AbstractService implements EventHandler<AgentEvent>{
private static final Log LOG = LogFactory.getLog(AgentManager.class);
private int expireInterval;
private int monitorInterval;
private Thread checkerThread;
private boolean stopped = false;
private Map<Agent, Long> running = new HashMap<>();
public AgentManager() {
super("AgentManager");
}
@Override
protected void serviceInit(Configuration conf) throws Exception {
super.serviceInit(conf);
int expireIntvl = conf.getInt("com.baidu.dscheduler.agent.expire.ms",
1000 * 60 * 10);
//need some sanity check
this.expireInterval = expireIntvl;
this.monitorInterval = expireInterval / 3;
this.checkerThread = new Thread(new PingChecker());
this.checkerThread.setName("Ping Checker");
}
@Override
protected void serviceStart() throws Exception {
super.serviceStart();
this.checkerThread.start();
}
@Override
protected void serviceStop() throws Exception {
stopped = true;
super.serviceStop();
checkerThread.interrupt();
}
@Override
public void handle(AgentEvent agentEvent) {
switch (agentEvent.getType()) {
case REGISTER:
System.out.println("receive register request in agent manager.");
if (!running.containsKey(agentEvent.getAgent())) {
running.putIfAbsent(agentEvent.getAgent(), System.currentTimeMillis());
agentEvent.getAgent().handle(agentEvent);
}
break;
case UNREGISTER:
if (running.containsKey(agentEvent.getAgent())) {
agentEvent.getAgent().handle(agentEvent);
running.remove(agentEvent.getAgent());
}
break;
case EXPIRED:
break;
case HEARTBEAT:
if (running.containsKey(agentEvent.getAgent())) {
Agent agent = agentEvent.getAgent();
running.put(agent, System.currentTimeMillis());
agentEvent.getAgent().handle(agentEvent);
}
break;
case JOBLAUNCH:
case JOBKILL:
JobEvent jobEvent = (JobEvent)agentEvent;
System.out.println(" agent master receive "
+" receive "+ jobEvent.getType()
+", agent: "+ agentEvent.getAgent()
+ ", jobId :" + jobEvent.getJobId());
if (running.containsKey(agentEvent.getAgent())) {
Agent agent = agentEvent.getAgent();
running.put(agent, System.currentTimeMillis());
agentEvent.getAgent().handle(jobEvent);
}
}
}
private class PingChecker implements Runnable {
private PingChecker() {
}
public void run() {
while (!AgentManager.this.stopped && !Thread.currentThread().isInterrupted()) {
synchronized (AgentManager.this) {
Iterator<Map.Entry<Agent, Long>> iterator = AgentManager.this.running.entrySet().iterator();
long currentTime = System.currentTimeMillis();
while (true) {
if (!iterator.hasNext()) {
break;
}
Map.Entry<Agent, Long> entry = (Map.Entry) iterator.next();
if (currentTime > ((Long) entry.getValue()).longValue() + (long) AgentManager.this.expireInterval) {
iterator.remove();
//AgentManager.this.expire(entry.getKey());
AgentManager.LOG.info("Expired:" + entry.getKey().toString() + " Timed out after " + AgentManager.this.expireInterval / 1000 + " secs");
}
}
}
try {
Thread.sleep((long) AgentManager.this.monitorInterval);
continue;
} catch (InterruptedException e) {
AgentManager.this.LOG.info( AgentManager.this.getName() + " thread interrupted");
}
}
}
}
}
public class AsyncDispatcherTest {
@Test
public void asyncDispatcherTest1() {
AgentManager agentManager = new AgentManager();
Configuration conf = new Configuration();
AsyncDispatcher dispatcher = new AsyncDispatcher();
dispatcher.init(conf);
dispatcher.register(AgentEventType.class, agentManager );
dispatcher.start();
dispatcher.getEventHandler().handle(
new AgentEvent(AgentEventType.REGISTER, new Agent(1)));
System.out.println("send register request in asyncDispatcherTest1");
dispatcher.getEventHandler().handle(
new JobEvent(AgentEventType.JOBLAUNCH, new Agent(1), 2));
System.out.println("send job launch request in asyncDispatcherTest1");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
dispatcher.stop();
}
}
output
send register request in asyncDispatcherTest1
send job launch request in asyncDispatcherTest1
receive register request in agent manager.
receive register event in agent
agent master receive receive JOBLAUNCH, agent: Agent: 1, jobId :2
agent:Agent: 1 receive JOBLAUNCH event, jobId :2
AsyncDispatcher Code Analysis
AsyncDispatcher is a service and implements Dispatcher interface.
public class AsyncDispatcher extends AbstractService implements Dispatcher {
Dispatcher
public interface Dispatcher {
EventHandler getEventHandler();
void register(Class<? extends Enum> eventType, EventHandler handler);
}
Fields of AsyncDispatcher
private final BlockingQueue<Event> eventQueue; // used to store events.
private volatile boolean stopped = false;
private final EventHandler handlerInstance = new GenericEventHandler();
private Thread eventHandlingThread;
protected final Map<Class<? extends Enum>, EventHandler> eventDispatchers;
Constructors of AsyncDispatcher
public AsyncDispatcher() {
this(new LinkedBlockingQueue<Event>());
}
public AsyncDispatcher(BlockingQueue<Event> eventQueue) {
super("Dispatcher");
this.eventQueue = eventQueue;
this.eventDispatchers = new HashMap<Class<? extends Enum>, EventHandler>();
}
AsyncDispatcher.serviceInit
AsyncDispatcher is a service, so it needs to implement serviceInit, serviceStart, serviceStop methods.
@Override
protected void serviceInit(Configuration conf) throws Exception {
this.exitOnDispatchException =
conf.getBoolean(Dispatcher.DISPATCHER_EXIT_ON_ERROR_KEY,
Dispatcher.DEFAULT_DISPATCHER_EXIT_ON_ERROR);
super.serviceInit(conf);
}
@Override
protected void serviceStart() throws Exception {
//start all the components
super.serviceStart();
eventHandlingThread = new Thread(createThread());
eventHandlingThread.setName("AsyncDispatcher event handler");
eventHandlingThread.start();
}
AsyncDispatcher.serviceStop
@Override
protected void serviceStop() throws Exception {
if (drainEventsOnStop) {
blockNewEvents = true;
LOG.info("AsyncDispatcher is draining to stop, igonring any new events.");
long endTime = System.currentTimeMillis() + getConfig()
.getLong(YarnConfiguration.DISPATCHER_DRAIN_EVENTS_TIMEOUT,
YarnConfiguration.DEFAULT_DISPATCHER_DRAIN_EVENTS_TIMEOUT);
synchronized (waitForDrained) {
while (!drained && eventHandlingThread != null
&& eventHandlingThread.isAlive()
&& System.currentTimeMillis() < endTime) {
waitForDrained.wait(1000);
LOG.info("Waiting for AsyncDispatcher to drain. Thread state is :" +
eventHandlingThread.getState());
}
}
}
stopped = true;
if (eventHandlingThread != null) {
eventHandlingThread.interrupt();
try {
eventHandlingThread.join();
} catch (InterruptedException ie) {
LOG.warn("Interrupted Exception while stopping", ie);
}
}
// stop all the components
super.serviceStop();
}
AsyncDispatcher.register, it can register several handler to the same event type.
@SuppressWarnings("unchecked")
@Override
public void register(Class<? extends Enum> eventType,
EventHandler handler) {
/* check to see if we have a listener registered */
EventHandler<Event> registeredHandler = (EventHandler<Event>)
eventDispatchers.get(eventType);
LOG.info("Registering " + eventType + " for " + handler.getClass());
if (registeredHandler == null) {
eventDispatchers.put(eventType, handler);
} else if (!(registeredHandler instanceof MultiListenerHandler)){
/* for multiple listeners of an event add the multiple listener handler */
MultiListenerHandler multiHandler = new MultiListenerHandler();
multiHandler.addHandler(registeredHandler);
multiHandler.addHandler(handler);
eventDispatchers.put(eventType, multiHandler);
} else {
/* already a multilistener, just add to it */
MultiListenerHandler multiHandler
= (MultiListenerHandler) registeredHandler;
multiHandler.addHandler(handler);
}
}
static class MultiListenerHandler implements EventHandler<Event> {
List<EventHandler<Event>> listofHandlers;
public MultiListenerHandler() {
listofHandlers = new ArrayList<EventHandler<Event>>();
}
@Override
public void handle(Event event) {
for (EventHandler<Event> handler: listofHandlers) {
handler.handle(event);
}
}
void addHandler(EventHandler<Event> handler) {
listofHandlers.add(handler);
}
}
The process of dispatcher.getEventHandler().handle( new AgentEvent(AgentEventType.REGISTER, new Agent(1)));
AsyncDispatcher.GenericEventHandler
@Override
public EventHandler getEventHandler() {
return handlerInstance;
}
class GenericEventHandler implements EventHandler<Event> {
public void handle(Event event) {
if (blockNewEvents) {
return;
}
drained = false;
/* all this method does is enqueue all the events onto the queue */
int qSize = eventQueue.size();
if (qSize != 0 && qSize % 1000 == 0
&& lastEventQueueSizeLogged != qSize) {
lastEventQueueSizeLogged = qSize;
LOG.info("Size of event-queue is " + qSize);
}
int remCapacity = eventQueue.remainingCapacity();
if (remCapacity < 1000) {
LOG.warn("Very low remaining capacity in the event-queue: "
+ remCapacity);
}
try {
eventQueue.put(event);
} catch (InterruptedException e) {
if (!stopped) {
LOG.warn("AsyncDispatcher thread interrupted", e);
}
// Need to reset drained flag to true if event queue is empty,
// otherwise dispatcher will hang on stop.
drained = eventQueue.isEmpty();
throw new YarnRuntimeException(e);
}
};
}
AsyncDispatcher.createThread
Runnable createThread() {
return new Runnable() {
@Override
public void run() {
while (!stopped && !Thread.currentThread().isInterrupted()) {
drained = eventQueue.isEmpty();
// blockNewEvents is only set when dispatcher is draining to stop,
// adding this check is to avoid the overhead of acquiring the lock
// and calling notify every time in the normal run of the loop.
if (blockNewEvents) {
synchronized (waitForDrained) {
if (drained) {
waitForDrained.notify();
}
}
}
Event event;
try {
event = eventQueue.take();
} catch(InterruptedException ie) {
if (!stopped) {
LOG.warn("AsyncDispatcher thread interrupted", ie);
}
return;
}
if (event != null) {
dispatch(event);
}
}
}
};
}
AsyncDispatcher.dispatch
protected void dispatch(Event event) {
Class<? extends Enum> type = event.getType().getDeclaringClass();
try{
EventHandler handler = eventDispatchers.get(type);
if(handler != null) {
handler.handle(event);
} else {
throw new Exception("No handler for registered for " + type);
}
} catch (Throwable t) {
//TODO Maybe log the state of the queue
LOG.fatal("Error in dispatcher thread", t);
// If serviceStop is called, we should exit this thread gracefully.
if (exitOnDispatchException
&& (ShutdownHookManager.get().isShutdownInProgress()) == false
&& stopped == false) {
Thread shutDownThread = new Thread(createShutDownThread());
shutDownThread.setName("AsyncDispatcher ShutDown handler");
shutDownThread.start();
}
}
}
Thinking
Advantages and disadvantages of AsyncDispatcher
- reduce the wait time.
function call will block the caller. - flexibility.
You can add additional handler of the event.
Disadvantages of AsyncDispatcher
- event dispatch is a single thread, it will block when dealing event, so the system will slow down when add too much event to one asynchronous dispatcher. You must use the dispatcher carefully. Sometimes, you can use several asynchronous dispatcher at different service.
- *