博文大纲:
一、Memcache简介
二、Memcache工作流程
三、Memcache调度算法
四、Memcache实现原理
五、安装Memcache
(1)安装nginx服务器
(2)安装PHP服务器
(3)安装MySQL数据库
(4)测试PHP与Nginx、MySQL的连通性
(5)安装Memcache服务器
(6)PHP服务器安装Memcache客户端
(7)使用 memcache 实现 session 共享
(8)测试Memcache缓存数据库
Memcache是一套自由、开源、高性能、分布式的高速缓存系统。由于Memcache通过在内存中缓存数据和对象来减少读取数据库的次数。目前被许多网站使用以提升网站的访问速度,尤其对于一些大型的、需要频繁访问数据库的网站访问速度提升效果十分显著。
Memcache是一个存储键值对的HashMap,在内存中对任意的数据都可以使用key-value的方式存储,数据库可以来自数据库调用或API调用。Memcache设计理念就是小而强大,她简单的设计促进了快速部署、易于开发并解决大规模的数据缓存的许多难题,而其所开放的API使得Memcache能用于Java、C/C++/C#、Perl、Python等大部分流行的程序语言。
注意Memcache虽然被称为“分布式缓存”,但是Memcache本身完全不具备分布式的功能,Memcache集群之间不会相互通信,所谓的“分布式”,完全依赖于客户端程序来实现,如图:
Memcahe工作流程:
(1)应用程序输入需要写入缓存的数据;
(2)API将Key输入路由算法模块,理由算法根据Key和Memcache集群服务器列表得到服务器的编号;
(3)由服务器编号得到Memcache及其IP地址和端口号;
(4)API调用通信模块和指定编号的服务器通信,讲数据写入该服务器,完成一次分布式缓存的写操作;
不管是读取缓存还是写入缓存,只要使用相同的路由算法和服务器列表、应用程序查询的是相同的key,Memcache客户端总是访问相同的客户端去读取数据,只要服务器缓存中还有该数据的缓存,就能保证缓存命中。
这种Memcache集群的方式也是从分区容错性的方面考虑的,假设Node2宕机了,那么Node2上存储的数据都不可用了,此时由于集群中Node0和Node2还存在,下一次请求获取Node2的缓存数据,肯定是没有命中的。这时首先会从数据库中获取到缓存的数据,通过路由算法根据将缓存的数据存储在Node0或Node1上,这种集群的做法很好,但是缺点是成本太大。
Memcache共有两种路由调度算法,分别是:余数Hash与一致性Hash。
简单的路由算法可以使用余数Hash:用服务器数目和缓存数据Key的hash值相除,余数为服务器列表中的服务器编号。由于HashCode随机性比较强,所以使用余数Hash路由算法就可以保证缓存数据在整个Memcache服务器集群中有比较均衡的分布。
如果不考虑服务器集群的伸缩性,那么余数Hash算法几乎可以满足绝大数的缓存路由器需求你,但是当分布式缓存集群需要扩容时,就会出现很大的问题。
比如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中的数据清理掉,然后放上最新的数据。
如图:
1、检查客户端的请求数据是否在 memcached 中,如果有,直接把请求数据返回,不再对数据库进行任何操作,路径操作为①②③⑦;
2、如果请求的数据不在 memcached 中,就去查数据库,把从数据库中获取的数据返回给客户端,同时把数据缓存一份到 memcached 中(memcached 客户端不负责,需要程序明确实现),路径操作为①②④⑤⑦⑥;
3、每次更新数据库的同时更新 memcached 中的数据,保证一致性;
4、当分配给 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
提供一键安装MySQL软件及脚本
[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);
?>
客户端访问测试:
客户端访问测试,确保没有问题!
下载Memcache软件包
[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
—————————本文到此结束,感谢观看——————————
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。