背景

最近碰到一个 case,一个 Redis 实例的内存突增,used_memory最大时达到了 78.9G,而该实例的maxmemory配置却只有 16G,最终导致实例中的数据被大量驱逐。

以下是问题发生时INFO MEMORY的部分输出内容。

# Memory
used_memory:84716542624
used_memory_human:78.90G
used_memory_rss:104497676288
used_memory_rss_human:97.32G
used_memory_peak:84716542624
used_memory_peak_human:78.90G
used_memory_peak_perc:100.00%
used_memory_overhead:75682545624
used_memory_startup:906952
used_memory_dataset:9033997000
used_memory_dataset_perc:10.66%
allocator_allocated:84715102264
allocator_active:101370822656
allocator_resident:102303637504
total_system_memory:810745470976
total_system_memory_human:755.07G
used_memory_lua:142336
used_memory_lua_human:139.00K
used_memory_scripts:6576
used_memory_scripts_human:6.42K
number_of_cached_scripts:13
maxmemory:17179869184
maxmemory_human:16.00G
maxmemory_policy:volatile-lru
allocator_frag_ratio:1.20
allocator_frag_bytes:16655720392

内存突增导致数据被驱逐,是 Redis 中一个较为常见的问题。很多童鞋在面对这类问题时往往缺乏清晰的分析思路,常常误以为是复制、RDB 持久化等操作引起的。接下来,我们看看如何系统地分析这类问题。

本文主要包括以下几部分:

  1. INFO 中的used_memory是怎么来的?
  2. 什么是used_memory
  3. used_memory内存通常会被用于哪些场景?
  4. Redis 7 在内存统计方面的变化。
  5. 数据驱逐的触发条件——当used_memory 超过maxmemory后,是否一定会触发驱逐?
  6. 最后,分享一个脚本,帮助实时分析used_memory增长时,具体是哪一部分的内存消耗导致的。

INFO 中的 used_memory 是怎么来的?

当我们执行INFO命令时,Redis 会调用genRedisInfoString函数来生成其输出。

// server.c
sds genRedisInfoString(const char *section) {
    ...
    /* Memory */
    if (allsections || defsections || !strcasecmp(section,"memory")) {
        ...
        size_t zmalloc_used = zmalloc_used_memory();
        ...
        if (sections++) info = sdscat(info,"\r\n");
        info = sdscatprintf(info,
            "# Memory\r\n"
            "used_memory:%zu\r\n"
            "used_memory_human:%s\r\n"
            "used_memory_rss:%zu\r\n"
            ...
            "lazyfreed_objects:%zu\r\n",
            zmalloc_used,
            hmem,
            server.cron_malloc_stats.process_rss,
            ...
            lazyfreeGetFreedObjectsCount()
        );
        freeMemoryOverheadData(mh);
    }
    ...
    return info;
}

可以看到,used_memory 的值来自 zmalloc_used,而 zmalloc_used 又是通过zmalloc_used_memory()函数获取的。

// zmalloc.c
size_t zmalloc_used_memory(void) {
    size_t um;
    atomicGet(used_memory,um);
    return um;
}

zmalloc_used_memory() 的实现很简单,就是以原子方式读取 used_memory 的值。

什么是 used_memory

used_memory是一个静态变量,其类型为redisAtomic size_t,其中redisAtomic_Atomic类型的别名。_Atomic是 C11 标准引入的关键字,用于声明原子类型,保证在多线程环境中对该类型的操作是原子的,避免数据竞争。

#define redisAtomic _Atomic
static redisAtomic size_t used_memory = 0;

used_memory 的更新主要通过两个宏定义实现:

#define update_zmalloc_stat_alloc(__n) atomicIncr(used_memory,(__n))
#define update_zmalloc_stat_free(__n) atomicDecr(used_memory,(__n))

其中,update_zmalloc_stat_alloc(__n)是在分配内存时调用,它通过原子操作让 used_memory 加__n。

update_zmalloc_stat_free(__n)则是在释放内存时调用,它通过原子操作让 used_memory 减__n

这两个宏确保了在内存分配和释放过程中used_memory的准确更新,并且避免了并发操作带来的数据竞争问题。

在通过内存分配器(常用的内存分配器有 glibc's malloc、jemalloc、tcmalloc,Redis 中一般使用 jemalloc)中的函数分配或释放内存时,会同步调用update_zmalloc_stat_allocupdate_zmalloc_stat_free来更新 used_memory 的值。

在 Redis 中,内存管理主要通过以下两个函数来实现:

// zmalloc.c
void *ztrymalloc_usable(size_t size, size_t *usable) {
    ASSERT_NO_SIZE_OVERFLOW(size);
    void *ptr = malloc(MALLOC_MIN_SIZE(size)+PREFIX_SIZE);

    if (!ptr) return NULL;
#ifdef HAVE_MALLOC_SIZE
    size = zmalloc_size(ptr);
    update_zmalloc_stat_alloc(size);
    if (usable) *usable = size;
    return ptr;
#else
    ...
#endif
}

void zfree(void *ptr) {
    ...
    if (ptr == NULL) return;
#ifdef HAVE_MALLOC_SIZE
    update_zmalloc_stat_free(zmalloc_size(ptr));
    free(ptr);
#else
   ...
#endif
}

其中,

  • ztrymalloc_usable函数用于分配内存。该函数首先会调用malloc分配内存。如果分配成功,则会通过 update_zmalloc_stat_alloc更新 used_memory 的值。
  • zfree 函数用于释放内存。在释放内存之前,先通过update_zmalloc_stat_free调整 used_memory 的值,然后再调用free释放内存。

这种机制保证了 Redis 能够准确跟踪内存的分配和释放情况,从而有效地管理内存使用。

used_memory 内存通常会被用于哪些场景?

used_memory主要由两部分组成:

  1. 数据本身:对应 INFO 中的used_memory_dataset
  2. 内部管理和维护数据结构的开销:对应 INFO 中的used_memory_overhead

需要注意的是,used_memory_dataset 并不是根据 Key 的数量及 Key 使用的内存计算出来的,而是通过 used_memory 减去 used_memory_overhead 得到的。

接下来,我们重点分析下used_memory_overhead 的来源。实际上,Redis 提供了一个单独的函数-getMemoryOverheadData,专门用于计算这一部分的内存开销。

// object.c
struct redisMemOverhead *getMemoryOverheadData(void) {
    int j;
    // mem_total 用于累积总的内存开销,最后会赋值给 used_memory_overhead。
    size_t mem_total = 0;
    // mem 用于计算每一部分的内存使用量。
    size_t mem = 0;
    // 调用 zmalloc_used_memory() 获取 used_memory。
    size_t zmalloc_used = zmalloc_used_memory();
    // 使用 zcalloc 分配一个 redisMemOverhead 结构体的内存。
    struct redisMemOverhead *mh = zcalloc(sizeof(*mh));
    ...
    // 将 Redis 启动时的内存使用量 server.initial_memory_usage 加入到总内存开销中。
    mem_total += server.initial_memory_usage;

    mem = 0;
    // 将复制积压缓冲区的内存开销加入到总内存开销中。
    if (server.repl_backlog)
        mem += zmalloc_size(server.repl_backlog);
    mh->repl_backlog = mem;
    mem_total += mem;

    /* Computing the memory used by the clients would be O(N) if done
     * here online. We use our values computed incrementally by
     * clientsCronTrackClientsMemUsage(). */
    // 计算客户端内存开销
    mh->clients_slaves = server.stat_clients_type_memory[CLIENT_TYPE_SLAVE];
    mh->clients_normal = server.stat_clients_type_memory[CLIENT_TYPE_MASTER]+
                         server.stat_clients_type_memory[CLIENT_TYPE_PUBSUB]+
                         server.stat_clients_type_memory[CLIENT_TYPE_NORMAL];
    mem_total += mh->clients_slaves;
    mem_total += mh->clients_normal;
    // 计算 AOF 缓冲区和 AOF Rewrite Buffer 的内存开销
    mem = 0;
    if (server.aof_state != AOF_OFF) {
        mem += sdsZmallocSize(server.aof_buf);
        mem += aofRewriteBufferSize();
    }
    mh->aof_buffer = mem;
    mem_total+=mem;
    // 计算 Lua 脚本缓存的内存开销
    mem = server.lua_scripts_mem;
    mem += dictSize(server.lua_scripts) * sizeof(dictEntry) +
        dictSlots(server.lua_scripts) * sizeof(dictEntry*);
    mem += dictSize(server.repl_scriptcache_dict) * sizeof(dictEntry) +
        dictSlots(server.repl_scriptcache_dict) * sizeof(dictEntry*);
    if (listLength(server.repl_scriptcache_fifo) > 0) {
        mem += listLength(server.repl_scriptcache_fifo) * (sizeof(listNode) +
            sdsZmallocSize(listNodeValue(listFirst(server.repl_scriptcache_fifo))));
    }
    mh->lua_caches = mem;
    mem_total+=mem;
    // 计算数据库的内存开销:遍历所有数据库 (server.dbnum)。对于每个数据库,计算主字典 (db->dict) 和过期字典 (db->expires) 的内存开销。
    for (j = 0; j < server.dbnum; j++) {
        redisDb *db = server.db+j;
        long long keyscount = dictSize(db->dict);
        if (keyscount==0) continue;

        mh->total_keys += keyscount;
        mh->db = zrealloc(mh->db,sizeof(mh->db[0])*(mh->num_dbs+1));
        mh->db[mh->num_dbs].dbid = j;

        mem = dictSize(db->dict) * sizeof(dictEntry) +
              dictSlots(db->dict) * sizeof(dictEntry*) +
              dictSize(db->dict) * sizeof(robj);
        mh->db[mh->num_dbs].overhead_ht_main = mem;
        mem_total+=mem;

        mem = dictSize(db->expires) * sizeof(dictEntry) +
              dictSlots(db->expires) * sizeof(dictEntry*);
        mh->db[mh->num_dbs].overhead_ht_expires = mem;
        mem_total+=mem;

        mh->num_dbs++;
    }
    // 将计算的 mem_total 赋值给 mh->overhead_total。
    mh->overhead_total = mem_total;
    // 计算数据的内存开销 (zmalloc_used - mem_total) 并存储在 mh->dataset。
    mh->dataset = zmalloc_used - mem_total;
    mh->peak_perc = (float)zmalloc_used*100/mh->peak_allocated;

    /* Metrics computed after subtracting the startup memory from
     * the total memory. */
    size_t net_usage = 1;
    if (zmalloc_used > mh->startup_allocated)
        net_usage = zmalloc_used - mh->startup_allocated;
    mh->dataset_perc = (float)mh->dataset*100/net_usage;
    mh->bytes_per_key = mh->total_keys ? (net_usage / mh->total_keys) : 0;

    return mh;
}

基于上面代码的分析,可以知道 used_memory_overhead 由以下几部分组成:

  • server.initial_memory_usage:Redis 启动时的内存使用量,对应 INFO 中used_memory_startup

  • mh->repl_backlog:复制积压缓冲区的内存开销,对应 INFO 中的mem_replication_backlog

  • mh->clients_slaves:从库的内存开销。对应 INFO 中的mem_clients_slaves

  • mh->clients_normal:其它客户端的内存开销,对应 INFO 中的mem_clients_normal

  • mh->aof_buffer:AOF 缓冲区和 AOF 重写缓冲区的内存开销,对应 INFO 中的mem_aof_buffer。AOF 缓冲区是数据写入 AOF 之前使用的缓冲区。AOF 重写缓冲区是 AOF 重写期间,用于存放新增数据的缓冲区。

  • mh->lua_caches:Lua 脚本缓存的内存开销,对应 INFO 中的used_memory_scripts。Redis 5.0 新增的。

  • 字典的内存开销,这部分内存在 INFO 中没有显示,需要通过MEMORY STATS查看。

    17) "db.0"
    18) 1) "overhead.hashtable.main"
        2) (integer) 2536870912
        3) "overhead.hashtable.expires"
        4) (integer) 0

在这些内存开销中,used_memory_startup 基本不变,mem_replication_backlog 受 repl-backlog-size 的限制,used_memory_scripts 开销一般不大,而字典的内存开销则与数据量的大小成正比。

所以,重点需要注意的主要有三项:mem_clients_slavesmem_clients_normal 和mem_aof_buffer

  • mem_aof_buffer:重点关注 AOF 重写期间缓冲区的大小。
  • mem_clients_slaves 和 mem_clients_normal:都是客户端,内存分配方式相同。客户端的内存开销主要包括以下三部分:
    1. 输入缓冲区:用于暂存客户端命令,大小由 client-query-buffer-limit 限制。
    2. 输出缓冲区:用于缓存发送给客户端的数据,大小受 client-output-buffer-limit 控制。如果数据超过软硬限制并持续一段时间,客户端会被关闭。
    3. 客户端对象本身占用的内存。

Redis 7 在内存统计方面的变化

在 Redis 7 中,还会统计以下项的内存开销:

  • mh->cluster_links:集群链接的内存开销,对应 INFO 中的mem_cluster_links
  • mh->functions_caches:Function 缓存的内存开销,对应 INFO 中的used_memory_functions
  • 集群模式下键到槽映射的内存开销,对应 MEMORY STATS 中的overhead.hashtable.slot-to-keys

此外,Redis 7 引入了 Multi-Part AOF,这个特性移除了 AOF 重写缓冲区。

需要注意的是,mh->repl_backlog 和 mh->clients_slaves 的内存计算方式也发生了变化。

在 Redis 7 之前,mh->repl_backlog 统计的是复制积压缓冲区的大小,mh->clients_slaves 统计的是所有从节点客户端的内存使用量。

if (server.repl_backlog)
    mem += zmalloc_size(server.repl_backlog);
mh->repl_backlog = mem;
mem_total += mem;

mem = 0;
// 遍历所有从节点客户端,累加它们的输出缓冲区、输入缓冲区的内存使用量以及客户端对象本身的内存占用。
if (listLength(server.slaves)) {
    listIter li;
    listNode *ln;

    listRewind(server.slaves,&li);
    while((ln = listNext(&li))) {
        client *c = listNodeValue(ln);
        mem += getClientOutputBufferMemoryUsage(c);
        mem += sdsAllocSize(c->querybuf);
        mem += sizeof(client);
    }
}
mh->clients_slaves = mem;

因为每个从节点都会分配一个独立的复制缓冲区(即从节点对应客户端的输出缓冲区),所以当从节点的数量增加时,这种实现方式会造成内存的浪费。不仅如此,当client-output-buffer-limit设置过大且从节点数量过多时,还容易导致主节点 OOM。

针对这个问题,Redis 7 引入了一个全局复制缓冲区。无论是复制积压缓冲区(repl-backlog),还是从节点的复制缓冲区都是共享这个缓冲区。

replBufBlock结构体用于存储全局复制缓冲区的一个块。

typedef struct replBufBlock {
    int refcount;           /* Number of replicas or repl backlog using. */
    long long id;           /* The unique incremental number. */
    long long repl_offset;  /* Start replication offset of the block. */
    size_t size, used;
    char buf[];
} replBufBlock;

每个replBufBlock包含一个refcount字段,用于记录该块被多少个复制实例(包括主节点的复制积压缓冲区和从节点)所引用。

当新的从节点添加时,Redis 不会为其分配新的复制缓冲区块,而是增加现有replBufBlockrefcount

相应地,在 Redis 7 中,mh->repl_backlog 和 mh->clients_slaves 的内存计算方式也发生了变化。

if (listLength(server.slaves) &&
    (long long)server.repl_buffer_mem > server.repl_backlog_size)
{
    mh->clients_slaves = server.repl_buffer_mem - server.repl_backlog_size;
    mh->repl_backlog = server.repl_backlog_size;
} else {
    mh->clients_slaves = 0;
    mh->repl_backlog = server.repl_buffer_mem;
}
if (server.repl_backlog) {
    /* The approximate memory of rax tree for indexed blocks. */
    mh->repl_backlog +=
        server.repl_backlog->blocks_index->numnodes * sizeof(raxNode) +
        raxSize(server.repl_backlog->blocks_index) * sizeof(void*);
}
mem_total += mh->repl_backlog;
mem_total += mh->clients_slaves;

具体而言,如果全局复制缓冲区的大小大于repl-backlog-size,则复制积压缓冲区(mh->repl_backlog)的大小取 repl-backlog-size,剩余部分视为从库使用的内存(mh->clients_slaves)。如果全局复制缓冲区的大小小于等于 repl-backlog-size,则直接取全局复制缓冲区的大小。

此外,由于引入了一个 Rax 树来索引全局复制缓冲区中的部分节点,复制积压缓冲区还需要计算 Rax 树的内存开销。

数据驱逐的触发条件

很多人有个误区,认为只要 used_memory 大于 maxmemory ,就会触发数据的驱逐。但实际上不是。

数据被驱逐需满足以下条件:

  1. maxmemory 必须大于 0。
  2. maxmemory-policy 不能是 noeviction。
  3. 内存使用需满足一定的条件。不是 used_memory 大于 maxmemory,而是 used_memory 减去 mem_not_counted_for_evict 后的值大于 maxmemory。

其中,mem_not_counted_for_evict的值可以通过 INFO 命令获取,它的大小是在freeMemoryGetNotCountedMemory函数中计算的。

size_t freeMemoryGetNotCountedMemory(void) {
    size_t overhead = 0;
    int slaves = listLength(server.slaves);

    if (slaves) {
        listIter li;
        listNode *ln;

        listRewind(server.slaves,&li);
        while((ln = listNext(&li))) {
            client *slave = listNodeValue(ln);
            overhead += getClientOutputBufferMemoryUsage(slave);
        }
    }
    if (server.aof_state != AOF_OFF) {
        overhead += sdsalloc(server.aof_buf)+aofRewriteBufferSize();
    }
    return overhead;
}

freeMemoryGetNotCountedMemory函数统计了所有从节点的复制缓存区、AOF 缓存区和 AOF 重写缓冲区的总大小。

所以,在 Redis 判断是否需要驱逐数据时,会从used_memory中剔除从节点复制缓存区、AOF 缓存区以及 AOF 重写缓冲区的内存占用。

Redis 内存分析脚本

最后,分享一个脚本。

这个脚本能够帮助我们快速分析 Redis 的内存使用情况。通过输出结果,我们可以直观地查看 Redis 各个部分的内存消耗情况并识别当 used_memory 增加时,具体是哪一部分的内存消耗导致的。

脚本地址:https://github.com/slowtech/dba-toolkit/blob/master/redis/redis_mem_usage_analyzer.py

# python3 redis_mem_usage_analyzer.py -host 10.0.1.182 -p 6379
Metric(2024-09-12 04:52:42)    Old Value            New Value(+3s)       Change per second   
==========================================================================================
Summary
---------------------------------------------
used_memory                    16.43G               16.44G               1.1M                
used_memory_dataset            11.93G               11.93G               22.66K              
used_memory_overhead           4.51G                4.51G                1.08M               

Overhead(Total)                4.51G                4.51G                1.08M               
---------------------------------------------
mem_clients_normal             440.57K              440.52K              -18.67B             
mem_clients_slaves             458.41M              461.63M              1.08M               
mem_replication_backlog        160M                 160M                 0B                  
mem_aof_buffer                 0B                   0B                   0B                  
used_memory_startup            793.17K              793.17K              0B                  
used_memory_scripts            0B                   0B                   0B                  
mem_hashtable                  3.9G                 3.9G                 0B                  

Evict & Fragmentation
---------------------------------------------
maxmemory                      20G                  20G                  0B                  
mem_not_counted_for_evict      458.45M              461.73M              1.1M                
mem_counted_for_evict          15.99G               15.99G               2.62K               
maxmemory_policy               volatile-lru         volatile-lru                             
used_memory_peak               16.43G               16.44G               1.1M                
used_memory_rss                16.77G               16.77G               1.32M               
mem_fragmentation_bytes        345.07M              345.75M              232.88K             

Others
---------------------------------------------
keys                           77860000             77860000             0.0                 
instantaneous_ops_per_sec      8339                 8435                                     
lazyfree_pending_objects       0                    0                    0.0       

该脚本每隔一段时间(由 -i 参数决定,默认是 3 秒)采集一次 Redis 的内存数据。然后,它会将当前采集到的数据(New Value)与上一次的数据(Old Value)进行对比,计算出每秒的增量(Change per second)。

输出主要分为四大部分:

  • Summary:汇总部分,used_memory = used_memory_dataset + used_memory_overhead。
  • Overhead(Total):展示 used_memory_overhead 中各个细项的内存消耗情况。Overhead(Total) 等于所有细项之和,理论上应与 used_memory_overhead 相等。
  • Evict & Fragmentation:显示驱逐和内存碎片的一些关键指标。其中,mem_counted_for_evict = used_memory - mem_not_counted_for_evict,当 mem_counted_for_evict 超过 maxmemory 时,才会触发数据驱逐。
  • Others:其他一些重要指标,包括 keys(键的总数量)、instantaneous_ops_per_sec(每秒操作数)以及 lazyfree_pending_objects(通过异步删除等待释放的对象数)。

如果发现mem_clients_normalmem_clients_slaves比较大,可指定 --client 查看客户端的内存使用情况。

# python3 redis_mem_usage_analyzer.py -host 10.0.1.182 -p 6379 --client
ID    Address            Name  Age    Command         User     Qbuf       Omem       Total Memory   
----------------------------------------------------------------------------------------------------
216   10.0.1.75:37811          721    psync           default  0B         232.83M    232.85M        
217   10.0.1.22:35057          715    psync           default  0B         232.11M    232.13M        
453   10.0.0.198:51172         0      client          default  26B        0B         60.03K         
...  

其中,

  • Qbuf:输入缓冲区的大小。
  • Omem:输出缓冲区的大小。
  • Total Memory:该连接占用的总内存。

结果按 Total Memory 从大到小的顺序输出。

Redis 内存突增时,如何定量分析其内存使用情况的更多相关文章

  1. 近期业务大量突增微服务性能优化总结-4.增加对于同步微服务的 HTTP 请求等待队列的监控

    最近,业务增长的很迅猛,对于我们后台这块也是一个不小的挑战,这次遇到的核心业务接口的性能瓶颈,并不是单独的一个问题导致的,而是几个问题揉在一起:我们解决一个之后,发上线,之后发现还有另一个的性能瓶颈问 ...

  2. 近期业务大量突增微服务性能优化总结-3.针对 x86 云环境改进异步日志等待策略

    最近,业务增长的很迅猛,对于我们后台这块也是一个不小的挑战,这次遇到的核心业务接口的性能瓶颈,并不是单独的一个问题导致的,而是几个问题揉在一起:我们解决一个之后,发上线,之后发现还有另一个的性能瓶颈问 ...

  3. Linux内存机制以及手动释放swap和内存

    今天我们来谈谈Linux的内存机制. 首先我们理一下概念 一.什么是linux的内存机制? 我们知道,直接从物理内存读写数据要比从硬盘读写数据要快的多,因此,我们希望所有数据的读取和写入都在内存完成, ...

  4. Redis基础篇(四)持久化:内存快照(RDB)

    AOF好处是每次执行只需要记录操作命令,记录量不大.但在故障恢复时,需要逐一执行AOF的操作命令,如果日志很大,恢复就很慢. 今天学习另一种持久化方式:内存快照.内存快照,是Redis某一时刻的状态, ...

  5. drawRect导致内存暴增的真正原因

    那么现在我们分析一下drawRect导致内存暴增的真正原因: 重写drawRect为何会导致内存大量上涨? 要想搞明白这个问题,我们需要撸一撸在 iOS 程序上图形显示的原理.在 iOS 系统中所有显 ...

  6. 读取redis中的数据时出现:MISCONF Redis is configured to save RDB snapshots

    读取redis中的数据时出现:MISCONF Redis is configured to save RDB snapshots   以下为异常详细信息: Exception in thread &q ...

  7. redis 的过期策略都有哪些?内存淘汰机制都有哪些?

    面试题 redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一下 LRU 代码实现? 面试官心理分析 如果你连这个问题都不知道,上来就懵了,回答不出来,那线上你写代码的时候,想当然的认为写进 r ...

  8. httpd2.4.6三种工作模式(如何配置),防止占用内存暴增的策略

    之前偷懒默认用yum安装了httpd.后来发现服务器内存暴增,一度达到75% 打开一看,好嘛后台休眠进程全是httpd. 重启之后再度访问发现内存还是稳步增长. [root@iz2ze3ayxs2yp ...

  9. 当 Redis 发生高延迟时,到底发生了什么

    Redis 是一种内存数据库,将数据保存在内存中,读写效率要比传统的将数据保存在磁盘上的数据库要快很多.但是 Redis 也会发生延迟时,这是就需要我们对其产生原因有深刻的了解,以便于快速排查问题,解 ...

  10. 4.redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一下 LRU 代码实现?

    作者:中华石杉 面试题 redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一下 LRU 代码实现? 面试官心理分析 如果你连这个问题都不知道,上来就懵了,回答不出来,那线上你写代码的时候,想当 ...

随机推荐

  1. Elasticjob执行job幂等

    ElasticJob的幂等机制,是指作业分片执行的幂等,他需要做到以下两点: 同一个分片在当前作业实例上不会被重复执行 一个作业分片不能同时在多个作业实例上执行 如何实现幂等 场景模拟:存在任务A执行 ...

  2. 浅谈Git架构和如何避免代码覆盖的事故

    浅谈Git架构和如何避免代码覆盖的事故 Git 不同于 SVN 的地方在于, Git 是分布式的版本管理系统, 所有的客户端和服务器都保存了一份代码, 涉及到仓库仓之间的同步, 所以处理不当极易造成冲 ...

  3. RHCA rh442 001 调优本质 调优方法 监控

    调优是一种感知 调优按照成本和性能 一.架构及调优 二.代码及调优 三.配置类调优 从调优效果和成本成正比 设计电商,日访问百万级,未来可能千万级 数据库 系统 服务器多少台 缓存 appache,n ...

  4. P5665 [CSP-S2019] 划分

    思路: 首先求出 \(a\) 的前缀和数组 \(s\). 考虑动态规划,令 \(dp_{i,j}\) 表示以 \(i\) 结尾,末尾有 \(j\) 个为一组的最小答案,则状态转移方程为: \[dp_{ ...

  5. 图书《数据资产管理核心技术与应用》核心章节节选-3.1.2. 从Spark 执行计划中获取数据血缘

    本文节选自清华大学出版社出版的图书<数据资产管理核心技术与应用>,作者为张永清等著. 从Spark 执行计划中获取数据血缘 因为数据处理任务会涉及到数据的转换和处理,所以从数据任务中解析血 ...

  6. 【ActiveJdbc】02

    一.基本的数据库操作 数据模型层: import org.javalite.activejdbc.Model; 数据访问层: import org.javalite.activejdbc.Base; ...

  7. 【Java-GUI】05 绘图 Part1

    案例: package cn.dzz; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.Acti ...

  8. 全网最适合入门的面向对象编程教程:30 Python的内置数据类型-object根类

    全网最适合入门的面向对象编程教程:30 Python 的内置数据类型-object 根类 摘要: 在 Python 中,所有的类都直接或间接继承自一个根类,这个根类是Object.Object类是 P ...

  9. 从hp的暗影精灵4来看移动cpu的实际性能表现与官方出厂性能数据之间的差距

    手上有一款暗影精灵4的笔记本,CPU为i7-9750H,官方给出的睿频为4.5Ghz,但是自己使用过程中最高睿频只能达到3.9Ghz与4.1Ghz之间,根本就没有超过4.1Ghz的时候. (图的原网址 ...

  10. windows系统下安装gym运行atari游戏报错:ale_interface/ale_c.dll OSError

    安装gym的atari支持: pip  install  gym[atari] 为gym下的atari环境下载游戏镜像ROMs文件: https://www.cnblogs.com/devilmayc ...