运维-10-缓存-memcached¶
基础介绍¶
Memcached 简介¶
- Memcached 是一个高性能、分布式的内存对象缓存系统。
- Memcached 是分布式的内存对象缓存系统,采用 C/S 模式。
- Memcached 通过 C 语言实现,采用了单进程,单线程,异步 I/O,基于事件 (event-based) 的服务方式。使用 libevent 作为事件通知。
- Memcached 本身并不是分布式的。Memcached 集群环境实际就是一个个 Memcached 服务器的堆积,多个 Server 可以协同工作,但这些 Server 之间是没有任何通信,每个 Server 只是对自己的数据进行管理。
- 分布式是由客户端实现的,客户端通过路由算法来达到分布式的目的。应用服务器在每次存取数据时,通过路由算法把 key 映射到一台 Memcached 服务器上,因此这个 key 所有操作都在这台服务器上。只要服务器还缓存着该数据,就能保证缓存命中。
- 缓存的对象或数据是以 key-value 对的形式保存在Server端的内存中。key 的值通过 hash 进行转换,根据 hash 值把 value 传递到对应的具体的某个 Server 上。当需要获取对象数据时,也根据 key 进行。首先对 key 进行 hash,通过获得的值可以确定它被保存在了哪台 Server 上,然后再向该 Server 发出请求。Client 端只需要知道保存 hash(key) 的值在哪台服务器上就可以了。
- 因此,Memcached 的工作就是在专门的机器的内存里维护一张巨大的 hash 表,来存储经常被访问的一些数据。
官方网站是 http://memcached.org/。
Memcached 特性¶
- 协议简单
Memcached 支持文本和二进制协议:文本协议调试简单,内容可视化;二进制性能高效,且相对文本协议安全性高。
- 基于 libevent 的事件处理
使用 IO 多路复用的 IO 模型,Linux 系统下使用 epoll 处理数据读写,具备极高的 IO 性能。
- 内置内存存储方式
所有数据存放在内存,相对于 Linux 提供的 malloc/free 产生的内存碎片,Memcached 独特的内存存储方式可以避免内存碎片,提高内存利用率和性能。因为数据存储在内存中,所以重启 Memcached 和操作系统,数据将全部丢失。
- Memcached 互不通信的分布式
Memcached 实际上不是一个真正的分布式服务器,集群的各个 Memcached 服务器不互相通信以共享数据,分布式特性通过客户端实现。实际上这也避免分布式集群特有的问题:脑裂。
Memcached 工作过程¶
Memcached 内存管理机制¶
Memcached 使用 Slab Allocation 机制管理内存,基本原理是按照预先规定的大小,将分配的内存分割成特定长度的块,以完全解决内存碎片问题。并且 Slab Allocator 还有重复使用已分配的内存的目的,也就是说,分配到的内存不会释放,而是重复利用。
Slab Allocation 的主要术语¶
Page 为内存分配的单位
分配给 slab 的内存空间,默认是 1MB,可以通过-I 参数修改。分配给 Slab 之后根据 slab 的大小切分成 chunk。 如果需要申请内存时,Memcached 会划分出一个新的 page 并分配给需要的 slab 区域。page 一旦被分配在 Memcached 重启前不会被回收或者重新分配。
Chunk 为存放缓存数据的单位
Chunk 是一系列固定的内存空间,它的大小和包含它的 slab 的大小是一致的。如果实际的数据大小小于 chunk 的大小,空余的空间将会被闲置,这个是为了防止内存碎片而设计的。
Slabs 划分数据空间
Slab 是 Memcached 用来划定存储空间的大小概念,每当 Memcached 启动的时候,它会按照-n 参数配置的值来决定第一个 slab 的大小,然后根据-f 参数的值来决定后续 slab 大小的增长速率,一个一个地决定后续的 slab 的大小,直到 slab 的大小达到设定的 page 大小(一般是 1M)。有相同大小 chunk 的 slab 被组织在一起,称为 slab_class。
Slab 缓存记录的原理¶
下图说明 Memcached 如何针对客户端发送的数据选择 slab 并缓存到 chunk 中。 Memcached 根据收到的数据的大小,选择最适合数据大小的 slab。 Memcached 中保存着 slab 内空闲 chunk 的列表,根据该列表选择 chunk,然后将数据缓存于其中。
Slab Allocator 的缺点¶
Slab Allocator 解决了当初的内存碎片问题,但新的机制也给 Memcached 带来了新的问题。 这个问题就是,由于分配的是特定长度的内存,因此无法有效利用分配的内存。 例如,将 100 字节的数据缓存到 128 字节的 chunk 中,剩余的 28 字节就浪费了。
对于内存浪费问题目前还没有完美的解决方案。 比较有效的方式是,如果预先知道客户端发送的数据的公用大小,或者仅缓存大小相同的数据的情况下,只要使用适合数据大小的组的列表,就可以减少浪费。
Growth Factor 调优¶
Memcached 启动时会指定增长因子,默认是 1.25,增长因子确定了 chunk 的大小。 初始化 slabclass,用于维护所有 slab class 信息,默认情况下一次性初始化为 200 个,并且 class 的最大数量也是 200 个。默认 chunk 的最小值是 48,根据增长因子递增。
[root@10e150e68e69 ~]# memcached -u memcached -p 20000 -m 64 -c 1024 -l 10.150.68.69 -vv
slab class 1: chunk size 96 perslab 10922
slab class 2: chunk size 120 perslab 8738
slab class 3: chunk size 152 perslab 6898
slab class 4: chunk size 192 perslab 5461
slab class 5: chunk size 240 perslab 4369
slab class 6: chunk size 304 perslab 3449
slab class 7: chunk size 384 perslab 2730
slab class 8: chunk size 480 perslab 2184
slab class 9: chunk size 600 perslab 1747
slab class 10: chunk size 752 perslab 1394
slab class 11: chunk size 944 perslab 1110
slab class 12: chunk size 1184 perslab 885
...
slab class 39: chunk size 493552 perslab 2
slab class 40: chunk size 616944 perslab 1
slab class 41: chunk size 771184 perslab 1
slab class 42: chunk size 1048576 perslab 1
设置增长因子为 2
[root@10e150e68e69 ~]# memcached -u memcached -p 20000 -m 64 -c 1024 -l 10.150.68.69 -f 2 -vv
slab class 1: chunk size 96 perslab 10922
slab class 2: chunk size 192 perslab 5461
slab class 3: chunk size 384 perslab 2730
slab class 4: chunk size 768 perslab 1365
slab class 5: chunk size 1536 perslab 682
slab class 6: chunk size 3072 perslab 341
slab class 7: chunk size 6144 perslab 170
slab class 8: chunk size 12288 perslab 85
slab class 9: chunk size 24576 perslab 42
slab class 10: chunk size 49152 perslab 21
slab class 11: chunk size 98304 perslab 10
slab class 12: chunk size 196608 perslab 5
slab class 13: chunk size 393216 perslab 2
slab class 14: chunk size 1048576 perslab 1
将 Memcached 引入产品,或是直接使用默认值进行部署时,最好是重新计算一下数据的预期平均长度,调整 growth factor,以获得最恰当的设置,减少内存浪费。
Memcached 内存管理需要注意的几个方面:
- chunk是 在 page 里面划分的,而 page 固定为 1M,所以 chunk 最大不能超过 1M。
- chunk 实际占用内存要加 48B,因为chunk数据结构本身需要占用 48B。
- 如果用户数据大于 1M,则 Memcached 会将其切割,放到多个 chunk 内。
- 已分配出去的 page 不能回收。
Memcached 分布式¶
Memcached 虽然称为“分布式”缓存服务器,但服务器端并没有“分布式”功能。至于 Memcached 的分布式,则是完全由客户端程序库实现的。 这种分布式是 Memcached 的最大特点。
下面假设 Memcached 服务器有 node1~node3 三台, 应用程序要保存数据{‘zone’:’nova’}。
首先向 Memcached 中添加“zone”。将“zone”传给客户端程序库后,客户端实现的算法就会根据“键”来决定保存数据的 Memcached 服务器。服务器选定后,即命令它保存“zone”及其值。
接下来获取保存的数据。获取时也要将要获取的键“zone”传递给函数库。函数库通过与数据保存时相同的算法,根据“键”选择服务器。使用的算法相同,就能选中与保存时相同的服务器,然后发送 get 命令。只要数据没有因为某些原因被删除,就能获得保存的值。
这样,将不同的键保存到不同的服务器上,就实现了 Memcached 的分布式。Memcached 服务器增多后,键就会分散,即使一台 Memcached 服务器发生故障无法连接,也不会影响其他的缓存,系统依然能继续运行。
余数哈希¶
在 Memecache 集群中,如何平均分配所有的 key?最简单的办法是用余数 hash。
node_id = hash_key % len(nodes)
随着负载的增加,现在线上要加一台机器。此时会出现什么情况呢?
- 假设原有机器 3 台,加入一台新机器后,会有 4 台机器。
- 原有 3 台机器的编号为 0、1、2。当加入 3 号机后,将会有 ¾ 的 key 不能命中原有的缓存机器。
分析如下:
- 对 0 号机而言,当有 3 台机器的时候,只接受被 3 整除的 key。现在,0 号机只接受被 4 整除的 key。所以,只有被 12 整除的 key 被保留下来。这台机器原来接受 ⅓ 的 key,其中 ¼ 的 key 还在,所以只有 ¼ 的 key 可以被保留。
- 对 1 号机也可以做类似的分析,留下来的必定是除 12 余 1 的key,因此也是 ¼ 的 key 被保留。同理可以知道 2 号机的情况也是如此。
- 于是,只有 ¼ 的 key 被保留,也就是说 ¾ 的 key 不能命中原有的机器。
事实上,可以写出 key 流失率的公式
miss_ratio = n/(n+1)
如果原来有 99 台机器,新加入一台机器后,将有 1 - 1/100 = 99/100 的 key 不能保留。这些不命中缓存的压力将转嫁给数据库,使得数据库崩坏。
一致性哈希¶
一致性哈希如下所示:首先求出 Memcached 服务器(节点)的哈希值,并将其配置到 0~232 的圆(continuum)上。然后用同样的方法求出存储数据的键的哈希值,并映射到圆上。然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。如果超过 232 仍然找不到服务器,就会保存到第一台 Memcached 服务器上。
从上图的状态中添加一台 Memcached 服务器。余数分布式算法由于保存键的服务器会发生巨大变化而影响缓存的命中率,但 Consistent Hashing 中,只有在 continuum 上增加服务器的地点逆时针方向的第一台服务器上的键会受到影响。
因此,Consistent Hashing 最大限度地抑制了键的重新分布。而且,有的 Consistent Hashing 的实现方法还采用了虚拟节点的思想。使用一般的 hash 函数的话,服务器的映射地点的分布非常不均匀。因此,使用虚拟节点的思想,为每个物理节点(服务器)在 continuum 上分配 100~200 个点。这样就能抑制分布不均匀,最大限度地减小服务器增减时的缓存重新分布。
Memcached 安装和启动¶
Memcached 基于 libevent 的事件处理,linux 系统安装 Memcached,首先要先安装 libevent 库
# yum install libevent libevent-devel
yum 安装 Memcached
# yum install memcached
配置文件
[root@10e150e68e69 ~]# cat /etc/sysconfig/memcached
PORT="18888"
USER="memcached"
MAXCONN="1024"
CACHESIZE="64"
OPTIONS="-l 10.150.68.69"
systemd 启动
# systemctl start memcahced
Memcached 命令启动
# /usr/bin/memcached -u memcached -p 11211 -m 64 -c 1024 -l 10.150.68.69 -d
Memcached 命令参数说明
-h 帮助
-p <num> 设置 TCP 端口号(默认设置为: 11211)
-U <num> UDP 监听端口(默认: 11211, 0 时关闭)
-l <ip_addr> 监听地址(默认:所有都允许,无论内外网或者本机更换 IP,有安全隐患)
-c <num> 最大同时连接数 (default: 1024)
-d 以 daemon 方式运行
-u <username> 绑定使用指定用于运行进程<username>
-m <num> 允许最大内存用量,单位 M(默认: 64 MB)
-M 禁止 LRU 策略
-f 增长因子,默认 1.25
-P <file> 将 PID 写入文件<file>,这样可以快速终止进程, 需要与-d 一起使用
-I slab 页的大小,范围 1K~128M,默认 1M
-v, --verbose
-vv
-vvv
常用命令¶
Memcached 连接¶
通过 telnet 连接 Memcached
[root@10e150e68e69 ~]# telnet 10.150.68.69 18888
Trying 10.150.68.69...
Connected to 10.150.68.69.
Escape character is '^]'.
Memcached 存储命令¶
<command name> <key> <flags> <exptime> <bytes> [noreply]\r\n
<data block>
存储命令参数说明
参数 | 说明 |
---|---|
command | set/add/replace/append/prepend/cas |
key | 存储的键 |
flags | 一个 16-bit 的标志值,存储关于键值对的额外信息。 |
exptime | 在缓存中保存键值对的时间(以秒为单位,0 表示永远) |
bytes | 在缓存中存储的字节数,必须与实际存储值的长度一致 |
noreply | 该参数告知服务器不需要返回数据 |
data block | 存储的值 |
set¶
set 命令将 value(数据值)存储在指定的 key(键)中。 如果 key 不存在,则创建新的 key-value,如果 key 已经存在,则更新 value。
set key1 0 600 1
a
STORED
get key1
VALUE key1 0 1
a
END
set key1 0 100 1
b
STORED
get key1
VALUE key1 0 1
b
END
add¶
add 命令创建新的 key-value。如果 key 已经存在,不会更新原来的 value 值。
set key1 0 100 10
key1_value
STORED
get key1
VALUE key1 0 10
key1_value
END
add key2 0 100 10
key2_value
STORED
get key2
VALUE key2 0 10
key2_value
END
add key1 0 100 8
key1_new
NOT_STORED
get key1
VALUE key1 0 10
key1_value
END
replace¶
replace 命令替换已存在 key 的 value 值。
add key1 0 100 10
key1_value
STORED
get key1
VALUE key1 0 10
key1_value
END
replace key1 1 200 12
key1_replace
STORED
get key1
VALUE key1 1 12
key1_replace
END
append¶
append 命令向已存在的 key 的 value 值后面追加数据。
add key1 0 100 10
key1_value
STORED
append key1 0 100 11
key1_append
STORED
get key1
VALUE key1 0 21
key1_valuekey1_append
END
prepend¶
prepend 命令向已存在的 key 的 value 值前面追加数据。
set key1 0 600 10
key1_value
STORED
get key1
VALUE key1 0 10
key1_value
END
prepend key1 0 600 12
key1_prepend
STORED
get key1
VALUE key1 0 22
key1_prependkey1_value
END
cas¶
cas 指 check and set,对已经存在的 key-value 进行检查并操作。检查是通过
cas <key> <flags> <exptime> <bytes> <cas unique> [noreply]\r\n
value
set key1 0 600 10
key1_value
STORED
gets key1
VALUE key1 0 10 550885
key1_value
END
cas key1 1 700 8 550885
key1_cas
STORED
gets key1
VALUE key1 1 8 550920
key1_cas
END
Memcached 查找命令¶
<command name> <key> [noreplay]\r\n
get¶
get 命令获取 key 对应的 value 值。
get key1
VALUE key1 0 10
key1_value
END
get key1 key2 key3
VALUE key1 0 10
key1_value
VALUE key2 0 10
key2_value
VALUE key3 0 10
key3_value
END
gets¶
gets 命令获取 key 对应的 value 值,并带有 cas 令牌。
gets key1
VALUE key1 0 10 560122
key1_value
END
gets key1 key2 key3
VALUE key1 0 10 560122
key1_value
VALUE key2 0 10 560125
key2_value
VALUE key3 0 10 560126
key3_value
END
delete¶
delete 命令删除一个 key。
delete key1
DELETED
delete key2
DELETED
delete key3
DELETED
get key1
END
incr/decr¶
incr 和 decr 对已存在的 value 值进行增减操作,只对十进制整数有效。
add key1 0 600 2
10
STORED
incr key1 2
12
get key1
VALUE key1 0 2
12
END
decr key1 3
9
get key1
VALUE key1 0 2
9
END
Memcached 统计命令¶
stats [items | slabs | sizes | settings]\r\n
stats¶
stats
STAT pid 4396
STAT uptime 11132335
STAT time 1535942641
STAT version 1.4.39
STAT libevent 2.0.21-stable
STAT pointer_size 64
STAT rusage_user 562.986215
STAT rusage_system 1079.217080
STAT curr_connections 527
STAT total_connections 6927872
STAT connection_structures 702
STAT reserved_fds 20
STAT cmd_get 10526030
STAT cmd_set 560222
STAT cmd_flush 0
STAT cmd_touch 0
STAT get_hits 9975676
STAT get_misses 550354
STAT get_expired 304004
STAT get_flushed 0
STAT delete_misses 665
STAT delete_hits 6619
STAT incr_misses 0
STAT incr_hits 1
STAT decr_misses 0
STAT decr_hits 1
STAT cas_misses 0
STAT cas_hits 1
STAT cas_badval 2
STAT touch_hits 0
STAT touch_misses 0
STAT auth_cmds 0
STAT auth_errors 0
STAT bytes_read 3825605705
STAT bytes_written 54223609045
STAT limit_maxbytes 67108864
STAT accepting_conns 1
STAT listen_disabled_num 0
STAT time_in_listen_disabled_us 0
STAT threads 4
STAT conn_yields 0
STAT hash_power_level 16
STAT hash_bytes 524288
STAT hash_is_expanding 0
STAT malloc_fails 0
STAT log_worker_dropped 0
STAT log_worker_written 0
STAT log_watcher_skipped 0
STAT log_watcher_sent 0
STAT bytes 7683182
STAT curr_items 1922
STAT total_items 560213
STAT expired_unfetched 48530
STAT evicted_unfetched 0
STAT evictions 0
STAT reclaimed 120683
STAT crawler_reclaimed 0
STAT crawler_items_checked 0
STAT lrutail_reflocked 0
END
说明
参数 | 说明 | 备注 |
---|---|---|
pid | Memcached 进程 ID | |
uptime | Memcached 运行时间,单位:s | |
time | Memcached 服务器当前的 UNIX 时间 | |
version | Memcached 版本 | |
rusage_user | 累计用户时间,单位:s | 分析CPU占用是否过高 |
rusage_system | 累计系统时间,单位:s | |
curr_connections | 当前连接数量 | 分析连接数量是否过多 |
total_connections | Memcached 运行以来连接的总数量 | |
cmd_get | 查询请求总数 | 分析命中率 |
get_hits | 查询命中数量 | |
get_misses | 查询未命中数量 | |
cmd_set | 存储/更新请求总数 | |
bytes | Memcached 当前存储数据使用字节数 | 分析字节数流量 |
bytes_read | Memcached 服务器从网络读取到的总字节数 | |
bytes_written | Memcached 服务器向网络发送的总字节数 | |
limit_maxbytes | Memcached 允许存储使用的字节总数 | |
curr_items | Memcached 当前存储的对象总数 | 分析对象LRU频率 |
total_items | Memcached运行以来存储的对象总数 | |
evcitions | LRU 释放对象总数,用来释放内存 |
stats reset¶
清空统计数据
stats items¶
stats items 输出每个 slab class 的 item 存储信息
stats items
STAT items:1:number 1
STAT items:1:age 12800
STAT items:1:evicted 0
STAT items:1:evicted_nonzero 0
STAT items:1:evicted_time 0
STAT items:1:outofmemory 0
STAT items:1:tailrepairs 0
STAT items:1:reclaimed 6
STAT items:1:expired_unfetched 0
STAT items:1:evicted_unfetched 0
STAT items:1:crawler_reclaimed 0
STAT items:1:crawler_items_checked 0
STAT items:1:lrutail_reflocked 0
...
END
stats slabs¶
输出 Memcached 启动后创建的 slabs 的信息
STAT 1:chunk_size 96
STAT 1:chunks_per_page 10922
STAT 1:total_pages 1
STAT 1:total_chunks 10922
STAT 1:used_chunks 1
STAT 1:free_chunks 10921
STAT 1:free_chunks_end 0
STAT 1:mem_requested 71
STAT 1:get_hits 28
STAT 1:cmd_set 30
STAT 1:delete_hits 3
STAT 1:incr_hits 1
STAT 1:decr_hits 1
STAT 1:cas_hits 1
STAT 1:cas_badval 2
STAT 1:touch_hits 0
...
END
stats conns¶
返回连接信息
...
STAT 580:addr tcp:10.150.68.70:44358
STAT 580:state conn_read
STAT 580:secs_since_last_cmd 51
...
END
stats settings¶
返回 Memcached 的详细设置
stats settings
STAT maxbytes 67108864
STAT maxconns 1024
STAT tcpport 18888
STAT udpport 18888
STAT inter 10.150.68.69
STAT verbosity 0
STAT oldest 0
STAT evictions on
STAT domain_socket NULL
STAT umask 700
STAT growth_factor 1.25
STAT chunk_size 48
STAT num_threads 4
STAT num_threads_per_udp 4
STAT stat_key_prefix :
STAT detail_enabled no
STAT reqs_per_event 20
STAT cas_enabled yes
STAT tcp_backlog 1024
STAT binding_protocol auto-negotiate
STAT auth_enabled_sasl no
STAT item_size_max 1048576
STAT maxconns_fast no
STAT hashpower_init 0
STAT slab_reassign no
STAT slab_automove 0
STAT slab_automove_ratio 0.80
STAT slab_automove_window 0.80
STAT slab_chunk_max 1048576
STAT lru_crawler no
STAT lru_crawler_sleep 100
STAT lru_crawler_tocrawl 0
STAT tail_repair_time 0
STAT flush_enabled yes
STAT dump_enabled yes
STAT hash_algorithm jenkins
STAT lru_maintainer_thread no
STAT lru_segmented no
STAT hot_lru_pct 20
STAT warm_lru_pct 40
STAT hot_max_factor 0.20
STAT warm_max_factor 2.00
STAT temp_lru no
STAT temporary_ttl 61
STAT idle_timeout 0
STAT watcher_logbuf_size 262144
STAT worker_logbuf_size 65536
STAT track_sizes no
STAT inline_ascii_response yes
END
flush_all¶
清除缓存中的所有缓存数据。