Memcached简介

Memcached是一个高性能的服务器内存缓存软件。在早期版本的Memcached使用的是alloc来分配内存,存在内存碎片,在新版本的Memcached采用了Slab Allocator来分配内存。在MC启动时会要求制定一块内存区域,然后会划分为多个Slab,每个Slab默认大小为1M,可以指定。每个Slab又包含多个truck,每个Slab的truck大小不同,但同一个Slab的truck大小是一样的。在存入数据到MC时,MC会查找最合适的truck来存储数据。比如数据是100字节,现在有truck大小分别为64byte,128byte,144byte,那么会找到128byte的truck存储。不过仍然存在空间的浪费,比如这个例子中就浪费了28byte。所以保证合适大小的空间很重要,我们可以在MC启动时指定负载因子-f参数来指定,默认是1.25.通常情况下这都是一个比较合理的值,无需修改。另外避免空间浪费的一个处理方法是,将数据大小差不多的放入到相同的MC中。比如一个MC的truck大小为1M,另外一个为1K。那么将数据量在1M左右的都放入到1M的那个MC。

与Redis比较

与Redis相比:
1.MC只支持key/value,Redis支持key/value,list,set,map等6种数据结构;

2.都支持key的过期
MC不主动检查item是否过期,只有在get时检查,但是检查到过期也不会删除,只是做一个标记。在有数据要set时该空间可以被重试使用。MC在分配空间时优先使用已经过期的key/value对的空间,如果分配的空间占满时,会使用LRU算法,删除最近最少使用的key。可以使用-M参数来启动MC,这样但MC内存耗尽时,会返回一个报错信息(对于完整数据缓存有用)。

Redis是主动监测,在key过期时会主动删除。

3.MC没有持久化功能,无法恢复数据;Redis可以定时保存数据到磁盘,Redis重启可以重新加载到内存。

应用场景

1.将变化频率低的数据事先就放入MC中;
比如商品分类、系统角色等等。

后续如有修改,修改数据库并同步到MC。
这样所有用户的请求全部请求的MC,不会请求数据库。

2.热点数据缓存

3.集群会话共享

分布式缓存设计思想

1.每台MC的内容不一样,所有的服务器内容加起来接近数据库的容量;
2.通过在客户端程序或在负载均衡器上使用Hash算法,让同一内容始终分配到同一台MC。
3.普通的HASH算法会导致MC节点宕机后数据发生流动,可能发生雪崩;
4.采用一致性HASH算法可以使节点宕机对数据的流动降到最低。
5.每个MC节点之间互不通信,数据独立存取。

安装Memcache

安装libevent

由于Memcached使用了libevent库,所以要先安装libevent。
https://github.com/libevent/libevent/releases获取最新稳定版本下载。这里下载的是libevent-2.1.8-stable.tar.gz。

1
2
3
4
5
tar zxvf libevent-2.1.8-stable.tar.gz
cd libevent-2.1.8-stable
./configure
make
make install

问题:
执行./configure时出错。错误信息:libevent error: no acceptable C compiler found in $PATH
解决办法:安装gcc,执行yum install gcc,然后再执行./configure

安装Memcached

http://memcached.org/downloads下载最新稳定版本,这里下载的是memcached-1.5.10.tar.gz。

1
2
3
4
5
tar -zxvf memcached-1.5.10.tar.gz 
cd memcached-1.5.10
./configure 
make 
make install

验证是否安装成功
memcached -h
提示:
memcached: error while loading shared libraries: libevent-2.1.so.6: cannot open shared object file: No such file or directory
解决:

1
find / -name "libevent-2.1.so.6"

可以找到

1
2
/usr/local/lib/libevent-2.1.so.6
/data/tools/libevent-2.1.8-stable/.libs/libevent-2.1.so.6

/usr/local/lib加到/etc/ld.so.conf中

然后执行ldconfig,然后再次执行memcached -h就正常了。

启动Memcached

1
memcached -m 64 -p 11211 -d -c 8192 -u root

相关参数可以使用memcached -h查看。本例中-m 64指定内存大小为64M,-p 11211指定端口,-d指定为守护进程,-c指定连接数,-u指定用户(在用root启动时需要指定)。

增加实例,只需修改启动的端口即可。下面新增一个实例

1
memcached -m 64 -p 11212 -d -c 8192 -u root

命令行客户端操作MC数据

MC命令

  • 添加数据(add)

    1
    2
    
    add key flag expiretime bytes
    value
    

    key : 给这个值设置一个名字
    flag : 标志,是一个整数
    expiretime : 有效期,以秒为单位,0表示没有延迟
    bytes : 这是一个需要存储在memcached的数据的长度
    value : 是一个需要存储的数据。数据需要将通过在新的一行后输入
    注意:bytes要和value的长度一致,否则会出现CLIENT_ERROR bad data chunk错误

  • 为一个新的或现有的键(key)设置一个值(set)

    1
    2
    
    set key flag expiretime bytes
    value
    
  • 替换已存在的 key(键) 的 value(数据值)(replace)

    1
    2
    
    replace key flag expiretime bytes
    value
    
  • 向已存在 key(键) 的 value(数据值) 后面追加数据(append)

    1
    2
    
    append key flag expiretime bytes
    value
    
  • 向已存在 key(键) 的 value(数据值) 前面追加数据(prepend)

    1
    2
    
    prepend key flag expiretime bytes
    value
    
  • 获取存储在 key(键) 中的 value(数据值)(get)

    1
    
    get key
    
  • 删除已存在的 key(键)(delete)

    1
    
    delete key
    
  • incr 与 decr 命令用于对已存在的 key(键) 的数字值进行自增或自减操作

    1
    2
    
    incr key increment_value 
    decr key increment_value
    
  • 清理缓存中的所有数据(flush_all)

    1
    2
    
    flush_all [time] 
    - time : (可选) 在指定时间后执行清理缓存操作
    
  • stats / stats slabs / stats sizes / stats items

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    stats 显示统计信息例如 PID(进程号)、版本号、连接数等 
    stats slabs 显示各个slab的信息,包括chunk的大小、数目、使用情况等 
    stats sizes 显示所有item的大小和个数 
    stats items 显示各个 slab 中 item 的数目和存储时长
    
    命令格式:
    gets key
    
    命令格式:
    cas key flags exptime bytes unique_cas_token [noreply]
    value
    
  • gets / cas

    1
    2
    
    gets 获取带有 CAS 令牌的 value(数据值) 
    cas 执行一个”检查并设置”的操作
    

参考MemCache 入门极简教程

通过Telnet来测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 添加数据
add key001 0 10 5 // 回车
hello

// 获取数据
get key001

// 删除数据
delete key001

// 向value前追加数据
prepend key001 0 0 3
123

// 向value后追加数据
append key001 0 0 3
123

// 将key001的value替换为world
replace key001 0 0 5
world

通过nc测试

1
2
3
4
5
6
7
8
9
10
11
12
写入:
printf "set key001 0 0 5\r\nhello\r\n" | nc 127.0.0.1 11211
读取:
printf "get key001\r\n" | nc 127.0.0.1 11211
删除:
printf "delete key001\r\n" | nc 127.0.0.1 11211

# cas操作需要先根据gets key来获取CAS令牌
# 1.获取令牌
printf "gets key001\r\n" | nc 127.0.0.1 11211
# 2.cas更新key的value
printf "cas key001 0 0 5 14 \r\nworld\r\n" | nc 127.0.0.1 11211

Memcached客户端Xmemcached使用

1.引入xmemcached的依赖

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

注意:xmemcached依赖slf4j,默认会打印一堆的Debug日志,可以引入slf4j和相关的日志实现的依赖,然后配置xmemcached的日志级别为info。
比如log4j,可以配置log4j.logger.com.google=info

2.基本使用

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
XMemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil.getAddresses("192.168.200.109:11211 192.168.200.109:11212"));
builder.setSessionLocator(new KetamaMemcachedSessionLocator()); // 一致性哈希,使得某个key存的时候存都某个节点,取的时候也会从该节点获取
MemcachedClient mc = builder.build();

// 清空数据
mc.flushAll();

mc.add("hello",30,"你好,Memcached!");
String value = mc.get("hello");
System.out.println("hello=" + value);

mc.delete("hello");
value = mc.get("hello");
System.out.println("hello==" + value);

// 如果不存在key则创建,存在则覆盖value
mc.set("key001",0,"test key");
// 更新过期时间
mc.touch("key001",3);
Thread.sleep(3000L);
value = mc.get("key001");
System.out.println("key001=====" + value);

// cas操作,内部先通过gets命令拿到token,然后再执行cas操作。
mc.set("abc",0,0);
mc.cas("abc", 0, new CASOperation<Integer>() {
    @Override
    public int getMaxTries() { // cas重试次数
        return 3;
    }

    @Override
    public Integer getNewValue(long currentCAS, Integer currentValue) {
        return 99; // 要设置的新值
    }
});

int casValue = mc.get("abc");
System.out.println("abc=" + casValue);

mc.add("hello",0,"world");
if (mc.add("hello",10,"abcd")) {
    System.out.println("key is exists");
}

// 数字原则更新,类似java中的AtomicInteger
System.out.println(mc.incr("aaa",5,0)); // 对aaa加5,默认值为0(aaa不存在时)
System.out.println(mc.decr("aaa",2)); // 对aaa减2

// 或者使用Counter
Counter counter = mc.getCounter("counter",0);
System.out.println(counter.addAndGet(5));
System.out.println(counter.decrementAndGet());


// 命名空间
String ns = "namespace";
mc.withNamespace(ns, new MemcachedClientCallable<Boolean>() {
    @Override
    public Boolean call(MemcachedClient memcachedClient) throws MemcachedException, InterruptedException, TimeoutException {
        boolean result = memcachedClient.set("key1",0,"hello");
        return result;
    }
});

// 或者使用
mc.beginWithNamespace(ns);
mc.add("key2",20,"world");
mc.add("key3",30,"你好");
System.out.println("key3==>" + mc.get("key3"));
mc.endWithNamespace();

value = mc.withNamespace(ns, new MemcachedClientCallable<String>() {
    @Override
    public String call(MemcachedClient client) throws MemcachedException, InterruptedException, TimeoutException {
        return client.get("key2");
    }
});
System.out.println("with namespace key2=" + value);

value = mc.get("key1");
System.out.println("key1=" + value);
value = mc.get("key2");
System.out.println("key2=" + value);
value = mc.get("key3");
System.out.println("key3=" + value);

// 让命名空间的所有key都失效
mc.invalidateNamespace(ns);
value = mc.get("key1");
System.out.println("key1=" + value);
value = mc.get("key2");
System.out.println("key2=" + value);
value = mc.get("key3");
System.out.println("key3=" + value);

mc.shutdown();

这里只是基本使用,可以查看下面的参考链接查看全部用法。

参考:Xmemcached 中文用户指南Memcached的几种Java客户端(待实践)

参考:Memcached 教程

Memcached监控

这里推荐使用MemAdmin,需要PHP环境。参考:windows下memadmin安装