第1章 Memcached在集群中缓存数据库案例
1.1 配置wordpress使用Memcached缓存数据
需要程序开发支持,也可上网搜索别人共享的程序文件,wordpress支持Memcached缓存数据只需下载object-cache.php文件,通过该文件即可使wordpress支持Memcached。
[root@web01 ~]# cd /usr/local/nginx/html/blog/wp-content/ [root@web01 ~]# wget http://192.168.12.200/memcache/object-cache.php
1.2 查看缓存数据
第2章 Memcached在集群中session共享案例
Memcache的session共享需要网站程序从源码上支持,所以需要运维和开发协同开发:
- 运维职责:准备环境
- 开发职责:使用环境
2.1 Memcached在集群中的session共享存储设置
2.1.1 编辑web服务器的php.ini
[root@m01 ~]# vim /usr/local/php/lib/php.ini 1358 session.save_handler = memcache 1387 session.save_path = "tcp://10.0.0.61:11211" # 或 [root@m01 ~]# sed -i.bak 's#session.save_handler = files#session.save_handler = memcache#;$a session.save_path = "tcp://10.0.0.61:11211"' /usr/local/php/lib/php.ini
- 配置说明:
- 0.0.61:11211为Memcached数据库缓存的IP及端口
- 此配置适合LNMP/LAMP环境
- Memcached服务器也可以是多台,并且可通过hash算法调度。
2.1.2 重启php
[root@web01 ~]# pkill php [root@web01 ~]# /usr/local/php/sbin/php-fpm
2.1.3 查看配置情况
2.2 Memcached在集群中的session共享存储的优缺点
2.2.1 优点
- 读写速度上会比普通files速度快很多
- 可以解决多个服务器共用session的难题
2.2.2 缺点
- session数据都保存在memory中,持久化方面有所欠缺,但对session数据来说不是问题。
- 一般是单台,如果部署多台之间无法数据同步,通过hash算法分配依然有session丢失的问题。
2.2.3 缺点解决思路
- 可以用其他的持久化系统存储session,例如:Redis、ttserver来替代
- 高性能高并发场景,cookies效率比session要好很多,因此大网站都会用cookes解决会话共享问题。
- 通过牺牲负载均衡的策略实现,例如:lvs-p、nginx ip_hash等,但这些不是好的方法。
扩展:大规模企业网站集群如何实现会话保持:http://oldboy.blog.51cto.com/2561410/1323468
大规模网站sesson会话保持思路及实践配置:http://oldboy.blog.51cto.com/2561410/1331316
第3章 Memcached应用管理
3.1 通过命令管理Memcached
3.1.1 编写监控Memcached服务是否异常的脚本
[root@m01 ~]# vim /server/scripts/mon_mc.sh #!/bin/bash export MemcachedIP=$1 export MemcachedPort=$2 export NcCmd="nc $MemcachedIP $MemcachedPort" export MD5=3fe396c01f03425cb5e2da8186eb090d USAGE(){ echo "$0 MemcachedIP MemcachedPort" exit 3 } [ $# -ne 2 ] && USAGE printf "set $MD5 0 0 6\r\noldboy\r\n"|$NcCmd >/dev/null 2>&1 if [ $ -eq 0 ];then if [ `printf "get $MD5\r\n"|$NcCmd|grep oldboy|wc -l` -eq 1 ];then echo "Memcached status is ok" printf "delete $MD5\r\n"|$NcCmd > /dev/null 2>&1 exit 0 else echo "Memcached status is error1" exit 2 fi else echo "Could not connect Mc server" exit 2 fi
3.1.2 检测脚本使用情况
- Memcached服务正常时:
[root@m01 ~]# memcached -m 16m -p 11211 -d -u root -c 1024 [root@m01 ~]# sh /server/scripts/mon_mc.sh 127.0.0.1 11211 Memcached status is ok
- Memcached服务关闭时:
[root@m01 ~]# pkill memcached [root@m01 ~]# sh /server/scripts/mon_mc.sh 127.0.0.1 11211 Could not connect Mc server
3.2 通过nc命令查看Memcached服务的运行状态信息
[root@m01 ~]# printf "stats\r\n"|nc 127.0.0.1 11211 STAT pid 27079 ..省略部分输出内容... STAT lru_bumps_dropped 0 END
3.2.1 Memcached状态命令说明
Memcached状态命令 | 说明 |
stats | 统计Memcached的各种信息 |
stats settings | 查看一些memcached的设置信息,例如:线程数 |
stats slabs | 查看slabs相关情况,例如:chunksize长度 |
stats items | 查看items相关情况 |
stats sizes | 查看items个数和大小 |
stats reset | 清理统计数据 |
3.2.2 Memcached状态信息详细说明
参数 | 值 | 描述 | 实际作用 |
pid | 27079 | Memcached服务进程id | 查看服务信息 |
uptime | 751 | 服务已运行的秒数 | |
time | 1507358296 | 服务当前UNIX时间戳 | |
version | 1.5.2 | Memcached版本 | |
libevent | 1.4.13-stable | libevent版本 | |
pointer_size | 64 | 操作系统指针大小 | |
rusage_user | 0.012998 | 进程累计用户时间 | 分析占用CPU的情况 |
rusage_system | 0.100984 | 进程累计系统时间 | |
max_connections | 1024 | 当前连接数 | 分析连接数情况 |
curr_connections | 10 | Memcached运行以来连接总数 | |
total_connections | 13 | Memcached分配的连接结构数量 | |
rejected_connections | 0 | ||
connection_structures | 11 | ||
reserved_fds | 20 | 内部使用的FD数 | |
cmd_get | 0 | get命令请求次数 | 分析命中率情况 |
cmd_set | 0 | set命令请求次数 | |
cmd_flush | 0 | flush命令请求次数 | |
cmd_touch | 0 | touch命令请求次数 | |
get_hits | 0 | get命令命中次数 | |
get_misses | 0 | get命令未命中次数 | |
get_expired | 0 | ||
get_flushed | 0 | ||
delete_misses | 0 | delete命令未命中次数 | |
delete_hits | 0 | delete命令命中次数 | |
incr_misses | 0 | incr命令未命中次数 | |
incr_hits | 0 | incr命令命中次数 | |
decr_misses | 0 | decr命令未命中次数 | |
decr_hits | 0 | decr命令命中次数 | |
cas_misses | 0 | cas命令未命中次数 | |
cas_hits | 0 | cas命令命中次数 | |
cas_badval | 0 | 使用擦拭次数 | |
touch_hits | 0 | touch命令命中次数 | |
touch_misses | 0 | touch命令未命中次数 | |
auth_cmds | 0 | 认证命令处理的次数 | |
auth_errors | 0 | 认证失败数目 | |
bytes_read | 22 | 读取总字节数 | 分析字节数情况 |
bytes_written | 1892 | 发送总字节数 | |
limit_maxbytes | 16777216 | 分配内存总大小(字节) | |
accepting_conns | 1 | 接收新的连接数 | |
listen_disabled_num | 0 | 失败的监听数 | |
time_in_listen_disabled_us | 0 | ||
threads | 4 | 当前线程数 | |
conn_yields | 0 | 连接操作主动放弃数目 | |
hash_power_level | 16 | hash表等级 | |
hash_bytes | 524288 | 当前hash表的大小 | |
hash_is_expanding | 0 | hash表正在扩展 | |
slab_reassign_rescues | 0 | ||
slab_reassign_chunk_rescues | 0 | ||
slab_reassign_evictions_nomem | 0 | ||
slab_reassign_inline_reclaim | 0 | ||
slab_reassign_busy_items | 0 | ||
slab_reassign_busy_deletes | 0 | ||
slab_reassign_running | 0 | ||
slabs_moved | 0 | ||
lru_crawler_running | 0 | ||
lru_crawler_starts | 1275 | ||
lru_maintainer_juggles | 800 | ||
malloc_fails | 0 | 分配内存失败数目 | |
log_worker_dropped | 0 | ||
log_worker_written | 0 | ||
log_watcher_skipped | 0 | ||
log_watcher_sent | 0 | ||
bytes | 0 | ||
curr_items | 0 | 当前的对象数目 | 分析对象LRU频率 |
total_items | 0 | 当前存储占用的字节数 | |
slab_global_page_pool | 0 | ||
expired_unfetched | 0 | 当前存储的数据总数 | |
evicted_unfetched | 0 | 启动以来存储的数据总数 | |
evicted_active | 0 | ||
evictions | 0 | LRU释放的对象数目 | |
reclaimed | 0 | 已过期回收的数据条目 | |
crawler_reclaimed | 0 | 爬虫回收的数目 | |
crawler_items_checked | 0 | ||
lrutail_reflocked | 0 | ||
moves_to_cold | 0 | ||
moves_to_warm | 0 | ||
moves_within_lru | 0 | ||
direct_reclaims | 0 | ||
lru_bumps_dropped | 0 |
第4章 Memcached服务应用的优化
4.1 Memcached服务优化策略
4.1.1 提高Memacached访问命中率
例如:每次新增数据到数据库的同时,就将数据写入或复制一份到Memcached里,然后从业务逻辑上让程序优先读缓存,没有数据再查数据库。
4.1.2 提高内存利用率,减少内存浪费
- 减少chunk内存空间浪费的调优方法为:根据业务数据的大小,利用-n参数设定chunk的初始值,及通过-f 参数factor增长因子设置chunk的大小尽可能接近业务数据的大小。
- 减少slab的浪费,设定slab的大小为chunk整数倍
- 采用一致性哈希分布式缓存集群架构
当网站后端的数据库数据量很大时,单台Memcached就无法存放绝大部分的数据库数据,导致Memcached服务的命中率很低,此时可以采用一致性哈希分布式缓存集群架构,提升网站的命中率,一致性哈希可以有程序实现或者支持一致性哈希算法的负载均衡器实现。
4.2 Memcached服务在大型网站站点中的架构优化
4.2.1 大型网站的架构设计原则
当访问量增大时,在整个网站集群架构中最先出现瓶颈的几乎都是后端的数据库或用于存储的服务器,在企业生产工作中应尽量把用户的访问请求往整个网站架构的最前面推,即当用户请求数据时,越是在靠近用户端返回数据,效果就越好。
提示:可参考:http://oldboy.blog.51cto.com/2561410/736710
4.2.2 大型网站的数据库架构常见设计
为了缓解应用对数据库的高并发访问压力,大型网站都会在数据库层配置数据读写分离,并提供读的数据库做负载均衡,以Amoeba软件作为读写分离说明。
由于访问数据库读写的是磁盘,所以对于高并发的业务场景,上述读写分离的架构还是远远解决不了问题的,根据离访问用户越近效率越高以及用内存代替磁盘的架构思想,可以在数据库架构的前端部署Memcached服务作为缓存区,最大限度地把数据库查询信息保存在Memcached服务的内存中,这样前端的应用服务就能够迅速地从Memcached中读取到原本在数据库中才能读取到的数据,从而加快网站的访问速度。
4.2.3 分布式Memcached缓存服务架构
由于单台Memcached服务器的内存容量是有限的,并且单台也存在单点故障,因此大型网站会将多个Memcached服务器组合成集群提供服务,memcached天生不支持分布式集群,但可以通过自己写程序实现分布式集群。
4.2.3.1 程序实现Memcached分布式缓存的方式:
- 常规的取余方法实现的分布式:但可能会导致血崩效应。
- 主流解决方案:一致性哈希算法。
提示:参考资料:http://blog.csdn.net/cywosp/article/details/23397179
4.2.3.2 缓存服务器使用常规负载均衡模式的问题
在常规负载均衡模式中,集群节点上的程序和数据都是一样的,例如web集群,而缓存集群设计下的所有节点缓存的数据的就不能都一样,因此访问数据命中率会非常低下,原因有两点:
- 当客户端访问缓存A无数据时,就会去查后端数据库,然后把查到的数据放入缓存A中一份,当下次客户端访问相同的数据时,可能被分配到的是缓存B,结果B中还是无数据,这样客户端又会去查后端数据库,这样就会给数据库造成了访问压力,同时造成缓存副武器的命中率很低。
- 在集群运行一段时间后,所有缓存的数据可能交叉并及其接近,如果数据库数据量远大于单台Memcached服务器的内存总容量,Memcached的命中率也会非常低下。理想的情况是所有缓存数据之和接近数据库总的数据容量,这样缓存的效率才会高。
解决上述问题的方案最常见的就是使用哈希算法或一致性哈希算法将需要同样数据的请求始终调度到同一台缓存服务器,提升访问命中率。其次所有缓存服务器缓存的数据都是不同的,所有服务器缓存的数据之和接近数据库总的数据容量,使得缓存集群无需换入换出,达到更高的命中率。
4.2.3.3 分布式缓存集群的优劣势
Memcached支持分布式集群,其中的方法之一就是在客户端应用程序上进行改造。例如:可以根据key适当进行有规律的封装。比如以用户为主的网站来说,每个用户都有UserID,那么可以按照固定的ID来进行提取和存取,假设以1开头的用户保存在第一台Memcached服务器上,以2开头的用户的数据保存在第二台Memcached服务器上,那么存取数据时都会按照UserID来进行相应的转换和存取。
但是这样也有缺点,就是需要对UserID进行判断,如果业务不一致,或者是其他类型的应用,可能不是那么合适,此时可以根据自己的实际业务来进行考虑,或者去想合适的方法。
此外,还可以在应用服务器上通过程序用哈希算法或一致性哈希算法调度Memcached服务器,所有Memcached服务器的地址池可以简单地配在每个程序的配置文件里,并且可在负载均衡器上使用哈希算法或一致性哈希算法,不过此时需要负载均衡器的支持。
4.2.4 分布式缓存集群设计思想
- 每一台Memcached服务器的内容都是不一样的。这些Memcached服务器缓存的内容加起来接近整个数据库的数据容量。
- 通过在客户端程序或者Memcached的负载均衡器上用hash算法,让同一数据内容都分配到一个Memcached服务器。
- 普通的hash算法对于节点宕机会带来大量的缓存数据流动(失效),可能会引起雪崩效应。
- 一致性哈希算法(还可以带虚拟节点),可以让缓存节点宕机对节点的数据流动(失效)降到最低。
4.2.5 分布式Memcached缓存集群的调度算法
- 取模计算hash
优点:简单、分散性优秀
缺点:添加/移除服务器时,缓存重组代价巨大,影响命中率。
例如:将26个字母缓存到3个节点,此时新增一个节点,访问命中率将下降23%。
- 一致性哈希算法(Consistent hash)
一致性哈希算法是一种特殊的hash算法,简单地说在移除以及添加一个cache节点时,它能够尽可能小地改变已存在key的映射关系,让缓存服务器缓存的内容收到的影响最小。
第5章 企业案例
5.1 数据库负载过高案例
5.1.1 现象排查
用户访问网站打开页面很慢,经运维人员排查后发现MySQL数据库负载很高,load值大概为20~30,如下:
[root@m01 ~]# uptime 15:55:48 up 12 min, 4 user, load average: 20, 15, 10
登录数据库后,使用“show full processlist;”查看,或者在Linux命令行使用下面的命令查看:
[root@m01 ~]# mysql -e "show full processlist"|grep -vi sleep
发现数据库中像“LIKE'%杜冷丁%'”这样的SQL语句特别多,导致数据库负载很高。
打开该网站发现首页有一个搜索框,根据前面查看的SQL语句形式,可以推测上述“LIKE'%杜冷丁%'”应该是搜索框的语句带来的结果。
5.1.2 优化方案思路
- 查看时候可以从业务上整改,例如:只有用户登录后才可以进行搜索,通过这种改进减少搜索的次数,达到减轻数据库服务压力的目的。
- 如果有大量频繁的搜索SQL语句,很有可能是有网络爬虫在爬我们的网站,可以通过分析Web日志或者网络连接状态,封掉这些非正常的搜索请求。
- 为主库配置多个从库,然后实现数据的读写分离,让“LIKE'%杜冷丁%'”这样的查询去多个从库查,从而减轻主库的读写压力。
- 在数据库前端加Memcached缓存服务器,这个效果在所有的方法里是最好的。
- 在数据库里使用“LIKE'%杜冷丁%'”实现搜索并非明智的选择,可以通过Sphinx等搜索服务实现用户搜索。
- 还可以利用C,Ruby等开发语言开发程序,部署计算服务器实现每日读取数据库计算全量搜索索引,然后保存在提供搜索的Web服务器上,除了设定每日计算全量搜索索引外,可以每分钟读单独的1~2个从库做增量计算索引。这是大公司针对站内搜索采取的基本解决方案之一。
其中1、2、3是短期的方案,简单,易实施,4、5、6是长期的方案目标。
5.1.3 方案6大型网站搜索集群架构逻辑图
5.1.4 案例最终原因
这个案例的最终问题是网站前端有爬虫爬站,通过分析Web日志获取到爬站的IP,临时封掉后MySQL数据库负载立刻下降了很多,网站服务恢复正常。
5.2 开机启动顺序案例
【问题】:
当机房断电,如何开启web服务器、memcached缓存服务器、数据库服务器的?
【解答】:
开机顺序:先开启数据库再开启缓存,然后预热缓存数据库,最后开启web服务器。
注意:如果不预热缓存数据库,当开启web后大量的访问流入MySQL,造成的结果是MySQL启动不了,一开就挂。
5.3 开启关闭集群服务器的顺序案例
【问题】:
如何正确关闭和开启网站集群服务器?
【解答】:
关闭网站集群服务器:从前端往后端依次关闭。
开启网站集群服务器:从后端依次往前端开启,缓存服务器要预热。
