xiaobaoqiu Blog

Think More, Code Less

Memcached

最近项目中的图片上传那块带来很大麻烦,原因是因为业务逻辑的问题,会导致统一张图片上传很多次(平均5次上传有4次是重复的),导致图片服务器压力很大.

直观想法是使用KV缓存,第一个想到的是Memcached.

其实很长时间之前就学习过一段时间的Memcached,但是一致没有整理,趁这次机会,整理一下Memcached相关的知识.

1.Memcached是什么

Memcached官方:http://memcached.org/

Memcached wiki:http://code.google.com/p/memcached/wiki/NewStart

Memcached是一个免费的开源的,高性能的,分布式的内存KV(key-value)缓存系统,一般通过缓解数据库压力来达到加速动态Web应用程序的目的.可以存储任意类型的小数据块,比如数据库查询结果,接口调用结果等.

Memcached可以称之为简单高效.它简单的设计保证了快速发布,使用者开发简单,面临大量数据缓存的时候能够解决很多问题.同时提供了绝大多数流行语言的API接口.

许多Web应用都将数据保存到RDBMS中,应用服务器从中读取数据并在浏览器中显示。 但随着数据量的增大、访问的集中,就会出现RDBMS的负担加重、数据库响应恶化、 网站显示延迟等重大影响。

这时就该memcached大显身手了。一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、提高可扩展性。

2.Memcached特性

memcached作为高速运行的分布式缓存服务器,具有以下的特点: 1. 协议简单 2. 基于libevent的事件处理 3. 内置内存存储方式 4. memcached不互相通信的分布式 下面分别介绍.

2.1协议简单

Memcached使用简单的基于文本行的协议。因此,通过telnet 也能在memcached上保存数据、取得数据。如下:

1
2
3
4
5
6
7
8
9
10
11
xiaobaoqiu@xiaobaoqiu:~$ telnet 127.0.0.1 11211
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
add myid 1 0 4
1234
STORED
get myid
VALUE myid 1 4
1234
END

协议文档位于memcached的源代码内,可以参考: https://github.com/memcached/memcached/blob/master/doc/protocol.txt

2.2基于libevent的事件处理

libevent是个程序库,它将Linux的epoll、BSD类操作系统的kqueue等事件处理功能 封装成统一的接口。即使对服务器的连接数增加,也能发挥O(1)的性能。 memcached使用这个libevent库,因此能在Linux、BSD、Solaris等操作系统上发挥其高性能。

关于libevent参考: http://libevent.org/ http://www.kegel.com/c10k.html

2.3内置内存存储方式

为了提高性能,memcached中保存的数据都存储在memcached内置的内存存储空间中。由于数据仅存在于内存中,因此重启memcached、重启操作系统会导致全部数据消失。另外,内容容量达到指定值之后,就基于LRU(Least Recently Used)算法自动删除不使用的缓存。

memcached本身是为缓存而设计的服务器,因此并没有过多考虑数据的永久性问题。

2.4memcached不互相通信的分布式

memcached虽然号称“分布式”缓存服务器,但服务器端并没有分布式功能。 各个memcached不会互相通信以共享信息。那么,怎样进行分布式呢? 这完全取决于客户端的实现。

3.Memcached安装

所有可下载版本见: http://code.google.com/p/memcached/wiki/ReleaseNotes

直接按照官网安装就可以,下面直接安装最新的版本,需要已经安装了libevent:

1
2
3
4
wget http://memcached.org/latest
tar -zxvf memcached-1.x.x.tar.gz
cd memcached-1.x.x
./configure && make && make test && sudo make install

memcached的命令参数见-h,从中也可以看出memcached的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
xiaobaoqiu@xiaobaoqiu:~$ memcached -h
memcached 1.4.14
-p <num>      TCP监听端口TCP(默认: 11211)
-U <num>      UDP监听端口TCP(默认: 11211, 0表示关闭)
-s <file>     用于监听的UNIX套接字路径(禁用网络支持)
-a <mask>     UNIX套接字访问掩码,八进制数字(default: 0700)
-l <addr>     监听的IP地址(默认:INADDR_ANY,所有地址), <addr> 形如host:port.
-d            以守护进程(daemon)形式运行
-r            最大核心文件(core file)限制
-u <username> 设定进程所属用户(只有root用户可以使用这个参数)
-m <num>      最大使用内存,单位MB(默认: 64 MB)
-M            当内存耗尽时候返回错误 (而不是移除过期项)
-c <num>      并发连接的最大数目(默认: 1024)
-k            锁定所有内存页。注意你可以锁定的内存是有上限的.
              试图分配更多内存会失败的,所以留意启动守护进程时所用的用户可分配的内存上限(不是前面的 -u <username> 参数;在sh下,使用命令"ulimit -S -l NUM_KB"来设置).
-v            提示信息(在事件循环中打印错误/警告信息)
-vv           详细信息(还打印客户端命令/响应)
-vvv          超详细信息(还打印内部状态的变化)
-h            帮助信息
-i            打印memcached版本和libevent版本
-P <file>     将进程id保存在文件中,只在-d选项下有效
-f <factor>   块大小增长因子(默认: 1.25)
-n <bytes>    chunk(key+value+flags)的最小空间 (默认: 48)
              (chunk数据结构本身需要消耗48个字节,所以一个chunk实际消耗的内存是n+48)
-L            尝试使用大内存页(如果可以的话). 增加内存页大小能够减少页表缓冲(TLB)
              丢失次数,提高效率.为了从操作系统获取大内存页,memcached会把全部数据项分配到一个大区块
-D <char>     使用 <char> 作为前缀和ID的分隔符.
              这个用于按前缀获得状态报告。默认是":"(冒号).
              如果指定了这个参数,则状态收集会自动开启;如果没指定,则需要用命令"stats detail on"来开启。
-t <num>      使用的线程数目 (默认: 4)
-R            每个事件的最大请求数目,即每个连接处理的最大请求数目(默认: 20)
-C            禁用CAS
-b            设置积压队列(backlog queue)的最大限制(默认: 1024)
-B            绑定协议,可选值:ascii,binary,auto(默认)
-I            重写每个数据页尺寸。调整数据项最大尺寸(默认: 1mb, min: 1k, max: 128m)
-S            Turn on Sasl authentication
-o            Comma separated list of extended or experimental options
              - (EXPERIMENTAL) maxconns_fast: immediately close new
                connections if over maxconns limit
              - hashpower: An integer multiplier for how large the hash
                table should be. Can be grown at runtime if not big enough.
                Set this based on "STAT hash_power_level" before a 
                restart.

启动memcached时候可以带上这些选项,启动memcached:

1
xiaobaoqiu@xiaobaoqiu:~$ memcached -d

需要注意的是,还有一个memcached.conf文件存在(通过whereis memcached可以看得到),他是memcached默认的配置参数,其内容就是我们上面介绍的memcached启动参数,我的文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# memcached default config file
# 2003 - Jay Bonci <jaybonci@debian.org>
# This configuration file is read by the start-memcached script provided as
# part of the Debian GNU/Linux distribution. 

# Run memcached as a daemon. This command is implied, and is not needed for the
# daemon to run. See the README.Debian that comes with this package for more
# information.
# 以守护进程的形似运行
-d

# Log memcached's output to /var/log/memcached
# 日志文件存放位置
logfile /var/log/memcached.log

# Be verbose
# -v

# Be even more verbose (print client commands as well)
# -vv

# Start with a cap of 64 megs of memory. It's reasonable, and the daemon default
# Note that the daemon will grow to this size, but does not start out holding this much
# memory
# 分配给mencached的内存数目,单位是MB
-m 256

# Default connection port is 11211
# Memcached的监听端口
-p 11211

# Run the daemon as root. The start-memcached will default to running as root if no
# -u command is present in this config file
# 允许memcached的用户
-u memcache

# Specify which IP address to listen on. The default is to listen on all IP addresses
# This parameter is one of the only security measures that memcached has, so make sure
# it's listening on a firewalled interface.
# 监听的服务器IP地址
-l 127.0.0.1

# Limit the number of simultaneous incoming connections. The daemon default is 1024
# -c 1024

# Lock down all paged memory. Consult with the README and homepage before you do this
# -k

# Return error when memory is exhausted (rather than removing items)
# -M

# Maximize core file limit
# -r

我们启动memecached默认会带上这个文件的参数,可以通过ps命令看我们memcached启动的参数:

1
2
xiaobaoqiu@xiaobaoqiu:~$ ps -ef | grep memcached
memcache  1241     1  0 10:38 ?        00:00:00 /usr/bin/memcached -m 256 -p 11211 -u memcache -l 127.0.0.1

可以使用telnet连接上memcached,之后就可以输入memcached的命令了:

1
2
3
4
xiaobaoqiu@xiaobaoqiu:~$ telnet 127.0.0.1 11211
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.

4.Memcached命令

介绍Memcached的常用命令,不断补充中,官方文档参考: https://code.google.com/p/memcached/wiki/NewCommands 按照命令左右,简单分为存储,删除,自增自减,读取和状态四类命令

4.1存储命令

命令:add set replace 格式:

1
2
3
<command name> <key> <flags> <exptime> <bytes>

<data block>

其中参数command name指操作命令,即set/add/replace;key指缓存的键值;flags指客户机使用它存储关于键值对的额外信息,exptime缓存过期时间,单位为秒,0表示永远存储;bytes缓存值的字节数;data block指数据块.

(1).set:无论如何都添加或更新的命令(key不存在则添加,存在则更新)

下面的示例包含了key存在和不存在两种情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
xiaobaoqiu@xiaobaoqiu:~$ telnet 127.0.0.1 11211
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
set name 0 0 11
baoqiu.xiao
STORED
get name
VALUE name 0 11
baoqiu.xiao
END
set name 0 0 9 
memcached
STORED
get name
VALUE name 0 9
memcached
END
(2).add:只有数据不存在时添加值的add命令

已经存在的key是不能再add

1
2
3
4
5
6
7
8
9
10
xiaobaoqiu@xiaobaoqiu:~$ telnet 127.0.0.1 11211
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
add sex 0 0 1
f
STORED
add sex 0 0 1
m
NOT_STORED
(3).replace:只有数据存在时替换的replace命令

只有存在才能replace:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
xiaobaoqiu@xiaobaoqiu:~$ telnet 127.0.0.1 11211
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
replace host 0 0 9
localhost
NOT_STORED
add host 0 0 9
127.0.0.1
STORED
get host
VALUE host 0 9
127.0.0.1
END
replace host 0 0 9
localhost
STORED
get host 
VALUE host 0 9
localhost
END

4.2删除命令

(1).delete:删除已经存在的数据
1
2
3
4
5
6
7
8
9
10
11
xiaobaoqiu@xiaobaoqiu:~$ telnet 127.0.0.1 11211
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
delete unknow
NOT_FOUND
add unknow 0 0 6
unknow
STORED
delete unknow
DELETED

4.3自增自减命令

incr和decr命令,注意只能在数字类型上进行自增自减操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
xiaobaoqiu@xiaobaoqiu:~$ telnet 127.0.0.1 11211
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
set age 2 0 2
25
STORED
incr age 1
26
incr age 3
29
decr age 2
27
set name 1 0 11
baoqiu.xiao
STORED
incr name 1
CLIENT_ERROR cannot increment or decrement non-numeric value

4.4读取命令

(1).get:获取一条或者多条数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
xiaobaoqiu@xiaobaoqiu:~$ telnet 127.0.0.1 11211
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
add name 1 0 11
baoqiu.xiao
STORED
add age 2 0 2
25
STORED
get name age
VALUE name 1 11
baoqiu.xiao
VALUE age 2 2
25
END
(2).gets:获取一条或者多条数据

gets命令比get返回的值多一个数字(类似于版本号)用来判断数据是否发生过改变.

1
2
3
4
5
6
gets name age
VALUE name 1 11 12
baoqiu.xiao
VALUE age 2 2 13
25
END
(3).cas:意思是check and set

只有当最后一个参数和gets获取的那个用来判断数据发生改变的那个值相同时才会存储成功,否则返回 exists.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
xiaobaoqiu@xiaobaoqiu:~$ telnet 127.0.0.1 11211
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
gets name
VALUE name 1 11 19
baoqiu.xiao
END
cas name 1 0 9 20
memcached
EXISTS
cas name 1 0 9 19
memcached
STORED

gets返回的版本参数为19,cas的最后一个参数必须为19才能设置成功.

4.5状态命令

(1).stat:显示memcachd状态

包括使用的内存大小等关键信息,下面包含各个项的意义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
xiaobaoqiu@xiaobaoqiu:~$ telnet 127.0.0.1 11211
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
stats
STAT pid 1241                   //memcached服务进程的进程ID
STAT uptime 24026               //memcached服务从启动到当前所经过的时间,单位是秒
STAT time 1411579124            //memcached服务器所在主机当前系统的时间,单位是秒
STAT version 1.4.14             //memcached组件的版本
STAT libevent 2.0.21-stable     //libevent版本
STAT pointer_size 64            //服务器所在主机操作系统的指针大小,一般为32或64
STAT rusage_user 0.404722       //进程累计使用用户时间
STAT rusage_system 0.313086     //进程累计使用系统时间
STAT curr_connections 5         //当前系统打开的连接数
STAT total_connections 21       //从memcached服务启动开始,系统打开过的连接总数
STAT connection_structures 6    //从memcached服务启动到现在,被服务器分配的连接结构数量
STAT reserved_fds 20            //
STAT cmd_get 15                 //累积get命令次数
STAT cmd_set 22                 //累积set命令次数
STAT cmd_flush 0                //累积flush命令次数
STAT cmd_touch 0                //
STAT get_hits 9                 //命中次数,即get成功的次数
STAT get_misses 6               //miss次数,即get失败的次数
STAT delete_misses 2            //delete miss次数
STAT delete_hits 5              //delete 命中次数
STAT incr_misses 0              //incr miss次数
STAT incr_hits 2                //incr 命中次数
STAT decr_misses 0              //decr miss次数
STAT decr_hits 1                //decr命中次数
STAT cas_misses 0               //cas命令miss的次数
STAT cas_hits 1                 //cas命令命中的次数
STAT cas_badval 1               //使用cas擦拭次数
STAT touch_hits 0
STAT touch_misses 0
STAT auth_cmds 0
STAT auth_errors 0
STAT bytes_read 968             //memcached服务器从网络读取的总的字节数
STAT bytes_written 706          //memcached服务器发送到网络的总的字节数
STAT limit_maxbytes 268435456   //memcached允许使用的最大字节,256M,见memcached.conf
STAT accepting_conns 1          //目前使用的连接数
STAT listen_disabled_num 0
STAT threads 4                  //工作线程的总数量
STAT conn_yields 0
STAT hash_power_level 16
STAT hash_bytes 524288
STAT hash_is_expanding 0
STAT expired_unfetched 0
STAT evicted_unfetched 0
STAT bytes 373                  //系统存储缓存对象所使用的存储空间,单位为字节
STAT curr_items 5               //当前缓存中存放的所有缓存对象的数量,不包括已删除的
STAT total_items 22             //自启动起所有缓存对象的数量,包括已删除的
STAT evictions 0                //从缓存移除的缓存对象数目,包括过期和空间不足时LRU移除
STAT reclaimed 0
END

5.Memcached客户端

许多语言都实现了连接memcached的客户端,见官网: https://code.google.com/p/memcached/wiki/Clients

主流的语言基本都支持:

1
C/C++, PHP, Java, Python, Ruby, Perl, .Net, Erlang, Lua等

这里以Java为例,典型的三种Client,其中spymemcached和Xmemcached: (1).Memcached-Java-Client https://github.com/gwhalin/Memcached-Java-Client (2).spymemcached http://code.google.com/p/spymemcached/ (3).Xmemcached https://code.google.com/p/xmemcached/

5.1性能比较

java memcached client官方发布的性能对比:https://github.com/gwhalin/Memcached-Java-Client/wiki/PERFORMANCE

XMemcached官方发布的性能对比:http://xmemcached.googlecode.com/svn/trunk/benchmark/benchmark.html

根据网络资料,Xmemcached是一个不错的选择,无论在低并发还是高并发访问的情况下,都可以保持一个比较优秀的性能表现.

5.2Xmemcache

maven支持:http://mvnrepository.com/artifact/com.googlecode.xmemcached/xmemcached

1
2
3
4
5
<dependency>
  <groupId>com.googlecode.xmemcached</groupId>
  <artifactId>xmemcached</artifactId>
  <version>1.4.1</version>
</dependency>

简单使用:

1
2
3
4
5
6
7
8
//memcached服务的IP及端口号
String addr="192.168.100.112:11211 192.168.100.113:11211"
MemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil.getAddresses(addr));
MemcachedClient client = builder.build();

String cacheKey = buildCacheKey();        //key
Object cacheObject = buildCacheObject();  //value
cacheClient.set(cacheKey, exp, cacheObject);  //memcached set