Модель, управляемая событиями, в Redis

Redis задняя часть сервер Linux

Оригинальный адрес:у-у-у-у. xilidu.com/2018/03/22/…

Redis — это управляемая событиями база данных в памяти, и сервер должен обрабатывать два типа событий.

  • файл события
  • событие времени

Принципы реализации этих двух событий описаны ниже.

файл события

Сервер Redis реализует взаимодействие с клиентом (или другим сервером Redis) через сокет, а файловое событие является абстракцией операции сокета сервером. Сервер Redis отвечает на вызовы клиентов, прослушивая файловые события, генерируемые этими сокетами, и обрабатывая эти события.

Reactor

Redis разработал собственный обработчик событий на основе шаблона Reactor.

Давайте сначала поговорим о шаблоне Reactor. Посмотрите на картинку ниже:

reactor

«Модуль мультиплексирования ввода-вывода» будет прослушивать несколько FD, и когда эти FD генерируют, принимают, читают, записывают или закрывают файловые события. События доставляются в «файловый диспетчер событий».

После того, как диспетчер файловых событий (диспетчер) получит событие, он отправит событие соответствующему обработчику в соответствии с типом события.

Мы следуем диаграмме и объясняем, как Redis реализует эту модель Reactor один за другим сверху вниз.

Модуль мультиплексирования ввода/вывода

Модуль мультиплексирования ввода-вывода Redis фактически инкапсулирует основные функции, такие как select, epoll, avport и kqueue, предоставляемые операционной системой. Предоставляет унифицированный интерфейс для верхнего уровня, скрывая детали базовой реализации.

Вообще говоря, Redis развертывается в системах Linux, поэтому давайте посмотрим, как использовать Redis для реализации мультиплексирования ввода-вывода с помощью epoll, предоставляемого Linux.

Сначала взгляните на три метода, предоставляемые epoll:

/*
 * 创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大
 */
int epoll_create(int size);

/*
 * 可以理解为,增删改 fd 需要监听的事件
 * epfd 是 epoll_create() 创建的句柄。
 * op 表示 增删改
 * epoll_event 表示需要监听的事件,Redis 只用到了可读,可写,错误,挂断 四个状态
 */
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

/*
 * 可以理解为查询符合条件的事件
 * epfd 是 epoll_create() 创建的句柄。
 * epoll_event 用来存放从内核得到事件的集合
 * maxevents 获取的最大时间数
 * timeout 等待超时时间
 */
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

Посмотрите на события файла Redis и инкапсулируйте интерфейс, предоставляемый epoll:


/*
 * 事件状态
 */
typedef struct aeApiState {

    // epoll_event 实例描述符
    int epfd;

    // 事件槽
    struct epoll_event *events;

} aeApiState;

/*
 * 创建一个新的 epoll 
 */
static int  aeApiCreate(aeEventLoop *eventLoop)
/*
 * 调整事件槽的大小
 */
static int  aeApiResize(aeEventLoop *eventLoop, int setsize)
/*
 * 释放 epoll 实例和事件槽
 */
static void aeApiFree(aeEventLoop *eventLoop)
/*
 * 关联给定事件到 fd
 */
static int  aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask)
/*
 * 从 fd 中删除给定事件
 */
static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask)
/*
 * 获取可执行事件
 */
static int  aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp)

Итак, посмотрите, как этот файл ae_peoll.c инкапсулирует epoll:

  • aeApiCreate()правдаepoll.epoll_create()упаковка.
  • aeApiAddEvent()иaeApiDelEvent()правдаepoll.epoll_ctl()упаковка.
  • aeApiPoll()правдаepoll_wait()упаковка.

Таким образом, мультиплексор ввода-вывода, реализованный Redis с использованием epoll, относительно ясен.

Пройдя еще один уровень, нам нужно посмотреть, как инкапсулируется ea.c?

Первое, на что следует обратить внимание, это структура данных обработчика события:


typedef struct aeFileEvent {

    // 监听事件类型掩码,
    // 值可以是 AE_READABLE 或 AE_WRITABLE ,
    // 或者 AE_READABLE | AE_WRITABLE
    int mask; /* one of AE_(READABLE|WRITABLE) */

    // 读事件处理器
    aeFileProc *rfileProc;

    // 写事件处理器
    aeFileProc *wfileProc;

    // 多路复用库的私有数据
    void *clientData;

} aeFileEvent;

maskЕго можно понимать как тип события.

В дополнение к использованию методов, предоставляемых ae_peoll.c, ae.c также добавляет несколько API для «добавления, удаления и проверки».

  • увеличивать:aeCreateFileEvent
  • Удалить:aeDeleteFileEvent
  • Проверка: проверка включает два измеренияaeGetFileEventsПолучите тип слушателя fd иaeWaitДождитесь fd, пока не будет достигнуто время ожидания или состояние.

Диспетчер событий (диспетчер)

Диспетчер событий для Redisae.c/aeProcessEventsОн обрабатывает не только файловые события, но и события времени, поэтому здесь размещается только исходящий код, связанный с раздачей файлов, а диспетчер вызывает разные обработчики событий в соответствии с маской.

    //从 epoll 中获关注的事件
    numevents = aeApiPoll(eventLoop, tvp);
    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 rfired = 0;

        // 读事件
        if (fe->mask & mask & AE_READABLE) {
            // rfired 确保读/写事件只能执行其中一个
            rfired = 1;
            fe->rfileProc(eventLoop,fd,fe->clientData,mask);
        }
        // 写事件
        if (fe->mask & mask & AE_WRITABLE) {
            if (!rfired || fe->wfileProc != fe->rfileProc)
                fe->wfileProc(eventLoop,fd,fe->clientData,mask);
        }

        processed++;
    }

Вы можете видеть, что этот диспетчер распределяет события для чтения и записи событий в соответствии с различными масками.

Типы обработчиков файловых событий

Redis имеет большое количество типов обработчиков событий, мы объясним три обработчика, участвующих в обработке простой команды:

  • Обработчик ответа на подключение acceptTcpHandler отвечает за обработку событий, связанных с подключением.Когда клиент подключается к Redis, он генерирует событие AE_READABLE. Заставьте его выполнить.
  • Команда readQueryFromClinet запрашивает обработчик, отвечающий за чтение команд, отправленных через сокет.
  • Команда sendReplyToClient отвечает процессору.Когда Redis обрабатывает команду, он генерирует событие AE_WRITEABLE и возвращает данные клиенту.

Сводка реализации файлового события

Согласно приведенной в начале модели Reactor, мы объяснили реализацию обработчика событий файла сверху вниз, а реализация времени и времени будет представлена ​​ниже.

событие времени

У Рейда есть много операций, которые необходимо обрабатывать в заданный момент времени, а временные события являются абстракцией для таких задач синхронизации.

Сначала взгляните на структуру данных временного события:

/* Time event structure
 *
 * 时间事件结构
 */
typedef struct aeTimeEvent {

    // 时间事件的唯一标识符
    long long id; /* time event identifier. */

    // 事件的到达时间
    long when_sec; /* seconds */
    long when_ms; /* milliseconds */

    // 事件处理函数
    aeTimeProc *timeProc;

    // 事件释放函数
    aeEventFinalizerProc *finalizerProc;

    // 多路复用库的私有数据
    void *clientData;

    // 指向下个时间事件结构,形成链表
    struct aeTimeEvent *next;

} aeTimeEvent;

видетьnextМы знаем, что это aeTimeEvent представляет собой структуру связанного списка. Посмотрите на картинку:

timeEvent

Обратите внимание, что это связанный список, отсортированный в обратном порядке по идентификатору, а не в порядке событий.

processTimeEvent

Redis использует эту функцию для обработки всех временных событий Давайте разберемся с идеями выполнения:

  1. Запишите последний раз, когда эта функция выполнялась, чтобы решить проблемы, вызванные изменением системного времени.
  2. Пройдитесь по связанному списку, чтобы найти все события, у которых время when_sec и when_ms меньше текущего времени.
  3. Выполнить функцию обработчика, соответствующую событию.
  4. Проверьте тип события, если это периодическое событие, обновите следующее событие выполнения события.
  5. В противном случае удалите событие из списка.

Встроенный планировщик (aeProcessEvents)

В синтетическом планировщике Redis единообразно обрабатывает все события. Разберем простую логику этой функции:

// 1. 获取离当前时间最近的时间事件
shortest = aeSearchNearestTimer(eventLoop);

// 2. 获取间隔时间
timeval = shortest - nowTime;

// 如果timeval 小于 0,说明已经有需要执行的时间事件了。
if(timeval < 0){
    timeval = 0
}

// 3. 在 timeval 时间内,取出文件事件。
numevents = aeApiPoll(eventLoop, timeval);

// 4.根据文件事件的类型指定不同的文件处理器
if (AE_READABLE) {
    // 读事件
    rfileProc(eventLoop,fd,fe->clientData,mask);
}
    // 写事件
if (AE_WRITABLE) {
    wfileProc(eventLoop,fd,fe->clientData,mask);
}

Приведенный выше псевдокод — это логика всего обработчика событий Redis.

Мы можем еще раз посмотреть, кто выполнил этоaeProcessEvents:


void aeMain(aeEventLoop *eventLoop) {

    eventLoop->stop = 0;

    while (!eventLoop->stop) {

        // 如果有需要在事件处理前执行的函数,那么运行它
        if (eventLoop->beforesleep != NULL)
            eventLoop->beforesleep(eventLoop);

        // 开始处理事件
        aeProcessEvents(eventLoop, AE_ALL_EVENTS);
    }
}

Тогда мы увидим, кто звонитeaMain:

int main(int argc, char **argv) {
    //一些配置和准备
    ...
    aeMain(server.el);
    
    //结束后的回收工作
    ...
}

Мы нашли его в основном методе Redis.

В это время наши мысли таковы:

  • Метод main() Redis вызывается после выполнения некоторой настройки и подготовки.eaMain()метод.

  • eaMain()вызов while(true)aeProcessEvents().

Итак, мы говорим, что Redis — это программа, управляемая событиями, в ходе которой мы обнаружили, что Redis не разветвляет ни одного потока. Таким образом, можно также сказать, что Redis — это однопоточное приложение, управляемое событиями.

Суммировать

Redis всегда является более или менее задаваемым вопросом на собеседованиях по бэкенду.

Прочитав эту статью, вы сможете ответить на следующие вопросы:

  • Почему Redis — однопоточное приложение?
  • Почему Redis — однопоточное приложение с такой высокой производительностью?

Если вы ответите на эти два вопроса с точки зрения знаний, представленных в этой статье, вы обязательно оставите высокий образ в сознании интервьюера.

Вы также можете прочитать мои статьи, связанные с Redis:

Базовая структура данных Redis (1) Переменная строка, связанный список, словарь

Базовая структура данных Redis (2) Целочисленный набор, таблица переходов, сжатый список

Базовая структура данных Redis (3) Объекты

База данных Redis, реализация истечения срока действия ключа

Добро пожаловать в мой публичный аккаунт WeChat:

二维码