博文结构
Memcache简介
Memcache工作流程
Memcache调度算法
Memcache实现原理
安装Memcache
一.Memcache简介
Memcache是一套自由、开源、高性能、分布式的高速缓存系统。由于Memcache通过在内存中缓存数据和对象来减少读取数据库的次数。目前被许多网站使用以提升网站的访问速度,尤其对于一些大型的、需要频繁访问数据库的网站访问速度提升效果十分显著。
memcache是一套分布式的高速缓存系统,可以提高网站访问的速度,尤其是对于一些大型的公司或者频繁访问数据库的网站访问速度提升效果十分显著,memecache是一个开源免费的软件,memcache通过键值对的方式将数据缓存在内存当中,减少从后端数据库读取数据的次数。
二.Memcache工作流程
MemCache 虽然被称为”分布式缓存”,但是 MemCache 本身完全不具备 分布式的功能,MemCache 集群之间不会相互通信(与之形成对比的,比如 JBoss Cache,某 台服务器有缓存数据更新时,会通知集群中其他机器更新缓存或清除缓存数据),所谓的” 分布式”,完全依赖于客户端程序的实现,就像上面这张图的流程一样。 同时基于这张图,理一下 MemCache 一次写缓存的流程:
(1)应用程序输入需要写入缓存的数据;
(2)API将Key输入路由算法模块,理由算法根据Key和Memcache集群服务器列表得到服务器的编号;
(3)由服务器编号得到Memcache及其IP地址和端口号;
(4)API调用通信模块和指定编号的服务器通信,讲数据写入该服务器,完成一次分布式缓存的写操作;
三、Memcache调度算法
- 余数Hash
简单的路由算法可以使用余数 Hash:用服务器数目和缓存数据 KEY 的 hash 值相除,余数为服 务器列表下标编号,假如某个 str 对应的 HashCode 是 52、服务器的数目是 3,取余数得到 1, str 对应节点 Node1,所以路由算法把 str 路由到 Node1 服务器上。由于 HashCode 随机性比 较强,所以使用余数 Hash 路由算法就可以保证缓存数据在整个 MemCache 服务器集群中有 比较均衡的分布。
比如Memcache服务器集群由3台变成4台,假设有HashCode为0~19的20个数据,如图:
现在扩容到 4 台,加粗标红的表示命中:
在网站业务中,大部分的业务数据度操作请求上事实上是 通过缓存获取的,只有少量读操作会访问数据库,因此数据库的负载能力是以有缓存为前提 而设计的。当大部分被缓存了的数据因为服务器扩容而不能正确读取时,这些数据访问的压 力就落在了数据库的身上,这将大大超过数据库的负载能力,严重的可能会导致数据库宕机。
这个问题有解决方案,解决步骤为:
(1)在网站访问量低谷,通常是深夜,技术团队加班,扩容、重启服务器
(2)通过模拟请求的方式逐渐预热缓存,使缓存服务器中的数据重新分布
- 一致性 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。
一致性哈希算法对于节点的增减都只需重定位环空间中的一小部分数据,
具有较好的容错性和可扩展性
一致性哈希的缺点:在服务节点太少时,容易因为节点分部不均匀而造成数据倾斜问题。我 们可以采用增加虚拟节点的方式解决。 更重要的是,集群中缓存服务器节点越多,增加/减少节点带来的影响越小,很好理解。换 句话说,随着集群规模的增大,继续命中原有缓存数据的概率会越来越大,虽然仍然有小部 分数据缓存在服务器中不能被读到,但是这个比例足够小,即使访问数据库,也不会对数据 库造成致命的负载压力。
四.MemCache实现原理
Memcache的数据存放在内存中,32位机器最多只能使用2GB的内存弓箭,64位机器可以认为没有显示
如图:Memcache的数据存储机制(内存分配机制): 固定空间分配
(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中。
Memcache的特点:
- 访问数据库的速度比传统的关系型数据库要快(因为Memcache是存放在内存中的,而传统的关系型数据库是存放在磁盘中);
- Memcache的数据存放在内存中,就意味着只要Memcache重启,数据便会丢失。
- 由于现在大部分都是64位操作系统,这里就不介绍内存对32位系统的影响了;
memcache的工作流程
1、检查客户端的请求数据是否在 memcached 中,如果有,直接把请求数据返回,不再对数据库进行任何操作,路径操作为①②③⑦;
2、如果请求的数据不在 memcached 中,就去查数据库,把从数据库中获取的数据返回给客户端,同时把数据缓存一份到 memcached 中(memcached 客户端不负责,需要程序明确实现),路径操作为①②④⑤⑦⑥;
3、每次更新数据库的同时更新 memcached 中的数据,保证一致性;
4、当分配给 memcached 内存空间用完之后,会使用 LRU(Least Recently Used,最近最少使用)策略加上到期失效策略,失效数据首先被替换,然后再替换掉最近未使用的数据;
Memcached的特征
-
基于文本行协议:常见的协议http、ftp、smtp都是基于文本行的,所谓基于文本行,指的是信息以文本传递
-
基于libevent事件处理:libevent是利用c语言开发的程序库,bsd系统的kqueue(BSD是unix的衍生版本),linux系统的epoll等事件,所有数据都保存在内存中,数据访问速度块
- 所有数据都保存在内存中,数据访问速度块
分布式
各个memcached服务器之间互不通信,各自独立存取数据,不共享任何信息,服务器并不具备分布式功能,分布式部署取决于memcache客户端,Memcache的安装分为两个过程,memcache服务器端的安装和memcached客户端的安装
五.安装Memcache
Memcache的搭建需要借助于LAMP或LNMP,本篇博文采用LNMP结构
安装环境如下:
192.168.148.129 Memcache
192.168.148.130 php
192.168.148.131 nginx
192.168.148.136 mysql
- 安装nginx
[root@bogon ~]# useradd -M -s /sbin/nologin nginx
[root@bogon ~]# yum -y install openssl-devel
[root@bogon ~]# tar zxf pcre-8.39.tar.gz -C /usr/src
[root@bogon ~]# tar zxf zlib-1.2.8.tar.gz -C /usr/src
[root@bogon ~]# tar zxf nginx-1.14.0.tar.gz -C /usr/src
[root@bogon ~]# cd /usr/src/nginx-1.14.0/
[root@bogon 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@bogon ~]# ln -s /usr/local/nginx/sbin/nginx /usr/local/sbin
[root@bogon ~]# nginx
[root@bogon ~]# netstat -anpt | grep 80
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 8460/nginx: master
- 安装PHP服务器
[root@localhost ~]# yum -y install libxml2-devel lzip2-devel libcurl-devel libmcrypt-devel openssl-devel bzip2-devel
[root@localhost ~]# tar zxf libmcrypt-2.5.7.tar.gz
[root@localhost ~]# cd libmcrypt-2.5.7/
[root@localhost libmcrypt-2.5.7]# ./configure --prefix=/usr/local/libmcrypt && make && make install
[root@localhost ~]# tar zxf php-5.6.27.tar.gz
[root@localhost ~]# cd php-5.6.27/
[root@localhost 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
[root@localhost php-5.6.27]# cp php.ini-production /etc/php.ini
[root@localhost php-5.6.27]# cp sapi/fpm/init.d.php-fpm /etc/init.d/php-fpm
[root@localhost php-5.6.27]# chmod +x /etc/init.d/php-fpm
[root@localhost php-5.6.27]# chkconfig --add php-fpm
[root@localhost php-5.6.27]# chkconfig php-fpm on
[root@localhost php-5.6.27]# cp /usr/local/php5.6/etc/php-fpm.conf.default /usr/local/php5.6/etc/php-fpm.conf
[root@localhost php-5.6.27]# vim /usr/local/php5.6/etc/php-fpm.conf
修改内容如下:
pid = run/php-fpm.pid
listen = 192.168.31.141:9000 \\本地ip地址(千万不能用127.0.0.1)
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
[root@localhost php-5.6.27]# service php-fpm start
Starting php-fpm done
[root@localhost php-5.6.27]# netstat -anpt | grep php-fpm
tcp 0 0 192.168.148.130:9000 0.0.0.0:* LISTEN 130988/php-fpm: mas
- 安装MySQL数据库
[root@bogon ~]# mysql -u root -p123
mysql> create database testdb1;
mysql> use testdb1;
mysql> grant all on *.* to xws@'192.168.148.%' 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 |
+----+------+
5 rows in set (0.00 sec)
nginx操作如下:
[root@bogon nginx-1.14.0]# vim /usr/local/nginx/conf/nginx.conf //修改如下(大约在43行)
location / {
root html;
index index.php index.html index.htm;
}
location ~ \.php$ {
root /var/www/html;
fastcgi_pass 192.168.148.130:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
include fastcgi.conf;
}
[root@bogon nginx-1.14.0]# 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@bogon nginx-1.14.0]# nginx -s reload
php操作如下:
[root@bogon ~]# mkdir -p /var/www/html
[root@bogon ~]# vim /var/www/html/test.php
<?php
phpinfo();
?>
[root@bogon ~]# vim /var/www/html/test1.php
<?php
$link=mysqli_connect('192.168.148.136','xws','123456');
if($link) echo "恭喜你,数据库连接成功!!!"; else echo "connect shibai";
mysqli_close($link);
?>
客户端访问如下:
- 安装Memecache
[root@bogon ~]# tar zxf libevent-2.0.22-stable.tar.gz -C /usr/src/
[root@bogon ~]# cd /usr/src/libevent-2.0.22-stable/
[root@bogon libevent-2.0.22-stable]# ./configure && make && make install
[root@bogon ~]# tar zxf memcached-1.4.33.tar.gz -C /usr/src/
[root@bogon ~]# cd /usr/src/memcached-1.4.33/
[root@bogon memcached-1.4.33]# ./configure --prefix=/usr/local/memcached \
> --with-libevent=/usr/local/ && make && make install
[root@bogon ~]# memcached -d -m 2048 -l 192.168.1.7 -p 11211 -c 10240 -P /usr/local/memcached/memcached.pid -u root
[root@bogon ~]# netstat -anpt | grep 11211
- php操作如下(安装Memecache客户端)
[root@bogon ~]# tar zxf memcache-3.0.8.tgz -C /usr/src/
[root@bogon ~]# cd /usr/src/memcache-3.0.8/
[root@bogon memcache-3.0.8]# /usr/local/php5.6/bin/phpize
[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.129', 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.148.129 11211 //登陆到memcached的11211端口
Trying 192.168.148.129...
Connected to 192.168.148.129.
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 ~]#
测试Memcache缓存数据库
[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软件中也有
第一次进行访问
第二次进行访问(刷新之后)
来源:51CTO
作者:mb5d03569e7eb16
链接:https://blog.51cto.com/14400213/2469682