zoukankan      html  css  js  c++  java
  • Redis 缓存失效和回收机制

    本文及后续文章,Redis版本均是v3.2.8

    一、内存回收策略

    maxmemory配置用于配置Redis存储数据时指定限制的内存大小。我们可以通过redis.conf配置或者使用CONFIG SET命令来进行运行时配置。

    例如在redis.conf文件中配置内存限制为100mb

    maxmemory 100mb

    设置maxmemory为0代表没有内存限制。对于64位的系统这是个默认值,对于32位的系统默认内存限制为3GB。

    当目前使用的内存超过了设置的最大内存,就要进行内存释放了, 当需要进行内存释放的时候,需要用某种策略对保存的的对象进行删除。

    redis中当内存超过限制时,按照配置的策略,淘汰掉相应的key-value,使得内存可以继续留有足够的空间保存新的数据。redis 在确定了要驱逐某个键值对后,会删除这个数据,并将这个数据变更消息发布到本地(AOF 持久化)和从机(主从连接)。

    当maxmemory限制达到的时候,Redis采取内存回收的策略由Redis的maxmemory-policy配置来进行决定的。有六种策略

    • volatile-lru:默认的策略,尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。

     

    • 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 ",

                    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;

    }

     

    未完待续

  • 相关阅读:
    sqlserver2005存储汉字成问号解决办法:
    .net c# 日期格式和常用处理
    文件夹无法访问拒绝访问,无法删除文件的,快速有效解决方法
    打印出所有的 info.plist 中的 keys、values
    利用时间戳来准确计算某个时间点具现在的时间差
    项目中的技巧经验汇总
    "Base SDK Missing"问题的解决
    UIPopoverController的使用
    给ios自带控件添加圆角边的方法
    实现程序内截屏功能的代码
  • 原文地址:https://www.cnblogs.com/exceptioneye/p/7123844.html
Copyright © 2011-2022 走看看