EDA style and Reactor mode

Posted by suzzane2020 on Wed, 02 Feb 2022 01:20:26 +0100

Source: https://www.cnblogs.com/ivaneye/p/10129896.html

This paper will discuss the following issues:

  • Constraints of event driven architecture style
  • The influence of EDA style on architecture attributes
  • Reactor architecture mode
  • Problems solved by Reactor
  • EventDriven in redis

From observer mode to EDA style

Among the 23 design modes of GOF, there is one observer mode! Personally, I think it's more reasonable to call it "monitoring mode"!

Defines a one to many dependency between objects. When the state of an object changes, all objects that depend on it are notified and automatically updated.

It is somewhat similar to EDA style! However, one application is at the level of code relationship; An application in architecture!

In EDA, there are three necessary components:

  • Event production component: corresponding to the observer mode, it is the object that causes the change of Subject state. The action that causes the change of Subject state is an event.
  • Event queue: corresponds to the observer mode, which is the Subject object. Events generated by the event producer will be added to the event queue.
  • Event handling component: handles events generated by the event production component. Corresponding to the Observer mode, it is the Observer object.

EDA style constraints

  • The event production component generates an event and adds it to the event queue
  • Event handling component listens to event queue
  • When there is an event in the event queue that can be processed by itself, the event processing component will process the event
  • After the event processing component processes the event, it can return the processing result to the event production component or not
  • The event production component can receive the processing results of the event processing component or not

EDA style can be subdivided into Mediator structure and Broker structure!

  • Mediator structure coordinates the relationship and execution sequence of multiple steps through a mediator component
  • Broker structure organizes the relationship and execution order of multiple steps in a loose way

Mediator structure is as follows:

There are four main components in the Mediator structure:

  • Event queue
  • Event mediator
  • Event channel
  • Event processor.

The execution process is as follows:

  • The event production component generates an event and adds it to the event queue
  • Event mediation listens to event queue
  • When there are events in the "event queue", the "event mediation" splits / combines them into step-by-step specific steps according to specific events
  • Add specific steps to the corresponding "event channel"
  • The event handler obtains events from the event channel and processes them

The Broker structure is as follows:

The Broker structure mainly includes two components:

  • In addition, brokers can be used together or in a set of events, and all brokers can be used together.
  • Event handler

The execution process is as follows:

  • The event production component generates events and adds them to the Broker
  • "Event handling component" listens to "Broker"
  • When there is an event in the Broker that can be handled by itself, the event handling component will handle the event
  • After the event processing component processes the event, it sends subsequent events to the Broker
  • Other event processing components will process the event after receiving the event they can handle. After processing, they may continue to send the event to the Broker
  • When there are no further events, all steps are completed

The influence of EDA style on architecture attributes

  • Performance: because EDA is an asynchronous architecture, it can significantly improve the user perceived performance for requests that do not need a return value.
  • Extensibility: event producers and event consumers are loosely coupled, can evolve independently, and can easily expand functions. At the same time, because it is convenient to add event consumers, it further improves the scalability.
  • Scalability: the loose coupling between event producers and event consumers and the convenient addition of event consumers make EDA have good scalability
  • Maintainability: the event producer and the event consumer are loosely coupled, and compared with the general calling method, it is difficult to understand, which makes the development more difficult, but the unit test is more convenient. Due to the asynchrony of EDA, the integration test is more troublesome. General maintainability
  • Operability: event producers and event consumers are loosely coupled and can be deployed independently, so the operability is relatively easy
  • Flexibility: highly scalable and easy to scale, making EDA more flexible

Reactor architecture mode

Reactor architecture mode is an implementation of EDA style! It is to deal with:

  • High concurrent IO requests
  • The amount of data contained in the request is not large
  • The processing time of each request is not long

The Reactor model has four components:

  • Acceptor: acceptor accepts Client connections. Belongs to EDA "event queue component".
  • Channel: receive IO events. Belongs to EDA "event queue component".
  • Reactor: listens to the Channel and Acceptor. When a connection event or IO event occurs, it will be sent to the corresponding Handler. Belongs to EDA "event queue component".
  • Handler: an entity that communicates with a Client for actual business processing. Corresponds to the "event handling component" of EDA.

Client belongs to the "event production component" of EDA!

Reactor can be divided into single thread model, multi thread model and master-slave reactor model.

  • Single thread model

After the Reactor polls the message, it will directly delegate it to the handler for processing. After processing, it will conduct subsequent polling, that is, after one handler is processed, the next handler can be processed! If a handler takes a long time, it will block the execution of subsequent handlers!

  • Multithreading model

The Reactor multithreading model is to separate IO operations and non IO operations in the Handler. The thread that operates IO is called IO thread, and the thread that operates non IO is called worker thread! In this way, the request of the client will be directly thrown into the thread pool, and the request sent by the client will not be blocked!

However, when users increase further, Reactor will have a bottleneck! Because Reactor not only processes IO operation requests, but also responds to connection requests! In order to share the burden of Reactor, the master-slave Reactor model is introduced!

  • Master slave Reactor model

The primary Reactor is used to respond to connection requests and the secondary Reactor is used to process IO operation requests!

For details, please refer to the previous blog posts High performance server - reactor model!

Here's why the amount of data requested in Reactor cannot be too large, and why the request processing time cannot be too long!

In fact, it's easy to understand. If the amount of requested data is too large and the processing time is very long! Even in the master-slave Reactor model, the thread pool will be exhausted! When exhausted, it leads to a backlog of subsequent requests.

Reactor solves the problem that under the traditional BIO model, the ability of Server to process requests concurrently is limited by the number of threads that the system can create!

EventDriven in redis

Redis uses "event driven programming" to process instructions. Event driven programming is a programming paradigm, which can also be said to be the code implementation of EDA. The implementation in redis is similar to the Reactor single thread model!

Event driven programming is a computer programming model. Different from the traditional way of waiting for a complete instruction at a time and then running, the basic architecture of the system under the event driver model is to design a program formed by an event cycle in advance. The event cycle program constantly checks the information to be processed, and runs a trigger function according to the information to be processed for necessary processing. The external information may come from a file in a folder, a keyboard or mouse action, or a time event.

Take a brief look at the code:

// server.c

int main(int argc, char **argv) {
    ......
    aeMain(server.el);  // Enter event listening polling and poll the registered file handle
    ......
}

// ae.c

void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;          // eventLoop is equivalent to the Reactor component in the Reactor mode
    while (!eventLoop->stop) {
        if (eventLoop->beforesleep != NULL)
            eventLoop->beforesleep(eventLoop);
        aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
    }
}

int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
    ......
        
    numevents = aeApiPoll(eventLoop, tvp);// select,epoll,evport,kqueue, underlying implementation, get events

    ......
    
    // Delegate the event to a specific Handler
    for (j = 0; j < numevents; j++) {
        aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
        int mask = eventLoop->fired[j].mask;
        int fd = eventLoop->fired[j].fd;
        int fired = 0; 

        int invert = fe->mask & AE_BARRIER;

        if (!invert && fe->mask & mask & AE_READABLE) {
            fe->rfileProc(eventLoop,fd,fe->clientData,mask);
            fired++;
        }

        if (fe->mask & mask & AE_WRITABLE) {
            if (!fired || fe->wfileProc != fe->rfileProc) {
                fe->wfileProc(eventLoop,fd,fe->clientData,mask);
                fired++;
            }
        }

        if (invert && fe->mask & mask & AE_READABLE) {
            if (!fired || fe->wfileProc != fe->rfileProc) {
                fe->rfileProc(eventLoop,fd,fe->clientData,mask);
                fired++;
            }
        }

        processed++;
    }
    ......
}

// ae.h
// The structure of aeFileEvent is as follows. During construction, the handler is assigned to aeFileProc
// In the above polling, you can directly take it out and execute it
typedef struct aeFileEvent {
    int mask; /* one of AE_(READABLE|WRITABLE|BARRIER) */
    aeFileProc *rfileProc;
    aeFileProc *wfileProc;
    void *clientData;
} aeFileEvent;

Let's start the process of redis server and redis cli to briefly explain the execution process of redis:

//After the redis server is started, it enters the polling state and waits for events
//A tcpHandler is built at startup to handle client connections

// server.c
for (j = 0; j < server.ipfd_count; j++) {
    if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
        acceptTcpHandler,NULL) == AE_ERR)
        {
            serverPanic(
                "Unrecoverable error creating server.ipfd file event.");
        }
}

//After redis cli is started, eventLoop polls for connection events, triggers tcpHandler, constructs aeFileEvent according to the obtained file handle, and sets readQueryFromClient Handler to process subsequent client instructions
client *createClient(int fd) {
    ......
    if (aeCreateFileEvent(server.el,fd,AE_READABLE,
        readQueryFromClient, c) == AE_ERR)
    {
        close(fd);
        zfree(c);
        return NULL;
    }
    ......
}

// After the client sends the instruction, trigger readQueryFromClient Handler to process the instruction
// After processing, wait for the next instruction

reference material