Memcached分布式缓存系统
Memcached介绍
什么是Memcached缓存数据库
Memcached是一个自由开源的,高性能,分布式内存对象缓存系统。
Memcached是以LiveJournal旗下Danga Interactive公司的Brad Fitzpatric为首开发的一款软件。现在已成为mixi、hatena、Facebook、Vox、LiveJournal等众多服务中提高Web应用扩展性的重要因素。
Memcached是一种基于内存的key-value存储,用来存储小块的任意数据(字符串、对象)。这些数据可以是数据库调用、API调用或者是页面渲染的结果。
Memcached简洁而强大。它的简洁设计便于快速开发,减轻开发难度,解决了大数据量缓存的很多问题。它的API兼容大部分流行的开发语言。
本质上,它是一个简洁的key-value存储系统。
一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、提高可扩展性。
Memcached 官网:https://memcached.org
Memcached和Redis对比
我们都知道,把一些热数据存到缓存中可以极大的提高速度,那么问题来了,是用Redis好还是Memcached好呢,以下是它们两者之间一些简单的区别与比较:
Redis不仅支持简单的k/v类型的数据,同时还支持list、set、zset(sorted set)、hash等丰富数据结构的存储,使得它拥有更广阔的应用场景。
Redis最大的亮点是支持数据持久化,它在运行的时候可以将数据备份在磁盘中,断电或重启后,缓存数据可以再次加载到内存中,只要Redis配置的合理,基本上不会丢失数据。
Redis支持主从模式的应用。
Redis单个value的最大限制是1GB,而Memcached则只能保存1MB内的数据。
Memcache在并发场景下,能用cas保证一致性,而Redis事务支持比较弱,只能保证事务中的每个操作连续执行。
性能方面,根据网友提供的测试,Redis在读操作和写操作上是略领先Memcached的。
Memcached的内存管理不像Redis那么复杂,元数据metadata更小,相对来说额外开销就很少。Memcached唯一支持的数据类型是字符串string,非常适合缓存只读数据,因为字符串不需要额外的处理。
快速开始
Memcached部署
- 通过yum安装memcache软件包
# 先安装一些依赖包
[root@localhost ~]# yum install libevent libevent-devel -y
# 安装memcached
[root@localhost ~]# yum install -y memcached
- 命令行启动测试
[root@localhost ~]# memcached -d -m 1024 -u memcached -l 127.0.0.1 -p 11211 -c 1024 -P /tmp/memcached.pid
memcached启动参数说明:
- -d是启动一个守护进程;
- -m是分配给Memcache使用的内存数量,单位是MB;
- -u是运行Memcache的用户;
- -l是监听的服务器IP地址,可以有多个地址;
- -p是设置Memcache监听的端口,最好是1024以上的端口;
- -c是最大运行的并发连接数,默认是1024;
- -P是设置保存Memcache的pid文件。
查看端口号是否启动成功
[root@localhost ~]# ss -nlt
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 *:11211 *:*
LISTEN 0 128 *:22 *:*
LISTEN 0 100 127.0.0.1:25 *:*
LISTEN 0 128 :::11211 :::*
LISTEN 0 128 :::22 :::*
LISTEN 0 100 ::1:25 :::*
memcached的端口号为:11211
- 关闭服务
[root@localhost ~]# pkill memcached
- 服务自启动(systemd)
[root@localhost ~]# systemctl start memcached
Memcached连接
对于Memcached的连接,这里要使用到一个协议:telnet(远程连接协议)
Telnet 是一种用于远程访问和控制计算机系统的网络协议。它允许用户从一台计算机连接到另一台计算机,并在远程系统上执行命令和操作。
Telnet 的主要特点如下:
- 远程访问:Telnet 协议可以让用户远程登录到其他计算机系统,就像直接在那台机器上工作一样。这对于管理远程服务器或设备非常有用。
- 文本界面:Telnet 使用纯文本界面进行交互,不需要图形界面。这使它适用于各种类型的终端设备和操作系统。
- 简单易用:Telnet 客户端通常内置在操作系统中,或者可以很容易地下载安装。建立连接只需输入远程主机的 IP 地址或主机名即可。
- 功能丰富:Telnet 协议支持各种命令和操作,如文件传输、远程执行命令等。用户可以在 Telnet 会话中执行系统管理任务。
- 不安全:Telnet 使用明文传输数据,包括用户名和密码,存在严重的安全隐患。因此在现代网络环境下,Telnet 逐步被更安全的 SSH 协议所取代。
Telnet和ssh对比:
特性 | Telnet | SSH |
---|---|---|
安全性 | 使用明文传输数据,非常不安全 | 使用强大的加密技术,提供高度安全性 |
加密 | 不提供任何加密功能 | 支持多种加密算法,如 AES、3DES 等 |
认证 | 仅依赖用户名和密码进行身份验证 | 支持多种认证方式,如用户名/密码、公钥/私钥等 |
端口 | 默认使用 23 号端口 | 默认使用 22 号端口 |
应用场景 | 用于简单的远程控制和诊断任务,但已很少使用 | 广泛用于企业和个人的远程管理和数据传输 |
[root@localhost ~]# yum install telnet -y
[root@localhost ~]# telnet localhost 11211
Trying ::1...
Connected to localhost.
Escape character is '^]'.
# 连接以后并不会给出明确提示,我们可以直接输入命令进行测试
查看Memcached信息
Memcached stats 命令用于返回统计信息例如 PID(进程号)、版本号、连接数等。
[root@localhost ~]# telnet localhost 11211
Trying ::1...
Connected to localhost.
Escape character is '^]'.
stats
STAT pid 16509
STAT uptime 8793
STAT time 1723623164
STAT version 1.4.15
STAT libevent 2.0.21-stable
STAT pointer_size 64
STAT rusage_user 0.219841
STAT rusage_system 0.109920
STAT curr_connections 10
STAT total_connections 12
STAT connection_structures 11
STAT reserved_fds 20
STAT cmd_get 8
STAT cmd_set 2
STAT cmd_flush 0
STAT cmd_touch 0
STAT get_hits 1
STAT get_misses 7
STAT delete_misses 0
STAT delete_hits 2
STAT incr_misses 0
STAT incr_hits 0
STAT decr_misses 0
STAT decr_hits 0
STAT cas_misses 0
STAT cas_hits 0
STAT cas_badval 0
STAT touch_hits 0
STAT touch_misses 0
STAT auth_cmds 0
STAT auth_errors 0
STAT bytes_read 222
STAT bytes_written 1197
STAT limit_maxbytes 67108864
STAT accepting_conns 1
STAT listen_disabled_num 0
STAT threads 4
STAT conn_yields 0
STAT hash_power_level 16
STAT hash_bytes 524288
STAT hash_is_expanding 0
STAT bytes 0
STAT curr_items 0
STAT total_items 2
STAT expired_unfetched 0
STAT evicted_unfetched 0
STAT evictions 0
STAT reclaimed 0
END
如果可以看到以上输出,说明安装并且连接成功,至于具体解释,后面再说....
slab存储机制
memcached接收来此客户端的存储请求,那么服务端是如何存储来自客户端的存储内容的呢,这里就涉及到了slabs存储机制,在此之前首先介绍memcached中关于内存管理的几个重要的概念
item数据存储节点
items:客户端传送的键-值包装成items结构体,其内存通过slab分配
源码说明:
typedef struct _stritem {
/* Protected by LRU locks */
//一个item的地址, 主要用于LRU链和freelist链
struct _stritem *next;
//下一个item的地址,主要用于LRU链和freelist链
struct _stritem *prev;
/* Rest are protected by an item lock */
//用于记录哈希表槽中下一个item节点的地址
struct _stritem *h_next; /* hash chain next */
//最近访问时间
rel_time_t time; /* least recent access */
//缓存过期时间
rel_time_t exptime; /* expire time */
int nbytes; /* size of data */
//当前item被引用的次数,用于判断item是否被其它的线程在操作中
//refcount == 1的情况下该节点才可以被删除
unsigned short refcount;
uint8_t nsuffix; /* length of flags-and-length string */
uint8_t it_flags; /* ITEM_* above */
//记录该item节点位于哪个slabclass_t中
uint8_t slabs_clsid;/* which slab class we're in */
uint8_t nkey; /* key length, w/terminating null and padding */
/* this odd type prevents type-punning issues when we do
* the little shuffle to save space when not using CAS. */
union {
uint64_t cas;
char end;
} data[];
/* if it_flags & ITEM_CAS we have 8 bytes CAS */
/* then null-terminated key */
/* then " flags length\r\n" (no terminating null) */
/* then data with terminating \r\n (no terminating null; it's binary!) */
} item;
slab与chunk
slab是一块内存空间,默认大小为1M,memcached会把一个slab分割成一个个chunk, 这些被切割的小的内存块,主要用来存储item
slabclass
每个item的大小都可能不一样,item存储于chunk,如果chunk大小不够,则不足以分配给item使用,如果chunk过大,则太过于浪费内存空间。因此memcached采取的做法是,将slab切割成不同大小的chunk,这样就满足了不同大小item的存储。被划分不同大小chunk的slab的内存在memcached就是用slabclass这个结构体来表现的
slabclass结构体源码:
typedef struct {
//chunk大小
unsigned int size; /* sizes of items */
//1M内存大小被分割为多少个chunck
unsigned int perslab; /* how many items per slab */
//空闲chunk链表
void *slots; /* list of item ptrs */
//空闲chunk的个数
unsigned int sl_curr; /* total free items in list */
//当前slabclass已经分配了所少个1M空间的slab
unsigned int slabs; /* how many slabs were allocated for this class */
//slab指针数组
void **slab_list; /* array of slab pointers */
//slab指针数组的大小
unsigned int list_size; /* size of prev array */
size_t requested; /* The number of requested bytes */
} slabclass_t;
- slabclass数组初始化的时候,每个slabclass_t都会分配一个1M大小的slab,slab会被切分为N个小的内存块,这个小的内存块的大小取决于slabclass_t结构上的size的大小
- 每个slabclass_t都只存储一定大小范围的数据,并且下一个slabclass切割的chunk块大于前一个slabclass切割的chunk块大小
- memcached中slabclass数组默认大小为64,slabclass切割块大小的增长因子默认是1.25 例如:slabclass[1]切割的chunk块大小为100字节,slabclass[2]为125,如果需要存储一个110字节的缓存,那么就需要到slabclass[2] 的空闲链表中获取一个空闲节点进行存储
item节点分配流程
- 根据大小,找到合适的slabclass
- slabclass空闲列表中是否有空闲的item节点,如果有直接分配item,用于存储内容
- 空闲列表没有空闲的item可以分配,会重新开辟一个slab(默认大小为1M)的内存块,然后切割slab并放入到空闲列表中,然后从空闲列表中获取节点
item节点的释放
释放一个item节点,并不会free内存空间,而是将item节点归还到slabclass的空闲列表中
Memcached常用操作
数据存储
set命令
语法:
set key flags exptime bytes [noreply]
value
相关参数说明:
- key:键值 key-value 结构中的 key,用于查找缓存值。
- flags:flags 是一个整型参数,用于存储关于键值对的额外信息。这些信息可以被客户端用来解释或处理存储的数据。通过 flags,客户端可以在存储数据时为该数据打上一些标记,以便后续处理时能够正确识别数据的类型或属性。
- exptime:在缓存中保存键值对的时间长度(以秒为单位,0 表示永远)
- bytes:在缓存中存储的字节数
- noreply(可选): 该参数告知服务器不需要返回数据
- value:存储的值(始终位于第二行)(可直接理解为key-value结构中的value)
示例:
set name 0 0 8
zhangsan
STORED
get name
VALUE name 0 8
zhangsan
END
add命令
Memcached add 命令用于将 value(数据值) 存储在不存在的 key(键) 中。
如果 add 的 key 已经存在,则不会更新数据(过期的 key 会更新),之前的值将仍然保持相同,并且您将获得响应 NOT_STORED
语法:
add key flags exptime bytes [noreply]
value
示例:
add name 0 0 10
zhangsan
NOT_STORED
get name
VALUE name 0 8
zhangsan
END
# 加入一个不存在的key就可以成功
add age 1 0 10
18
STORED
get age
VALUE age 1 10
18
END
replace命令
Memcached replace 命令用于替换已存在的 key(键) 的 value(数据值)。
如果 key 不存在,则替换失败,并且您将获得响应 NOT_STORED。
语法:
replace key flags exptime bytes [noreply]
value
示例:
replace name 0 900 8
lisilisi
replace gender 0 900 4
male
append命令
Memcached append 命令用于向已存在 key(键) 的 value(数据值) 后面追加数据 。
示例:
get key1
END
set key1 0 900 9
memcached
STORED
get key1
VALUE key1 0 9
memcached
END
append key1 0 900 5
redis
STORED
get key1
VALUE key1 0 14
memcachedredis
END
prepend命令
Memcached prepend 命令用于向已存在 key(键) 的 value(数据值) 前面追加数据 。
语法:
prepend key flags exptime bytes [noreply]
value
示例:
prepend key1 0 900 5
hello
STORED
get key1
VALUE key1 0 19
hellomemcachedredis
END
CAS命令
CAS (Check-And-Set) 是 Memcached 中一个非常有用的原子操作特性。它可以帮助我们解决多客户端并发更新同一数据的问题。
CAS 命令格式
CAS 命令的完整格式为:
复制
cas key flags exptime bytes casunique
其中:
key
: 缓存数据的键flags
: 缓存数据的标志位exptime
: 缓存数据的过期时间(单位为秒)bytes
: 缓存数据的长度casunique
: 一个唯一标识符,用于检查值是否被修改过
CAS 操作流程
CAS 操作的流程如下:
- 客户端先使用
get
命令获取某个 key 的值,并记录下返回的casunique
。 - 客户端准备更新这个值时,会使用
cas
命令,并附带之前获取的casunique
。 - Memcached 服务器收到
cas
命令后,会先检查当前值的casunique
是否与客户端传来的一致。 - 如果一致,说明这个值自从客户端获取后就没有被其他人修改过,服务器会接受这次更新。
- 如果不一致,说明这个值在客户端获取后已经被其他人修改过了,服务器会拒绝这次更新。
CAS 的应用场景
CAS 命令主要用于解决多客户端并发更新同一缓存数据的问题,避免出现"丢失更新"的情况。
例如,在一个电商网站上,多个用户可能同时操作同一个购物车。这时就可以使用 CAS 来确保只有最后一个更新成功的客户端的修改生效。
假设我们有一个电商网站,需要缓存用户的购物车信息。多个用户可能同时操作同一个购物车,此时就需要使用 CAS 来避免"丢失更新"的问题。
案例流程如下:
- 用户 A 访问网站,获取自己的购物车信息:
- 使用
get
命令从 Memcached 中获取购物车数据 - 同时记录下返回的
casunique
值
- 使用
- 用户 A 添加一件商品到购物车:
- 使用
cas
命令更新购物车数据 - 同时带上之前获取的
casunique
值
- 使用
- 与此同时,用户 B 也访问网站,获取自己的购物车信息:
- 同样使用
get
命令从 Memcached 中获取购物车数据 - 记录下返回的
casunique
值
- 同样使用
- 用户 B 也想修改购物车中的商品:
- 使用
cas
命令尝试更新购物车数据 - 但此时 Memcached 检查发现
casunique
已经不一致了 - 因此拒绝了用户 B 的更新请求
- 使用
- 最终只有用户 A 的更新生效,用户 B 的更新被拒绝。
示例:
set name 0 0 3
nls
STORED
get name
VALUE name 0 3
nls
END
gets name
VALUE name 0 3 12
nls
END
cas name 0 0 3 12
xls
STORED
get name
VALUE name 0 3
xls
END
cas name 0 0 2 12
cs
EXISTS
输出信息说明:
- STORED:保存成功后输出。
- ERROR:保存出错或语法错误。
- EXISTS:在最后一次取值后另外一个用户也在更新该数据。
- NOT_FOUND:Memcached 服务上不存在该键值。
数据查找
get命令
get 命令的基本语法格式如下:
get key
多个 key 使用空格隔开,如下:
get key1 key2 key3
参数说明如下:
- key:键值 key-value 结构中的 key,用于查找缓存值。
gets命令
Memcached gets 命令获取带有 CAS 令牌存 的 value(数据值) ,如果 key 不存在,则返回空。不带的也可以正常获取
语法
gets 命令的基本语法格式如下:
gets key
多个 key 使用空格隔开,如下:
gets key1 key2 key3
参数说明如下:
- key:键值 key-value 结构中的 key,用于查找缓存值。
delete命令
Memcached delete 命令用于删除已存在的 key(键)。
语法
delete 命令的基本语法格式如下:
delete key [noreply]
参数说明如下:
- key:键值 key-value 结构中的 key,用于查找缓存值。
- noreply(可选): 该参数告知服务器不需要返回数据
输出信息说明:
- DELETED:删除成功。
- ERROR:语法错误或删除失败。
- NOT_FOUND:key 不存在。
统计命令
stat命令
Memcached stats 命令用于返回统计信息例如 PID(进程号)、版本号、连接数等。
stats
stats
STAT pid 1162
STAT uptime 5022
STAT time 1415208270
STAT version 1.4.14
STAT libevent 2.0.19-stable
STAT pointer_size 64
STAT rusage_user 0.096006
STAT rusage_system 0.152009
STAT curr_connections 5
STAT total_connections 6
STAT connection_structures 6
STAT reserved_fds 20
STAT cmd_get 6
STAT cmd_set 4
STAT cmd_flush 0
STAT cmd_touch 0
STAT get_hits 4
STAT get_misses 2
STAT delete_misses 1
STAT delete_hits 1
STAT incr_misses 2
STAT incr_hits 1
STAT decr_misses 0
STAT decr_hits 1
STAT cas_misses 0
STAT cas_hits 0
STAT cas_badval 0
STAT touch_hits 0
STAT touch_misses 0
STAT auth_cmds 0
STAT auth_errors 0
STAT bytes_read 262
STAT bytes_written 313
STAT limit_maxbytes 67108864
STAT accepting_conns 1
STAT listen_disabled_num 0
STAT threads 4
STAT conn_yields 0
STAT hash_power_level 16
STAT hash_bytes 524288
STAT hash_is_expanding 0
STAT expired_unfetched 1
STAT evicted_unfetched 0
STAT bytes 142
STAT curr_items 2
STAT total_items 6
STAT evictions 0
STAT reclaimed 1
END
这里显示了很多状态信息,下边详细解释每个状态项:
- pid: memcache服务器进程ID
- uptime:服务器已运行秒数
- time:服务器当前Unix时间戳
- version:memcache版本
- pointer_size:操作系统指针大小
- rusage_user:进程累计用户时间
- rusage_system:进程累计系统时间
- curr_connections:当前连接数量
- total_connections:Memcached运行以来连接总数
- connection_structures:Memcached分配的连接结构数量
- cmd_get:get命令请求次数
- cmd_set:set命令请求次数
- cmd_flush:flush命令请求次数
- get_hits:get命令命中次数
- get_misses:get命令未命中次数
- delete_misses:delete命令未命中次数
- delete_hits:delete命令命中次数
- incr_misses:incr命令未命中次数
- incr_hits:incr命令命中次数
- decr_misses:decr命令未命中次数
- decr_hits:decr命令命中次数
- cas_misses:cas命令未命中次数
- cas_hits:cas命令命中次数
- cas_badval:使用擦拭次数
- auth_cmds:认证命令处理的次数
- auth_errors:认证失败数目
- bytes_read:读取总字节数
- bytes_written:发送总字节数
- limit_maxbytes:分配的内存总大小(字节)
- accepting_conns:服务器是否达到过最大连接(0/1)
- listen_disabled_num:失效的监听数
- threads:当前线程数
- conn_yields:连接操作主动放弃数目
- bytes:当前存储占用的字节数
- curr_items:当前存储的数据总数
- total_items:启动以来存储的数据总数
- evictions:LRU释放的对象数目
- reclaimed:已过期的数据条目来存储新数据的数目
stats items
Memcached stats items 命令用于显示各个 slab 中 item 的数目和存储时长(最后一次访问距离现在的秒数)。
语法
stats items
示例
stats items
STAT items:1:number 1
STAT items:1:age 7
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 0
STAT items:1:expired_unfetched 0
STAT items:1:evicted_unfetched 0
END
stats slab
Memcached stats slabs 命令用于显示各个slab的信息,包括chunk的大小、数目、使用情况等。
stats slabs
示例
stats 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 0
STAT 1:cmd_set 1
STAT 1:delete_hits 0
STAT 1:incr_hits 0
STAT 1:decr_hits 0
STAT 1:cas_hits 0
STAT 1:cas_badval 0
STAT 1:touch_hits 0
STAT active_slabs 1
STAT total_malloced 1048512
END
stats sizes
Memcached stats sizes 命令用于显示所有item的大小和个数。
该信息返回两列,第一列是 item 的大小,第二列是 item 的个数。
语法:stats sizes 命令的基本语法格式如下:
stats sizes
实例:
stats sizes
STAT 96 1
END
flush_all命令
Memcached flush_all 命令用于清理缓存中的所有 key=>value(键=>值) 对。
该命令提供了一个可选参数 time,用于在制定的时间后执行清理缓存操作。
语法:
flush_all 命令的基本语法格式如下:
flush_all [time] [noreply]
实例
清理缓存:
set runoob 0 900 9
memcached
STORED
get runoob
VALUE runoob 0 9
memcached
END
flush_all
OK
get runoob
END