一、MemCache简介:

  MemCache是一个自由、源码开放、高性能、分布式的分布式内存对象缓存系统,用于动态Web应用以减轻数据库的负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高了网站访问的速度。MemCaChe是一个存储键值对的HashMap,在内存中对任意的数据(比如字符串、对象等)所使用的key-value存储,数据可以来自数据库调用、API调用,或者页面渲染的结果。MemCache设计理念就是小而强大,它简单的设计促进了快速部署、易于开发并解决面对大规模的数据缓存的许多难题,而所开放的API使得MemCache能用于JavaC/C++/C#PerlPythonPHPRuby等大部分流行的程序语言。

 另外,说一下为什么会有Memcachememcached两种名称?其实Memcache是这个项目的名称,而memcached是它服务器端的主程序文件名

MemCache的官方网站为 http://memcached.org/

MemCache访问模型

为了加深对memcache的理解,以memcache为代表的分布式缓存,访问模型如下:

wKioL1jU0hqzrUVeAAD1I27DLQg296.png

特别澄清一个问题,MemCache虽然被称为分布式缓存,但是MemCache本身完全不具备分布式的功能,MemCache集群之间不会相互通信(与之形成对比的,比如JBoss Cache,某台服务器有缓存数据更新时,会通知集群中其他机器更新缓存或清除缓存数据),所谓的分布式,完全依赖于客户端程序的实现,就像上面这张图的流程一样。

同时基于这张图,理一下MemCache一次写缓存的流程:

1、应用程序输入需要写缓存的数据

2APIKey输入路由算法模块,路由算法根据KeyMemCache集群服务器列表得到一台服务器编号

3、由服务器编号得到MemCache及其的ip地址和端口号

4API调用通信模块和指定编号的服务器通信,将数据写入该服务器,完成一次分布式缓存的写操作

读缓存和写缓存一样,只要使用相同的路由算法和服务器列表,只要应用程序查询的是相同的KeyMemCache客户端总是访问相同的客户端去读取数据,只要服务器中还缓存着该数据,就能保证缓存命中。

这种MemCache集群的方式也是从分区容错性的方面考虑的,假如Node2宕机了,那么Node2上面存储的数据都不可用了,此时由于集群中Node0Node1还存在,下一次请求Node2中存储的Key值的时候,肯定是没有命中的,这时先从数据库中拿到要缓存的数据,然后路由算法模块根据Key值在Node0Node1中选取一个节点,把对应的数据放进去,这样下一次就又可以走缓存了,这种集群的做法很好,但是缺点是成本比较大。

一致性Hash算法 

从上面的图中,可以看出一个很重要的问题,就是对服务器集群的管理,路由算法至关重要,就和负载均衡算法一样,路由算法决定着究竟该访问集群中的哪台服务器,先看一个简单的路由算法。

1、余数Hash

简单的路由算法可以使用余数Hash:用服务器数目和缓存数据KEYhash值相除,余数为服务器列表下标编号,假如某个str对应的HashCode52、服务器的数目是3,取余数得到1str对应节点Node1,所以路由算法把str路由到Node1服务器上。由于HashCode随机性比较强,所以使用余数Hash路由算法就可以保证缓存数据在整个MemCache服务器集群中有比较均衡的分布。

如果不考虑服务器集群的伸缩性,那么余数Hash算法几乎可以满足绝大多数的缓存路由需求,但是当分布式缓存集群需要扩容的时候,就难办了。

就假设MemCache服务器集群由3台变为4台吧,更改服务器列表,仍然使用余数Hash524的余数是0,对应Node0,但是str原来是存在Node1上的,这就导致了缓存没有命中。再举个例子,原来有HashCode0~19的20个数据

wKiom1jU0tjD1oOOAABmgOZ9zGg029.png

那么现在扩容到4台,加粗标红的表示命中: 

wKioL1jU0vWQGQS9AABsWKJPYFE634.png

如果扩容到20+的台数,只有前三个HashCode对应的Key是命中的,也就是15%。当然现实情况肯定比这个复杂得多,不过足以说明,使用余数Hash的路由算法,在扩容的时候会造成大量的数据无法正确命中(其实不仅仅是无法命中,那些大量的无法命中的数据还在原缓存中在被移除前占据着内存)。在网站业务中,大部分的业务数据度操作请求上事实上是通过缓存获取的,只有少量读操作会访问数据库,因此数据库的负载能力是以有缓存为前提而设计的。当大部分被缓存了的数据因为服务器扩容而不能正确读取时,这些数据访问的压力就落在了数据库的身上,这将大大超过数据库的负载能力,严重的可能会导致数据库宕机。

这个问题有解决方案,解决步骤为:

1)在网站访问量低谷,通常是深夜,技术团队加班,扩容、重启服务器

2)通过模拟请求的方式逐渐预热缓存,使缓存服务器中的数据重新分布

2、一致性Hash算法 

一致性Hash算法通过一个叫做一致性Hash环的数据结构实现Key到缓存服务器的Hash映射。简单地说,一致性哈希将整个哈希值空间组织成一个虚拟的圆环(这个环被称为一致性Hash环),如假设某空间哈希函数H的值空间是0~2^32-1(即哈希值是一个32位无符号×××),整个哈希空间如下:

wKiom1jU06SArrz1AABXXZ6oagM896.png

下一步将各个服务器使用H进行一个哈希计算,具体可以使用服务器的IP地址或者主机名作为关键字,这样每台机器能确定其在上面的哈希环上的位置了,并且是按照顺时针排列,这里我们假设三台节点memcache经计算后位置如下

wKiom1jU07SgvSFZAAB2-TIK9HE226.png

接下来使用相同算法计算出数据的哈希值h,并由此确定数据在此哈希环上的位置

假如我们有数据ABCD4个对象,经过哈希计算后位置如下:

wKioL1jU08jRLD3OAABzeyJQxTY867.png

根据一致性哈希算法,数据A就被绑定到了server01上,D被绑定到了server02上,BCserver03上,是按照顺时针找最近服务节点方法

这样得到的哈希环调度方法,有很高的容错性和可扩展性:

假设server03宕机

wKiom1jU09-hu_U0AABxoCr5hcQ549.png

可以看到此时CB会受到影响,将BC被重定位到Server01。一般的,在一致性哈希算法中,如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即顺着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响。

考虑另外一种情况,如果我们在系统中增加一台服务器Memcached Server 04

wKioL1jU0_bQdnpDAAB59e_89Lw328.png

此时ADC不受影响,只有B需要重定位到新的Server04。一般的,在一致性哈希算法中,如果增加一台服务器,则受影响的数据仅仅是新服务器到其环空间中前一台服务器(即顺着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响。

综上所述,一致性哈希算法对于节点的增减都只需重定位环空间中的一小部分数据,具有较好的容错性和可扩展性。

一致性哈希的缺点:在服务节点太少时,容易因为节点分部不均匀而造成数据倾斜问题。我们可以采用增加虚拟节点的方式解决。

更重要的是,集群中缓存服务器节点越多,增加/减少节点带来的影响越小,很好理解。换句话说,随着集群规模的增大,继续命中原有缓存数据的概率会越来越大,虽然仍然有小部分数据缓存在服务器中不能被读到,但是这个比例足够小,即使访问数据库,也不会对数据库造成致命的负载压力。

MemCache实现原理

首先要说明一点,MemCache的数据存放在内存中

1、访问数据的速度比传统的关系型数据库要快,因为OracleMySQL这些传统的关系型数据库为了保持数据的持久性,数据存放在硬盘中,IO操作速度慢

2MemCache的数据存放在内存中同时意味着只要MemCache重启了,数据就会消失

3、既然MemCache的数据存放在内存中,那么势必受到机器位数的限制,32位机器最多只能使用2GB的内存空间,64位机器可以认为没有上限

然后我们来看一下MemCache的原理,MemCache最重要的是内存如何分配的,MemCache采用的内存分配方式是固定空间分配,如下图所示:

wKioL1jU1A7jMShCAACsjWHSFW4157.png

这张图片里面涉及了slab_classslabpagechunk四个概念,它们之间的关系是:

1MemCache将内存空间分为一组slab

2、每个slab下又有若干个page,每个page默认是1M,如果一个slab占用100M内存的话,那么这个slab下应该有100page

3、每个page里面包含一组chunkchunk是真正存放数据的地方,同一个slab里面的chunk的大小是固定的

4、有相同大小chunkslab被组织在一起,称为slab_class

MemCache内存分配的方式称为allocator(分配运算),slab的数量是有限的,几个、十几个或者几十个,这个和启动参数的配置相关。

MemCache中的value存放的地方是由value的大小决定的,value总是会被存放到与chunk大小最接近的一个slab中,比如slab[1]chunk大小为80字节、slab[2]chunk大小为100字节、slab[3]chunk大小为125字节(相邻slab内的chunk基本以1.25为比例进行增长,MemCache启动时可以用-f指定这个比例),那么过来一个88字节的value,这个value将被放到2slab中。放slab的时候,首先slab要申请内存,申请内存是以page为单位的,所以在放入第一个数据的时候,无论大小为多少,都会有1M大小的page被分配给该slab。申请到page后,slab会将这个page的内存按chunk的大小进行切分,这样就变成了一个chunk数组,最后从这个chunk数组中选择一个用于存储数据。

如果这个slab中没有chunk可以分配了怎么办,如果MemCache启动没有追加-M(禁止LRU,这种情况下内存不够会报Out Of Memory错误),那么MemCache会把这个slab中最近最少使用的chunk中的数据清理掉,然后放上最新的数据。

Memcache的工作流程:

wKioL1jU1EzwWm8qAACQRZqKRII953.png

1、检查客户端的请求数据是否在memcached中,如果有,直接把请求数据返回,不再对数据库进行任何操作,路径操作为①②③⑦。
2、如果请求的数据不在memcached中,就去查数据库,把从数据库中获取的数据返回给客户端,同时把数据缓存一份到memcached中(memcached客户端不负责,需要程序明确实现),路径操作为①②④⑤⑦⑥。
3、每次更新数据库的同时更新memcached中的数据,保证一致性。
4、当分配给memcached内存空间用完之后,会使用LRULeast Recently Used,最近最少使用)策略加上到期失效策略,失效数据首先被替换,然后再替换掉最近未使用的数据。

Memcached特征:
协议简单:
   它是基于文本行的协议,直接通过telnetmemcached服务器上可进行存取数据操作

注:文本行的协议:指的是信息以文本传送,一个信息单元传递完毕后要传送换行。比如对于HTTPGET请求来说,GET /index.html HTTP/1.1是一行,接下去每个头部信息各占一行。一个空行表示整个请求结束

基于libevent事件处理:
    Libevent是一套利用C开发的程序库,它将BSD系统的kqueue,Linux系统的epoll等事件处理功能封装成一个接口,与传统的select相比,提高了性能。
内置的内存管理方式:
    所有数据都保存在内存中,存取数据比硬盘快,当内存满后,通过LRU算法自动删除不使用的缓存,但没有考虑数据的容灾问题,重启服务,所有数据会丢失。
分布式
   各个memcached服务器之间互不通信,各自独立存取数据,不共享任何信息。服务器并不具有分布式功能,分布式部署取决于memcache客户端。

Memcache的安装分为两个过程:memcache服务器端的安装和memcached客户端的安装。

所谓服务器端的安装就是在服务器(一般都是linux系统)上安装Memcache实现数据的存储。

所谓客户端的安装就是指php(或者其他程序,Memcache还有其他不错的api接口提供)去使用服务器端的Memcache提供的数据,需要php添加扩展。

PHPMemcache

二、centos7.2+nginx+php+memcache+mysql

环境描述:

OSCentOS Linux release7.2.1511 (Core)


   nginxphp

    memcache

    mysql

IP

 192.168.31.141/24

  192.168.31.250/24

192.168.31.225/24

主要软件

nginx-1.10.2.tar.gz

php-5.6.27.tar.g

Memcached-1.4.33.tar.gz 

mysql-5.7.13.tar.gz

1、安装nginx(在192.168.31.141主机操作)
解压zlib
[root@nginx-php ~]# tar zxf zlib-1.2.8.tar.gz    说明:不需要编译,只需要解压就行。
解压pcre
[root@nginx-php ~]# tar zxf pcre-8.39.tar.gz     说明:不需要编译,只需要解压就行。
[root@nginx-php ~]# yum -y install gcc gcc-c++ make libtool openssl openssl-devel
下载nginx的源码包:http://nginx.org/download
解压源码包:
[root@nginx-php ~]# tar zxf nginx-1.10.2.tar.gz 
[root@nginx-php ~]# cd nginx-1.10.2/
[root@nginx-php nginx-1.10.2]# groupadd www && useradd -g www www -s /sbin/nologin   #添加www组&& 创建nginx运行账户www并加入到www组,不允许www用户直接登录系统 
[root@nginx-php nginx-1.10.2]# ./configure --prefix=/usr/local/nginx1.10 --with-http_dav_module --with-http_stub_status_module --with-http_addition_module --with-http_sub_module --with-http_flv_module --with-http_mp4_module --with-pcre=/root/pcre-8.39 --with-zlib=/root/zlib-1.2.8 --with-http_ssl_module --with-http_gzip_static_module --user=www --group=www && make && make install
注:
--with-pcre:用来设置pcre的源码目录。
 --with-zlib:用来设置zlib的源码目录。
 因为编译nginx需要用到这两个库的源码
[root@nginx-php nginx-1.10.2]# ln -s /usr/local/nginx1.10/sbin/nginx /usr/local/sbin/
[root@nginx-php nginx-1.10.2]# nginx    #启动nginx
[root@nginx-php nginx-1.10.2]# netstat -anpt | grep nginx
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      10737/nginx: master 
[root@nginx-php nginx-1.10.2]# firewall-cmd --permanent --add-port=80/tcp
success
[root@nginx-php nginx-1.10.2]# firewall-cmd --reload
success

启动后可以再浏览器中打开页面,会显示nginx默认页面。

wKioL1jU2D6RYdS5AACNIK7INQg429.png-wh_50

2、安装php(在192.168.31.141主机上操作)

安装libmcrypt

[root@nginx-php ~]# tar zxf libmcrypt-2.5.7.tar.gz 
[root@nginx-php ~]# cd libmcrypt-2.5.7/
[root@nginx-php libmcrypt-2.5.7]# ./configure --prefix=/usr/local/libmcrypt && make && make install
[root@nginx-php libmcrypt-2.5.7]# yum -y install libxml2-devel libcurl-devel bzip2-devel
[root@nginx-php php-5.6.27]# cd
[root@nginx-php ~]# tar zxf php-5.6.27.tar.gz 
[root@nginx-php ~]# cd php-5.6.27/
[root@nginx-php php-5.6.27]# ./configure --prefix=/usr/local/php5.6 --with-mysql=mysqlnd --with-pdo-mysql=mysqlnd --with-mysqli=mysqlnd --with-openssl --enable-fpm --enable-sockets --enable-sysvshm --enable-mbstring --with-freetype-dir --with-jpeg-dir --with-png-dir --with-zlib --with-libxml-dir=/usr --enable-xml --with-mhash --with-mcrypt=/usr/local/libmcrypt --with-config-file-path=/etc --with-config-file-scan-dir=/etc/php.d --with-bz2 --enable-maintainer-zts && make && make install
[root@nginx-php php-5.6.27]# cp php.ini-production /etc/php.ini
修改/etc/php.ini文件,将short_open_tag修改为on,修改后的内容如下:
[root@nginx-php php-5.6.27]# sed -i 's/short_open_tag = Off/short_open_tag = On/g' /etc/php.ini     #short_open_tag = On //支持php短标签
创建php-fpm服务启动脚本:
[root@nginx-php php-5.6.27]# cp sapi/fpm/init.d.php-fpm /etc/init.d/php-fpm
[root@nginx-php php-5.6.27]# chmod +x /etc/init.d/php-fpm 
[root@nginx-php php-5.6.27]# chkconfig --add php-fpm
[root@nginx-php php-5.6.27]# chkconfig php-fpm on
提供php-fpm配置文件并编辑:
[root@nginx-php php-5.6.27]# cp /usr/local/php5.6/etc/php-fpm.conf.default /usr/local/php5.6/etc/php-fpm.conf
[root@nginx-php php-5.6.27]# vi /usr/local/php5.6/etc/php-fpm.conf
pid = run/php-fpm.pid
listen =127.0.0.1:9000
pm.max_children = 300
pm.start_servers = 10
pm.min_spare_servers = 10
pm.max_spare_servers =50
启动php-fpm服务:
[root@nginx-php php-5.6.27]# service php-fpm start
Starting php-fpm  done
[root@nginx-php php-5.6.27]# netstat -anpt | grep php-fpm
tcp        0      0 127.0.0.1:9000          0.0.0.0:*               LISTEN      35786/php-fpm: mast

3、安装mysql(在192.168.31.225主机操作)

过程略

4、安装memcached服务端(在192.168.31.250主机操作)

memcached是基于libevent的事件处理。libevent是个程序库,它将LinuxepollBSD类操作系统的kqueue等事件处理功能封装成统一的接口。即使对服务器的连接数增加,也能发挥I/O的性能。 memcached使用这个libevent库,因此能在LinuxBSDSolaris等操作系统上发挥其高性能。

[root@memcache ~]# tar zxf libevent-2.0.22-stable.tar.gz 
[root@memcache ~]# cd libevent-2.0.22-stable/
[root@memcache libevent-2.0.22-stable]# ./configure && make && make install
安装memcached
[root@memcache libevent-2.0.22-stable]# cd
[root@memcache ~]# tar zxf memcached-1.4.33.tar.gz 
[root@memcache ~]# cd memcached-1.4.33/
[root@memcache memcached-1.4.33]# ./configure --prefix=/usr/local/memcached --with-libevent=/usr/local && make && make install
检测是否成功安装
[root@memcache memcached-1.4.33]# ls /usr/local/memcached/bin/memcached 
/usr/local/memcached/bin/memcached
通过以上操作就很简单的把memcached服务端编译好了。这时候就可以打开服务端进行工作了。
配置环境变量:
进入用户宿主目录,编辑.bash_profile,为系统环境变量LD_LIBRARY_PATH增加新的目录,需要增加的内容如下:
[root@memcache memcached-1.4.33]# sed -i '1a MEMCACHED_HOME=/usr/local/memcached' /root/.bash_profile 
[root@memcache memcached-1.4.33]# sed -i '2a LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$MEMCACHED_HOME/lib' /root/.bash_profile
[root@memcache memcached-1.4.33]# /usr/local/memcached/bin/memcached -d -m 1024 -l 192.168.31.250 -p 11211 -u root -c 10240 -P /usr/local/memcached/memcached.pid
启动参数说明:
-d  选项是启动一个守护进程。
-m  分配给Memcache使用的内存数量,单位是MB,默认64MB。
-l  监听的IP地址。(默认:INADDR_ANY,所有地址)
-p  设置Memcache的TCP监听的端口,最好是1024以上的端口。
-u  运行Memcache的用户,如果当前为root的话,需要使用此参数指定用户。
-c  选项是最大运行的并发连接数,默认是1024。
-P  设置保存Memcache的pid文件。
-M 内存耗尽时返回错误,而不是删除项
-f 块大小增长因子,默认是1.25
-n 最小分配空间,key+value+flags默认是48
-h 显示帮助
[root@memcache memcached-1.4.33]# netstat -anpt | grep memcached
tcp        0      0 192.168.31.250:11211    0.0.0.0:*               LISTEN      12590/memcached 
设置防火墙:
[root@memcache memcached-1.4.33]# firewall-cmd --permanent --add-port=11211/tcp
success
[root@memcache memcached-1.4.33]# firewall-cmd --reload 
success
刷新用户环境变量:
[root@memcache memcached-1.4.33]# source /root/.bash_profile
设置memcached服务每次开机自启动
在/etc/rc.d/rc.local文件中最后一行添加/usr/local/memcached/bin/memcached -d -m 1024 -l 192.168.31.250 -p 11211 -u root -c 10240 -P /usr/local/memcached/memcached.pid
为这个文件设置可执行权限:chmod +x /etc/rc.d/rc.local
或
编写memcached服务启停脚本
[root@memcache ~]# cat /etc/init.d/memcached 
#!/bin/sh
#
# pidfile:/usr/local/memcached/memcached.pid
# memcached_home: /usr/local/memcached
# chkconfig: 35 21 79
# description: Start and stop memcachedService
 
# Source function library
. /etc/rc.d/init.d/functions
 
RETVAL=0
 
prog="memcached"
basedir=/usr/local/memcached
cmd=${basedir}/bin/memcached
pidfile="$basedir/${prog}.pid"
 
#interface to listen on (default:INADDR_ANY, all addresses)
ipaddr="192.168.31.250"
#listen port
port=11211
#username for memcached
username="root"
#max memory for memcached,default is 64M
max_memory=2048
#max connections for memcached
max_simul_conn=10240
start() {
echo -n $"Starting service:$prog"
$cmd -d -m $max_memory -u $username -l$ipaddr -p $port -c $max_simul_conn -P $pidfile
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && touch/var/lock/subsys/$prog
}
 
stop() {
echo -n $"Stopping service:$prog  "
run_user=$(whoami)
pidlist=$(ps -ef | grep $run_user | grepmemcached | grep -v grep | awk '{print($2)}')
for pid in $pidlist
do
kill -9 $pid
if [ $? -ne 0 ]; then
return 1
fi
done
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && rm -f/var/lock/subsys/$prog
}
 
# See how we were called.
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
*)
echo "Usage: $0{start|stop|restart|status}"
exit 1
esac
exit$RETVAL
设置脚本可被执行:
[root@memcache ~]# chmod +x/etc/init.d/memcached
[root@memcache ~]# chkconfig --add memcached
[root@memcache ~]# chkconfig memcached on
说明:
shell脚本中return的作用
1)终止一个函数. 
2)return命令允许带一个整型参数, 这个整数将作为函数的"退出状态
码"返回给调用这个函数的脚本, 并且这个整数也被赋值给变量$?.
3)命令格式:return value

5、配置nginx.conf文件(在nginx主机操作)

配置内容如下:

user www www;
worker_processes  4;
worker_cpu_affinity 0001 0010 0100 1000;
error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;
 
pid        logs/nginx.pid;
 
events {
use epoll;
   worker_connections  65535;
multi_accept on;
}
 
http {
include      mime.types;
   default_type application/octet-stream;
 
   #log_format  main  '$remote_addr - $remote_user [$time_local]"$request" '
   #                  '$status$body_bytes_sent "$http_referer" '
   #                 '"$http_user_agent" "$http_x_forwarded_for"';
 
   #access_log  logs/access.log  main;
 
sendfile        on;
tcp_nopush     on;
   keepalive_timeout  65;
tcp_nodelay on;
client_header_buffer_size 4k;
open_file_cache max=102400 inactive=20s;
   open_file_cache_valid 30s;
   open_file_cache_min_uses 1;
   client_header_timeout 15;
   client_body_timeout 15;
reset_timedout_connection on;
   send_timeout 15;
server_tokens off;
client_max_body_size 10m;
 
   fastcgi_connect_timeout     600;
   fastcgi_send_timeout 600;
   fastcgi_read_timeout 600;
fastcgi_buffer_size 64k;
   fastcgi_buffers     4 64k;
fastcgi_busy_buffers_size 128k;
fastcgi_temp_file_write_size 128k;
   fastcgi_temp_path /usr/local/nginx1.10/nginx_tmp;
fastcgi_intercept_errors on;
   fastcgi_cache_path /usr/local/nginx1.10/fastcgi_cache levels=1:2keys_zone=cache_fastcgi:128m inactive=1d max_size=10g;
 
gzip on;
   gzip_min_length  2k;
   gzip_buffers     4 32k;
   gzip_http_version 1.1;
   gzip_comp_level 6;
   gzip_types  text/plain text/csstext/javascript application/json application/javascriptapplication/x-javascript application/xml;
gzip_vary on;
gzip_proxied any;
server {
listen       80;
       server_name  www.benet.com;
 
       #charset koi8-r;
 
       #access_log logs/host.access.log  main;
 
location ~*^.+\.(jpg|gif|png|swf|flv|wma|wmv|asf|mp3|mmf|zip|rar)$ {
valid_referers none blocked  www.benet.com benet.com;
if ($invalid_referer) {
                #return 302  http://www.benet.com/img/nolink.jpg;
return 404;
break;
             }
access_log off;
       }
location / {
root  html;
index index.php index.html index.htm;
       }
location ~*\.(ico|jpe?g|gif|png|bmp|swf|flv)$ {
expires 30d;
            #log_not_found off;
access_log off;
       }
 
location ~* \.(js|css)$ {
expires 7d;
log_not_found off;
access_log off;
       }     
 
location = /(favicon.ico|roboots.txt) {
access_log off;
log_not_found off;
       }
location /status {
stub_status on;
       }
location ~ .*\.(php|php5)?$ {
root html;
            fastcgi_pass 127.0.0.1:9000;
            fastcgi_index index.php;
include fastcgi.conf;
            fastcgi_cache cache_fastcgi;
            fastcgi_cache_valid 200 302 1h;
            fastcgi_cache_valid 301 1d;
fastcgi_cache_valid any 1m;
            fastcgi_cache_min_uses 1;
fastcgi_cache_use_stale error timeoutinvalid_header http_500;
            fastcgi_cache_keyhttp://$host$request_uri;
       }
       #error_page  404              /404.html;
       # redirect server error pages to the static page /50x.html
       #
       error_page   500 502 503 504  /50x.html;
location = /50x.html {
root  html;
       }
  }
}

重载nginx服务

[root@nginx-php php-5.6.27]# nginx -s reload

生成一个php测试页
[root@www memcache-3.0.8]# cat/usr/local/nginx1.10/html/test1.php
<?php
phpinfo();
?>

使用浏览器访问test1.php测试页

wKioL1jU7BuRC56pAABnYc3j9zk309.png

6memcache客户端(在php服务器操作):


memcache分为服务端和客户端。服务端用来存放缓存,客户端用来操作缓存。

安装php扩展库(phpmemcache)。

安装PHP Memcache扩展:

可以使用php自带的pecl安装程序

# /usr/local/php5.6/bin/pecl install memcache

也可以从源码安装,他是生成php的扩展库文件memcache.so

安装memcache扩展库

[root@nginx-php ~]# tar zxf memcache-3.0.8.tgz 
[root@nginx-php ~]# cd memcache-3.0.8/
[root@nginx-php memcache-3.0.8]# /usr/local/php5.6/bin/phpize 
Configuring for:
PHP Api Version:         20131106
Zend Module Api No:      20131226
Zend Extension Api No:   220131226
[root@nginx-php memcache-3.0.8]# ./configure --enable-memcache --with-php-config=/usr/local/php5.6/bin/php-config && make && make install

安装完后会有类似这样的提示:

Installing shared extensions:     /usr/local/php5.6/lib/php/extensions/no-debug-zts-20131226/

把这个记住,然后修改php.ini

添加一行

extension=/usr/local/php5.6/lib/php/extensions/no-debug-zts-20131226/memcache.so

重启php-fpm服务

[root@nginx-php memcache-3.0.8]# service php-fpm restart

Gracefully shutting down php-fpm . done

Starting php-fpm  done

测试:

检查php扩展是否正确安装

1[root@www html]#/usr/local/php5.6/bin/php -m

命令行执行php -m 查询结果中是否有memcache

[root@nginx-php memcache-3.0.8]# /usr/local/php5.6/bin/php -m

[PHP Modules]

bz2

Core

ctype

date

dom

ereg

fileinfo

filter

hash

iconv

json

libxml

mbstring

mcrypt

memcache

mhash

mysql

mysqli

mysqlnd

openssl

pcre

PDO

pdo_mysql

pdo_sqlite

Phar

posix

Reflection

session

SimpleXML

sockets

SPL

sqlite3

standard

sysvshm

tokenizer

xml

xmlreader

xmlwriter

zlib


[Zend Modules]


2、创建phpinfo()页面,查询session项下面的Registeredsave handlers值中是否有memcache

浏览器访问test1.php

wKioL1jU8ZCCV-wTAABiGsv5UvY329.png

wKiom1jU8ZHBn-lDAABly5PZNBk122.png


测试代码:

[root@nginx-php ~]# cat /usr/local/nginx1.10/html/test2.php 
<?php
$memcache = new Memcache;
$memcache->connect('192.168.31.250', 11211) or die ("Could not connect");
$version = $memcache->getVersion();
echo "Server's version: ".$version."<br/>";
$tmp_object = new stdClass;
$tmp_object->str_attr = 'test';
$tmp_object->int_attr = 123;
$memcache->set('key', $tmp_object, false, 10) or die ("Failed to save data at the server");
echo "Store data in the cache (data will expire in 10 seconds)<br/>";
$get_result = $memcache->get('key');
echo "Data from the cache:<br/>";
var_dump($get_result);
?>
You have mail in /var/spool/mail/root

浏览器访问test2.php

wKioL1jU8mSAhDroAABqu6BVWjI245.png-wh_50

使用memcache实现session共享

1)配置php.ini中的Sessionmemcache方式。

session.save_handler = memcache
session.save_path = "tcp://192.168.31.250:11211?persistent=1&weight=1&timeout=1&retry_interval=15
注:
session.save_handler:设置session的储存方式为memcache。默认以文件方式存取session数据,如果想要使用自定义的处理来存取session数据,比如memcache方式则修为session.save_handler = memcache
session.save_path:设置session储存的位置,多台memcache用逗号隔开
使用多个 memcached server 时用逗号”,”隔开,可以带额外的参数”persistent”、”weight”、”timeout”、”retry_interval”等等,
类似这样的:"tcp://host:port?persistent=1&weight=2,tcp://host2:port2"。

2)memcache实现session共享也可以在某个一个应用中设置: 

ini_set("session.save_handler", "memcache"); 
ini_set("session.save_path", "tcp://192.168.31.141:11211"); 

ini_set()只对当前php页面有效,并且不会去修改php.ini文件本身,也不会影响其他php页面。

测试memcache可用性

重启php-fpm

web服务器上新建//usr/local/nginx1.10/html/memcache.php文件。内容如

[root@nginx-php ~]# cat /usr/local/nginx1.10/html/memcache.php 
<?php
session_start();
if (!isset($_SESSION['session_time']))
{
 $_SESSION['session_time'] = time();
}
echo "session_time:".$_SESSION['session_time']."<br />";
echo "now_time:".time()."<br />"; 
echo "session_id:".session_id()."<br />";
?>

访问网址http://192.168.31.141/memcache.php可以查看session_time是否都是为memcache中的Session,同时可以在不同的服务器上修改不同的标识查看是否为不同的服务器上的。

wKiom1jU9EPy2uSsAABbqWT2aP4211.png-wh_50

可以直接用sessionid memcached 里查询一下:

[root@nginx-php ~]# telnet 192.168.31.250 11211
Trying 192.168.31.250...
Connected to 192.168.31.250.
Escape character is '^]'.
get 940t1dqt22r49op3h3c9rho510
VALUE 940t1dqt22r49op3h3c9rho510 0 26
session_time|i:1490351103;
得到session_time|i:1490351103;这样的结果,说明session 正常工作 
默认memcache会监听11221端口,如果想清空服务器上memecache的缓存,一般使用的是:
[root@nginx-php ~]# telnet 192.168.31.250 11211
Trying 192.168.31.250...
Connected to 192.168.31.250.
Escape character is '^]'.
flush_all
OK


[root@nginx-php ~]# echo "flush_all" | nc 192.168.31.250 11211

OK

使用flush_all 后并不是删除memcache上的key,而是置为过期

memcache安全配置

因为memcache不进行权限控制,因此需要通过iptablesmemcache仅开放个web服务器。

7、测试memcache缓存数据库数据

Mysql服务器上创建测试表

mysql> create database testdb1;
Query OK, 1 row affected (0.02 sec)

mysql> use testdb1;
Database changed
mysql> create table test1(id int not null auto_increment,name varchar(20) default null,primary key (id)) engine=innodb auto_increment=1 default charset=utf8;
Query OK, 0 rows affected (0.20 sec)

mysql>  insert into test1(name) values ('tom1'),('tom2'),('tom3'),('tom4'),('tom5');
Query OK, 5 rows affected (0.03 sec)
Records: 5  Duplicates: 0  Warnings: 0

mysql> select * from test1;
+----+------+
| id | name |
+----+------+
|  1 | tom1 |
|  2 | tom2 |
|  3 | tom3 |
|  4 | tom4 |
|  5 | tom5 |
+----+------+
5 rows in set (0.01 sec)

测试

下面就是测试的工作了,这里有个php脚本,用于测试memcache是否缓存数据成功
需要为这个脚本添加一个只读的数据库用户,命令格式

grant select on testdb1.* to user@'%' identified by '123456';
Query OK, 0 rows affected, 1 warning (0.16 sec)

web服务器上创建测试脚本内容如下:

[root@nginx-php ~]# cat /usr/local/nginx1.10/html/test_db.php
<?php
$memcachehost = '192.168.31.250';
$memcacheport = 11211;
$memcachelife = 60;
$memcache = new Memcache;
$memcache->connect($memcachehost,$memcacheport) or die ("Could not connect");
$query="select * from test1 limit 10";
$key=md5($query);
if(!$memcache->get($key))
{
                $conn=mysql_connect("192.168.31.225","user","123456");
                mysql_select_db(testdb1);
                $result=mysql_query($query);
                while ($row=mysql_fetch_assoc($result))
                {
                        $arr[]=$row;
                }
                $f = 'mysql';
                $memcache->add($key,serialize($arr),0,600);
                $data = $arr ;
}
else{
        $f = 'memcache';
        $data_mem=$memcache->get($key);
        $data = unserialize($data_mem);
}
echo $f;
echo "<br>";
echo "$key";
echo "<br>";
//print_r($data);
foreach($data as $a)
{
                echo "number is <b><font color=#FF0000>$a[id]</font></b>";
                echo "<br>";
                echo "name is <b><font color=#FF0000>$a[name]</font></b>";
                echo "<br>";
}
?>

访问页面测试

wKiom1jU-SfwXfxEAABtA77DmIo037.png-wh_50

如果出现mysql表示memcached中没有内容,需要memcached从数据库中取得

再刷新页面,如果有memcache标志表示这次的数据是从memcached中取得的。

memcached有个缓存时间默认是1分钟,过了一分钟后,memcached需要重新从数据库中取得数据

wKioL1jU-aiAtx_XAABudJ-Zuqw819.png-wh_50

查看 Memcached 缓存情况

我们需要使用 telnet 命令查看

[root@nginx-php ~]# telnet 192.168.31.250 11211
Trying 192.168.31.250...
Connected to 192.168.31.250.
Escape character is '^]'.
stats
STAT pid 1596    //Memcached进程的ID
STAT uptime 3417  //进程运行时间   
STAT time 1490352577  //当前时间
STAT version 1.4.33  // Memcached 版本
STAT libevent 2.0.22-stable
STAT pointer_size 64
STAT rusage_user 0.267961
STAT rusage_system 1.306311
STAT curr_connections 16
STAT total_connections 30
STAT connection_structures 17
STAT reserved_fds 20
STAT cmd_get 33    //总共获取数据的次数(等于 get_hits + get_misses )
STAT cmd_set 75   //总共设置数据的次数
STAT cmd_flush 2
STAT cmd_touch 0
STAT get_hits 29     //命中了多少次数据,也就是从 Memcached 缓存中成功获取数据的次数
STAT get_misses 4   //没有命中的次数
STAT get_expired 0
STAT get_flushed 1
STAT delete_misses 0
STAT delete_hits 0
STAT incr_misses 2
STAT incr_hits 21
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 6705
STAT bytes_written 3696
STAT limit_maxbytes 1073741824   //总的存储大小,默认为 64M
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 702
STAT curr_items 4
STAT total_items 54
STAT expired_unfetched 0
STAT evicted_unfetched 0
STAT evictions 0
STAT reclaimed 2
STAT crawler_reclaimed 0
STAT crawler_items_checked 0
STAT lrutail_reflocked 0
END

命中率= get_hits/ cmd_get

总结:

  1. Memcache缓存服务器将数据缓存到内存中,提高用户访问效率,但是一旦服务器宕机数据将丢失,所以不安全。

  2. Memcache服务器之间本身互不通信也不数据共享还不具备分布式功能,如果要实现memcache服务器数据session共享机制,需要在memcache客户端部署,例如本实例中在php客户端安装memcache软件


  3. 什么是短标签,下面来举个例子:

通常php网页文件书写格式是:<?php

                phpinfo();

               ?>

但是如果启用了短标签功能(short_open_tag = On)可以用如下的书写格式:

             <?

                phpinfo();

              ?>

注:如果没有启用短标签功能(short_open_tag = Off)的话,必须写成第一种格式类型。