Catalog
  1. 1. 数据接结构
  2. 2. 缓存淘汰
    1. 2.1. 策略
  3. 3. 过期删除策略
  4. 4. 慢查询日志
    1. 4.1. 获取配置
    2. 4.2. 修改配置
    3. 4.3. 查询
  5. 5. 性能
    1. 5.1. zscore
  6. 6. lazydel
  7. 7. 删除 HASH 做了哪些事情
redis备忘录

记录一些 redis 相关的,比较散的点。

数据接结构

redis 数据结构内部实现

  1. hash - ziplist
    redis的哈希对象的底层存储可以使用ziplist(压缩列表)和hashtable。当hash对象可以同时满足一下两个条件时,哈希对象使用ziplist编码。

    • 哈希对象保存的所有键值对的键和值的字符串长度都小于64字节
    • 哈希对象保存的键值对数量小于512个
  2. zset - skiplist
    单向链表
    ZSKIPLIST_MAXLEVEL 64, 最大层数。

  3. list - quicklist
    双向链表

缓存淘汰

当 redis 内存不足时,清理数据的策略。

策略

  1. noeviction:当内存使用超过配置的时候会返回错误,不会驱逐任何键
  2. allkeys-lru:加入键的时候,如果过限,首先通过LRU算法驱逐最久没有使用的键
  3. volatile-lru:加入键的时候如果过限,首先从设置了过期时间的键集合中驱逐最久没有使用的键
  4. allkeys-random:加入键的时候如果过限,从所有key随机删除
  5. volatile-random:加入键的时候如果过限,从过期键的集合中随机驱逐
  6. volatile-ttl:从配置了过期时间的键中驱逐马上就要过期的键
  7. volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
  8. allkeys-lfu:从所有键中驱逐使用频率最少的键

过期删除策略

redis 会有一个 dict 存储所有设置过过期时间的 key。

  1. 惰性删除
    每次操作 key 时,检测 key 是否超时,做清理。对内存不太友好,可能有些 key 永远删不掉。
  2. 定期删除
    每个一定时间对所有的设置过过期时间的 key 做回收操作。这个操作规定了执行时间,以免影响 redis 的服务。

慢查询日志

获取配置

1
CONFIG GET *slower*

修改配置

1
config set slowlog-log-slower-than 10000 //ms

查询

1
slowlog get len

性能

zscore

时间复杂度为 O(1)。 是不是觉得挺奇怪,zset 是通过 skiplist 实现的,即使按照 key 来查询,那么时间复杂度也应该是 O(log(n)), 更别说查找某个 member 的 score 了。但是官方文档上写的都是 O(1)。所以只能去看源码,原来,zset 数据结构有一个 skiplist 以及一个 dict, 这个 dict 就存储了 member-score 键值对。

1
2
3
4
typedef struct zset {
dict *dict;
zskiplist *zsl;
} zset;

lazydel

在线上删除较大的 key 时所产生的的耗时是不可忍受的。可以使用 unlink 命令来删除。实现原理应该是讲删除操作放在独立线程执行,不占用主线程时间。

1
2
//异步删除
dbAsyncDelete

删除 HASH 做了哪些事情

有一次删除一个打给 2.5G 的 HASH key, 大概有 2000W 条记录,存储的使用户 ID 对应的 昵称, ID 长度 8 个字符, 昵称最长 32 个字符,耗时 30s 左右,分析一下时间耗在哪里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* Destroy an entire dictionary */
int _dictClear(dict *d, dictht *ht, void(callback)(void *)) {
unsigned long i;

/* Free all the elements 清除所有的元素*/
for (i = 0; i < ht->size && ht->used > 0; i++) {
dictEntry *he, *nextHe;

if (callback && (i & 65535) == 0) callback(d->privdata);

if ((he = ht->table[i]) == NULL) continue;
while(he) {
nextHe = he->next;
// 释放 key 内存,redis 内都是字符串,所以这里仅是释放指针
dictFreeKey(d, he);
// 释放 value 内存,redis 内都是字符串,所以这里仅是释放指针
dictFreeVal(d, he);
// 释放 dictEntry 内存
zfree(he);
ht->used--;
he = nextHe;
}
}
/* Free the table and the allocated cache structure */
zfree(ht->table);
/* Re-initialize the table */
_dictReset(ht);
return DICT_OK; /* never fails */
}

看这里似乎并没有如此耗时的地方,虽然有 2000w+ 的数据,但是这里仅释放指针,不会有这么长的耗时。

Author: 42
Link: http://blog.ikernel.cn/2019/11/22/redis%E5%A4%87%E5%BF%98%E5%BD%95/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.

Comment