1、MemCache简介
MemCache 是一个自由、源码开放、高性能、分布式的分布式内存对象缓存系统,用于动态Web应用以减轻数据库的负载。它经过在内存中缓存数据和对象来减小读取数据库的次数,从而提升了网站访问的速度。 MemCaChe 是一个存储键值对的 HashMap,在内存中对任意的数据(好比字符串、对象等)所使用的 key-value 存储,数据能够来自数据库调用、API调用,或者页面渲染的结果。MemCache 设计理念就是小而强大,它简单的设计促进了快速部署、易于开发并解决面对大规模的数据缓存的许多难题,而所开放的 API 使得 MemCache用于 Java、C/C++/C#、Perl、Python、PHP、Ruby 等大部分流行的程序语言。
另外,说一下为何会有 Memcache 和 memcached 两种名称?其实 Memcache 是这个项目的名称(也时它客户端的名称),而 memcached 是它服务器端的主程序文件名。php
memcached是一个键/值系统,系统相对于MySQL简单不少,虽然MySQL也有缓存,可是数据库的SQL解析会耗费性能,查询慢于memcached,另外MySQL的缓存设计得更加复杂,由于要考虑事务,日志,存储引擎等模块,它的性能也没有memcached好。html
memcached只作一件事情,简单高效,在cache上比MySQL强,这应该容易理解。前端
memcached做为高速运行的分布式缓存服务器,具备如下的特色:mysql
- 协议简单;
- 基于libevent的事件处理;
- 内置内存存储方式;
- memcached不互相通讯的分布式;
一、协议
memcached的服务器客户端通讯并不使用复杂的XML等格式,而使用简单的基于文本行的协议。
所以,经过telnet也能在memcached上保存数据、取得数据。nginx
二、事件处理
libevent是个程序库,它将Linux的epoll、BSD类操做系统的kqueue等事件处理功能封装成统一的接口。即便对服务器的链接数增长,也能发挥O(1)的性能。memcached使用这个libevent库,所以能在Linux、BSD、Solaris等操做系统上发挥其高性能。算法
三、存储方式
为了提升性能,memcached中保存的数据都存储在memcached内置的内存存储空间中。因为数据仅存在于内存中,所以重启memcached、重启操做系统会致使所有数据消失。另外,内容容量达到指定值以后,就基于LRU(Least Recently Used)算法自动删除不使用的缓存。memcached自己是为缓存而设计的服务器,所以并无过多考虑数据的永久性问题。sql
四、通讯分布式
memcached尽管是“分布式”缓存服务器,但服务器端并无分布式功能。各个memcached不会互相通讯以共享信息。那么,怎样进行分布式呢?这彻底取决于客户端的实现。shell
五、memcached的应用场景
1)数据库的前端缓存应用:让它来分担数据的并发压力,当数据更新时,可使程序通知缓存进行更新
2)session会话共享的共享存储数据库
六、memcached应用中的工做流程
它是一种内存缓存,可经过API的方式读取内存中缓存的这些数据,当用户须要读取数据时,会首先访问memcached缓存,若是缓存中有数据就直接返回给前端的应用程序,若是没有,再转发给后台端的服务器,这时服务器除了返回数据给用户,就会将数据更新给memcached缓存。vim
若是实际生产环境中,缓存服务器须要重启(或者断电),那么缓存中的数据将会丢失,那么这时替换的服务器并发压力会扩大,可能会致使引入的服务器也跟着停机,没法提供服务,那么这时咱们的处理流程是这样的:
首先从负载均衡中将WEB应用停掉- - - >让负载均衡再也不转发数据给WEB - - >接着启动缓存服务器- - - - > 经过程序把数据库的内容初始化到缓存服务器中- - - - >而后将网页应用启用- - - - >重启数据库服务器
七、memcached的一致性Hash算法
一致性 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。通常的,在一致性哈希算法中,若是增长一台服务器,则受影响的数据仅仅是新服务器到其环空间中前一台服务器(即顺着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响。
综上所述,一致性哈希算法对于节点的增减都只需重定位环空间中的一小部分数据,具备较好的容错性和可扩展性。
一致性哈希的缺点:在服务节点太少时,容易由于节点分部不均匀而形成数据倾斜问题。咱们能够采用增长虚拟节点的方式解决。
更重要的是,集群中缓存服务器节点越多,增长/减小节点带来的影响越小,很好理解。换句话说,随着集群规模的增大,继续命中原有缓存数据的几率会愈来愈大,虽然仍然有小部分数据缓存在服务器中不能被读到,可是这个比例足够小,即便访问数据库,也不会对数据库形成致命的负载压力。
2、部署LNMP动静分离&&memcache缓存服务器
环境以下:
所需源码包可在此处下载并上传至各服务器:https://pan.baidu.com/s/1-2pS702mz41e94nBXSgUnA
提取码:rldk
一、部署Nginx服务
[root@nginx /]# yum -y install openssl-devel pcre-devel # 安装所需依赖包 [root@nginx /]# mkdir nginx # 我的习惯而已 [root@nginx /]# cd nginx/ [root@nginx nginx]# rz # 使用的xshell链接的服务器,使用rz上传所需的源码包 [root@nginx nginx]# tar zxf nginx-1.14.0.tar.gz # 解压到当前目录 [root@nginx nginx]# cd nginx-1.14.0/ [root@nginx nginx-1.14.0]# useradd -M -s /sbin/nologin www # 建立Nginx运行用户 [root@nginx nginx-1.14.0]# ./configure --prefix=/usr/local/nginx --user=www --group=www && make && make install # 编译并安装 [root@nginx nginx-1.14.0]# ln -s /usr/local/nginx/sbin/nginx /usr/local/sbin/ # 建立命令软链接 [root@nginx nginx-1.14.0]# nginx # 启动服务 [root@nginx nginx-1.14.0]# netstat -anput | grep 80 # 查看端口,肯定服务已经启动 tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 6827/nginx: master [root@nginx nginx-1.14.0]# cd / [root@nginx /]# vim /usr/local/nginx/conf/nginx.conf ............................... // 省略部份内容 在server{} 字段中添加以下内容 location ~ \.php$ { root /var/www/html; # 指定PHP的网页存放路径 fastcgi_pass 192.168.171.133:9000; # 指定PHP服务监听端口及地址 fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; include fastcgi.conf; } [root@nginx /]# nginx -s reload # 重启服务使配置生效
二、部署PHP服务
#首先须要为PHP安装依赖包 [root@php php]# yum -y install libxml2-devel openssl-devel bzip2-devel [root@php php]# tar zxf libmcrypt-2.5.7 [root@php php]# cd libmcrypt-2.5.7/ [root@php libmcrypt-2.5.7]# ./configure --prefix=/usr/local/libmcrypt && make && make install [root@php libmcrypt-2.5.7]# cd .. [root@php php]# tar zxf php-5.6.27.tar.gz [root@php php]# cd php-5.6.27/ [root@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的配置文件及控制服务的启停 [root@php php-5.6.27]# cp php.ini-production /etc/php.ini #复制源码中中提供的PHP配置文件 [root@php php-5.6.27]# cp sapi/fpm/init.d.php-fpm /etc/init.d/php-fpm #复制其服务控制脚本文件 [root@php php-5.6.27]# chmod +x /etc/init.d/php-fpm #赋予执行权限 [root@php php-5.6.27]# chkconfig --add php-fpm #添加为系统服务,以便支持systemctl管理 [root@php php-5.6.27]# chkconfig php-fpm on #开启 #复制php-fpm提供的默认配置文件并编辑它 [root@php php-5.6.27]# cp /usr/local/php5.6/etc/php-fpm.conf.default /usr/local/php5.6/etc/php-fpm.conf [root@php php-5.6.27]# vim /usr/local/php5.6/etc/php-fpm.conf listen = 192.168.171.133:9000 #监听地址是本机的IP9000端口 pm.max_children = 50 #最大启动的进程数 pm.start_servers = 5 #初始启动进程数 pm.min_spare_servers = 5 #最小空闲进程 pm.max_spare_servers = 35 #最大空闲进程 #修改完成后,保存退出便可 [root@php /]# service php-fpm restart # 重启PHP使配置生效 Gracefully shutting down php-fpm . done Starting php-fpm done [root@php /]# netstat -anput | grep 9000 # 查看是否运行 tcp 0 0 192.168.171.133:9000 0.0.0.0:* LISTEN 3054/php-fpm: maste
# 准备网页测试文件 [root@php /]# mkdir -p /var/www/html [root@php /]# cd /var/www/html/ [root@php html]# cat index.php <?php phpinfo(); ?> [root@php html]# cat index1.php <?php $link=mysqli_connect('192.168.171.135','zyz','pwd@123'); if($link) echo "恭喜你,数据库链接成功!!!"; else echo "connect shibai"; mysqli_close($link); ?>
到这,便可访问Nginx服务器的80端口来查看php服务器上定义的两个网页文件(在访问链接数据库的脚本文件时,须要先部署数据库,并建立用来链接的用户):
三、部署MySQL服务
# 这里部署一个简单的数据库便可 [root@mysql /]# mkdir -p mysql [root@mysql /]# cd mysql/ [root@mysql mysql]# rz # 上传所需 [root@mysql mysql]# sh mysql.sh # 直接sh执行脚本安装便可,安装完毕以后默认密码是123 Starting MySQL.. SUCCESS! mysql: [Warning] Using a password on the command line interface can be insecure. [root@mysql mysql]# netstat -anput | grep 3306 # 确保服务已经启动 tcp6 0 0 :::3306 :::* LISTEN 3290/mysqld [root@mysql mysql]# mysql -u root -p # 登陆数据库 Enter password: mysql> create database bbs; Query OK, 1 row affected (0.00 sec) mysql> grant all on bbs.* to zyz@"192.168.171.%" identified by 'pwd@123'; Query OK, 0 rows affected, 1 warning (0.00 sec)
测试验证:
四、部署Memcached服务
[root@memcached /]# mkdir memcached [root@memcached /]# cd memcached/ [root@memcached memcached]# rz # 上传所需源码包 [root@memcached memcached]# tar zxf libevent-2.0.22-stable.tar.gz # 解包 [root@memcached memcached]# cd libevent-2.0.22-stable/ [root@memcached libevent-2.0.22-stable]# ./configure && make && make install # 编译并安装 [root@memcached libevent-2.0.22-stable]# cd .. [root@memcached memcached]# tar zxf memcached-1.4.33.tar.gz [root@memcached memcached]# cd memcached-1.4.33/ [root@memcached memcached-1.4.33]# ./configure --prefix=/usr/local/memcached --with-libevent=/usr/local / && make && make install [root@memcached memcached-1.4.33]# ln -s /usr/local/memcached/bin/memcached /usr/local/bin/ # 命令制做软链接 [root@memcached memcached-1.4.33]# memcached -d -m 1024 -l 192.168.171.132 -p 11211 -c 10240 -P /usr/local/memcached/memcached.pid -u root #启动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 显示帮助 [root@memcached memcached-1.4.33]# netstat -anput | grep 11211 # 肯定TCP及udp都在监听 tcp 0 0 192.168.171.132:11211 0.0.0.0:* LISTEN 11666/memcached udp 0 0 192.168.171.132:11211 0.0.0.0:* 11666/memcached
五、部署Memcache客户端(返回PHP服务器操做)
[root@php /]# mkdir memcache [root@php /]# cd memcache/ [root@php memcache]# rz # 上传以下源码包 [root@php memcache]# ls memcache-3.0.8.tgz [root@php memcache]# tar zxf memcache-3.0.8.tgz [root@php memcache]# cd memcache-3.0.8/ [root@php memcache-3.0.8]# /usr/local/php5.6/bin/phpize # #执行该命令,以便生成configure文件 # 若在执行上述命令时报错,则须要执行“yun -y install autoconf "安装提示的autoconf包。 Configuring for: # 执行成功会显示次此几行 PHP Api Version: 20131106 Zend Module Api No: 20131226 Zend Extension Api No: 220131226 [root@php memcache-3.0.8]# ./configure --enable-memcache --with-php-config=/usr/local/php5.6/bin/php-config && make && make install # 编译并安装 # 执行完上述命令后,会显示memcache.so存放的路径 [root@php memcache-3.0.8]# vim /etc/php.ini # 编辑此文件 # 在最后一行添加以下内容,注意不要直接复制本人的路径 extension = /usr/local/php5.6/lib/php/extensions/no-debug-non-zts-20131226/memcache.so [root@php memcache-3.0.8]# service php-fpm restart # 重启php使配置生效 Gracefully shutting down php-fpm . done Starting php-fpm done
编写测试文件:
[root@php memcache-3.0.8]# cd /var/www/html/ [root@php html]# vim test1.php <?php $memcache = new Memcache; $memcache->connect('192.168.171.132', 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”
客户端访问编辑的test1.php文件,会看到如下内容:
在memcached服务器上安装Telnet命令,并登录缓存库,查看是否能够获得其键值对
[root@memcached /]# yum -y install telnet # 安装Telnet命令 [root@memcached /]# telnet 192.168.171.132 11211 Trying 192.168.171.132... Connected to 192.168.171.132. 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验证时,须要将test1.php文件中插入的键值对的保存时间值改大一些 #或者从新访问一下,以避免缓存失效,查询不到
至此,LNMP动静分离&&memcache缓存服务器已经基本部署完成,接下来,配置PHP与memcached服务器沟通保存session会话
六、使用 memcache 实现 session 共享(在PHP服务器进行如下操做)
[root@php /]# vim /etc/php.ini # 在末尾添加以下内容 session.save_handler = memcache session.save_path = "tcp://192.168.171.132: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" [root@php /]# service php-fpm restart # 重启服务使配置生效 Gracefully shutting down php-fpm . done Starting php-fpm done [root@php /]# 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的值,以下:
[root@memcached /]# telnet 192.168.171.132 11211 Trying 192.168.171.132... Connected to 192.168.171.132. Escape character is '^]'. get d1n4umig3aq8okqrg7ep95t321 VALUE d1n4umig3aq8okqrg7ep95t321 0 26 session_time|i:1581082210; END #能够看到,查询到的session_time和咱们网页访问到的值是同样的,说明其被缓存了
七、测试Memcached缓存数据库
在MySQL数据库上建立用于测试的表(全部操做都在MySQL数据库上)以下:
[root@mysql mysql]# mysql -u root -p Enter password: mysql> create database testdb; # 建立数据库 mysql> use testdb; # 进入库中 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; # 建立表 mysql> insert into test1(name) values ('aaa1'),('aaa2'),('aaa3'),('aaa4'),('aaa5'); # 向表中添加内容 mysql> select * from test1 # 查询表中内容 -> ; +----+------+ | id | name | +----+------+ | 1 | aaa1 | | 2 | aaa2 | | 3 | aaa3 | | 4 | aaa4 | | 5 | aaa5 | +----+------+ 5 rows in set (0.00 sec) mysql> desc test1; # 可查询表结构 +-------+-------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | name | varchar(20) | YES | | NULL | | +-------+-------------+------+-----+---------+----------------+ 2 rows in set (0.00 sec) mysql> grant select on testdb.* to test@'%' identified by 'pwd@123'; # 建立用于测试的用户
在PHP服务器上编写如下测试文件,测试memcache 是否缓存数据成功:
[root@php html]# vim test3.php <?php $memcachehost = '192.168.171.132'; $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.171.135","test","pwd@123"); mysql_select_db(testdb); $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 获取到对应的缓存数据,以下(在memcache服务器上进行操做):
[root@memcached memcached]# telnet 192.168.171.132 11211 Trying 192.168.171.132... Connected to 192.168.171.132. 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:"aaa1";}i:1;a:2:{s:2:"id";s:1:"2";s:4:"name";s:4:"aaa2";}i:2;a:2:{s:2:"id";s:1:"3";s:4:"name";s:4:"aaa3";}i:3;a:2:{s:2:"id";s:1:"4";s:4:"name";s:4:"aaa4";}i:4;a:2:{s:2:"id";s:1:"5";s:4:"name";s:4:"aaa5";}} END