搭建Memcache服务详解

博文大纲:
一、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通过在内存中缓存数据和对象来减少读取数据库的次数。目前被许多网站使用以提升网站的访问速度,尤其对于一些大型的、需要频繁访问数据库的网站访问速度提升效果十分显著。

Memcache是一个存储键值对的HashMap,在内存中对任意的数据都可以使用key-value的方式存储,数据库可以来自数据库调用或API调用。Memcache设计理念就是小而强大,她简单的设计促进了快速部署、易于开发并解决大规模的数据缓存的许多难题,而其所开放的API使得Memcache能用于Java、C/C++/C#、Perl、Python等大部分流行的程序语言。

二、Memcache工作流程

注意Memcache虽然被称为“分布式缓存”,但是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调度算法

Memcache共有两种路由调度算法,分别是:余数Hash与一致性Hash。

(1)余数Hash

简单的路由算法可以使用余数Hash:用服务器数目和缓存数据Key的hash值相除,余数为服务器列表中的服务器编号。由于HashCode随机性比较强,所以使用余数Hash路由算法就可以保证缓存数据在整个Memcache服务器集群中有比较均衡的分布。

如果不考虑服务器集群的伸缩性,那么余数Hash算法几乎可以满足绝大数的缓存路由器需求你,但是当分布式缓存集群需要扩容时,就会出现很大的问题。

比如Memcache服务器集群由3台变成4台,假设有HashCode为0~19的20个数据,如图:
3台Memcache服务器环境:
搭建Memcache服务详解
现在扩容到4台Memcache服务器:
搭建Memcache服务详解
仅仅是扩展到4台,原本缓存的数据就只可以命中6次,那么如果扩容到20台一行,只有前三个缓存中对应的数据可以命中。通过以上举例就可以说明:
使用余数Hash的路由算法,在扩容的时候会造成大量的数据无法正确命中缓存。

在网站业务中,大部分的业务数据操作请求都会通过缓存来获取的,只有少量的数据操作才会访问数据库,因此数据库的负载能力是在有缓存为前提的情况下设计的。当大部分缓存的数据因为Memcache服务器的扩容而不能正确读取时,这些数据访问的压力就落到了数据库的身上,这将大大超过数据库的负载能力,严重的情况还会造成数据库宕机。

对于使用余数Hash算法的Memcache集群环境,需要扩容时,解决方案:

  • 在网站访问量低谷,通常是深夜,技术团队加班、扩容、重启服务器;
  • 通过模拟请求的方式逐渐预热缓存,使缓存服务器中的数据重新分布;

(2)一致性Hash

一致性Hash算法通过一个叫做一致性Hash环的数据结构实现key到缓存服务器的Hash映射,简单来说,一致性Hash将整个Hash空间组织成一个虚拟的圆环,假设某空间哈希函数H的值空间是0~2^32-1,那么整个Hash空间如图:
搭建Memcache服务详解

将各个服务器使用H进行一个Hash计算,具体可以使用服务器的IP地址或主机名作为关键字,这样每台服务器都能确定其在上面的Hash环上的位置了,并且是按照顺时针排序。

假设三台Memcache经计算后位置如下:
搭建Memcache服务详解
接下来使用相同的算法计算出数据的哈希值,并由此确定数据在此哈希环上的位置,比如有四个数据,经过哈希计算后位置如下:
搭建Memcache服务详解
根据一致性Hash算法,按照顺时针找最近服务节点方法,这样得到的Hash环调度方法,有很高的容错性和可扩展性。

假设server03宕机,就会出现以下情况:
搭建Memcache服务详解
可以但看到此时C、B会收到影响,将B、C节点重定向到server01上。

在一致性Hash算法中,如果一台服务器不可用时,则受到影响的仅仅是此服务器按照顺时针方向的第一台服务器,其他服务器并不会受到影响。

假设在原本的环境中,再添加一台服务器server04,就会出现以下情况:
搭建Memcache服务详解
此时,A、D、C都不会受到影响,只有B需要重定向到新的Server04。

在一致性Hash算法中,如果增加一台服务器,则受影响的数据仅仅是新服务器按照顺时针方向的第一台服务器,其他并不会受到影响。

综上所述,一致性Hash算法的优缺点:

  • 优点:对于节点的增减都只需重定位环空间的一部分数据,具有较好的容错行和可扩展性;
  • 缺点:在服务节点太少是,容器因为节点分布均匀而造成数据倾斜问题。

当然对于其缺点,我们可以通过增加虚拟节点的方式来解决。

总之就是:集群中的缓存服务器节点越多,增加、减少节点的影响越小;随着集群规模的增大,继续命中原有缓存数据的概率会越来越大,虽然仍有小部分数据缓存服务器中不能被读取,但是比例相对来说,较小。即使访问数据库,也不会对数据库产生致命的负载压力。

四、Memcache实现原理

Memcache的特点:

  • 访问数据库的速度比传统的关系型数据库要快(因为Memcache是存放在内存中的,而传统的关系型数据库是存放在磁盘中);
  • Memcache的数据存放在内存中,就意味着只要Memcache重启,数据便会丢失。
  • 由于现在大部分都是64位操作系统,这里就不介绍内存对32位系统的影响了;

Memcache的原理:最重要的就是内存如何分配,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.Memcache的工作流程

如图:
搭建Memcache服务详解
1、检查客户端的请求数据是否在 memcached 中,如果有,直接把请求数据返回,不再对数据库进行任何操作,路径操作为①②③⑦;
2、如果请求的数据不在 memcached 中,就去查数据库,把从数据库中获取的数据返回给客户端,同时把数据缓存一份到 memcached 中(memcached 客户端不负责,需要程序明确实现),路径操作为①②④⑤⑦⑥;
3、每次更新数据库的同时更新 memcached 中的数据,保证一致性;
4、当分配给 memcached 内存空间用完之后,会使用 LRU(Least Recently Used,最近最少使用)策略加上到期失效策略,失效数据首先被替换,然后再替换掉最近未使用的数据;

2.Memcached的特征

协议简单:

  • 基于文本行的协议,直接通过 telnet 在 memcached 服务器上可进行存取数据操作;
  • 基于 libevent 事件处理;
  • 所有数据都保存在内存中,存取数据比硬盘快,当内存满后,通过 LRU 算法自动删除不使用的缓存,但没有考虑数据的容灾问题,重启服务,所有数据会丢失;
    分布式:
  • 各个 memcached 服务器之间互不通信,各自独立存取数据,不共享任何信息。服务器并不具有分布式功能,分布式部署取决于 memcache 客户端。

五、安装Memcache

Memcache 的安装分为两个过程:memcache 服务器端的安装和 memcached 客户端的安装。所谓服务器端的安装就是在服务器(一般都是 linux 系统)上安装 Memcache 实现数据的存储。

所谓客户端的安装就是指 php(或者其他程序,Memcache 还有其他不错的 api 接口提供)去使用服务器端的 Memcache 提供的函数,需要 php 添加扩展。

由此可以看出Memcache的搭建需要借助于LAMP或LNMP,本篇博文采用LNMP结构。

安装环境:
搭建Memcache服务详解

(1)安装nginx服务器

下载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  

(2)安装PHP服务器

下载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 

(3)安装MySQL数据库

提供一键安装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 |
+----+------+
//创建数据库,及添加数据并创建授权用户

(4)测试PHP与Nginx、MySQL的连通性

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

[root@PHP ~]# vim /var/www/html/test1.php

客户端访问测试:

搭建Memcache服务详解
搭建Memcache服务详解

客户端访问测试,确保没有问题!

(5)安装Memcache服务器

下载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:显示帮助;

(6)PHP服务器安装Memcache客户端

[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
connect('192.168.1.7', 11211) or die ("Could not connect");
$version = $memcache->getVersion();
echo "Server's version: ".$version."
"; $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)
"; $get_result = $memcache->get('key'); echo "Data from the cache:
"; var_dump($get_result); ?> //此测试脚本是显示memcached的版本 //并且向里面插入了一个缓存时间为600秒的键值对“test=123”,其ID为“key”

客户段访问如下:
搭建Memcache服务详解

在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 ~]# 

(7)使用 memcache 实现 session 共享

在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
";
echo "now_time:".time()."
"; echo "session_id:".session_id()."
"; ?>

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";

客户端访问如下:
搭建Memcache服务详解
搭建Memcache服务详解
搭建Memcache服务详解

[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 ~]# 

(8)测试Memcache缓存数据库

由于步骤3创建数据库时,就已经在数据库中写入了测试所用的值,接下来就是创建测试脚本就可以了!

PHP服务器的操作:

[root@PHP ~]# vim /var/www/html/test4.php 
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 "
"; echo "$key"; echo "
"; //print_r($data); foreach($data as $a) { echo "number is $a[id]"; echo "
"; echo "name is $a[name]"; //突出显示信息的字体颜色 echo "
"; } ?> //经常需要修改的地方已经标注了!而且这个测试脚本在Memcache软件中也有

客户端进行测试:
第一次进行访问
搭建Memcache服务详解
第二次进行访问(刷新之后)
搭建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

—————————本文到此结束,感谢观看——————————


网页标题:搭建Memcache服务详解
网页网址:http://pwwzsj.com/article/jdgiji.html