10-memcache集群原理、缓存实现

1. memcache理论部分

1)简介

MemCache 是一个自由、源码开放、高性能、分布式的分布式内存对象缓存系统,用于动态
Web 应用以减轻数据库的负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,
从而提高了网站访问的速度。 MemCaChe 是一个存储键值对的 HashMap,在内存中对任意
的数据(比如字符串、对象等)所使用的 key-value 存储,数据可以来自数据库调用、API
调用,或者页面渲染的结果。

2)memcache分布式缓存模型

在这里插入图片描述

同时基于这张图,理一下 MemCache 一次写缓存的流程:
1、应用程序输入需要写缓存的数据
2、API 将 Key 输入路由算法模块,路由算法根据 Key 和 MemCache 集群服务器列表得到一
台服务器编号
3、由服务器编号得到 MemCache 及其的 ip 地址和端口号
4、API 调用通信模块和指定编号的服务器通信,将数据写入该服务器,完成一次分布式缓
存的写操作

这种 MemCache 集群的方式也是从分区容错性的方面考虑的,假如 Node2 宕机了,那么Node2 上面存储的数据都不可用了,此时由于集群中 Node0 和 Node1 还存在,下一次请求Node2 中存储的 Key 值的时候,肯定是没有命中的,这时先从数据库中拿到要缓存的数据,然后路由算法模块根据 Key 值在 Node0 和 Node1 中选取一个节点,把对应的数据放进去,这样下一次就又可以走缓存了,这种集群的做法很好,但是缺点是成本比

3)余数hash算法和一致性hash算法

1、余数Hash

比方说,字符串str对应的HashCode是50、服务器的数目是3,取余数得到1,str对应节点Node1,所以路由算法把str路由到Node1服务器上。由于HashCode随机性比较强,所以使用余数Hash路由算法就可以保证缓存数据在整个MemCache服务器集群中有比较均衡的分布。

如果不考虑服务器集群的伸缩性(什么是伸缩性,请参见大型网站架构学习笔记),那么余数Hash算法几乎可以满足绝大多数的缓存路由需求,但是当分布式缓存集群需要扩容的时候,就难办了。

就假设MemCache服务器集群由3台变为4台吧,更改服务器列表,仍然使用余数Hash,50对4的余数是2,对应Node2,但是str原来是存在Node1上的,这就导致了缓存没有命中。如果这么说不够明白,那么不妨举个例子,原来有HashCode为0~19的20个数据,那么:
在这里插入图片描述
在这里插入图片描述
如果我扩容到20+的台数,只有前三个HashCode对应的Key是命中的,也就是15%。当然这只是个简单例子,现实情况肯定比这个复杂得多,不过足以说明,使用余数Hash的路由算法,在扩容的时候会造成大量的数据无法正确命中(其实不仅仅是无法命中,那些大量的无法命中的数据还在原缓存中在被移除前占据着内存)。这个结果显然是无法接受的,在网站业务中,大部分的业务数据度操作请求上事实上是通过缓存获取的,只有少量读操作会访问数据库,因此数据库的负载能力是以有缓存为前提而设计的。当大部分被缓存了的数据因为服务器扩容而不能正确读取时,这些数据访问的压力就落在了数据库的身上,这将大大超过数据库的负载能力,严重的可能会导致数据库宕机。

这个问题有解决方案,解决步骤为:

(1)在网站访问量低谷,通常是深夜,技术团队加班,扩容、重启服务器

(2)通过模拟请求的方式逐渐预热缓存,使缓存服务器中的数据重新分布

2、一致性Hash算法

一致性Hash算法通过一个叫做一致性Hash环的数据结构实现Key到缓存服务器的Hash映射,看一下我自己画的一张图:
在这里插入图片描述

具体算法过程为:先构造一个长度为232的整数环(这个环被称为一致性Hash环),根据节点名称的Hash值(其分布为[0, 232-1])将缓存服务器节点放置在这个Hash环上,然后根据需要缓存的数据的Key值计算得到其Hash值(其分布也为[0, 232-1]),然后在Hash环上顺时针查找距离这个Key值的Hash值最近的服务器节点,完成Key到服务器的映射查找。

就如同图上所示,三个Node点分别位于Hash环上的三个位置,然后Key值根据其HashCode,在Hash环上有一个固定位置,位置固定下之后,Key就会顺时针去寻找离它最近的一个Node,把数据存储在这个Node的MemCache服务器中。使用Hash环如果加了一个节点会怎么样,看一下:

在这里插入图片描述
看到我加了一个Node4节点,只影响到了一个Key值的数据,本来这个Key值应该是在Node1服务器上的,现在要去Node4了。采用一致性Hash算法,的确也会影响到整个集群,但是影响的只是加粗的那一段而已,相比余数Hash算法影响了远超一半的影响率,这种影响要小得多。更重要的是,集群中缓存服务器节点越多,增加节点带来的影响越小,很好理解。换句话说,随着集群规模的增大,继续命中原有缓存数据的概率会越来越大,虽然仍然有小部分数据缓存在服务器中不能被读到,但是这个比例足够小,即使访问数据库,也不会对数据库造成致命的负载压力。

4)memcache实现原理

首先要说明一点,MemCache的数据存放在内存中,存放在内存中个人认为意味着几点:

1、访问数据的速度比传统的关系型数据库要快,因为Oracle、MySQL这些传统的关系型数据库为了保持数据的持久性,数据存放在硬盘中,IO操作速度慢

2、MemCache的数据存放在内存中同时意味着只要MemCache重启了,数据就会消失

3、既然MemCache的数据存放在内存中,那么势必受到机器位数的限制,memcache主机如果是32位机器,最多只能使用2GB的内存空间,64位机器可以认为没有上限

然后我们来看一下MemCache的原理,MemCache最重要的是内存分配的内容,MemCache采用的内存分配方式是固定空间分配,还是自己画一张图说明:

在这里插入图片描述
这张图片里面涉及了slab_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[1]的chunk大小为80字节、slab[2]的chunk大小为100字节、slab[3]的chunk大小为128字节(相邻slab内的chunk基本以1.25为比例进行增长,MemCache启动时可以用-f指定这个比例),那么过来一个88字节的value,这个value将被放到2号slab中。放slab的时候,首先slab要申请内存,申请内存是以page为单位的,所以在放入第一个数据的时候,无论大小为多少,都会有1M大小的page被分配给该slab。申请到page后,slab会将这个page的内存按chunk的大小进行切分,这样就变成了一个chunk数组,最后从这个chunk数组中选择一个用于存储数据。

如果这个slab中没有chunk可以分配了怎么办,如果MemCache启动没有追加-M(禁止LRU,这种情况下内存不够会报Out Of Memory错误),那么MemCache会把这个slab中最近最少使用的chunk中的数据清理掉,然后放上最新的数据。

针对MemCache的内存分配及回收算法,总结三点:

  • MemCache的内存分配chunk里面会有内存浪费,88字节的value分配在128字节(紧接着大的用)的chunk中,就损失了30字节,但是这也避免了管理内存碎片的问题
  • MemCache的LRU算法不是针对全局的,是针对slab的
  • 应该可以理解为什么MemCache存放的value大小是限制的,因为一个新数据过来,slab会先以page为单位申请一块内存,申请的内存最多就只有1M,所以value大小自然不能大于1M了

memcache的lru算法

假如我们以122字节大小的chunk举例,122的chunk都满了,又有新的值(长度也为122)要加入,那么要挤掉谁?
MC这时使用的是LRU的删除机制
注:操作系统的常用内存管理,经常使用的算法是FIFO,LRU算法
lru:least recently used 最近最少使用
fifo:first in,first out (先进先出)
LRU原理:当某个单元被请求的时候,维护一个计数器,通过计数器来判断最近谁最少使用,那就把谁踢出去。
注:即使某个key设置的永久有效,也会被踢出来,这个就是永久数据被踢的现象。

2. memcache安装、启动、语法

1)安装memcache

    5  tar -zxf libevent-2.0.22-stable.tar.gz -C /usr/src
    6  cd /usr/src/libevent-2.0.22-stable/
    7  ./configure --prefix=/usr/
    8  make && make install
    7  tar -zxf memcached-1.4.36.tar.gz -C /usr/src
    8  cd /usr/src/memcached-1.4.36/
    9  ./configure --prefix=/usr/local/memcached --with-libevent=/usr/
   10  make
   11  make install
   16  ln -s /usr/local/memcached/bin/* /usr/local/bin/

2)启动memcache

启动命令参数
memcached命令
-d  以守护进程的方式,后台运行
-m 最大内存,默认64M
-l  监听的服务器的ip
-u  指定memcache启动的用户
-p 使用tcp端口为11211
-P 设置指定的pid文件
-vv 显示调度信息
-c 运行的最大并发链接数,默认1024个

启动memcache

memcached -d -m 512 -u root -l 192.168.1.117 -p 11211

3) memcache语法

(0)quit 退出memcache
(1)set
set key flags exptime bytes 【noreply】
value
flags 可以包括键值对的整型参数,客户机使用它存储关于键值对的额外信息
exptime 缓存时间,单位是秒,0表示永远
bytes 缓存中存储的字节数
noreply 告知服务不需要返回数据
返回结果:stored、error

(2)add(用于添加不存在的键,如果存在,在不改变键值)
用法和set相同
(3)replace(替换已经存在的键值,如果不存在,替换失败)
语法和set相同
(4)append  向后追加(用于对已存在的键追加值)
语法和set相同
(5)prepend 向前追加(用于已存在的键)
(6)cas (check and set) 用于执行一个检查并设置的操作,用于已存在 键
cas key flags exptime bytes  unique_cas_token【noreply】
value

set c 0 900 9  
memcached
STORED
gets c
VALUE c 0 9 6 //6位token值
memcached
END
cas c 0 900 5 6
abcde
STORED
get c
VALUE c 0 5
abcde
END
(7)get  用于查看,可跟多个键
(8)gets  可以得到cas需要的token值
(9)delete key
(10)incr key 5
(11)decr key 5
(12)stats 查看并返回
stats items
stats slabs
stats sizes
(13)flush_all  清空所有键值对

3. memcache集群

1. 实验准备

1)服务器规划

主机IP角色
host1192.168.1.115客户机
host2192.168.1.116magnet 1
host3192.168.1.118magnet 2
host4192.168.1.117memcache主
host5192.168.1.131memcache从

2)需要软件包

magnet:libevent-2.022 keepalived
memcache:libevent-2.022 memcache-1.436

2. 操作过程

1)host2~5安装libevent

    5  tar -zxf libevent-2.0.22-stable.tar.gz -C /usr/src
    6  cd /usr/src/libevent-2.0.22-stable/
    7  ./configure --prefix=/usr/
    8  make && make install

2)host4 host5安装memcache

    7  tar -zxf memcached-1.4.36.tar.gz -C /usr/src
    8  cd /usr/src/memcached-1.4.36/
    9  ./configure --prefix=/usr/local/memcached --with-libevent=/usr/
   10  make
   11  make install
   16  ln -s /usr/local/memcached/bin/* /usr/local/bin/

3)启动两台memcache

host4 启动memcache
[root@localhost ~]# memcached -d -m 512 -u root -l 192.168.1.117 -p 11211

host 5 启动memcache
[root@localhost ~]# memcached -d -m 512 -u root -l 192.168.1.131 -p 11211

4)host2 host3 安装magnet

   10  tar -zxf magent-0.6.tar.gz -C /usr/local/magnet/
   11  cd /usr/local/magnet/
   12  vim ketama.h
   添加(头部添加):
   #ifndef SSIZE_MAX
   #define SSIZE_MAX 32767
   #endif
   13  ln -s /usr/lib64/libm.so /usr/lib64/libm.a
   23   ln -s /usr/lib64/libevent-2.0.so.5 /usr/lib64/libevent.a
   15  ldconfig
   16  sed -i "s#LIBS = -levent#LIBS = -levent -lm#g" Makefile
   18  make
   26  cp magent /usr/bin/

5) host2 host3 上搭建keepalived 虚拟IP设定为192.168.1.100

   32  yum -y install openssl-devel popt-devel
   31  tar -zxf keepalived-1.2.13.tar.gz -C /usr/src/
   32  cd /usr/src/keepalived-1.2.13/
   33  ./configure --prefix=/ 
   34  make && make install
[root@localhost keepalived-1.2.13]# vim /etc/keepalived/keepalived.conf 
vrrp_instance VI_1 {
    state MASTER  #第二台改为BACKUP
    interface ens33  #本机内网网卡名
    virtual_router_id 51
    priority 100  # 第二台改为90


    virtual_ipaddress {
        192.168.1.100
    }

[root@localhost keepalived-1.2.13]# /etc/init.d/keepalived start

6)启动两台magent(启动命令相同)

magent 启动参数
-u:用户
-n:最大连接数
-l:magent 对外监听IP 地址
-p:magent 对外监听端口
-s:magent 主缓存IP 地址和端口
-b:magent 备缓存IP 地址和端口
# magent -u root -n 51200 -l 192.168.1.100 -p 12000 -s 192.168.1.117:11211 -b 192.168.1.131:11211 

7)各主机关闭防火墙

systemctl stop firewalld

7)客户机登陆验证

# yum -y install telnet
[root@localhost ~]# telnet 192.168.1.100 12000
Trying 192.168.1.100...
Connected to 192.168.1.100.
Escape character is '^]'.
set today 0 0 6
sunday
STORED

去主memcache上查看键值对情况
[root@localhost ~]# telnet 192.168.1.117 11211
Trying 192.168.1.117...
Connected to 192.168.1.117.
Escape character is '^]'.
get today
VALUE today 0 6
sunday
END

去从memcache查看键值对情况
[root@localhost ~]# telnet 192.168.1.131 11211
Trying 192.168.1.131...
Connected to 192.168.1.131.
Escape character is '^]'.
get today 
VALUE today 0 6
sunday
END

memcache主从转进行同步,集群搭建完成

4. memcache做数据库缓存

前提:源码安装MySQL和memcached

cd /mnt/hgfs/soft/
   74  tar -zxf libmemcached-1.0.17.tar.gz -C /usr/src
   75  cd /usr/src/libmemcached-1.0.17/
   76  ./configure
   77  make 
   78  make install
   79  echo "/usr/loacl/lib" >> /etc/ld.so.conf
   80  ldconfig
    cd /mnt/hgfs/soft/
    tar -zxf libmemcached-0.34.tar.gz
    4  cd /usr/src/libmemcached-0.34/
    5  ./configure --prefix=/usr/local/libmemcache34
    6  make && make install

    8   cd /mnt/hgfs/soft/
    9  tar -zxf memcached_functions_mysql-1.1.tar.gz -C /usr/src/
   10  cd /usr/src/memcached_functions_mysql-1.1/
  11  ./configure --with-mysql=/usr/local/mysql/bin/mysql_config --with-libmemcached=/usr/local/libmemcache34/
   12  make 
   13  make install
   14  cp /usr/local/lib/libmemcached_functions* /usr/local/mysql/lib/plugin/

[root@qukuoyuan07 memcached_functions_mysql-1.1]# mysql -u root -p < sql/install_functions.sql 
Enter password: 
[root@qukuoyuan07 memcached_functions_mysql-1.1]# mysql -u root -p
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema  |
| mysql              |
| performance_schema |
| test               |
+--------------------+
4 rows in set (0.10 sec)

mysql> use mysql
Database changed


mysql> select name,dl from func;
+------------------------------+---------------------------------+
| name                         | dl                              |
+------------------------------+---------------------------------+
| memc_add                     | libmemcached_functions_mysql.so |
| memc_add_by_key              | libmemcached_functions_mysql.so |
| memc_servers_set             | libmemcached_functions_mysql.so |
| memc_server_count            | libmemcached_functions_mysql.so |
| memc_set                     | libmemcached_functions_mysql.so |
| memc_set_by_key              | libmemcached_functions_mysql.so |
| memc_cas                     | libmemcached_functions_mysql.so |
| memc_cas_by_key              | libmemcached_functions_mysql.so |
| memc_get                     | libmemcached_functions_mysql.so |
| memc_get_by_key              | libmemcached_functions_mysql.so |
| memc_get_cas                 | libmemcached_functions_mysql.so |
| memc_get_cas_by_key          | libmemcached_functions_mysql.so |
| memc_delete                  | libmemcached_functions_mysql.so |
| memc_delete_by_key           | libmemcached_functions_mysql.so |
| memc_append                  | libmemcached_functions_mysql.so |
| memc_append_by_key           | libmemcached_functions_mysql.so |
| memc_prepend                 | libmemcached_functions_mysql.so |
| memc_prepend_by_key          | libmemcached_functions_mysql.so |
| memc_increment               | libmemcached_functions_mysql.so |
| memc_decrement               | libmemcached_functions_mysql.so |
| memc_replace                 | libmemcached_functions_mysql.so |
| memc_replace_by_key          | libmemcached_functions_mysql.so |
| memc_servers_behavior_set    | libmemcached_functions_mysql.so |
| memc_servers_behavior_get    | libmemcached_functions_mysql.so |
| memc_behavior_set            | libmemcached_functions_mysql.so |
| memc_behavior_get            | libmemcached_functions_mysql.so |
| memc_list_behaviors          | libmemcached_functions_mysql.so |
| memc_list_hash_types         | libmemcached_functions_mysql.so |
| memc_list_distribution_types | libmemcached_functions_mysql.so |
| memc_udf_version             | libmemcached_functions_mysql.so |
| memc_libmemcached_version    | libmemcached_functions_mysql.so |
| memc_stats                   | libmemcached_functions_mysql.so |
| memc_stat_get_keys           | libmemcached_functions_mysql.so |
| memc_stat_get_value          | libmemcached_functions_mysql.so |
+------------------------------+---------------------------------+
34 rows in set (0.00 sec)

mysql> use test
Database changed
mysql> create table urls ( id int(10) not null, url varchar(255) not null default '' );
Query OK, 0 rows affected (0.18 sec) 
mysql> create table results ( id int(10) not null, results varchar(255) not null default 'error' , time timestamp null default current_timestamp );
Query OK, 0 rows affected (0.33 sec)

mysql> delimiter //             //切换结束符

mysql> create trigger url_mem_insert before insert on urls for each row begin set @num = memc_set(NEW.id,NEW.url); if @num <> 0 then insert into results(id) values(NEW.id); end if ; end//
Query OK, 0 rows affected (0.05 sec)

mysql> create trigger url_mem_delete before delete on urls for each row begin set @num = memc_delete(OLD.id); if @num <> 0 then insert into results(id) values(OLD.id); end if ; end//
Query OK, 0 rows affected (0.31 sec)?
mysql> create trigger url_mem_update before update on urls for each row begin set @num = memc_replace(OLD.id,NEW.url); if @num <> 0 then insert into results(id) values(OLD.id); end if ; end //
Query OK, 0 rows affected (0.05 sec)

mysql> delimiter ;                                                                      
mysql> select memc_servers_set('192.168.1.10:11211');
+----------------------------------------+
| memc_servers_set('192.168.1.10:11211') |
+----------------------------------------+
|                                 0 |
+----------------------------------------+
1 row in set (0.38 sec)

mysql> select memc_server_count();
+---------------------+
| memc_server_count()  |
+---------------------+
|                   1 |
+---------------------+
1 row in set (0.00 sec)

mysql> select memc_list_behaviors()\G

mysql> select memc_servers_behavior_set('MEMCACHED_BEHAVIOR_NO_BLOCK','1');
+--------------------------------------------------------------+
| memc_servers_behavior_set('MEMCACHED_BEHAVIOR_NO_BLOCK','1') |
+--------------------------------------------------------------+
|                                                          0 |
+--------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> select memc_servers_behavior_set('MEMCACHED_BEHAVIOR_TCP_NODELAY','1');
+-----------------------------------------------------------------+
| memc_servers_behavior_set('MEMCACHED_BEHAVIOR_TCP_NODELAY','1') |
+-----------------------------------------------------------------+
|                                                             0 |
+-----------------------------------------------------------------+
1 row in set (0.00 sec)


mysql> insert into urls(id,url) values(1,'http://www.test.com');           
Query OK, 1 row affected (0.02 sec)

mysql> insert into urls(id,url) values(2,'http://www.qu.com');
Query OK, 1 row affected (0.31 sec)

mysql> delete from urls where id=2;
Query OK, 1 row affected (0.33 sec)

在另一个终端验证 
[root@qukuoyuan07 support-files]# telnet 192.168.1.10 11211
Trying 192.168.1.10...
Connected to 192.168.1.10.
Escape character is '^]'.
get 1  
VALUE 1 0 19
http://www.test.com
END

get 2
VALUE 2 0 17
http://www.qu.com
END

get 2
END
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值