Redis 緩存失效和回收機制

本文及後續文章,Redis版本均是v3.2.8redis

1、內存回收策略算法

maxmemory配置用於配置Redis存儲數據時指定限制的內存大小。咱們能夠通過redis.conf配置或者使用CONFIG SET命令來進行運行時配置。數據庫

例如在redis.conf文件中配置內存限制爲100mb緩存

maxmemory 100mbdom

設置maxmemory爲0表明沒有內存限制。對於64位的系統這是個默認值,對於32位的系統默認內存限制爲3GB。ui

當目前使用的內存超過了設置的最大內存,就要進行內存釋放了, 當須要進行內存釋放的時候,須要用某種策略對保存的的對象進行刪除。this

redis中當內存超過限制時,按照配置的策略,淘汰掉相應的key-value,使得內存能夠繼續留有足夠的空間保存新的數據。redis 在肯定了要驅逐某個鍵值對後,會刪除這個數據,並將這個數據變動消息發佈到本地(AOF 持久化)和從機(主從鏈接)。lua

當maxmemory限制達到的時候,Redis採起內存回收的策略由Redis的maxmemory-policy配置來進行決定的。有六種策略spa

  • volatile-lru:默認的策略,嘗試回收最少使用的鍵(LRU),但僅限於在過時集合的鍵,使得新添加的數據有空間存放。code

 

  • volatile-random:回收隨機的鍵使得新添加的數據有空間存放,但僅限於在過時集合的鍵。

  • volatile-ttl:回收在過時集合的鍵,而且優先回收存活時間(TTL)較短的鍵,使得新添加的數據有空間存放。

  • allkeys-lru:嘗試回收最少使用的鍵(LRU),使得新添加的數據有空間存放。

  • allkeys-random:回收隨機的鍵使得新添加的數據有空間存放。

  • no-enviction:禁止淘汰數據

若是沒有鍵知足回收的前提條件的話,策略volatile-lru, volatile-random,volatile-ttl就和no-eviction 差很少了。

選擇正確的回收策略是很是重要的,這取決於你的應用的訪問模式,不過你能夠在運行時進行相關的策略調整,而且監控緩存命中率和沒命中的次數,經過Redis INFO命令輸出以便調優。

通常的經驗規則:

  • 使用allkeys-lru策略:當你但願你的請求符合一個冪定律分佈,也就是說,你但願部分的子集元素將比其它其它元素被訪問的更多。

  • 使用allkeys-random:若是你是循環訪問,全部的鍵被連續的掃描,或者你但願請求分佈正常(全部元素被訪問的機率都差很少)。

  • 使用volatile-ttl:若是你想要經過建立緩存對象時設置TTL值,來決定哪些對象應該被過時。

  • allkeys-lru 和 volatile-random策略:當你想要單一的實例實現緩存及持久化一些鍵時頗有用。不過通常運行兩個實例是解決這個問題的更好方法。

 

爲鍵設置過時時間也是須要消耗內存的,因此使用allkeys-lru這種策略更加高效,由於沒有必要爲鍵取設置過時時間當內存有壓力時。

 

除此以外還有一個配置項,就是maxmemory-samples,默認值是3,由於上面的策略代碼實現的都是近似算法,因此無論是lru算法,仍是ttl,都並非在數據庫中全部的數據爲基礎的算法,由於當數據庫的數據不少的時候,這樣效率過低,因此代碼中都是基於maxmemory-samples個數據的近似算法

近似LRU的算法,經過對少許keys進行取樣,而後回收其中一個最被訪問時間較早的key。咱們能夠經過調整每次回收時檢查的採樣數量,以實現調整算法的精度Redis爲何不使用真實的LRU實現是由於這須要太多的內存。不過近似的LRU算法對於應用而言應該是等價的 

 

最後,咱們看下命令在執行前,Redis server作了什麼事情?

/* If this function gets called we already read a whole

 * command, arguments are in the client argv/argc fields.

 * processCommand() execute the command or prepare the

 * server for a bulk read from the client.

 *

 * If C_OK is returned the client is still alive and valid and

 * other operations can be performed by the caller. Otherwise

 * if C_ERR is returned the client was destroyed (i.e. after QUIT). */

int processCommand(client *c) {

    /* The QUIT command is handled separately. Normal command procs will

     * go through checking for replication and QUIT will cause trouble

     * when FORCE_REPLICATION is enabled and would be implemented in

     * a regular command proc. */

    if (!strcasecmp(c->argv[0]->ptr,"quit")) {

        addReply(c,shared.ok);

        c->flags |= CLIENT_CLOSE_AFTER_REPLY;

        return C_ERR;

    }

 

    /* Now lookup the command and check ASAP about trivial error conditions

     * such as wrong arity, bad command name and so forth. */

    c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr);

    if (!c->cmd) {

        flagTransaction(c);

        addReplyErrorFormat(c,"unknown command '%s'",

            (char*)c->argv[0]->ptr);

        return C_OK;

    } else if ((c->cmd->arity > 0 && c->cmd->arity != c->argc) ||

               (c->argc < -c->cmd->arity)) {

        flagTransaction(c);

        addReplyErrorFormat(c,"wrong number of arguments for '%s' command",

            c->cmd->name);

        return C_OK;

    }

 

    /* Check if the user is authenticated */

    if (server.requirepass && !c->authenticated && c->cmd->proc != authCommand)

    {

        flagTransaction(c);

        addReply(c,shared.noautherr);

        return C_OK;

    }

 

    /* If cluster is enabled perform the cluster redirection here.

     * However we don't perform the redirection if:

     * 1) The sender of this command is our master.

     * 2) The command has no key arguments. */

    if (server.cluster_enabled &&

        !(c->flags & CLIENT_MASTER) &&

        !(c->flags & CLIENT_LUA &&

          server.lua_caller->flags & CLIENT_MASTER) &&

        !(c->cmd->getkeys_proc == NULL && c->cmd->firstkey == 0 &&

          c->cmd->proc != execCommand))

    {

        int hashslot;

        int error_code;

        clusterNode *n = getNodeByQuery(c,c->cmd,c->argv,c->argc,

                                        &hashslot,&error_code);

        if (n == NULL || n != server.cluster->myself) {

            if (c->cmd->proc == execCommand) {

                discardTransaction(c);

            } else {

                flagTransaction(c);

            }

            clusterRedirectClient(c,n,hashslot,error_code);

            return C_OK;

        }

    }

 

    /* Handle the maxmemory directive.

     *

     * First we try to free some memory if possible (if there are volatile

     * keys in the dataset). If there are not the only thing we can do

     * is returning an error. */

    if (server.maxmemory) {

        int retval = freeMemoryIfNeeded();

        /* freeMemoryIfNeeded may flush slave output buffers. This may result

         * into a slave, that may be the active client, to be freed. */

        if (server.current_client == NULL) return C_ERR;

 

        /* It was impossible to free enough memory, and the command the client

         * is trying to execute is denied during OOM conditions? Error. */

        if ((c->cmd->flags & CMD_DENYOOM) && retval == C_ERR) {

            flagTransaction(c);

            addReply(c, shared.oomerr);

            return C_OK;

        }

    }

 

    /* Don't accept write commands if there are problems persisting on disk

     * and if this is a master instance. */

    if (((server.stop_writes_on_bgsave_err &&

          server.saveparamslen > 0 &&

          server.lastbgsave_status == C_ERR) ||

          server.aof_last_write_status == C_ERR) &&

        server.masterhost == NULL &&

        (c->cmd->flags & CMD_WRITE ||

         c->cmd->proc == pingCommand))

    {

        flagTransaction(c);

        if (server.aof_last_write_status == C_OK)

            addReply(c, shared.bgsaveerr);

        else

            addReplySds(c,

                sdscatprintf(sdsempty(),

                "-MISCONF Errors writing to the AOF file: %s\r\n",

                strerror(server.aof_last_write_errno)));

        return C_OK;

    }

 

    /* Don't accept write commands if there are not enough good slaves and

     * user configured the min-slaves-to-write option. */

    if (server.masterhost == NULL &&

        server.repl_min_slaves_to_write &&

        server.repl_min_slaves_max_lag &&

        c->cmd->flags & CMD_WRITE &&

        server.repl_good_slaves_count < server.repl_min_slaves_to_write)

    {

        flagTransaction(c);

        addReply(c, shared.noreplicaserr);

        return C_OK;

    }

 

    /* Don't accept write commands if this is a read only slave. But

     * accept write commands if this is our master. */

    if (server.masterhost && server.repl_slave_ro &&

        !(c->flags & CLIENT_MASTER) &&

        c->cmd->flags & CMD_WRITE)

    {

        addReply(c, shared.roslaveerr);

        return C_OK;

    }

 

    /* Only allow SUBSCRIBE and UNSUBSCRIBE in the context of Pub/Sub */

    if (c->flags & CLIENT_PUBSUB &&

        c->cmd->proc != pingCommand &&

        c->cmd->proc != subscribeCommand &&

        c->cmd->proc != unsubscribeCommand &&

        c->cmd->proc != psubscribeCommand &&

        c->cmd->proc != punsubscribeCommand) {

        addReplyError(c,"only (P)SUBSCRIBE / (P)UNSUBSCRIBE / PING / QUIT allowed in this context");

        return C_OK;

    }

 

    /* Only allow INFO and SLAVEOF when slave-serve-stale-data is no and

     * we are a slave with a broken link with master. */

    if (server.masterhost && server.repl_state != REPL_STATE_CONNECTED &&

        server.repl_serve_stale_data == 0 &&

        !(c->cmd->flags & CMD_STALE))

    {

        flagTransaction(c);

        addReply(c, shared.masterdownerr);

        return C_OK;

    }

 

    /* Loading DB? Return an error if the command has not the

     * CMD_LOADING flag. */

    if (server.loading && !(c->cmd->flags & CMD_LOADING)) {

        addReply(c, shared.loadingerr);

        return C_OK;

    }

 

    /* Lua script too slow? Only allow a limited number of commands. */

    if (server.lua_timedout &&

          c->cmd->proc != authCommand &&

          c->cmd->proc != replconfCommand &&

        !(c->cmd->proc == shutdownCommand &&

          c->argc == 2 &&

          tolower(((char*)c->argv[1]->ptr)[0]) == 'n') &&

        !(c->cmd->proc == scriptCommand &&

          c->argc == 2 &&

          tolower(((char*)c->argv[1]->ptr)[0]) == 'k'))

    {

        flagTransaction(c);

        addReply(c, shared.slowscripterr);

        return C_OK;

    }

 

    /* Exec the command */

    if (c->flags & CLIENT_MULTI &&

        c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&

        c->cmd->proc != multiCommand && c->cmd->proc != watchCommand)

    {

        queueMultiCommand(c);

        addReply(c,shared.queued);

    } else {

        call(c,CMD_CALL_FULL);

        c->woff = server.master_repl_offset;

        if (listLength(server.ready_keys))

            handleClientsBlockedOnLists();

    }

    return C_OK;

}

 

 

未完待續

相關文章
相關標籤/搜索