85590296 2019-10-31
博文大纲:
一、MemCache简介
MemCache 是一个自由、源码开放、高性能、分布式的分布式内存对象缓存系统,用于动态Web应用以减轻数据库的负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高了网站访问的速度。 MemCaChe 是一个存储键值对的 HashMap,在内存中对任意的数据(比如字符串、对象等)所使用的 key-value 存储,数据可以来自数据库调用、API调用,或者页面渲染的结果。MemCache 设计理念就是小而强大,它简单的设计促进了快速部署、易于开发并解决面对大规模的数据缓存的许多难题,而所开放的 API 使得 MemCache用于 Java、C/C++/C#、Perl、Python、PHP、Ruby 等大部分流行的程序语言。
另外,说一下为什么会有 Memcache 和 memcached 两种名称?其实 Memcache 是这个项目的名称(也时它客户端的名称),而 memcached 是它服务器端的主程序文件名。
memcached是一个键/值系统,系统相对于MySQL简单很多,虽然MySQL也有缓存,但是数据库的SQL解析会耗费性能,查询慢于memcached,另外MySQL的缓存设计得更加复杂,因为要考虑事务,日志,存储引擎等模块,它的性能也没有memcached好。
memcached只做一件事情,简单高效,在cache上比MySQL强,这应该容易理解。
memcached作为高速运行的分布式缓存服务器,具有以下的特点:
- 协议简单;
- 基于libevent的事件处理;
- 内置内存存储方式;
- memcached不互相通信的分布式。
memcached的服务器客户端通信并不使用复杂的XML等格式,而使用简单的基于文本行的协议。
因此,通过telnet也能在memcached上保存数据、取得数据。
libevent是个程序库,它将Linux的epoll、BSD类操作系统的kqueue等事件处理功能封装成统一的接口。即使对服务器的连接数增加,也能发挥O(1)的性能。memcached使用这个libevent库,因此能在Linux、BSD、Solaris等操作系统上发挥其高性能。
为了提高性能,memcached中保存的数据都存储在memcached内置的内存存储空间中。由于数据仅存在于内存中,因此重启memcached、重启操作系统会导致全部数据消失。另外,内容容量达到指定值之后,就基于LRU(Least Recently Used)算法自动删除不使用的缓存。memcached本身是为缓存而设计的服务器,因此并没有过多考虑数据的永久性问题。
memcached尽管是“分布式”缓存服务器,但服务器端并没有分布式功能。各个memcached不会互相通信以共享信息。那么,怎样进行分布式呢?这完全取决于客户端的实现。
1)数据库的前端缓存应用:让它来分担数据的并发压力,当数据更新时,可以使程序通知缓存进行更新
2)session会话共享的共享存储
它是一种内存缓存,可通过API的方式读取内存中缓存的这些数据,当用户需要读取数据时,会首先访问memcached缓存,如果缓存中有数据就直接返回给前端的应用程序,如果没有,再转发给后台端的服务器,这时服务器除了返回数据给用户,就会将数据更新给memcached缓存。
如果实际生产环境中,缓存服务器需要重启(或者断电),那么缓存中的数据将会丢失,那么这时替换的服务器并发压力会扩大,可能会导致引入的服务器也跟着停机,无法提供服务,那么这时我们的处理流程是这样的:
首先从负载均衡中将WEB应用停掉- - - >让负载均衡不再转发数据给WEB - - >接着启动缓存服务器- - - - > 通过程序把数据库的内容初始化到缓存服务器中- - - - >然后将网页应用启用- - - - >重启数据库服务器
一致性 Hash 算法通过一个叫做一致性 Hash 环的数据结构实现 Key 到缓存服务器的 Hash 映射。简单地说,一致性哈希将整个哈希值空间组织成一个虚拟的圆环(这个环被称为一致性Hash 环),如假设某空间哈希函数 H 的值空间是 0~2^ 32 -1(即哈希值是一个 32 位无符号整型),整个哈希空间如下:
下一步将各个服务器使用 H 进行一个哈希计算,具体可以使用服务器的 IP 地址或者主机名作为关键字,这样每台机器能确定其在上面的哈希环上的位置了,并且是按照顺时针排列,这里我们假设三台节点 memcache经计算后位置如下:
接下来使用相同算法计算出数据的哈希值 h,并由此确定数据在此哈希环上的位置。假如我们有数据 A、B、C、D、4 个对象,经过哈希计算后位置如下:
根据一致性哈希算法,数据 A 就被绑定到了 server01 上,D 被绑定到了 server02 上,B、C在 server03 上,是按照顺时针找最近服务节点方法。
这样得到的哈希环调度方法,有很高的容错性和可扩展性:
假设 server03 宕机:
可以看到此时 C、B 会受到影响,将 B、C 节点被重定位到 Server01。一般的,在一致性哈希算法中,如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即顺着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响。
考虑另外一种情况,如果我们在系统中增加一台服务器 Memcached Server 04:
此时 A、D、C 不受影响,只有 B 需要重定位到新的 Server04。一般的,在一致性哈希算法中,如果增加一台服务器,则受影响的数据仅仅是新服务器到其环空间中前一台服务器(即顺着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响。
综上所述,一致性哈希算法对于节点的增减都只需重定位环空间中的一小部分数据,具有较好的容错性和可扩展性。
一致性哈希的缺点:在服务节点太少时,容易因为节点分部不均匀而造成数据倾斜问题。我们可以采用增加虚拟节点的方式解决。
更重要的是,集群中缓存服务器节点越多,增加/减少节点带来的影响越小,很好理解。换句话说,随着集群规模的增大,继续命中原有缓存数据的概率会越来越大,虽然仍然有小部分数据缓存在服务器中不能被读到,但是这个比例足够小,即使访问数据库,也不会对数据库造成致命的负载压力。
下载我提供的源码包,并将对应的源码包上传至各个服务器。
[ ~]# yum -y erase httpd #卸载自带的httpd服务 [ ~]# yum -y install openssl-devel pcre-devel #安装所需依赖 [ ~]# cd /usr/src [ src]# rz #上传Nginx源码包 [ src]# tar zxf nginx-1.14.0.tar.gz [ src]# cd nginx-1.14.0/ [ nginx-1.14.0]# ./configure --prefix=/usr/local/nginx --user=www --group=www && make && make install #进行编译安装 [ nginx-1.14.0]# useradd -M -s /sbin/nologin www #创建运行用户 [ nginx-1.14.0]# ln -sf /usr/local/nginx/sbin/nginx /usr/local/sbin/ #对命令做软链接 [ nginx-1.14.0]# nginx #启动Nginx服务 [ nginx-1.14.0]# netstat -anput | grep 80 #确定80端口在监听 #以下是配置nginx与PHP服务器关联 [ nginx-1.14.0]# cd /usr/local/nginx/conf/ [ conf]# vim nginx.conf #编辑主配置文件 #在server{ }字段中添加以下内容 location ~ \.php$ { root /var/www/html; #指定PHP服务器的网页存放路径 fastcgi_pass 192.168.20.3:9000; #指定PHP服务器监听端口 fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; include fastcgi.conf; } #更改完成后,保存退出即可 [ conf]# nginx -s reload #重启使更改生效
[ ~]# rz #上传源码包libmcrypt及php-5.6.27 [ ~]# tar zxf libmcrypt-2.5.7.tar.gz -C /usr/src #解包 [ ~]# tar zxf php-5.6.27.tar.gz -C /usr/src #解包 [ ~]# yum -y install libxml2-devel openssl-devel bzip2-devel #安装依赖 [ ~]# cd /usr/src/libmcrypt-2.5.7 #切换至解压后的路径 [ libmcrypt-2.5.7]# ./configure --prefix=/usr/local/libmcrypt && make && make install #编辑安装 [ libmcrypt-2.5.7]# cd ../php-5.6.27/ #进入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 #编译安装PHP #接下来为调整PHP的配置文件及控制服务的启停 [ php-5.6.27]# cp php.ini-production /etc/php.ini #复制源码中中提供的PHP配置文件 [ php-5.6.27]# cp sapi/fpm/init.d.php-fpm /etc/init.d/php-fpm #复制其服务控制脚本文件 [ php-5.6.27]# chmod +x /etc/init.d/php-fpm #赋予执行权限 [ php-5.6.27]# chkconfig --add php-fpm #添加为系统服务,以便支持systemctl管理 [ php-5.6.27]# chkconfig php-fpm on #开启 [ php-5.6.27]# cp /usr/local/php5.6/etc/php-fpm.conf.default /usr/local/php5.6/etc/php-fpm.conf #复制php-fpm提供的默认配置文件并编辑它 [ php-5.6.27]# vim /usr/local/php5.6/etc/php-fpm.conf #修改一下,进行优化 listen = 192.168.20.3:9000 #监听地址是本机的IP9000端口 pm.max_children = 50 #最大启动的进程数 pm.start_servers = 5 #初始启动进程数 pm.min_spare_servers = 5 #最小空闲进程 pm.max_spare_servers = 35 #最大空闲进程 #修改完成后,保存退出即可 [ php-5.6.27]# systemctl start php-fpm #启动PHP服务 [ php-5.6.27]# netstat -anput | grep php-fpm #确认其9000端口已启动 [ ~]# mkdir -p /var/www/html #创建其网页存放目录,须和nginx服务器的网页存放路径一样 [ php-5.6.27]# vim /var/www/html/index.php #编写PHP测试页面 <?php phpinfo(); ?> [ php-5.6.27]# vim /var/www/html/index1.php #连接数据库的测试脚本 <?php $link=mysqli_connect(‘192.168.20.5‘,‘ljz‘,‘‘); if($link) echo "恭喜你,数据库连接成功!!!"; else echo "connect shibai"; mysqli_close($link); ?>
至此,即可访问Nginx服务器的80端口来查看php服务器上定义的两个网页文件(在访问连接数据库的脚本文件时,需要先部署数据库,并创建用来连接的用户):
访问index.php,看到以下页面说明php运行正常:
访问index1.php,看到以下页面,说明LNMP之间的协调工作没有问题(我在访问下面的地址前,已经部署了MySQL数据库,并且创建了相应的用户):
我这里部署一个简易的MySQL数据库,提供测试功能即可。
[ ~]# rz #上传我提供的mysql.sh脚本文件及mysql-5.7.22-linux.....源码包 [ ~]# sh mysql.sh #执行mysql.sh脚本,执行后,需要等待较长时间 Starting MySQL... SUCCESS! #当出现此提示信息时,则表示MySQL部署成功 mysql: [Warning] Using a password on the command line interface can be insecure. #安装MySQL成功后,默认的数据库root密码为123 [ ~]# mysql -uroot -p123 #登录MySQL数据库 mysql> create database bbs; mysql> grant all on bbs.* to "192.168.20.%" identified by ‘‘;
[ ~]# cd /usr/src #切换至指定目录 [ src]# rz #上传所需源码包 [ src]# ls #就是上传以下的后面两个源码包 debug kernels libevent-2.0.22-stable.tar.gz memcached-1.4.33.tar.gz [ src]# tar zxf libevent-2.0.22-stable.tar.gz #解包 [ src]# tar zxf memcached-1.4.33.tar.gz #解包 [ src]# cd libevent-2.0.22-stable/ #切换至解压后的目录 [ libevent-2.0.22-stable]# ./configure && make && make install #编译安装 [ libevent-2.0.22-stable]# cd ../memcached-1.4.33/ #切换至memcached目录 [ memcached-1.4.33]# ./configure --prefix=/usr/local/memcached --with-libevent=/usr/local && make && make install #编译安装 [ ~]# ls /usr/local/memcached/bin/memcached #确认该命令已安装 /usr/local/memcached/bin/memcached [ ~]# ln -sf /usr/local/memcached/bin/memcached /usr/local/bin/ #对命令做软链接 [ memcached-1.4.33]# memcached -d -m 1024 -l 192.168.20.4 -p 11211 -u root -c 10240 -P /usr/local/memcached/memcached.pid #启动memcached服务,上述启动参数说明如下: # -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 显示帮助 [ ~]# netstat -anput | grep 11211 #确定TCP及udp端口在监听
[ ~]# cd /usr/src [ src]# rz #上传memcache-3.0.8.tgz源码包 [ src]# tar zxf memcache-3.0.8.tgz #解包 [ src]# cd memcache-3.0.8/ #进入解压后的目录 [ memcache-3.0.8]# /usr/local/php5.6/bin/phpize #生成该命令,以便生成configure文件 #若在执行上述命令时报错,则需要执行“yun -y install autoconf "安装提示的autoconf包。 [ memcache-3.0.8]# ./configure --enable-memcache --with-php-config=/usr/local/php5.6/bin/php-config && make && make install #上述命令执行后,会显示memcache.so存放的路径 [ memcache-3.0.8]# vim /etc/php.ini #编辑主配置文件 #在配置文件末尾添加上述内容,以便指定memcache.so文件的存放路径 extension = /usr/local/php5.6/lib/php/extensions/no-debug-zts-20131226/memcache.so [ memcache-3.0.8]# systemctl restart php-fpm #重启php服务,使更改生效 [ memcache-3.0.8]# cd /var/www/html/ [ html]# vim test3.php #编写测试文件 <?php $memcache = new Memcache; $memcache->connect(‘192.168.20.4‘, 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”
客户端访问编辑的test3.php文件,会看到以下内容:
在memcached服务器上安装Telnet命令,并登陆缓存库,查看是否可以得到其键值对(以下操作在memcached服务器上进行操作):
[ ~]# yum -y install telnet #安装Telnet命令 [ ~]# telnet 192.168.20.4 11211 #登陆到memcached的11211端口 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验证时,需要将test3.php文件中插入的键值对的保存时间值改大一些 #或者重新访问一下,以免缓存失效,查询不到
至此,LNMP动静分离&&memcache缓存服务器已经基本部署完成,接下来,配置PHP与memcached服务器沟通保存session会话
接下来实现PHP与memcached服务器沟通保存session会话。
[ ~]# vim /etc/php.ini #在配置文件末尾添加下面内容 ession.save_handler = memcache session.save_path = "tcp://192.168.20.4:11211?persistent=1&weight=1&timeout=1&retry_interval=15" #注上面写入的内容解释如下: # 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"。 [ html]# systemctl restart php-fpm #重启使更改生效 [ html]# vim /var/www/html/test2.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 />"; ?> #编写完成后,保存退出即可
客户端访问编写的test2.php测试文件,如下:
同样,使用Telnet命令在memcached服务器上进行查询其session_id的值,如下:
[ ~]# telnet 192.168.20.4 11211 #登录缓存监听的端口 get naapo2eet2d9s4to4mt7hchnr1 #执行get命令 VALUE naapo2eet2d9s4to4mt7hchnr1 0 26 session_time|i:1572450021; #可以看到,查询到的session_time和我们网页访问到的值是一样的,说明其被缓存了
在MySQL数据库上创建用于测试的表(所有操作都在MySQL数据库上)如下:
mysql> create database testdb1; #创建一个库 mysql> use testdb1; #切换至创建的库中 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; #创建表,共两列,分别是ID号和姓名 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 | +----+------+ 5 rows in set (0.00 sec) mysql> desc test1; #可以执行此语句查看test1的表结构 mysql> grant select on testdb1.* to ‘%‘ identified by ‘‘; #对test1表创建一个只读的数据库用户,用于接下来的测试
接下来就是测试的工作了,这里有个 php 脚本,用于测试memcache 是否缓存数据成功
在PHP服务器上编写以下测试文件:
客户端访问用于测试的脚本文件,第一次访问的页面如下:
客户端刷新后,会看到以下页面:
在查询到的缓存过期前,可以在memcache上通过get 获取到对应的缓存数据,如下(在memcache服务器上进行操作):
[ ~]# telnet 192.168.20.4 11211 #登录缓存监听的端口 get d8c961e9895ba4b463841924dbcefc2b #执行get命令 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";}}
———————— 本文至此结束,感谢阅读 ————————