Memcached分布式缓存系统
介绍
优化访问速度
-
随着网站迭代开发,访问会变慢。
-
LNMP架构中网站应用访问流程
- 浏览器(app)=>web服务器=>后端服务(php)=>数据库(mysql)
-
访问流程越多,访问速度越慢,出现问题的几率也越大
-
优化访问速度,可以减少访问步骤或者提高单步访问速度
-
具体优化方法:
- 提高web服务器的并发量:如采用nginx作为web服务器;多台服务器实现负载均衡
- 页面静态化:把经常访问,但数据不常变动的动态页面,制作为静态页面
- 优化数据库
- 内存缓存优化:把经常访问的数据,加载到缓存中间件(memcached)的内存中
Memcached介绍
-
cache in memory 缓存放入内存中
-
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不仅支持简单的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,非常适合缓存只读数据,因为字符串不需要额外的处理
slab存储机制
相关概念
- item:数据存储节点,主要用于存储数据
- slab:slab是一块内存空间,默认大小为1M
- chunk:memcached会把一个slab分割成一个个chunk, 这些被切割的小的内存块,主要用来存储item
- slabclass:每个item的大小都可能不一样,item存储于chunk。如果chunk大小不够,则不足以分配给item使用,如果chunk过大,则太过于浪费内存空间。因此memcached采取的做法是,将slab切割成不同大小的chunk,这样就满足了不同大小item的存储。被划分不同大小chunk的slab的内存在memcached就是用slabclass这个结构体来表现的
slabclass的初始化
- 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的空闲列表中
失效机制
Lazy Expiration懒惰机制
-
memcached 内部不会监视记录是否过期,而是在 get时查看记录的时间戳,检查记录是否过期。这种技术被称为 lazy(惰性)expiration。因此,memcached 不会在过期监视上耗费 CPU 时间
-
类似的有:php的里session机制,懒惰机制;php垃圾回收机制;gc回收;python变量垃圾回收机制
-
memcached1.4.25之后,懒惰机制失效
LRU机制
-
memcached 会优先使用已超时的记录的空间,但即使如此,也会发生追加新记录时空间不足的情况,此时就要使用名为 Least Recently Used(LRU)机制来分配空间。
-
顾名思义,这是删除“最近最少使用”的记录的机制。因此,当 memcached 的内存空间不足时,就从最近未被使用的记录中搜索,并将其空间分配给新的记录。
-
从缓存的实用角度来看,该模型十分理想。不过,有些情况下 LRU 机制反倒会造成麻烦,memcached 启动时通过“M”参数可以禁止 LRU
Memcached安装
- 基于server07克隆server08
- 基本环境配置
vim /etc/sysconfig/network
vim /etc/sysconfig/network-scripts/ifcfg-eth
cd /etc/udev/rules.d/
rm -rf 70-persistent-net.rules
reboot
cat >> /etc/hosts <<EOF
192.168.139.135 memcached
EOF
service nginx stop
service php-fpm stop
service mysqld stop
- yum安装
yum install libevent libevent-devel -y
yum install memcached -y
- 编译安装
yum install libevent libevent-devel -y
rz
#memcached-1.5.8.tar.gz
tar -xcf memcached-1.5.8.tar.gz
cd memcached-1.5.8
./configure --prefix=/usr/local/memcached
make && make install
- 启动
cd /usr/local/memcached/
cd bin
./memcached -h
#memcached选项说明
-d 是启动一个守护进程
-p 是设置Memcache监听的端口,最好是1024以上的端口
-d 是启动一个守护进程
-m 是分配给Memcache使用的内存数量,单位是MB
-u 是运行Memcache的用户
-l 是监听的服务器IP地址,可以有多个地址
-c 是最大运行的并发连接数,默认是1024
-P 是设置保存Memcache的pid文件
./memcached -uroot -d
ss -tnalp|grep memcached
LISTEN 0 128 *:11211 *:* users:(("memcached",3661,26))
#想要关闭memcached,使用killall工具即可
Memcached使用
talnet客户端工具连接memcached
[root@memcached ~]# yum install -y telnet
[root@memcached ~]# telnet 127.0.0.1 11211
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
ERROR
#多次回车出现error,表名连接成功;error表明,没有键入命令
#退出连接:quit
存储命令
增
- set:添加数据;如果数据存在,则更新
- set=add+replace
- prepend和append的功能都可以通过set实现
语法:
set key flags exptime bytes [noreply]
value
参数说明:
key 键,key-value结构中的key
flags 可以包括键值对的整型参数,客户机使用它存储关于键值对的额外信息,默认为0
exptime 过期时间,以秒为单位,0表示永远
bytes 在缓存中存储的字节数,严格限制,不能多一位,也不能少一位
noreply 不需要返回数据
value 值,key-value结构中的value
STORED 存储成功,系统默认返回
案例:
set name 0 0 8
zhangsan
STORED
- add:添加数据;如果数据存在,不会更新数据(过期的 key 会更新),响应NOT_STORED
语法:
add key flags exptime bytes [noreply]
value
案例:
add name 0 0 4
lisi
NOT_STORED
- replace:替换数据;如果数据不存在,则替换失败,响应NOT_STORED
语法:
replace key flags exptime bytes [noreply]
value
案例:
replace name 0 0 4
lisi
STORED
- append: 在value后追加数据
语法:
append key flags exptime bytes [noreply]
value
案例:
append name 0 0 3
123
STORED
get name
VALUE name 0 7
lisi123
- prepend: 在value前追加数据
语法:
prepend key flags exptime bytes [noreply]
value
案例:
prepend name 0 0 3
456
STORED
get name
VALUE name 0 10
456lisi123
删
- delete:删除已存在的 key,响应DELETE表示删除成功
语法:
delete key [noreply]
案例:
delete name
DELETED
-
flush_all:清理缓存中的所有key-value
-
实际业务中禁止使用!如果执行,可能会造成所有缓存清空不存在,所有的数据请求都直接到了数据库服务器,造成数据库压力瞬间变,可能会导致 数据库宕机
语法:
flush_all [time] [noreply]
参数说明:
time 设置执行清理缓存的时间,默认立即清理缓存
案例:
flush_all
OK
get age
END
改
- incr:对已存在的 key 的value(十进制的32位无符号整数)进行自增
语法:
incr key value
案例:
incr age 1
21
incr age 10
31
- decr:对已存在的 key 的value(十进制的32位无符号整数)进行自减
语法:
incr key value
案例:
decr age 5
26
decr age 10
16
查
- get:通过key获取value;如果 key 不存在,则返回空
语法:
get key1 key2 key3
案例:
get name age
VALUE name 0 8
zhangsan
VALUE age 0 2
20
- gets:获取带有 CAS 令牌存的 value(数据值),如果 key 不存在,则返回空
语法:
gets key1 key2 key3
案例:
gets name age
VALUE name 0 8 7
zhangsan
VALUE age 0 2 12
16
END
- stats:返回系统信息,例如 PID(进程号)、版本号、连接数
语法:
stats
案例:
stats
STAT pid 3661 memcache进程ID
STAT uptime 18059 服务器已运行秒数
STAT time 1634849584 服务器当前Unix时间戳
STAT version 1.5.8 版本
STAT libevent 1.4.13-stable
STAT pointer_size 64 操作系统指针大小
STAT rusage_user 2.779577 进程累计用户时间
STAT rusage_system 1.628752 进程累计系统时间
STAT max_connections 1024 最大连接数
STAT curr_connections 2 当前连接数
STAT total_connections 4 Memcached运行以来连接总数
STAT rejected_connections 0
STAT connection_structures 3 Memcached分配的连接结构数量
STAT reserved_fds 20
STAT cmd_get 8 get命令请求次数
STAT cmd_set 8
STAT cmd_flush 1
STAT cmd_touch 0
STAT get_hits 6 get命令命中次数
STAT get_misses 2 get命令未命中次数
STAT get_expired 0
STAT get_flushed 0
STAT delete_misses 0
STAT delete_hits 1
STAT incr_misses 0
STAT incr_hits 2
STAT decr_misses 0
STAT decr_hits 2
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 377 读取总字节数
STAT bytes_written 339 发送总字节数
STAT limit_maxbytes 67108864 分配的内存总大小(字节)
STAT accepting_conns 1 服务器是否达到过最大连接(0/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 slab_reassign_rescues 0
STAT slab_reassign_chunk_rescues 0
STAT slab_reassign_evictions_nomem 0
STAT slab_reassign_inline_reclaim 0
STAT slab_reassign_busy_items 0
STAT slab_reassign_busy_deletes 0
STAT slab_reassign_running 0
STAT slabs_moved 0
STAT lru_crawler_running 0
STAT lru_crawler_starts 6120
STAT lru_maintainer_juggles 23105
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 135 当前存储占用的字节数
STAT curr_items 2 当前存储的数据总数
STAT total_items 7 启动以来存储的数据总数
STAT slab_global_page_pool 0
STAT expired_unfetched 1
STAT evicted_unfetched 0
STAT evicted_active 0
STAT evictions 0 LRU释放的对象数目
STAT reclaimed 1 已过期的数据条目来存储新数据的数目
STAT crawler_reclaimed 0
STAT crawler_items_checked 2
STAT lrutail_reflocked 0
STAT moves_to_cold 9
STAT moves_to_warm 2
STAT moves_within_lru 0
STAT direct_reclaims 0
STAT lru_bumps_dropped 0
END
扩展:
缓存命中率=命中数(get_hits)/获取次数(cmd_get)
- 如果命中率低,可能业务代码缓存有问题;命中率为0,说明缓存没有起作用
缓存穿透
- 访问的数据,数据库中不存在,每次都不能生成缓存。以至于每次请求都直接访问数据库,穿透了缓存,缓存没有起到作用,数据库压力没有得到缓解
- 解决方案:数据库查不到的数据,做空缓存
缓存雪崩
- 缓存具有失效时间,如果缓存失效时间都是一样,本来应该请求缓存的,但是因为缓存失效了,全部请求到了数据库,数据库压力剧增,可能会造成数据库宕机,进而造成系统崩溃。
- 解决方案:设置缓存的失效时间均匀分布
- stats items:显示各个 slab 中 item 的数目和存储时长(最后一次访问距离现在的秒数)
语法:
stats items
案例:
stats items
STAT items:1:number 2
STAT items:1:number_hot 0
STAT items:1:number_warm 0
STAT items:1:number_cold 2
STAT items:1:age_hot 0
STAT items:1:age_warm 0
STAT items:1:age 1703
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 1
STAT items:1:expired_unfetched 1
STAT items:1:evicted_unfetched 0
STAT items:1:evicted_active 0
STAT items:1:crawler_reclaimed 0
STAT items:1:crawler_items_checked 4
STAT items:1:lrutail_reflocked 0
STAT items:1:moves_to_cold 9
STAT items:1:moves_to_warm 2
STAT items:1:moves_within_lru 0
STAT items:1:direct_reclaims 0
STAT items:1:hits_to_hot 0
STAT items:1:hits_to_warm 0
STAT items:1:hits_to_cold 6
STAT items:1:hits_to_temp 0
END
- 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 2
STAT 1:free_chunks 10920
STAT 1:free_chunks_end 0
STAT 1:mem_requested 135
STAT 1:get_hits 6
STAT 1:cmd_set 8
STAT 1:delete_hits 1
STAT 1:incr_hits 2
STAT 1:decr_hits 2
STAT 1:cas_hits 0
STAT 1:cas_badval 0
STAT 1:touch_hits 0
STAT active_slabs 1
STAT total_malloced 1048576
END
- stats sizes :显示所有item的大小和个数,大小在前,个数在后
语法:
stats sizes
案例:
stats sizes
STAT 96 1
END
动态监控memcached
-
基于Memcached_tool.php工具
-
该软件,可以查看memcached运行状态、key(item)的数量、内存使用量等
-
需要PHP的运行环境
-
在web服务器上配置
传入memcached_tool.php工具
cd /usr/local/nginx/html/
rz
配置
[root@web1 html]# vim memcached_tool.php
22 define('ADMIN_USERNAME','root'); // Admin Username
23 define('ADMIN_PASSWORD','123456'); // Admin Password
24 define('DATE_FORMAT','Y/m/d H:i:s');
25 define('GRAPH_SIZE',200);
26 define('MAX_ITEM_DUMP',50);
27
28 $MEMCACHE_SERVERS[] = '192.168.139.135:11211'; // add more as an array
- 浏览器访问
PHP连接Memcached
-
PHP连接memcached需要事先安装相应的扩展类库
-
官方扩展地址:http://pecl.php.net/
-
memcached-3.1.3.tgz php的扩展,依赖libmemcached1.x以上版本。yum 本地源的 libmemcached依赖版本太低,不能够满足php扩展的依赖需要,需要手动源码编译
-
安装
上传源码包
rz
rz
ls
memcached-3.1.3.tgz
libmemcached-1.0.18.tar.gz 依赖
tar -xzf memcached-3.1.3.tgz
tar -xzf libmemcached-1.0.18.tar.gz
cd libmemcached-1.0.18
./configure --prefix=/usr/local/libmemcached && make && make install
cd ../memcached-3.1.3
扩展源码包和php关联生成configure文件
phpize
#如果执行找不到phpize,说明之前没有给php安装目录bin目录配置环境变量,重新配置即可
安装
./configure --with-libmemcached-dir=/usr/local/libmemcached --disable-memcached-sasl
make && make install
修改php.ini文件
vim /usr/local/php/etc/php.ini
#添加extension=memcached.so(860行左右)
重启php-frm服务
service php-fpm reload
php -m |grep memcached
- 测试
cd /usr/local/nginx/html/
vim text_memcached.php
------------------------------------
<?php
//实例化类
$mem = new memcached();
//调用连接memcached方法 注意连接地址和端口号
$mem->addServer('192.168.17.107',11211);
//存数据
var_dump($mem->set('name','lnmp'));
//取数据
var_dump($mem->get('name'));
----------------------------------
常用案列
session共享
- session不共享问题:分布式负载均衡架构中,web服务器间的session不共享(默认session存储在本地的文件中),会造成session校验不一致,校验验证码不通过,造成无法判断是否登录
- 解决方法:
- session生成校验在同一台服务器
- session共享,多台web服务器可以调用到session
- 开启nginx负载均衡
上述问题是因为数据库代理中间件mycat没有开启
- session共享配置
vim /usr/local/nginx/html/tp5shop/application/config.php
-----------------------------------------------
205 'session' => [
206 'id' => '',
207 // SESSION_ID的提交变量,解决flash上传跨域
208 'var_session_id' => '',
209 // SESSION 前缀
210 'prefix' => 'think',
211 // 驱动方式 支持redis memcache memcached
212 'type' => 'memcached',
213 // 连接memcached的主机,需要自己添加
214 'host' => '192.168.139.135,
----------------------------------------------------
service nginx reload
在memcached中,session_id作为key,session的内容作为value进行存储。
缓存热点数据
- 修改代码文件
vim /usr/local/nginx/html/tp5shop/application/home/controller/Base.php
--------------------------------------------------------------------
27 //查询在前台首页显示的分类
28 if(!$category = cache('category')) {
29 $category = Category::where('is_show', 1)->select();
30 cache('category', $category);
31 }
--------------------------------------------------------------------
- 修改项目缓存配置
vim /usr/local/nginx/html/tp5shop/application/config.php
-------------------------------------------------------
190 'cache' => [
191 // 驱动方式
192 'type' => 'memcached',
193 // 缓存保存目录
194 'host' => '192.168.139.135',
195 // 缓存前缀
196 'prefix' => '',
197 // 缓存有效期 0表示永久缓存
198 'expire' => 10,
199 ],
-------------------------------------------------------
service nginx reload