博文大纲:
1、Memcache简介
2、Memcache工做流程
3、Memcache调度算法
4、Memcache实现原理
5、安装Memcache
(1)安装nginx服务器
(2)安装PHP服务器
(3)安装MySQL数据库
(4)测试PHP与Nginx、MySQL的连通性
(5)安装Memcache服务器
(6)PHP服务器安装Memcache客户端
(7)使用 memcache 实现 session 共享
(8)测试Memcache缓存数据库php
Memcache是一套自由、开源、高性能、分布式的高速缓存系统。因为Memcache经过在内存中缓存数据和对象来减小读取数据库的次数。目前被许多网站使用以提高网站的访问速度,尤为对于一些大型的、须要频繁访问数据库的网站访问速度提高效果十分显著。html
Memcache是一个存储键值对的HashMap,在内存中对任意的数据均可以使用key-value的方式存储,数据库能够来自数据库调用或API调用。Memcache设计理念就是小而强大,她简单的设计促进了快速部署、易于开发并解决大规模的数据缓存的许多难题,而其所开放的API使得Memcache能用于Java、C/C++/C#、Perl、Python等大部分流行的程序语言。mysql
注意Memcache虽然被称为“分布式缓存”,可是Memcache自己彻底不具有分布式的功能,Memcache集群之间不会相互通讯,所谓的“分布式”,彻底依赖于客户端程序来实现,如图:linux
Memcahe工做流程:
(1)应用程序输入须要写入缓存的数据;
(2)API将Key输入路由算法模块,理由算法根据Key和Memcache集群服务器列表获得服务器的编号;
(3)由服务器编号获得Memcache及其IP地址和端口号;
(4)API调用通讯模块和指定编号的服务器通讯,讲数据写入该服务器,完成一次分布式缓存的写操做;nginx
无论是读取缓存仍是写入缓存,只要使用相同的路由算法和服务器列表、应用程序查询的是相同的key,Memcache客户端老是访问相同的客户端去读取数据,只要服务器缓存中还有该数据的缓存,就能保证缓存命中。算法
这种Memcache集群的方式也是从分区容错性的方面考虑的,假设Node2宕机了,那么Node2上存储的数据都不可用了,此时因为集群中Node0和Node2还存在,下一次请求获取Node2的缓存数据,确定是没有命中的。这时首先会从数据库中获取到缓存的数据,经过路由算法根据将缓存的数据存储在Node0或Node1上,这种集群的作法很好,可是缺点是成本太大。sql
Memcache共有两种路由调度算法,分别是:余数Hash与一致性Hash。数据库
简单的路由算法可使用余数Hash:用服务器数目和缓存数据Key的hash值相除,余数为服务器列表中的服务器编号。因为HashCode随机性比较强,因此使用余数Hash路由算法就能够保证缓存数据在整个Memcache服务器集群中有比较均衡的分布。vim
若是不考虑服务器集群的伸缩性,那么余数Hash算法几乎能够知足绝大数的缓存路由器需求你,可是当分布式缓存集群须要扩容时,就会出现很大的问题。api
好比Memcache服务器集群由3台变成4台,假设有HashCode为0~19的20个数据,如图:
3台Memcache服务器环境:
如今扩容到4台Memcache服务器:
仅仅是扩展到4台,本来缓存的数据就只能够命中6次,那么若是扩容到20台一行,只有前三个缓存中对应的数据能够命中。经过以上举例就能够说明:
使用余数Hash的路由算法,在扩容的时候会形成大量的数据没法正确命中缓存。
在网站业务中,大部分的业务数据操做请求都会经过缓存来获取的,只有少许的数据操做才会访问数据库,所以数据库的负载能力是在有缓存为前提的状况下设计的。当大部分缓存的数据由于Memcache服务器的扩容而不能正确读取时,这些数据访问的压力就落到了数据库的身上,这将大大超过数据库的负载能力,严重的状况还会形成数据库宕机。
对于使用余数Hash算法的Memcache集群环境,须要扩容时,解决方案:
一致性Hash算法经过一个叫作一致性Hash环的数据结构实现key到缓存服务器的Hash映射,简单来讲,一致性Hash将整个Hash空间组织成一个虚拟的圆环,假设某空间哈希函数H的值空间是0~2^32-1,那么整个Hash空间如图:
将各个服务器使用H进行一个Hash计算,具体可使用服务器的IP地址或主机名做为关键字,这样每台服务器都能肯定其在上面的Hash环上的位置了,而且是按照顺时针排序。
假设三台Memcache经计算后位置以下:
接下来使用相同的算法计算出数据的哈希值,并由此肯定数据在此哈希环上的位置,好比有四个数据,通过哈希计算后位置以下:
根据一致性Hash算法,按照顺时针找最近服务节点方法,这样获得的Hash环调度方法,有很高的容错性和可扩展性。
假设server03宕机,就会出现如下状况:
能够但看到此时C、B会收到影响,将B、C节点重定向到server01上。
在一致性Hash算法中,若是一台服务器不可用时,则受到影响的仅仅是此服务器按照顺时针方向的第一台服务器,其余服务器并不会受到影响。
假设在本来的环境中,再添加一台服务器server04,就会出现如下状况:
此时,A、D、C都不会受到影响,只有B须要重定向到新的Server04。
在一致性Hash算法中,若是增长一台服务器,则受影响的数据仅仅是新服务器按照顺时针方向的第一台服务器,其余并不会受到影响。
综上所述,一致性Hash算法的优缺点:
固然对于其缺点,咱们能够经过增长虚拟节点的方式来解决。
总之就是:集群中的缓存服务器节点越多,增长、减小节点的影响越小;随着集群规模的增大,继续命中原有缓存数据的几率会愈来愈大,虽然仍有小部分数据缓存服务器中不能被读取,可是比例相对来讲,较小。即便访问数据库,也不会对数据库产生致命的负载压力。
Memcache的特色:
- 访问数据库的速度比传统的关系型数据库要快(由于Memcache是存放在内存中的,而传统的关系型数据库是存放在磁盘中);
- Memcache的数据存放在内存中,就意味着只要Memcache重启,数据便会丢失。
- 因为如今大部分都是64位操做系统,这里就不介绍内存对32位系统的影响了;
Memcache的原理:最重要的就是内存如何分配,Memcache采用的内存分配方式是固定空间分配的,如图:
图中主要设计到了stab_class、slab、page、chunk四个概念,四者的关系:
(1)Memcache将内存空间分为一组slab;
(2)每一个slab下又有若干个page,每一个page默认是1M,若是一个slab占用100M内存的话,那么这个slab下应该有100个page;
(3)每一个page里面包含一组chunk,chunk是真正存放数据的地方,同一个slab里面的chunk的大小是固定的;
(4)有相同大小chunk的slab被组织在一块儿,称为slab_class;
Memcache内存分配的方式称为allocator(分配运算),slab的数量是有限的,几个、十几个或者几十个,这个和启动参数的配置相关;
Memcache中的value存放的地方是由value的大小决定的,value老是会存放到与chunk大小最接近的一个slab中。
若是这个slab中没有chunk能够分配了怎么办?若是Memcache启动没有追加“-M”,那么Memcache会把这个slab中最近最少使用的chunk中的数据清理掉,而后放上最新的数据。
如图:
一、检查客户端的请求数据是否在 memcached 中,若是有,直接把请求数据返回,再也不对数据库进行任何操做,路径操做为①②③⑦;
二、若是请求的数据不在 memcached 中,就去查数据库,把从数据库中获取的数据返回给客户端,同时把数据缓存一份到 memcached 中(memcached 客户端不负责,须要程序明确实现),路径操做为①②④⑤⑦⑥;
三、每次更新数据库的同时更新 memcached 中的数据,保证一致性;
四、当分配给 memcached 内存空间用完以后,会使用 LRU(Least Recently Used,最近最少使用)策略加上到期失效策略,失效数据首先被替换,而后再替换掉最近未使用的数据;
协议简单:
- 基于文本行的协议,直接经过 telnet 在 memcached 服务器上可进行存取数据操做;
- 基于 libevent 事件处理;
- 全部数据都保存在内存中,存取数据比硬盘快,当内存满后,经过 LRU 算法自动删除不使用的缓存,但没有考虑数据的容灾问题,重启服务,全部数据会丢失;
分布式:- 各个 memcached 服务器之间互不通讯,各自独立存取数据,不共享任何信息。服务器并不具备分布式功能,分布式部署取决于 memcache 客户端。
Memcache 的安装分为两个过程:memcache 服务器端的安装和 memcached 客户端的安装。所谓服务器端的安装就是在服务器(通常都是 linux 系统)上安装 Memcache 实现数据的存储。
所谓客户端的安装就是指 php(或者其余程序,Memcache 还有其余不错的 api 接口提供)去使用服务器端的 Memcache 提供的函数,须要 php 添加扩展。
由此能够看出Memcache的搭建须要借助于LAMP或LNMP,本篇博文采用LNMP结构。
安装环境:
下载Nginx软件包
关于安装Nginx的详细介绍能够参考Nginx深度优化(二),那么这里就很少作解释了!
[root@Nginx ~]# useradd -M -s /sbin/nologin nginx [root@Nginx ~]# yum -y install openssl-devel [root@Nginx ~]# tar zxf pcre-8.39.tar.gz -C /usr/src [root@Nginx ~]# tar zxf zlib-1.2.8.tar.gz -C /usr/src [root@Nginx ~]# tar zxf nginx-1.14.0.tar.gz -C /usr/src [root@Nginx ~]# cd /usr/src/nginx-1.14.0/ [root@Nginx nginx-1.14.0]# ./configure --prefix=/usr/local/nginx \ --user=nginx --group=nginx --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=/usr/src/pcre-8.39 --with-zlib=/usr/src/zlib-1.2.8 \ --with-http_ssl_module --with-http_gzip_static_module && make && make install [root@Nginx ~]# ln -s /usr/local/nginx/sbin/nginx /usr/local/sbin [root@Nginx ~]# nginx [root@Nginx ~]# netstat -anpt | grep 80 tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 8460/nginx: master
下载PHP软件包
关于PHP的安装详细介绍能够参考部署LAMP动静分离以及部署Discuz论坛这里就很少作解释了!
[root@PHP ~]# yum -y install openssl-devel libxml2-devel bzip2-devel libcurl-devel [root@PHP ~]# tar zxf libmcrypt-2.5.7.tar.gz -C /usr/src [root@PHP ~]# cd /usr/src/libmcrypt-2.5.7/ [root@PHP libmcrypt-2.5.7]# ./configure --prefix=/usr/local/libmcrypt && make && make install [root@PHP ~]# tar zxf php-5.6.27.tar.gz -C /usr/src [root@PHP ~]# cd /usr/src/php-5.6.27/ [root@PHP php-5.6.27]# ./configure --prefix=/usr/local/php \ --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-libxml-dir=/usr --enable-xml --with-mhash --with-zlib \ --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@PHP ~]# cp /usr/src/php-5.6.27/php.ini-production /etc/php.ini [root@PHP ~]# cp /usr/src/php-5.6.27/sapi/fpm/init.d.php-fpm /etc/init.d/php-fpm [root@PHP ~]# chmod +x /etc/init.d/php-fpm [root@PHP ~]# chkconfig --add php-fpm [root@PHP ~]# cd /usr/local/php/etc/ [root@PHP etc]# cp php-fpm.conf.default php-fpm.conf [root@PHP etc]# sed -i 's#;pid = run/php-fpm.pid#pid = run/php-fpm.pid#g' php-fpm.conf [root@PHP etc]# sed -i 's/listen = 127.0.0.1:9000/listen = 0.0.0.0:9000/g' php-fpm.conf [root@PHP etc]# sed -i 's/pm.max_children = 5/pm.max_children = 50/g' php-fpm.conf [root@PHP etc]# sed -i 's/pm.start_servers = 2/pm.start_servers = 5/g' php-fpm.conf [root@PHP etc]# sed -i 's/pm.min_spare_servers = 1/pm.min_spare_servers = 5/g' php-fpm.conf [root@PHP etc]# sed -i 's/pm.max_spare_servers = 3/pm.max_spare_servers = 35/g' php-fpm.conf [root@PHP ~]# systemctl start php-fpm [root@PHP ~]# netstat -anpt | grep 9000 tcp 0 0 0.0.0.0:9000 0.0.0.0:* LISTEN 118146/php-fpm: mas
[root@Mysql ~]# ls anaconda-ks.cfg initial-setup-ks.cfg mysql-5.7.22-linux-glibc2.12-x86_64.tar.gz mysql.sh [root@Mysql ~]# sh mysql.sh Starting MySQL.. SUCCESS! [root@Mysql ~]# mysql -u root -p123 mysql> create database testdb1; mysql> use testdb1; mysql> grant all on *.* to lzj@'192.168.1.%' identified by '123456'; 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; mysql> insert into test1(name) values ('tom1'),('tom2'),('tom3'),('tom4'),('tom5'); mysql> select * from test1; +----+------+ | id | name | +----+------+ | 1 | tom1 | | 2 | tom2 | | 3 | tom3 | | 4 | tom4 | | 5 | tom5 | +----+------+ //建立数据库,及添加数据并建立受权用户
Nginx服务器的操做:
[root@Nginx ~]# vim /usr/local/nginx/conf/nginx.conf location / { root html; index index.php index.html index.htm; } location ~ \.php$ { root /var/www/html; fastcgi_pass 192.168.1.6:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; include fastcgi.conf; } [root@Nginx ~]# nginx -t nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful [root@Nginx ~]# nginx -s reload
PHP服务器的操做:
[root@PHP ~]# mkdir -p /var/www/html [root@PHP ~]# vim /var/www/html/test.php <?php phpinfo(); ?> [root@PHP ~]# vim /var/www/html/test1.php <?php $link=mysqli_connect('192.168.1.8','lzj','123456'); if($link) echo "恭喜你,数据库链接成功!!!"; else echo "connect shibai"; mysqli_close($link); ?>
客户端访问测试:
客户端访问测试,确保没有问题!
[root@Memcache ~]# tar zxf libevent-2.0.22-stable.tar.gz -C /usr/src [root@Memcache ~]# cd /usr/src/libevent-2.0.22-stable/ [root@Memcache libevent-2.0.22-stable]# ./configure && make && make install //安装Memcache依赖软件包 [root@Memcache ~]# tar zxf memcached-1.4.33.tar.gz -C /usr/src [root@Memcache ~]# cd /usr/src/memcached-1.4.33/ [root@Memcache memcached-1.4.33]# ./configure --prefix=/usr/local/memcached \ --with-libevent=/usr/local/ && make && make install [root@Memcache ~]# ln -s /usr/local/memcached/bin/memcached /usr/local/bin [root@Memcache ~]# memcached -d -m 2048 -l 192.168.1.7 -p 11211 -c 10240 -P /usr/local/memcached/memcached.pid -u root [root@Memcache ~]# netstat -anpt | grep 11211 tcp 0 0 192.168.1.7:11211 0.0.0.0:* LISTEN 10886/memcached
启动Memcache经常使用参数说明以下:
- -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@PHP ~]# scp 192.168.1.7:/root/memcache-3.0.8.tgz . [root@PHP ~]# tar zxf memcache-3.0.8.tgz -C /usr/src [root@PHP ~]# cd /usr/src/memcache-3.0.8/ [root@PHP memcache-3.0.8]# /usr/local/php/bin/phpize //生成configure文件 //若在执行上述命令时报错,则须要执行“yun -y install autoconf "安装提示的autoconf包 [root@PHP memcache-3.0.8]# ./configure --enable-memcache \ --with-php-config=/usr/local/php/bin/php-config && make && make install //执行后会显示memcache.so存放的路径 [root@PHP ~]# echo "extension = /usr/local/php/lib/php/extensions/no-debug-zts-20131226/memcache.so" >> /etc/php.ini //在PHP主配置文件中填写memcache.so模块存放的路径 [root@PHP ~]# systemctl restart php-fpm [root@PHP ~]# vim /var/www/html/test2.php <?php $memcache = new Memcache; $memcache->connect('192.168.1.7', 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, 600) or die ("Failed to save data at the server"); echo "Store data in the cache (data will expire in 600 seconds)<br/>"; $get_result = $memcache->get('key'); echo "Data from the cache:<br/>"; var_dump($get_result); ?> //此测试脚本是显示memcached的版本 //而且向里面插入了一个缓存时间为600秒的键值对“test=123”,其ID为“key”
客户段访问以下:
在PHP服务器上安装telnet工具测试
[root@PHP ~]# yum -y install telnet [root@PHP ~]# telnet 192.168.1.7 11211 //登录到memcached的11211端口 Trying 192.168.1.7... Connected to 192.168.1.7. Escape character is '^]'. get key //查询ID为“key”的键值对,能够看到咱们测试脚本写入的“test=123” VALUE key 1 66 O:8:"stdClass":2:{s:8:"str_attr";s:4:"test";s:8:"int_attr";i:123;} END //在进行上面的get验证时,须要将test2.php文件中插入的键值对的保存时间值改大一些 //或者从新访问一下,以避免缓存失效,查询不到 quit //退出当前环境 Connection closed by foreign host. [root@PHP ~]#
在PHP服务器上进行如下操做:
[root@PHP ~]# vim /etc/php.ini //编写PHP主配置文件,并在末尾添加如下内容 session.save_handler = memcache session.save_path = "tcp://192.168.1.7:11211?persistent=1&weight=1&timeout=1&retry_interval=15" [root@PHP ~]# systemctl restart php-fpm [root@PHP ~]# vim /var/www/html/test3.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 />"; ?>
PHP配置文件中写入的内容解释以下:
- session.save_handler:设置 session 的储存方式为 memcache ;
- 默认以文件方式存取 session数据;
- session.save_path: 设置 session 储存的位置;
- 使用多个 memcached server 时用逗号”,”隔开,能够带额外的参数”persistent”、”weight”、”timeout”、”retry_interval”等等;
- 相似这样的:"tcp://host:port?persistent=1&weight=2,tcp://host2:port2";
客户端访问以下:
[root@PHP ~]# telnet 192.168.1.7 11211 Trying 192.168.1.7... Connected to 192.168.1.7. Escape character is '^]'. get a8aujbnie16p29rj4pf9ltfjp3 //使用网页出现的server_id号,来获取其对应的值 VALUE a8aujbnie16p29rj4pf9ltfjp3 0 26 session_time|i:1576329213; END quit Connection closed by foreign host. [root@PHP ~]#
因为步骤3建立数据库时,就已经在数据库中写入了测试所用的值,接下来就是建立测试脚本就能够了!
PHP服务器的操做:
[root@PHP ~]# vim /var/www/html/test4.php <?php $memcachehost = '192.168.1.7'; //指定Memcache服务器地址 $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.1.8","lzj","123456"); //指定数据库服务器的IP地址、用户及密码 mysql_select_db(testdb1); $result=mysql_query($query); while ($row=mysql_fetch_assoc($result)) { $arr[]=$row; } $f = 'mysql'; $memcache->add($key,serialize($arr),0,30); $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>"; } ?> //常常须要修改的地方已经标注了!并且这个测试脚本在Memcache软件中也有
客户端进行测试:
第一次进行访问
第二次进行访问(刷新以后)
在缓存过时以前,也可以使用get来获取缓存所对应的值:
[root@PHP ~]# telnet 192.168.1.7 11211 Trying 192.168.1.7... Connected to 192.168.1.7. Escape character is '^]'. get d8c961e9895ba4b463841924dbcefc2b VALUE d8c961e9895ba4b463841924dbcefc2b 0 251 a:5:{i:0;a:2:{s:2:"id";s:1:"1";s:4:"name";s:4:"tom1";}i:1;a:2:{s:2:"id";s:1:"2";s:4:"name";s:4:"tom2";}i:2;a:2:{s:2:"id";s:1:"3";s:4:"name";s:4:"tom3";}i:3;a:2:{s:2:"id";s:1:"4";s:4:"name";s:4:"tom4";}i:4;a:2:{s:2:"id";s:1:"5";s:4:"name";s:4:"tom5";}} END
—————————本文到此结束,感谢观看——————————