memcache集群方案

1.方案选型

缓存方案的基本要求:避免单点故障;较好的性能和稳定性;便于运维管理。

常见方案如下:
A. 客户端直接访问多个memcache 实例
优点:简单,未引入新的节点;
缺点:维护不方便,未实现集中管理;性能不满足,实例宕机后不能自动踢出(hash到该实例的请求都要等到超时才能转到其他正常实例)。
B. magent代理
优点:简单,满足缓存对代理的大部分要求;
缺点:无成熟案例;性能不满足,实例宕机后不能自动踢出。
C. moxi代理
优点:功能丰富,提供本地缓存、Memcache实例数据复制等。
缺点:无成熟案例;代码很久没更新。
D. twemproxy代理
优点:twitter的成熟案例。
缺点:主要配合是twemproxy+redis,与memcache配合使用的案例较少;不支持缓存的复制功能。
E. mcrouter
优点:facebook的成熟案例;功能强大,支持Memcache实例分组、实例复制功能,实例宕机后可自动踢出。

缺点:一般用于ubuntu平台,在centos上安装较复杂。

2.mcrouter介绍

Mcrouter是一个支持memcached实例的代理,被facebook用于遍布全球的数十个集群几千个服务器之间控制流量。它适用于大规模的缓存处理中,在峰值的时候,mcrouter处理接近50亿的请求/秒。


2.1.mcrouter主要特点

1.支持标准开源的memcached ASCII编码协议
支持memcahed协议的所有客户端无需做任何修改即可被mcrouter支持。
2.多种hash算法
支持consistent hashing算法,集群扩容只有少量数据需要迁移。也可根据需要自定义其他hash算法:weighted ch3,crc32,HostIdRoute 等。
3.前缀路由
mcrouter可以根据key前缀把客户端分配到不同的memcahed池,从而实现集群中的memcache实例分组。
4.memcached池备份
通过将add\set\delete分发到两个池,而get只从其中一个池中获取数据,实现两个池的备份。当其中一个池有实例宕机时,可以从备份池中获取数据。
5.热加载
mcrouter监控它所有的配置文件,一旦检测到任何配置文件被修改,mcrouter的一个后台线程将自动的重新加载并分析这些文件。该过程对客户端完全透明。
6.自动故障转移
一旦某实例无响应,mcrouter将直接将相关请求转移到另一个可用的实例。同时,后台继续向无响应实例发送心跳请求,只要该实例恢复正常,mcrouter将会重新启用这个实例。该过程对客户端完全透明。

2.2.自动故障转移策略

Memcache实例异常将被mcrouter标记为"TKO" ("technical knockout")状态。
根据不同情况分为Soft TKO和Hard TKP两种状态:
Soft TKO:连续3次请求超时(failures_until_tko控制);
Hard TKO:发生1次connect error或1次connect timeout。
Memcache实例被标记为TKO状态后,原本应该Hash到该实例的请求将转发到”FailoverRouter”指定的备份实例;同时,mcrouter通过探针以一定频率(初始probe_delay_initial_ms 默认10000,以50%左右的幅度增长,最大probe_delay_max_ms 默认60000)检测TKO状态的实例,只要该实例恢复正常,mcrouter将会重新启用这个实例。该过程对客户端完全透明。

3.功能测试

3.1.数据切分

3.1.1.数据分布

测试数据在Memcache实例上的分布。
配置文件:

{
  "pools": {
    "A": {
      "servers": [
        "192.168.188.150:11211",
        "192.168.188.150:11212",
        "192.168.188.150:11213",
        "192.168.188.150:11214",
        "192.168.188.150:11215",
        "192.168.188.150:11216",
        "192.168.188.150:11217",
        "192.168.188.150:11218",
        "192.168.188.150:11219",
      ]
    }
  },
  "route": "PoolRoute|A"
}


测试结果:
10000个数据,9个Memcache实例,每个实例的数据量分别是:
1096, 1116, 1110, 1075, 1180, 1058, 1129, 1138, 1098

3.1.2.一致性HASH

测试新增两个实例后,数据在Memcache实例上的分布变化情况。
配置文件:

{
  "pools": {
    "A": {
      "servers": [
        "192.168.188.150:11211",
        "192.168.188.150:11212",
        "192.168.188.150:11213",
        "192.168.188.150:11214",
        "192.168.188.150:11215",
        "192.168.188.150:11216",
        "192.168.188.150:11217",
        "192.168.188.150:11218",
        "192.168.188.150:11219",
        "192.168.188.150:11220",
        "192.168.188.150:11221",
      ]
    }
  },
  "route": "PoolRoute|A"
}


测试结果:
新增实例后数据量移动比例:新增机器数 / 调整后服务器总数 = 2 / 11
新增2个Memcache实例后,get miss 数:1805 (10000* 2/11 = 1818)
注:新增实例在配置文件中往后顺延,不能插入现存的的实例配置之前。

3.2.实例分组

测试不同前缀的KV能够分发到不同实例分组中。
前缀p1,p2,p3的key分别分发到pool1,pool2,pool3中,其他默认到common_cache中,每个 pool由2个memcache构成。

{
  "pools": {
    "pool1": { "servers": [ "192.168.188.150:11211","192.168.188.150:11212" ] },
    "pool2": { "servers": [ "192.168.188.150:11213","192.168.188.150:11214" ] },
    "pool3": { "servers": [ "192.168.188.150:11215","192.168.188.150:11216" ] },
    "common_cache": { "servers": [ "192.168.188.150:11217","192.168.188.150:11218" ] }
  },
  "route": {
    "type": "PrefixSelectorRoute",
    "policies": {
      "p1": "PoolRoute|pool1",
      "p2": "PoolRoute|pool2",
      "p3": "PoolRoute|pool3"
    },
    "wildcard": "PoolRoute|common_cache"
  }
}

测试结果:
p1,p2,p3为前缀的key自动分发到相应的pool中,其他key分发到默认pool中。

3.3.实例故障检测

测试TKO状态的运行机制。
配置文件

{
  "pools": {
    "p1": {
      "servers": [
        "192.168.188.150:11211"
      ]
    },
    "p2": {
      "servers": [
        "192.168.188.150:11212"
      ]
    }
  },
  "route": "PoolRoute|p1",
  "route": "PoolRoute|p2",
  "route": {
    "type": "FailoverRoute",
    "children": [ "PoolRoute|p1","PoolRoute|p2" ],
    "failover_errors": [ "tko" ]
  }
}

3.3.1.soft TKO

测试结果:

模拟超时:iptables -I INPUT -p tcp --dport 11211 --tcp-flags PSH PSH -j DROP
取消超时:iptables -D INPUT 1

3.3.2.hard TKO

测试结果:   


3.4.不同代理分发的一致性

两个代理都用默认的一致性hash算法,分发到同1个由8个Memcache实例组成的集群(配置文件相同)。以一个代理set 1000个不同的key,再用另一个代理get,可以正常get到所有结果。

3.5.Memcache命令支持

客户端:java_memcached-release_2.6.6
服务器:mcrouter-0.6.0
客户端对代理的以下操作正常:

//add
public boolean add(String key, Object val)
public boolean add(String key, Object val, Date expireDate)
public boolean add(String key, Object val, Integer hashCode)
public boolean add(String key, Object val, Date expireDate, Integer hashCode)
    
//set
public boolean set(String key, Object val)
publicboolean set(String key, Object val, Date expireDate)
publicboolean set(String key, Object val, Integer hashCode)
publicboolean set(String key, Object val, Date expireDate, Integer hashCode)
    
//get
public Object get(String key)
publicObject get(String key, Integer hashCode)
publicObject get(String key, Integer hashCode, Boolean asString)
    
//delete
public boolean delete(String key)
    
//gets and cas
public MemcachedItem gets(String key)
publicMemcachedItem gets(String key, Integer hashCode)
publicboolean cas(String key, Object val, long casUnique)
publicboolean cas(String key, Object val, Date expireDate, long casUnique)
publicboolean cas(String key, Object val, Integer hashCode, long casUnique)
publicboolean cas(String key, Object val, Date expireDate, Integer hashCode, long casUnique)

//incr and decr
public long incr(String key)
publiclong incr(String key, long inc)
publiclong incr(String key, long inc, Integer hashCode)
publiclong decr(String key)
publiclong decr(String key, long inc)
publiclong decr(String key, long inc, Integer hashCode)
    
//thread safe way to incr and decr
public long addOrIncr(String key)
publiclong addOrIncr(String key, long inc)
publiclong addOrIncr(String key, long inc, Integer hashCode)
publiclong addOrDecr(String key)
publiclong addOrDecr(String key, long inc)
publiclong addOrDecr(String key, long inc, Integer hashCode)

4.性能测试

测试环境

  • 客户端
硬件:物理机一台:16C,144G
软件:shell调用java执行jar文件:java -jar mc.jar;
每个shell中最多起100个java线程,10个shell并发可进行1000并发测试;
每个线程死循环不停set或get;
SET与GET操作比1:10,KV大小30与KV大小500之比1:10。

  • 代理
硬件:虚拟机一台:4C,4G
软件:mcrouter-0.6,启动4线程(线程数量=CPU核数量)。
  • Memcache
硬件:虚拟机一台,4C,4G
软件:memcache-1.4,根据需要创建N个实例。

4.1.代理对性能的影响

测试方法:每个线程连续set 10000个kv,key\value大小分别为4字节、60字节。
  • 性能比较

客户端并发数

memcache

mcrouter

请求数比较

memcache/mcrouter

每秒请求数

CPU负载

每秒请求数

CPU负载

1

3500

4%

1500

5%

2

10

20000

13%

9600

22%

2

50

43000

30%

23000

32%

2

100

104000

60%

40300

48%

3

500

320000

89%

64600

55%

5

1000

323000

90%

87000

82%

4


注:“客户端并发数”不是指缓存只能支持这么多并发,而是指在这么多并发下,压力测试出相应的每秒请求数。
测试结果:
压力不同的情况下,memcache处理能力是mcrouter的2-5倍;
假设生产环境下服务器CPU的正常负载规划不得超过30%,可认定memcache的性能是mcrouter的2倍。

  • 性能下降原因
以50个并发压力测试以下三种情况下的性能:与访问memcache比较,每个请求耗时依次增加9微秒、11微妙。

访问方式

每秒请求数

每个请求耗时(微秒)

远程访问memcache

43000

23

本地访问mcrouter

31300

32

远程访问mcrouter

23000

43


测试结果:
性能下降由代理自身和增加网络节点导致,两者性能消耗各50%左右。

4.2.不同实例数对性能的影响

测试方法:
以50个并发压力测试,逐步增加集群中的Memcache实例数。

Memcache实例数

每秒请求数

性能下降

代理服务器CPU负载

1

25564

0%

23%

2

24667

4%

30%

4

24093

6%

23%

8

24473

4%

32%

16

23290

9%

33%

32

21810

15%

23%


测试结果:
集群中实例数增加后,CPU负载在一定范围波动,性能逐步小范围下降。

4.3.连接数

4.3.1.代理最大并发连接数

测试最佳吞吐量负载下的不同连接数的性能情况。
通过客户端并发线程和每个线程set后的sleep时间,实现单个代理最佳吞吐量:每秒20000个请求。

客户端并发线程

sleep时间(秒)

每秒请求数

Socket连接

平均延迟(毫秒)

CPU负载

5000

0.25

20000

4900

1

23%

10000

0.5

20000

5600

1.6

24%

20000

1

20000

7500

1.1

23%

30000

1.5

20000

14800

7.4

30%


测试结果:
单个代理在每秒20000请求的负载下,10000个客户端并发连接可正常工作。

4.3.2.客户端连接数设置

java memcached client中关于连接数的设置:
setInitConn:无效
setMinConn:无效
setMaxConn:有效
测试客户端setMaxConn为5时的每秒请求数:

单个客户端并发线程

每秒请求

1

1512

5

5785

10

5928

50

5524

100

5979


测试结果:
最大连接设置为5时,可为客户端提供每秒5000个请求,满足要求。

4.4.稳定性

持续测试满4天,mcrouter性能与负载稳定,具体数据如下:
GET平均79000次/秒,SET平均8700次/秒,合计平均87000次/秒。
服务器网络吞吐量:平均进26MB/S,出23MB/S,合计49MB/S。
CPU负载:平均使用率82%。
内存、IO消耗忽略不计。

5.高可用

两台Mcrouter服务器分别为应用A、应用B提供服务,通过keepalived组成集群,形成互备。
两台服务器各安装1个Mcrouter实例,端口相同。Keepalived监测Mcrouter端口11200,判断是否需要漂移VIP。

 
检测脚本:
vrrp_script chk_11200 {
    script "</dev/tcp/127.0.0.1/11200"       --检测端口
    interval 2                           --监测频率2秒                
    weight 2                            --监测成功权重+2
    fall 3                               --端口连续3次不通返回失败
    rise 3                              --失败后,端口连续3次正常返回成功
}

测试:每秒set一次,关闭mcrouter后,6秒内请求失败,之后恢复正常。
16:08:02 set k9 true
16:08:03 set k10 true
16:08:04 set k11 true
11081 [main] ERROR com.danga.MemCached.MemCachedClient - ++++ exception thrown while writing bytes to server on set
11081 [main] ERROR com.danga.MemCached.MemCachedClient - 您的主机中的软件中止了一个已建立的连接。
java.io.IOException: 您的主机中的软件中止了一个已建立的连接。
……
16:08:05 set k12 false
16:08:06 set k13 false
16:08:08 set k14 false
16:08:10 set k15 true
16:08:17 set k16 true
16:08:18 set k17 true

6.实施

6.1.环境部署

  • 物理机:5台
  • 虚拟机:11台
  • mcrouter:2个实例
  • memcache:9个实例(8个组成pool1,1个作为poolbak)
部署关系如下:

 

6.2.安装配置

6.2.1.mcrouter

  • 操作系统
版本:Centos6.5-64bit
硬件配置:4C4G
软件配置:关闭iptables,selinux
参数调整:

/etc/security/limits.conf
*               soft    nofile          102400
*               hard    nofile          102400
*               soft    nproc           102400
*               hard    nproc           102400
/etc/pam.d/login
session    required     /lib64/security/pam_limits.so
/etc/sysctl.conf
net.ipv4.ip_local_port_range = 9000 65500
net.ipv4.tcp_keepalive_time = 600
  • mcrouter
版本:v0.6.0
启动命令:
cd /root/mcrouter
nohup mcrouter myflavor &
启动选项:

cat myflavor-standalone 
{
  "libmcrouter_options": {
    "config_file": "/root/mcrouter/mcrouter.conf",
    "async_spool": "/root/mcrouter/spool",
    "num_proxies": "4",
    "reset_inactive_connection_interval": "0"
  },
  "standalone_options": {
    "log_file": "/root/mcrouter/mcrouter.log",
    "ports": "11200"
  }
}

配置文件:

{
  "pools": {
    "pool1": {
      "servers": [
        "192.168.188.150:11211",
        "192.168.188.150:11212",
        "192.168.188.150:11213",
        "192.168.188.150:11214",
        "192.168.188.150:11215",
        "192.168.188.150:11216",
        "192.168.188.150:11217",
        "192.168.188.150:11218"
      ]
    },
    "pool1_bak": {
      "servers": [
        "192.168.188.150:11219",
      ]
    }
  },
  "route": {
    "type": "FailoverRoute",
    "default_policy": "PoolRoute|pool1",
    "children": [ "PoolRoute|pool1", "PoolRoute|pool1_bak" ],
    "failover_errors": [ "tko" ]
  }
}

6.2.2.memcache

  • 操作系统
版本:Centos6.5-64bit
硬件配置:4C4G
软件配置:关闭iptables,selinux
  • memcache
版本:libevent-1.3,memcached-1.4.24
启动命令:
memcached -d -l 192.168.188.150 -p 11211 -m 3200 -c 4096 -u nobody -v >> /root/memcache_log/11211.log 2>&1

6.3.key规划

Key包含5个字节的前缀,如“p1aaa”:
前两位指定前缀路由,如p1作为前缀的转发到pool1中(目前未使用该特性,设计key便于以后使用);
后三位作为功能模块的标识,确保不同功能的缓存不会冲突,同时便于运维管理,有问题的key可立即定位来源,维护清单:

前缀

项目

功能模块

分配日期

p1aaa

项目A  

  主站session共享

20150929

p1aab

项目B  

  主站session共享

20150929

7.监控

通过Zabbix实时监控操作系统的CPU、内存、网络负载,以及软件本身的关键指标,分别用于实时报警和负载分析。

7.1.mcrouter

可用性 </dev/tcp/127.0.0.1/11200;echo $?
异常Memcache实例 echo "stats suspect_servers" | nc 0 11200 | grep -v END
每秒SET次数 echo "stats" | nc 0 11200 | grep cmd_set_count | awk '{print $3}'
每秒GET次数 echo "stats" | nc 0 11200 | grep cmd_get_count | awk '{print $3}'
连接数 echo "stats" | nc 0 11200 | grep num_client | awk '{print $3}'

7.2.memcache

每秒SET次数 echo "stats" | nc ip 11211 | grep cmd_set | awk '{print $3}'
每秒GET次数 echo "stats" | nc ip 11211 | grep cmd_get | awk '{print $3}'
每秒GET命中次数 echo "stats" | nc ip 11211 | grep get_hits | awk '{print $3}'
每秒被踢出的未过期KV数量 echo "stats" | nc ip 11211 | grep evictions | awk '{print $3}'
每秒回收的已到期KV数量 echo "stats" | nc ip 11211 | grep “ reclaimed “ | awk '{print $3}'
各SLAB中“最老KV的存活时间“的最小值 echo "stats items" | nc ip 11211 | grep age | awk '{print $3}' | sort | head -1

8.问题处理

8.1.对象序列化

mcrouter只支持assci协议,不支持binary协议,因此对象要序列化之后再存储(未测试验证);

8.2.超时配置

tomcat session共享,延迟至少设置为3秒,默认100毫秒会超时
<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager" 
         memcachedNodes="n1:192.168.n.n:11200" 
         requestUriIgnorePattern= ".*\.(png|gif|jpg|css|js)$" 
         sessionBackupAsync= "false" 
         sessionBackupTimeout= "3000"    --至少设置为3000毫秒
         transcoderFactoryClass="de.javakaffee.web.msm.JavaSerializationTranscoderFactory"/>

否则会报错:
警告: Could not store session 12B8D86FC987FBAA902B559E735451B5-n1 in memcached.
net.spy.memcached.internal.CheckedOperationTimeoutException: Timed out waiting for operation - failing node: /192.168.n.n:11200

发布了24 篇原创文章 · 获赞 25 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/sdmei/article/details/78696868