您的位置:首页 > 数据库 > Memcache

Memcached安装和使用详解

2014-05-14 16:48 483 查看

Memcached介绍:

什么是Memcached?Memcached是国外社区网站 LiveJournal 的开发团队开发的高性能的分布式内Memcached是国外社区网站的开发团队开发的高性能的分布式内存缓存服务器。一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、提高可扩展性。问次数,以提高动态Web应用的速度、提高可扩展性。

Memcached运行图



从数据源(数据库或文件系统)加载数据十分低效,若是每次想要访问该信息时都运行相同的查询,就尤显低效。要是能从内存中直接加载这些信息,可想而知速度会快多少倍。

虽然很多 web 服务器都可被配置成使用缓存发回信息,但那与大多数应用程序的动态特性无法相适。而这正是 memcached 的用武之地。它提供了一个通用的内存存储器,可保存任何东西,包括本地语言的对象,这就让您可以存储各种各样的信息并可以从诸多的应用程序和环境访问这些 信息。

memcached存储的是key/value的键值对,但是值必须是可序列化的对象(这里我说的java),还可以是json,xml,html等,这里要说明memcached集群,server端之间并不会进行相互的通信,通信完全由你的客户端来完成,你只需在客户端规定好你的key值,然后set进行,此时会有一个散列算法,来决定你key会存放在哪台server上。(这点和我之前使用过的Coherence网格数据库是有区别的,Coherence个态服务器之间是会互相通信交换数据)。

基于C/S架构,协议简单



几个参数的解释:

-m 指定缓存所使用的最大内存容量,单位是Megabytes,默认是64MB

-u 只有以root身份运行时才指定该参数

-d 以daemon的形式运行

-l 指定监听的地址

-p 指定监听的TCP端口号,默认是11211

其他常用的参数

-t 指定线程数,默认是4个

-h 打印帮助信息

-c 最大同时连接数,默认是1024.

-U 指定监听的UDP端口号,默认是11211

-M 内存耗尽时显示错误,而不是删除项

一开始说的“-d”参数需要进行进一步的解释

-d install 安装memcached

-d uninstall 卸载memcached

-d start 启动memcached服务

-d restart 重启memcached服务

-d stop 停止memcached服务

-d shutdown 停止memcached服务

更多参数

使用"-h"去了解

基于libevent的事件处理

libevent是一套跨平台的事件处理接口的封装,能够兼容包括这些操作系统:

libevent是一套跨平台的事件处理接口的封装,能够兼容包括这些操作系统:

Windows/Linux/BSD/Solaris 等操作系统的的事件处理。

包装的接口包括:

poll、select(Windows)、epoll(Linux)、kqueue(BSD)、/dev/pool(Solaris)

poll、select(Windows)、epoll(Linux)、 kqueue(BSD)、

Memcached 使用libevent来进行网络并发连接的处理,能够保持在很大并发情

使用libevent来进行网络并发连接的处理,能够保持在很大并发情

况下,仍旧能够保持快速的响应能力。

libevent: http://www.monkey.org/~provos/libevent/

数据过期方式:Lazy Expiration + LRU

Lazy Expiration

memcached内部不会监视记录是否过期,而是在get时查看记录的时间戳,检查记录是否过

期。这种技术被称为lazy(惰性)expiration。因此,memcached不会在过期监视上耗费CPU时间。

LRU

memcached会优先使用已超时的记录的空间,但即使如此,也会发生追加新记录时空间不

足的情况,此时就要使用名为 Least Recently Used(LRU)机制来分配空间。当memcached的内存空间不足时(无法从slab class 获取到新的空间时),就从最近未被使用的记录中搜索,并将其空间分配给新的记录。

基于客户端的Memcached分布式





//按照Key值,获取一个服务器ID
//按照Key值,获取一个服务器ID
int getServerId(char *key, int serverTotal) {
getServerId(char
int c, hash = 0;
while (c = *key++) {
hash += c;
}
return hash % serverTotal;
}
//服务器列表
//服务器列表
node[0] => 192.168.0.1:11211
node[1] => 192.168.0.2:11211
node[2] => 192.168.0.3:11211
//获取key是tokyo的节点ID(服务器ID)
//获取key是 tokyo的节点ID(服务器ID)
int id = getServerId("test", 3);
getServerId("test",
//得出的结果是1,那么对应的机器就是
//得出的结果是1
node[id] == node[1]



Memcached安装和使用:

安装 libevent

# tar zxvf libevent-1.4.9-stable.tar.gz

# cd libevent-1.4.9-stable

# ./configure --prefix=/usr/local/libevent

# make

# make install

安装 Memcached

# tar zxvf memcached-1.2.6.tar.gz

# cd memcached-1.2.6

# ./configure --prefix=/usr/local/memcached

# make

# make install

运行:
./memcached -d -m 10 -u root -l 127.0.0.1 -p 12001 -P /tmp/mem.pid

查看运行状况:
ps -ef | grep memcached



Memcached 基本协议

yanglin@yanglin-Aspire-4740:~$ telnet localhost 11211
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
set key1 0 180 3
abc
STORED
add key1 0 180 3
xyz
NOT_STORED
get key1
VALUE key1 0 3
abc
END
replace key1 0 180 3
xyz
STORED
get key1
VALUE key1 0 3
xyz
END
delete key1
DELETED
set key2 0 180 4
yanglin
CLIENT_ERROR bad data chunk
ERROR
set key2 0 180 4
1234
STORED
incr key2 3
1237
get key2
VALUE key2 0 4
1237
END
quit
Connection closed by foreign host.

Memcached 和 java 结合使用

1. 构造函数

public MemcachedClient(InetSocketAddress[] ia) throws IOException;

public MemcachedClient(List<InetSocketAddress> addrs) throws IOException;

public MemcachedClient(ConnectionFactory cf, List<InetSocketAddress> addrs) throws IOException;

既可以传递一个InetSocketAddress,也可以为InetSocketAddress的数组。

eg:

MemcachedClient cache = new MemcachedClient(new InetSocketAddress("127.0.0.1", 11211));

2. 常用方法

常用操作有:set(add+replace)、get、replace、add

public Future<Boolean> set(String key, int exp, Object object)

String key:键(key)

int exp:过期时间(单位是秒)

Object object:该键的新值(new value),如果有则修改。

public Object get(String key)

String key:键(key)

public Future<Boolean> replace(String key, int exp, Object object)

String key:键(key)

int exp:过期时间(单位是秒)

Object o:该键的新值(new value),如果有则修改。

public Future<Boolean> add(String key, int exp, Object object)

String key:键(key)

int exp:过期时间(单位是秒)

Object o:该键的新值(new value),如果有则修改。

先写一个要序列化入缓存的对象:
package com.yanglin.memcached.test.model;

import java.io.Serializable;

public class Book implements Serializable {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public Book(int id, String name) {
super();
this.id = id;
this.name = name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Book [id=" + id + ", name=" + name + "]";
}
}


在写一个客户端程序:
package com.yanglin.memcached.test.client;

import java.io.IOException;
import java.net.InetSocketAddress;

import com.yanglin.memcached.test.model.Book;

import net.spy.memcached.MemcachedClient;

public class Client {
public static void main(String[] args) throws IOException {
MemcachedClient cache = new MemcachedClient(new InetSocketAddress("127.0.0.1", 11211));
for (int i = 1; i < 10; i++) {
cache.set("T0001" + i, 3600, new Book(i,i + ""));
}
Book myObject = (Book) cache.get("T00011");
System.out.println("Get object from mem :" + myObject);
}
}

使用场景

比如一个购物网站,里面有各种的商品类别,正常情况下类别是不会经常变化的,可以使用缓存来缓存他们,减少与数据库的交互。

如果数据中的数据改变:更新数据库中分类的信息 to 找到memcached中key值 replace就可以了

memcached 内的存储操作是原子的,所以信息的更新不会让客户机只获得部分数据;

可以使用CAS保证:

CAS的基本原理

基本原理非常简单,一言以蔽之,就是“版本号”。每个存储的数据对象,多有一个版本号。我们可以从下面的例子来理解:

如果不采用CAS,则有如下的情景:

第一步,A取出数据对象X;

第二步,B取出数据对象X;

第三步,B修改数据对象X,并将其放入缓存;

第四步,A修改数据对象X,并将其放入缓存。

我们可以发现,第四步中会产生数据写入冲突。

如果采用CAS协议,则是如下的情景。

第一步,A取出数据对象X,并获取到CAS-ID1;

第二步,B取出数据对象X,并获取到CAS-ID2;

第三步,B修改数据对象X,在写入缓存前,检查CAS-ID与缓存空间中该数据的CAS-ID是否一致。结果是“一致”,就将修改后的带有CAS-ID2的X写入到缓存。

第四步,A修改数据对象Y,在写入缓存前,检查CAS-ID与缓存空间中该数据的CAS-ID是否一致。结果是“不一致”,则拒绝写入,返回存储失败。

其实也就是乐观锁的原理。

在使用Memcached时,一般实时读写的场景并不多见。但多是Memcached写入后,在一定时间后才会有读操作。但是如果应用场景,是写入后瞬间即会有读操作呢?似乎没有什么特别之处,我们依然可以这样写:

注:此处使用的是spymemcached客户端。

MemcachedClient cache = new MemcachedClient(cacheServerAddr);
cache.set("key", 3600, bigData);
return cache.get("key");


如此写入缓存后,如果立刻就有其他客户端进行读操作,则会读取失败,因为set是异步操作(async),很可能仍还没有写入完

一种可行的方法,是采用同步写操作。常用的set方法没有这种方式,需要采用遵守Memcached的CAS(Check And Set)协议的写操作。而这种写操作,一般是基于读取后得到CAS ID(类似于SVN中的版本ID),根据这个CAS ID来保证写入时,没有和其他写入操作产生“写重复”冲突。因此,在我们现在所讨论的场景中,可以如下使用CAS协议:

(1)初始写入:写一个简单的初始值(set,异步操作);

(2)获取版本:使用异步方式获取CAS ID;

(3)同步写入:以同步方式写入数据,保证在读取前,已经写入结束。

MemcachedClient cache = new MemcachedClient(cacheServerAddr);
cache.set(“key”, 3600, "");
long casId = cache.asyncGets("key").get().getCas();
cache.cas("key", casid, bigData);
return cache.get("key");
以这种“Set-Asyncgets-Cas”方式的缓存异步实时读写问题的解决方案

在使用memcached中key的约定和命名规范

这里给大家做一下总结:(转自/article/8752465.html)

第一种:一般都是公司的项目名称+字符常量+返回PO的id(或者唯一标示都可以)

第二种:可以用spring aop来拦截你要缓存的service,通过类名+方法名+参数名,来做到key值得唯一

第三种:用你的sql语句+id(或者查询条件)

其中第一种比较灵活你可以嵌入到你service的代码中,下面写一段伪代码:

[java] view
plaincopyprint?

String key = "taobao"+"cat"+catAll

Object o = getKey(key);

if(o==null){

//查询你的数据库操作

cat c = catService.findAll();

setKey(key,c);//set到memcached中

return c;//返回结果

}else{

return (Cat)c;//返回结果

}

但是是嵌入到你service层,就会破坏你service的业务逻辑,耦合性高,这里我们老大提出了解决方法,就是可以在你service和action中间在加一层来做缓存处理,这样似乎可以降低耦合。

第二种适用于分模块开发 ,因为调用的都是同一个类中的方法,但是拦截器也是回影响性能的,但是开发效率会提高的,还有就是不会破坏你的service的业务逻辑。

第三种 个人觉得不是很好 ,因为sql语句要是很长得话,也是会占用一部分内存的。

客户端语言包括 Java、Perl、PHP 等,都能串行化语言对象以便存储在 memcached 内,大家可以google一下他的客户端来做自己的实验。

怎么有规则弹性的使用memcached(多服务器使用)

提一个问题 ,当memcached的服务器宕掉怎么办呢?

这里要说明的一点就是缓存不是你信息的唯一来源,你不能把memcached当做你的数据库来使用,他仅仅是一个缓存,一旦宕掉,信息全无,很是可怕。此时你必须保证能从别的地方加载到你数据(如你的mysql数据库),有人可能会想到,我可以使用多台server,相互复制彼此的信息,一台宕掉 ,其他的还可以接着使用,我觉得这样的想法是很糟糕的,假设你使用了三台server 都是1g的内存,你们把信息复制到这三台上,但是你仔细想想,实际上你只拥有1g的内存可用,而你却浪费了2台server ,这似乎代价很大。

此时你可以这样解决 ,还是有3台server ,但是这三台server不会拥有相同的信息 ,也就是不会复制信息到对方的server上去,其中一台宕掉的时候,当你在次加载信息的时候,会从数据库查询,而这个信息会存储在其他两台中的任意一台server上,这样使用的好处为:同样式三台server,但是你却不像第一种那样,只拥有1g的可用内存,你现在而是3g可用,何乐而不为呢,只是宕掉的时候多查一次库而已,后面还是会从缓存中获取。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: