一、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强,这应该容易理解。
1、协议
memcached的服务器客户端通信并不使用复杂的XML等格式,而使用简单的基于文本行的协议。
因此,通过telnet也能在memcached上保存数据、取得数据。
2、事件处理
libevent是个程序库,它将Linux的epoll、BSD类操作系统的kqueue等事件处理功能封装成统一的接口。即使对服务器的连接数增加,也能发挥O(1)的性能。memcached使用这个libevent库,因此能在Linux、BSD、Solaris等操作系统上发挥其高性能。
3、存储方式
为了提高性能,memcached中保存的数据都存储在memcached内置的内存存储空间中。由于数据仅存在于内存中,因此重启memcached、重启操作系统会导致全部数据消失。另外,内容容量达到指定值之后,就基于LRU(Least Recently Used)算法自动删除不使用的缓存。memcached本身是为缓存而设计的服务器,因此并没有过多考虑数据的永久性问题。
4、通信分布式
memcached尽管是“分布式”缓存服务器,但服务器端并没有分布式功能。各个memcached不会互相通信以共享信息。那么,怎样进行分布式呢?这完全取决于客户端的实现。
5、memcached的应用场景
1)数据库的前端缓存应用:让它来分担数据的并发压力,当数据更新时,可以使程序通知缓存进行更新
2)session会话共享的共享存储
6、memcached应用中的工作流程
它是一种内存缓存,可通过API的方式读取内存中缓存的这些数据,当用户需要读取数据时,会首先访问memcached缓存,如果缓存中有数据就直接返回给前端的应用程序,如果没有,再转发给后台端的服务器,这时服务器除了返回数据给用户,就会将数据更新给memcached缓存。
如果实际生产环境中,缓存服务器需要重启(或者断电),那么缓存中的数据将会丢失,那么这时替换的服务器并发压力会扩大,可能会导致引入的服务器也跟着停机,无法提供服务,那么这时我们的处理流程是这样的:
首先从负载均衡中将WEB应用停掉- - - >让负载均衡不再转发数据给WEB - - >接着启动缓存服务器- - - - > 通过程序把数据库的内容初始化到缓存服务器中- - - - >然后将网页应用启用- - - - >重启数据库服务器
7、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。一般的,在一致性哈希算法中,如果增加一台服务器,则受影响的数据仅仅是新服务器到其环空间中前一台服务器(即顺着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响。
综上所述,一致性哈希算法对于节点的增减都只需重定位环空间中的一小部分数据,具有较好的容错性和可扩展性。
一致性哈希的缺点:在服务节点太少时,容易因为节点分部不均匀而造成数据倾斜问题。我们可以采用增加虚拟节点的方式解决。
更重要的是,集群中缓存服务器节点越多,增加/减少节点带来的影响越小,很好理解。换句话说,随着集群规模的增大,继续命中原有缓存数据的概率会越来越大,虽然仍然有小部分数据缓存在服务器中不能被读到,但是这个比例足够小,即使访问数据库,也不会对数据库造成致命的负载压力。
二、部署LNMP动静分离&&memcache缓存服务器
环境如下:
所需源码包可在此处下载并上传至各服务器:https://pan.baidu.com/s/1-2pS702mz41e94nBXSgUnA
提取码:rldk
1、部署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 # 重启服务使配置生效
2、部署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服务器上定义的两个网页文件(在访问连接数据库的脚本文件时,需要先部署数据库,并创建用来连接的用户):
3、部署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)
测试验证:
4、部署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
5、部署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会话
6、使用 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和我们网页访问到的值是一样的,说明其被缓存了
7、测试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