zoukankan      html  css  js  c++  java
  • redis作为缓存场景使用,内存耗尽时,突然出现大量的逐出,在这个逐出的过程中阻塞正常的读写请求,导致 redis 短时间不可用

    redis 突然大量逐出导致读写请求block

     

    现象

    redis作为缓存场景使用,内存耗尽时,突然出现大量的逐出,在这个逐出的过程中阻塞正常的读写请求,导致 redis 短时间不可用;

    背景

    redis 中的LRU是如何实现的?

    1. 当mem_used内存已经超过maxmemory的设定,对于所有的读写请求,都会触发redis.c/freeMemoryIfNeeded(void)函数以清理超出的内存。
    2. 这个清理过程是阻塞的,直到清理出足够的内存空间。
    3. 这里的LRU或TTL策略并不是针对redis的所有key,而是以配置文件中的maxmemory-samples个key作为样本池进行抽样清理。
      maxmemory-samples在redis-3.0.0中的默认配置为5,如果增加,会提高LRU或TTL的精准度,redis作者测试的结果是当这个配置为10时已经非常接近全量LRU的精准度.

    原因

    逐出qps突增非常大的原因:一次需要逐出释放太多的空间会导致阻塞;具体的原因是 mem_tofree 的计算逻辑有问题;
    mem_tofree 统计的是:实际已分配的内存总量 - AOF 缓冲区相关的内存;
    如果这时候有rehash,会临时分配一个桶来做rehash,这部分内存未排除,所以在rehash阶段,算出来的mem_tofree 就会很大,造成一个时刻需要逐出大量的key,逐出的loop是阻塞的,这个阶段会block redis的请求;

    逐出qps的计算:

    freeMemoryIfNeeded(...)
        // 计算出 Redis 目前占用的内存总数,但有两个方面的内存不会计算在内:
        // 1)从服务器的输出缓冲区的内存
        // 2)AOF 缓冲区的内存
        // 3)AOF 重写缓冲区中的内存
        mem_used = zmalloc_used_memory();
        if (slaves) {
            listIter li;
            listNode *ln;
    
            listRewind(server.slaves,&li);
            while((ln = listNext(&li))) {
                redisClient *slave = listNodeValue(ln);
                unsigned long obuf_bytes = getClientOutputBufferMemoryUsage(slave);
                if (obuf_bytes > mem_used)
                    mem_used = 0;
                else
                    mem_used -= obuf_bytes;
            }
        }
        if (server.aof_state != REDIS_AOF_OFF) {
            mem_used -= sdslen(server.aof_buf);
            mem_used -= aofRewriteBufferSize();
        }
        // 计算需要释放多少字节的内存
        mem_tofree = mem_used - server.maxmemory;
        propagateExpire(db,keyobj);
        // 计算删除键所释放的内存数量
        delta = (long long) zmalloc_used_memory();
        dbDelete(db,keyobj);
        delta -= (long long) zmalloc_used_memory();
        mem_freed += delta;
        // 对淘汰键的计数器增一
        server.stat_evictedkeys++;

    解决方案

    github上 @Rosanta 给出的解决方案:释放内存的循环逻辑中最多执行一定次数,达到阈值了就不再逐出,到下个请求来时再释放一点空间;这个方案的好处是不会 block 整个进程,正常的业务读写请求无影响;潜在问题是可能单次写入的数据比释放的空间还大,导致总的内存是一直上升,而不是下降;

    @antirez 给的方案:同样是迭代删除,但会加个标志,保证在迭代删除的逻辑下内存是逐渐下降的,而如果是上升的,还是会block住正常的请求(要控制主总的内存大小);
    详见:
    https://github.com/antirez/redis/pull/4583

    ref

    关于 redis 4.0的逐出算法优化
    http://antirez.com/news/109

  • 相关阅读:
    [破解]java打包Exe工具
    weblogic:local class incompatible: stream classdesc serialVersionUID
    funny alphabet
    Apache Thrift
    nginx 学习笔记(9) 配置HTTPS服务器--转载
    nginx学习笔记(8)虚拟主机名---转载
    nginx学习笔记(7)Nginx如何处理一个请求---转载
    nginx 学习笔记(6) nginx配置文件中的度量单位
    nginx 学习笔记(5) nginx调试日志
    nginx 学习笔记(4) Connection处理方法
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/8527405.html
Copyright © 2011-2022 走看看