一、简介
MemCache虽然被称为”分布式缓存”,但是MemCache本身完全不具备分布式的功能
Memcache 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高了网站访问的速度。 MemCaChe是一个存储键值对的HashMap,在内存中对任意的数据(比如字符串、对象等)所使用的key-value存储,数据可以来自数据库调用、API调用,或者页面渲染的结果。MemCache设计理念就是小而强大,它简单的设计促进了快速部署、易于开发并解决面对大规模的数据缓存的许多难题,而所开放的API使得MemCache能用于Java、C/C++/C#、Perl、Python、PHP、Ruby等大部分流行的程序语言。
MemCache会在内存中开辟一块空间,建立一个统一的巨大的hash表,hash表能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。
二、MemCached使用场景
通常,我们会在访问量高的Web网站和应用中使用MemCache,用来缓解数据库的压力,并且提升网站和应用的响应速度。
在应用程序中,我们通常在以下节点来使用MemCached:
- 访问频繁的数据库数据(身份token、首页动态)
- 访问频繁的查询条件和结果
- 作为Session的存储方式(提升Session存取性能)
- 页面缓存
- 更新频繁的非重要数据(访客量、点击次数)
- 大量的hot数据
常用工作流程(如下图):
- 客户端请求数据
- 检查MemCached中是否有对应数据
- 有的话直接返回,结束
- 没有的话,去数据库里请求数据
- 将数据写入MemCached,供下次请求时使用
- 返回数据,结束
三、MemCached工作原理
(1)Memcached内存管理
内存结构
- slab_class里,存放的是一组组chunk大小相同的slab
- 每个slab里面包含若干个page,page的默认大小是1M,如果slab大小100M,就包含100个page
- 每个page里面包含若干个chunk,chunk是数据的实际存放单位,每个slab里面的chunk大小相同
内存分配方式
- Memcached利用slab allocation机制来分配和管理内存,它按照预先规定的大小,将分配的内存分割成特定长度的内存块,再把尺寸相同的内存块分成组,数据在存放时,根据键值大小去匹配slab大小,找就近的slab存放,所以存在空间浪费现象。而传统的内存管理方式是,使用完通过malloc分配的内存后通过free来回收内存,这种方式容易产生内存碎片并降低操作系统对内存的管理效率。
- 存放数据时,首先slab要申请内存,申请内存是以page为单位的。所以在放入第一个数据的时候,无论大小为多少,都会有1M大小的page被分配给该slab。申请到page后,slab会将这个page的内存按chunk的大小进行切分,这样就变成了一个chunk数组,最后从这个chunk数组中选择一个用于存储数据。
示例:
MemCache中的value存放位置是由value的大小决定,value会被存放到与chunk大小最接近的一个slab中,比如slab[1]的chunk大小为88字节、slab[2]的chunk大小为112字节、slab[3]的chunk大小为144字节(默认相邻slab内的chunk基本以1.25为比例进行增长,MemCache启动时可以用-f指定这个比例),那么一个100字节的value,将被放到2号slab中。
内存回收方式
-
当数据容量用完MemCached分配的内存后,就会基于LRU(Least Recently
Used)算法清理失效的缓存数据(放入数据时可设置失效时间),或者清理最近最少使用的缓存数据,然后放入新的数据。 -
在LRU中,MemCached使用的是一种Lazy
Expiration策略,自己不会监控存入的key/vlue对是否过期,而是在获取key值时查看记录的时间戳,检查key/value对空间是否过期,这样可减轻服务器的负载。 -
需要注意的是,如果如果MemCache启动没有追加-M,则表示禁止LRU,这种情况下内存不够会报Out Of Memory错误。
针对MemCache的内存分配及回收算法,总结三点:
-
MemCache的内存分配chunk里面会有内存浪费,88字节的value分配在128字节(紧接着大的用)的chunk中,就损失了30字节,但是这也避免了管理内存碎片的问题
-
MemCache的LRU算法不是针对全局的,是针对slab的
-
应该可以理解为什么MemCache存放的value大小是限制的,因为一个新数据过来,slab会先以page为单位申请一块内存,申请的内存最多就只有1M,所以value大小自然不能大于1M了
(2)、MemCached分布式
为了提升MemCached的存储容量和性能,我们应用的客户端可能会对应多个MemCached服务器来提供服务,这就是MemCached的分布式。
分布式实现原理
MemCached的目前版本是通过C实现,采用了单进程、单线程、异步I/O,基于事件(event_based)的服务方式.使用libevent作为事件通知实现。多个Server可以协同工作,但这些 Server 之间保存的数据各不相同,而且并不通信(与之形成对比的,比如JBoss Cache,某台服务器有缓存数据更新时,会通知集群中其他机器更新缓存或清除缓存数据),每个Server只是对自己的数据进行管理。
Client端通过IP地址和端口号指定Server端,将需要缓存的数据是以key->value对的形式保存在Server端。key的值通过hash进行转换,根据hash值把value传递到对应的具体的某个Server上。当需要获取对象数据时,也根据key进行。首先对key进行hash,通过获得的值可以确定它被保存在了哪台Server上,然后再向该Server发出请求。Client端只需要知道保存hash(key)的值在哪台服务器上就可以了。
当向MemCached集群存入/取出key/value时,MemCached客户端程序根据一定的算法计算存入哪台服务器,然后再把key/value值存到此服务器中。也就是说,存取数据分二步走,第一步,选择服务器,第二步存取数据。
分布式算法解析
- 余数算法:
先求得键的整数散列值(也是就键string的HashCODE值 什么是HashCode),再除以服务器台数,根据余数确定存取服务器,这种方法计算简单,高效,但在memcached服务器增加或减少时,几乎所有的缓存都会失效。 - 散列算法:
先算出MemCached服务器的散列值,并将其分布到0到2的32次方的圆上,然后用同样的方法算出存储数据的键的散列值并映射至圆上,最后从数据映射到的位置开始顺时针查找,将数据保存到查找到的第一个服务器上,如果超过2的32次方,依然找不到服务器,就将数据保存到第一台MemCached服务器上。如果添加了一台MemCached服务器,只在圆上增加服务器的逆时针方向的第一台服务器上的键会受到影响。
(3)MemCached线程管理
MemCached网络模型是典型的单进程多线程模型,采用libevent处理网络请求,主进程负责将新来的连接分配给work线程,work线程负责处理连接,有点类似与负载均衡,通过主进程分发到对应的工作线程。
MemCached默认有7个线程,4个主要的工作线程,3个辅助线程,线程可划分为以下4种:
- 主线程,负责MemCached服务器初始化,监听TCP、Unix Domain连接请求;
- 工作线程池,MemCached默认4个工作线程,可通过启动参数修改,负责处理TCP、- - UDP,Unix域套接口链路上的请求;
- assoc维护线程,MemCached内存中维护一张巨大的hash表,该线程负责hash表动态增长;
- slab维护线程,即内存管理模块维护线程,负责class中slab的平衡,MemCached启动选项中可关闭该线程。
四、MemCached特性与限制
- MemCache中可以保存的item数据量是没有限制的,只要内存足够
- MemCache单进程在32位机中最大使用内存为2G,这个之前的文章提了多次了,64位机则没有限制
- Key最大为250个字节,超过该长度无法存储
- 单个item最大数据是1MB,超过1MB的数据不予存储
- MemCache服务端是不安全的,比如已知某个MemCache节点,可以直接telnet过去,并通过flush_all让已经存在的键值对立即失效,所以MemCache服务器最好配置到内网环境,通过防火墙制定可访问客户端
- 不能够遍历MemCache中所有的item,因为这个操作的速度相对缓慢且会阻塞其他的操作
- MemCached的高性能源自于两阶段哈希结构:第一阶段在客户端,通过Hash算法根据Key值算出一个节点;第二阶段在服务端,通过一个内部的Hash算法,查找真正的item并返回给客户端。从实现的角度看,MemCache是一个非阻塞的、基于事件的服务器程序
- MemCache设置添加某一个Key值的时候,传入expiry为0表示这个Key值永久有效,这个Key值也会在30天之后失效
五、编译安装Memcached
注意:安装Memcached可以使用yum安装,这里我们使用编译来进行安装!!!
1、下载Memcached
[root@server1 opt]# wget http://www.memcached.org/files/memcached-1.5.0.tar.gz
2、安装依赖
[root@server1 opt]# wget http://mirror.centos.org/centos/6/os/x86_64/Packages/libevent-devel-1.4.13-4.el6.x86_64.rpm
[root@server1 opt]# wget http://mirror.centos.org/centos/6/os/x86_64/Packages/libevent-headers-1.4.13-4.el6.noarch.rpm
[root@server1 opt]# wget http://mirror.centos.org/centos/6/os/x86_64/Packages/libevent-doc-1.4.13-4.el6.noarch.rpm
[root@server1 opt]# yum install -y libevent-*
3、解压tar包
[root@server1 ~]# tar xf /opt/memcached-1.5.0.tar.gz
4、编译安装
[root@server1 ~]# cd /opt/memcached-1.5.0/
[root@server1 memcached-1.5.0]# ./configure --prefix=/usr/local/lnmp/memcached
[root@server1 memcached-1.5.0]# make && make install
5、启动查看
[root@server1 ~]# /usr/local/memcached/bin/memcached -d -m 64M -u root -l 172.25.1.1 -p 11211 -c 256 -P /tmp/memcached.pid
[root@server1 ~]# netstat -lntup|grep 11211
tcp 0 0 172.25.1.1:11211 0.0.0.0:* LISTEN 20628/memcached
udp 0 0 172.25.1.1:11211 0.0.0.0:* 20628/memcached
6、启动参数
-l :<ip_addr>:指定进程监听的地址
-d:以服务模式运行
-u <username>:以指定用户身份运行memcached进程
-m <num>:用于缓存数据的最大内存空间,单位为MB,默认为64MB
-c <num>:最大支持的并发连接数,默认为1024
-p <num>:指定监听的TCP端口,默认 11211
-U <num>:指定监听额UDP端口,默认为11211,0代表关闭UDP端口
-t <threads>:用于处理入站的最大线程数,仅在memacached编译时开启了支持线程才有效
-f <num>:设定Slab Allocator定义预先分配内存空间大小固定的块时使用的增长因子
-M:当内存空间不够使用时返回错误信息,而不是按LRU算法利用空间
-n :指定最小额slab chunk大小:单位为字节
六、PHP扩展Memcache安装
1、下载Memcache
下载地址:http://pecl.php.net/package/memcache
[root@server1 opt]# wget http://pecl.php.net/get/memcache-2.2.7.tgz
2、解压tar包
[root@server1 ~]# tar xf /opt/memcache-2.2.7.tgz
3、使用phpize添加外挂模块
phpize是用来扩展php扩展模块的,通过phpize可以建立php的外挂模块,我们可以查看官方文档进行安装!!!
[root@server1 memcache-2.2.7]# /usr/local/php/bin/phpize
Configuring for:
PHP Api Version: 20131106
Zend Module Api No: 20131226
Zend Extension Api No: 220131226
4、编译安装Memcache
[root@server1 memcache-2.2.7]# ./configure --enable-memcache --with-php-config=/usr/local/php/bin/php-config
[root@server1 memcache-2.2.7]# make && make install
模块已经添加到php的模块目录
5、php开启Memcache模块
[root@server1 memcache-2.2.7]# vim /usr/local/php/etc/php.ini
878 extension=memcache.so #开启memcache模块
6、查看连接是否成功
[root@server1 memcache-2.2.7]# /etc/init.d/php-fpm reload
[root@server1 memcache-2.2.7]# /usr/local/lnmp/php/bin/php -m |grep memcache
memcache
默认发布目录中memcache模块已经添加成功
7、配置memcache.php查看memcache状态
[root@server1 ~]# cp /opt/memcache-2.2.7/{example.php,memcache.php} /usr/local/lnmp/nginx/html/
[root@server1 ~]# vim /usr/local/nginx/html/memcache.php
8、查看memcache状态
9、压力测试
[kiosk@foundation1 lamp]$ ab -c 10 -n 1000 http://172.25.1.1/example.php
七、通过OpenResty编译安装Nginx
1、OpenResty介绍
-
OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。‘
-
OpenResty 通过汇聚各种设计精良的 Nginx 模块(主要由 OpenResty 团队自主开发),从而将 Nginx 有效地变成一个强大的通用 Web 应用平台。这样,Web 开发人员和系统工程师可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,快速构造出足以胜任 10K 乃至 1000K 以上单机并发连接的高性能 Web 应用系统。
-
OpenResty的目标是让你的Web服务直接跑在 Nginx 服务内部,充分利用 Nginx 的非阻塞 I/O 模型,不仅仅对 HTTP 客户端请求,甚至于对远程后端诸如 MySQL、PostgreSQL、Memcached 以及 Redis 等都进行一致的高性能响应。
2、删除之前的Nginx
[root@server1 ~]# nginx -s stop
[root@server1 ~]# rm -rf /usr/local/nginx
3、解压tar包
[root@server1 opt]# tar zxf nginx-sticky-module-ng.tar.gz
[root@server1 opt]# tar zxf openresty-1.13.6.1.tar.gz
4、编译安装
[root@server1 openresty-1.13.6.1]# ./configure --prefix=/usr/local/openresty --with-http_ssl_module --with-http_stub_status_module --add-module=/opt/nginx-sticky-module-ng
[root@server1 openresty-1.13.6.1]# gmake && gmake install
5、配置nginx.conf
http {
upstream redhat { #定义集群
server localhost:11211;
keepalive 512;
}
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;
location /memc { #定义缓存
root html;
index index.php;
internal;
memc_connect_timeout 100ms;
memc_send_timeout 100ms;
memc_read_timeout 100ms;
set $memc_key $query_string;
set $memc_exptime 300;
memc_pass redhat;
}
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
location ~ \.php$ { #访问php文件
set $key $uri$args;
srcache_fetch GET /redhat $key;
srcache_store PUT /redhat $key;
root html;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
#fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
include fastcgi.conf;
}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
6、启动服务器
[root@server1 memcache-2.2.7]# cp example.php /usr/local/openresty/nginx/html/
[root@server1 memcache-2.2.7]# cp memcache.php /usr/local/openresty/nginx/html/
[root@server1 html]# vim memcache.php
[root@server1 conf]# /usr/local/openresty/nginx/sbin/nginx
7、访问测试
[kiosk@foundation1 Desktop]$ ab -c 10 -n 1000 http://172.25.1.1/example.php
对比nginx的访问请求
性能提升了3倍