在 Memcached 和 Redis 的比较中,总会提到它们存储字符串的区别。
Memcached 默认上限是 1MB (最大上限是 1GB),而 Redis 是 512MB 。
但是这样就够了吗?我们很自然的会对此提出一些问题。
这个“字符串存储容量上限”的限制配置在哪?
看看源码吧。
memcached/memcached.c
:
1
2
3
4
5
6
| static void settings_init(void) {
// ... 省略代码
settings.item_size_max = 1024 * 1024; /* The famous 1MB upper limit. 著名的 1MB 上限 */
settings.slab_page_size = 1024 * 1024; /* chunks are split from 1MB pages. 数据块由 1MB 的页分割得到 */
// ... 省略代码
}
|
当然,这是默认的 1MB。启动时可以通过参数修改这个最大上限。这个最大上限能调整到什么程度呢?
memcached/memcached.h
:
1
2
3
4
5
6
7
8
9
10
| /** Maximum length of a key. */
#define KEY_MAX_LENGTH 250
// ... 省略代码
/*
* Valid range of the maximum size of an item, in bytes.
*/
#define ITEM_SIZE_MAX_LOWER_LIMIT 1024
#define ITEM_SIZE_MAX_UPPER_LIMIT 1024 * 1024 * 1024
|
顺便可以看到,上限还能往下调,调到 1KB。 Key 的最大长度为 250 Byte。
redis/src/t_string.c
:
1
2
3
4
5
6
7
| static int checkStringLength(client *c, long long size) {
if (size > 512*1024*1024) {
addReplyError(c,"string exceeds maximum allowed size (512MB)");
return C_ERR;
}
return C_OK;
}
|
这个配置是由什么引起的?
Memcached
Memcached 在紧挨 item_size_max 后面还有个重要的配置 —— slab_page_size ,它决定了每次向系统申请多少内存,将这一块内存称之为页(page)。
在申请一页的内存后, Memcached 会将其切割成大小相等的数据块 (chunk)。
但是数据块并不是最小的单位, chunk 还会做等量切割,形成一系列的 item 。这也是上限的 item_size_max 前缀 item 的指向。
1
2
3
| all = x * page
page = n * chunk
chunk = m * item
|
page 的大小限制了数据的上限,如果想要存储超过该上限的值,则必须在客户端切割字符串。
Redis
Redis 是直接把大小限制放在存入字符串之前,直接用大小判断。与内存无直接联系。
内存是如何申请的?
Memcached
do_slabs_newslab
Redis
Redis 由于是直接使用 TCMalloc 和 Jemalloc ,所以其实是要了解这两者。
TCMalloc
Jemalloc
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
| divided into region_size = 2 pages
+--> page_0 +
| +--> region_0
divided into +--> page_1 +
|
+--> run_0 +---------> +--> ... ...
| |
+--> run_1 <--+ +--> page_n-1 +
| | | +--> region_y
+--> chunk_0 +--> +--> run_2 | +--> page_n +
| | |
| +--> run_3 <---------------------------------+
| | | |
| +--> run_4 | |
| | | |
| +--> run_5 <---------------------------------------+
+--> chunks +--> +--> ... | | | |
| | +--> ... | | |
| | | | | |
| | +--> run_n | | |
| | | | |
| | | | |
| | | | |
| +--> chunk_x | | |
| point to | | |
| | | |
| +---> run_1 +-+ + | |
| | | | |
| +--> bin_0 +--> +---> ... +--> region_size_3_runs | |
| | | | | |
| | +---> run_p + | |
arena +--> +--> bins +--> +--> ... | |
| | | |
| | | |
| +--> bin_z | |
| | |
| point to | |
| | |
| +--> run_3 +-------------------------------------------------+ |
| | |
+--> avail_clean +--> +--> ... |
| | |
| +--> run_t |
| |
| point to |
| |
| +--> run_5 +-------------------------------------------------------+
| |
+--> avail_dirty +--> +--> ...
|
+--> run_j
|
- arena :最高层次的内存管理单元,具有多个。每个 arena 管理多个内存单元 chunk , arena 的其他信息是对 chunk 内部信息的汇总。
- chunk :内存单元。chunk 的空间被切割成多个大小相等的 run 。
- run :每个 run 由一个或多个连续的页(page)组成。每个 run 的空间被切割成多个大小相等的 region 。这些 region 以页为单位。
- region :用户申请到的内存。划分为三类:
- 小(Small):单个 region 小于 page。
- 大(Large):单个 region 大于 page 但小于 chunk 。
- 巨大(Huge):单个 region 大于 chunk ,不归 arena 管。
- bin :每个 bin 管理 arena 内所有 region 相同的 run 。
未完待续…
参考文档
Structures in jemalloc: https://medium.com/iskakaushik/eli5-jemalloc-e9bd412abd70
jemalloc 源码分析: https://youjiali1995.github.io/allocator/jemalloc/
ptmalloc,tcmalloc和jemalloc内存分配策略研究: https://cloud.tencent.com/developer/article/1173720
图解 TCMalloc: https://zhuanlan.zhihu.com/p/29216091
Memcache-内存模型-源码分析: https://www.jianshu.com/p/a824ae00d9bb
https://medium.com/iskakaushik/eli5-jemalloc-e9bd412abd70