Распределитель Slab - это основной модуль кэш-системы, который в значительной степени определяет, насколько эффективно может использоваться узкое место - память. Остальные 3 части, а именно алгоритм LRU истечения срока входа; и модель, управляемая событиями, основанная на libevent; и постоянная жесткость в отношении распределения данных построены вокруг этого.

Плита I
Плита II
Плита III
LRU I (эта статья)
LRU II
LRU III
Событийный я

Чаще всего алгоритм LRU сочетается с хэш-картой и называется

Кэш LRU

В LRU-кэше хэш-карта обеспечивает быстрый доступ к кэшированным объектам; а LRU предотвращает бесконечное увеличение кеша, отмечая просроченные или так называемые наименее недавно использованные объекты. Затем мы посмотрим, как LRU работает с точки зрения высокого уровня.

Связанный список

Технически алгоритм LRU работает с связанным списком: всякий раз, когда запись в списке используется (открывается или обновляется), она удаляется из списка и прикрепляется к заголовку списка. Таким образом, чем ближе элемент к концу списка, тем он реже использовался. Следовательно, легко удалить нерелевантные или «просроченные» элементы из хвоста на основе определенной конфигурации.

Суровая карта

Связанный список работает медленно, когда дело доходит до доступа к элементам, поэтому используется другая структура данных. Мы видели, как связанный список объединяет блоки в блоки, чтобы получить свободные списки. В кэше LRU механизм аналогичен, однако это записи хэш-карты вместо фрагментов в slabs на этот раз подключили, что выглядит так:

Мы также можем сгладить связанный список и сделать структуру более понятной,

Основная структура данных - элемент

typedef struct _stritem {
    /* Protected by LRU locks */
    struct _stritem *next;
    struct _stritem *prev;
    /* Rest are protected by an item lock */
    struct _stritem *h_next;    /* hash chain next */
    rel_time_t      time;       /* least recent access */
    rel_time_t      exptime;    /* expire time */
    int             nbytes;     /* size of data */
    unsigned short  refcount;
    uint8_t         nsuffix;    /* length of flags-and-length string */
    uint8_t         it_flags;   /* ITEM_* above */
    uint8_t         slabs_clsid;/* which slab class we're in */
    uint8_t         nkey;       /* key length, w/terminating null and padding */
    /* this odd type prevents type-punning issues when we do
     * the little shuffle to save space when not using CAS. */
    union {
... // scr: cas
        char end; // scr: flexible array member indicating the item header "end"
    } data[];
    /* if it_flags & ITEM_CAS we have 8 bytes CAS */
    /* then null-terminated key */
    /* then " flags length\r\n" (no terminating null) */
    /* then data with terminating \r\n (no terminating null; it's binary!) */
} item;

Недвижимость в использовании

next, prev, slabs_clsid - item_link_q, item_unlink_q
it_flags - do_item_link, do_item_unlink
time, refcount - do_item_link
h_next - assoc_insert, assoc_delete
nkey - assoc_delete

Структура памяти блока элемента

Мы упомянули элемент данных в do_slabs_free. С помощью этой структуры данных мы теперь можем более внимательно изучить фрагмент.

Затем мы читаем соответствующий код, который выполняет описанные выше операции LRU.

do_item_link

int do_item_link(item *it, const uint32_t hv) { // scr: --------> 1)
...
    it->it_flags |= ITEM_LINKED;                // scr: --------> 2)
    it->time = current_time;

... // scr: stat

    /* Allocate a new CAS ID on link. */
... // scr: cas
    assoc_insert(it, hv);                       // scr: --------> 3)
    item_link_q(it);                            // scr: --------> 4)
    refcount_incr(&it->refcount);               // scr: --------> 5)
... // scr: stat

    return 1;
}

1) hv должно быть сокращенным «хешированным значением».

2) Установите ITEM_LINKED в it->it_flags и установите текущее время на it->time.

Поле it_flags используется в do_slabs_free и do_slabs_alloc

3) Вставьте элемент в хэш-карту.

4) Вставьте элемент в связанный список.

5) Увеличьте количество ссылок.

Это поле инициализируется как 1 в do_slabs_alloc

Здесь стоит отметить, что счетчик ссылок указывает, сколько подмодулей используют один и тот же ресурс, чтобы определить, когда фактически освободить ресурс (в данном конкретном случае item упоминается как slab, так и LRU). Я написал эту статью, в которой объясняется аналогичный механизм C ++.

item_link_q - добавить в связанный список

item_link_q - это потокобезопасная оболочка метода рабочей лошади do_item_link_q.

static void do_item_link_q(item *it) { /* item is the new head */
    item **head, **tail;
    assert((it->it_flags & ITEM_SLABBED) == 0);

    head = &heads[it->slabs_clsid];           // scr: ----------> 1)
    tail = &tails[it->slabs_clsid];
    assert(it != *head);
    assert((*head && *tail) || (*head == 0 && *tail == 0));
    it->prev = 0;                             // scr: ----------> 2)
    it->next = *head;
    if (it->next) it->next->prev = it;
    *head = it;
    if (*tail == 0) *tail = it;
    sizes[it->slabs_clsid]++;                 // scr: ----------> 3)
    return;
}

1) Получите head и tail соответствующего связанного списка LRU, обозначенного slabs_clsid. Обратите внимание, что slabs_clsid солено с типом очереди, поэтому каждая группа плит может включать несколько списков.

2) Стандартные операции «добавления элемента на передний план».

3) Увеличиваем глобальный массив sizes.

assoc_insert - добавить в хеш-карту

int assoc_insert(item *it, const uint32_t hv) { // scr: again, hv -> hash value
    unsigned int oldbucket;

... // scr: expanding related operations
    } else {
        it->h_next = primary_hashtable[hv & hashmask(hashpower)]; // scr:  1)
        primary_hashtable[hv & hashmask(hashpower)] = it;         // scr:  2)
    }

... // scr: expanding related operations
}

1) Разберитесь с потенциальным конфликтом. Если нет, h_next будет установлен на null.

2) Установите элемент в ведро в primary_hashtable.

Опущенная здесь расширяющая логика будет рассмотрена в следующих статьях.

do_item_unlink

void do_item_unlink(item *it, const uint32_t hv) {
    MEMCACHED_ITEM_UNLINK(ITEM_key(it), it->nkey, it->nbytes);
    if ((it->it_flags & ITEM_LINKED) != 0) {
        it->it_flags &= ~ITEM_LINKED;         // scr: ----------> 1)
... // scr: stat
        assoc_delete(ITEM_key(it), it->nkey, hv); // scr: ------> 2)
        item_unlink_q(it);                    // scr: ----------> 3)
        do_item_remove(it);                   // scr: ----------> *)
    }
}

1) Очистите ITEM_LINKED в it->it_flags.

2) Удалите элемент из хеш-карты.

3) Удалите элемент из связанного списка.

*) Фактический выпуск item будет рассмотрен в следующей статье.

item_unlink_q - удалить из связанного списка

Точно так же item_unlink_q является потокобезопасной оболочкой метода рабочей лошади do_item_unlink_q.

static void do_item_unlink_q(item *it) {
    item **head, **tail;
    head = &heads[it->slabs_clsid];           // scr: ----------> 1)
    tail = &tails[it->slabs_clsid];

    if (*head == it) {                        // scr: ----------> 2)
        assert(it->prev == 0);
        *head = it->next;
    }
    if (*tail == it) {
        assert(it->next == 0);
        *tail = it->prev;
    }
    assert(it->next != it);
    assert(it->prev != it);

    if (it->next) it->next->prev = it->prev;
    if (it->prev) it->prev->next = it->next;
    sizes[it->slabs_clsid]--;                 // scr: ----------> 3)
    return;
}

1) То же самое, получите head и tail соответствующего связанного списка LRU, обозначенного slabs_clsid.

2) Стандартные операции «удаления элемента из связного списка».

3) Уменьшаем глобальный массив sizes.

assoc_delete - удалить с хеш-карты

static item** _hashitem_before (const char *key, const size_t nkey, const uint32_t hv) {
    item **pos;
    unsigned int oldbucket;

... // scr: expanding related operations
    } else {
        pos = &primary_hashtable[hv & hashmask(hashpower)]; //scr:1)
    }

    while (*pos && ((nkey != (*pos)->nkey) || memcmp(key, ITEM_key(*pos), nkey))) {
        pos = &(*pos)->h_next; // scr: -------------------------> 2)
    }
    return pos;
}

...

void assoc_delete(const char *key, const size_t nkey, const uint32_t hv) {
    item **before = _hashitem_before(key, nkey, hv);

    if (*before) {
        item *nxt;
...
        nxt = (*before)->h_next; // scr: -----------------------> 3)
        (*before)->h_next = 0;   /* probably pointless, but whatever. */
        *before = nxt; // scr: ---------------------------------> 4)
        return;
    }
    /* Note:  we never actually get here.  the callers don't delete things
       they can't find. */
    assert(*before != 0);
}

1) Получите хеш-ведро, используя hv.

2) Просмотрите цепочку конфликтов и сравните key. Обратите внимание, что значение результата - это адрес next члена элемента before найденного. Когда нет конфликта, адресом является сама корзина.

3) Установите следующий элемент после найденного во временную переменную nxt.

4) Обновите next член элемента before найденным.

Забрать домой

Попробуй это".

Изначально опубликовано на holmeshe.me.