跳转至

运维-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 还有重复使用已分配的内存的目的,也就是说,分配到的内存不会释放,而是重复利用。

../../../_images/slab_allocation.png

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。

../../../_images/slab_class.png

Slab 缓存记录的原理

下图说明 Memcached 如何针对客户端发送的数据选择 slab 并缓存到 chunk 中。 Memcached 根据收到的数据的大小,选择最适合数据大小的 slab。 Memcached 中保存着 slab 内空闲 chunk 的列表,根据该列表选择 chunk,然后将数据缓存于其中。

../../../_images/slab_cache.png

Slab Allocator 的缺点

Slab Allocator 解决了当初的内存碎片问题,但新的机制也给 Memcached 带来了新的问题。 这个问题就是,由于分配的是特定长度的内存,因此无法有效利用分配的内存。 例如,将 100 字节的数据缓存到 128 字节的 chunk 中,剩余的 28 字节就浪费了。

../../../_images/slab_allocator.png

对于内存浪费问题目前还没有完美的解决方案。 比较有效的方式是,如果预先知道客户端发送的数据的公用大小,或者仅缓存大小相同的数据的情况下,只要使用适合数据大小的组的列表,就可以减少浪费。

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’}。

../../../_images/distributed_1.png

首先向 Memcached 中添加“zone”。将“zone”传给客户端程序库后,客户端实现的算法就会根据“键”来决定保存数据的 Memcached 服务器。服务器选定后,即命令它保存“zone”及其值。

../../../_images/distributed_2.png

接下来获取保存的数据。获取时也要将要获取的键“zone”传递给函数库。函数库通过与数据保存时相同的算法,根据“键”选择服务器。使用的算法相同,就能选中与保存时相同的服务器,然后发送 get 命令。只要数据没有因为某些原因被删除,就能获得保存的值。

../../../_images/distributed_3.png

这样,将不同的键保存到不同的服务器上,就实现了 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 服务器上。

../../../_images/Consistent_1.png

从上图的状态中添加一台 Memcached 服务器。余数分布式算法由于保存键的服务器会发生巨大变化而影响缓存的命中率,但 Consistent Hashing 中,只有在 continuum 上增加服务器的地点逆时针方向的第一台服务器上的键会受到影响。

../../../_images/Consistent_2.png

因此,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

是一个唯一的 64-bit value,通过 gets 命令获得。

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

清除缓存中的所有缓存数据。