转载请注明出处:http://blog.csdn.net/tang9140/article/details/43445511
前言
本文主要讲述如何使用XMemcached客户端与Memcached服务端进行交互。通过XMemcached的API调用与Memcached的set/get命令对比及跟踪XMemcached源码,使大家对XMemcached的API有更深层次的理解,能够从底层上去了解其工作原理,从而能在项目中进行一些针对性的接口封闭及优化工作。
是叫Memcache还是Memcached?
网上有种说法是:Memcache是这个项目的名称,而memcached是它服务器端的主程序文件名。我又查了Memcache的官网http://memcached.org/,home页一直引用的是Memcached。姑且不论该叫什么名称合适,在这里统一称呼为Memcached,仅代表我的个人习惯。
Memcached简介
言归正题,Memcached是分布式高性能内存级别的对象缓存系统,并且是开源免费项目。它的所有key-value数据全部放在内存中,这是其高效的一个原因,同时也意味着系统关闭时,全部数据就会丢失。利用Memcached作用缓存系统,可以减少动态网站数据库查询次数,提升网站性能,常作为web2.0网站缓存解决方案。Memcached客户端提供多种语言API支持,像C/C++、Perl、PHP、Java、C#、Ruby等。
Memcached的Java客户端目前有3个
- Memcached Client for Java 比 SpyMemcached更稳定、更早、更广泛;
- SpyMemcached 比 Memcached Client for Java更高效;
- XMemcached 比 SpyMemcache并发效果更好;
前两个客户端的使用,这里不做详述。
分三部分讲解XMemcached客户端
- XMemcached客户端使用演示
- set/get方法源码追踪
- 对比Memcached的set/get命令
一、XMemcached客户端使用演示
本人是用Maven构建的项目,为了使用XMemcached,需要在pom.xml中加入
<dependency>
<groupId>com.googlecode.xmemcached</groupId>
<artifactId>xmemcached</artifactId>
<version>1.4.3</version>
</dependency>
XMemcached使用示例Demo如下
public static void main(String[] args) throws IOException {
MemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil.getAddresses("127.0.0.1:11211"));
MemcachedClient memcachedClient = builder.build();
try {
memcachedClient.set("key", 0, "Hello World!");
String value = memcachedClient.get("key");
System.out.println("key值:" + value);
}
catch (Exception e) {
e.printStackTrace();
}
try {
memcachedClient.shutdown();
}
catch (IOException e) {
e.printStackTrace();
}
}
接下来详细追踪下这两个方法的源码
二、set/get方法源码追踪
1.set
大家可以用过debug模式,一步步追踪set及get过程,具体过程不演示了,先直接列出set方法大概的源码调用过程如下(中间可能省略了某些方法调用)
XMemcachedClient.set()
XMemcachedClient.sendCommand()
MemcachedConnector.send()
AbstractSession.write()
MemcachedTCPSession.wrapMessage()
TextStoreCommand.encode()
TextStoreCommand.encodeValue()
SerializingTranscoder.encode()
BaseSerializingTranscoder.serialize()
先是调用XMemcacheClient.set(final String key, final int exp, final Object value)方法,key形参对应字符串“key”,exp形参对应整数0(表达缓存永不过期),value形参对应字符串“Hello World!”。经过上述一系列方法调用,最终调用到SerializingTranscoder.encode(Object o)方法,此时形参o接收到的实参值就是set的字符串“Hello World!”,该方法体代码如下:
public final CachedData encode(Object o) {
byte[] b = null;
int flags = 0;
if (o instanceof String) {
b = encodeString((String) o);
} else if (o instanceof Long) {
if (this.primitiveAsString) {
b = encodeString(o.toString());
} else {
b = this.transcoderUtils.encodeLong((Long) o);
}
flags |= SPECIAL_LONG;
} else if (o instanceof Integer) {
if (this.primitiveAsString) {
b = encodeString(o.toString());
} else {
b = this.transcoderUtils.encodeInt((Integer) o);
}
flags |= SPECIAL_INT;
} else if (o instanceof Boolean) {
if (this.primitiveAsString) {
b = encodeString(o.toString());
} else {
b = this.transcoderUtils.encodeBoolean((Boolean) o);
}
flags |= SPECIAL_BOOLEAN;
} else if (o instanceof Date) {
b = this.transcoderUtils.encodeLong(((Date) o).getTime());
flags |= SPECIAL_DATE;
} else if (o instanceof Byte) {
if (this.primitiveAsString) {
b = encodeString(o.toString());
} else {
b = this.transcoderUtils.encodeByte((Byte) o);
}
flags |= SPECIAL_BYTE;
} else if (o instanceof Float) {
if (this.primitiveAsString) {
b = encodeString(o.toString());
} else {
b = this.transcoderUtils.encodeInt(Float
.floatToRawIntBits((Float) o));
}
flags |= SPECIAL_FLOAT;
} else if (o instanceof Double) {
if (this.primitiveAsString) {
b = encodeString(o.toString());
} else {
b = this.transcoderUtils.encodeLong(Double
.doubleToRawLongBits((Double) o));
}
flags |= SPECIAL_DOUBLE;
} else if (o instanceof byte[]) {
b = (byte[]) o;
flags |= SPECIAL_BYTEARRAY;
} else {
b = serialize(o);
flags |= SERIALIZED;
}
assert b != null;
if (this.primitiveAsString) {
// It is not be SERIALIZED,so change it to string type
if ((flags & SERIALIZED) == 0) {
flags = 0;
}
}
if (b.length > this.compressionThreshold) {
byte[] compressed = compress(b);
if (compressed.length < b.length) {
if (log.isDebugEnabled()) {
log.debug("Compressed " + o.getClass().getName() + " from "
+ b.length + " to " + compressed.length);
}
b = compressed;
flags |= COMPRESSED;
} else {
if (log.isDebugEnabled()) {
log.debug("Compression increased the size of "
+ o.getClass().getName() + " from " + b.length
+ " to " + compressed.length);
}
}
}
return new CachedData(flags, b, this.maxSize, -1);
}
先是申明了局部变量b(用来存储需要放入memcached服务器的字节数组)及flags(用来存储标志信息)。然后依次判断对象o是否字符串类型、长整型类型等,并将对象o编码成相应的字节数组存放在局部变量b中。
特别注意第57行,当o的类型不是字符串、基本类型的包装类型及byte[]数组时,会调用BaseSerializingTranscoder.serialize()方法,该方法源代码如下:
protected byte[] serialize(Object o) {
if (o == null) {
throw new NullPointerException("Can't serialize null");
}
byte[] rv = null;
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream os = new ObjectOutputStream(bos);
os.writeObject(o);
os.close();
bos.close();
rv = bos.toByteArray();
} catch (IOException e) {
throw new IllegalArgumentException("Non-serializable object", e);
}
return rv;
}
很明显,该方法就是进行对象序列化,将Java对象转化成byte数组并返回。相信大家看到这里,应该明白了为什么自定义对象需要实现Serializable接口才能保存进Memcached中。如果数据对象没有实现Serializable接口,那么在进行对象序列化时,将会抛出IOException,最终抛出IllegalArgumentException,并提示Non-serializable object。
另着重说明下CachedData类的作用,该类封装了cas值(该值用来实现原子更新,即客户端每次发出更新请求时,请求信息中都会附带该cas值,memcached服务端在收到请求后,会将该cas值与服务器中存储数据的cas值对比,如果相等,则用新的数据覆盖老的数据;否则,更新失败。在并发环境下特别有用)、data数据(即要缓存的数据值或者获取到的缓存数据,以byte[]数组形式存储),flag信息(标识byte[]数组额外数据类型信息及byte[]数组是否进行过压缩等信息,用一个int类型存储)及其它信息。
set源码分析到这里,下面说下get源码。
2.get
同样的,先列出get方法大概的源码调用过程如下:
XMemcachedClient.get()
XMemcachedClient.fetch0()
XMemcachedClient.sendCommand()
MemcachedConnector.send()
AbstractSession.write()
MemcachedTCPSession.wrapMessage()
TextGetCommand.encode()
SerializingTranscoder.decode()
SerializingTranscoder.decode0()
BaseSerializingTranscoder.deserialize()
先是调用XMemcacheClient.get(final String key)方法,key形参对应字符串“key"。从该方法一直到TextGetCommand.encode()调用,可以看作是组装get命令并发送到服务器过程,在收到服务器响应消息后,将响应消息组装成CachedData,并调用SerializingTranscoder.decode(CachedData d)方法,即进行字节流解码工作。该方法代码如下:
public final Object decode(CachedData d) {
byte[] data = d.getData();
int flags = d.getFlag();
if ((flags & COMPRESSED) != 0) {
data = decompress(d.getData());
}
flags = flags & SPECIAL_MASK;
return decode0(d,data, flags);
}
先是获取字节数组及标志信息,根据标志位决定是否要解压缩字节数组。最后调用decode0(CachedData cachedData,byte[] data, int flags)方法,代码如下:
protected final Object decode0(CachedData cachedData,byte[] data, int flags) {
Object rv = null;
if ((cachedData.getFlag() & SERIALIZED) != 0 && data != null) {
rv = deserialize(data);
} else {
if (this.primitiveAsString) {
if (flags == 0) {
return decodeString(data);
}
}
if (flags != 0 && data != null) {
switch (flags) {
case SPECIAL_BOOLEAN:
rv = Boolean.valueOf(this.transcoderUtils
.decodeBoolean(data));
break;
case SPECIAL_INT:
rv = Integer.valueOf(this.transcoderUtils.decodeInt(data));
break;
case SPECIAL_LONG:
rv = Long.valueOf(this.transcoderUtils.decodeLong(data));
break;
case SPECIAL_BYTE:
rv = Byte.valueOf(this.transcoderUtils.decodeByte(data));
break;
case SPECIAL_FLOAT:
rv = new Float(Float.intBitsToFloat(this.transcoderUtils
.decodeInt(data)));
break;
case SPECIAL_DOUBLE:
rv = new Double(Double
.longBitsToDouble(this.transcoderUtils
.decodeLong(data)));
break;
case SPECIAL_DATE:
rv = new Date(this.transcoderUtils.decodeLong(data));
break;
case SPECIAL_BYTEARRAY:
rv = data;
break;
default:
log
.warn(String.format("Undecodeable with flags %x",
flags));
}
} else {
rv = decodeString(data);
}
}
return rv;
}
上面方法实际上就是encode(Object o)方法的逆向实现,即将字节数组转化成Object对象。注意第4行调用了deserialize(byte[] in)方法,该方法代码如下(省略了catch、finally部分):
protected Object deserialize(byte[] in) {
Object rv = null;
ByteArrayInputStream bis = null;
ObjectInputStream is = null;
try {
if (in != null) {
bis = new ByteArrayInputStream(in);
is = new ObjectInputStream(bis) {
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
try {
//When class is not found,try to load it from context class loader.
return super.resolveClass(desc);
} catch (ClassNotFoundException e) {
return Thread.currentThread().getContextClassLoader().loadClass(desc.getName());
}
}
};
rv = is.readObject();
}
}
...
return rv;
}
上述代码就是反序列化对象并返回。每次反序列化操作,得到的都是一个
全新对象,对该新对象进行的任何操作并不会影响memcached中存储的值。
三、对比Memcached的set/get命令
除了上述通过java代码与memcached交互外,我们还可以直接通过命令方式与其交互。步骤如下:
1.先打开cmd窗口
2.通过telnet连上memcached服务器,命令如下:
telnet 127.0.0.1 11211
3.若连接成功,可直接输入命令与memcached服务器交互。
1.存储命令
格式如下
<command name> <key> <flags> <exptime> <bytes>
<data block>
参数说明如下:
<command name> set/add/replace
<key> 查找关键字
<flags> 客户机使用它存储关于键值对的额外信息
<exptime> 该数据的存活时间,0表示永远
<bytes> 存储字节数
<data block> 存储的数据块(可直接理解为key-value结构中的value)
各存储命令特别说明:
- set命令在key不存在时,进行添加操作,否则进行更新操作。
- add命令在key不存在时,才能添加成功
- replace命令在key存在时,才能替换成功
实例演示:
set myname 0 0 9
super man
STORED
2.读取命令
get key [key1]...
获取一个或多个键值,键之间以空格分隔。
实例演示:
get myname
VALUE myname 0 9
super man
END
除了上述常用命令外,还有gets、cas、stats等命令,大家有兴趣的可以去学习下。
3.类比
类比下Xmemcached客户端使用,不难发现很有趣的地方:
set命令中<key>和<exptime>分别对应XMemcacheClient.set(final String key, final int exp, final Object value)方法中的key和exp参数。而<flags>和<data block>则对应CachedData封装类的flag和data成员变量。
get命令<key>对应到XMemcacheClient.get(final String key)方法的key参数,get命令的返回结果对应SerializingTranscoder.decode(CachedData d)方法的参数d,d的类型是CachedData,该类正是封装了flag和data信息。
通过上面的对比,不难发现,无论是Memcached命令还是Xmemcached客户端,都不过是memcached客户端一种实现而已,他们遵守相同的请求及应答消息规范。更底层来看,这两种方式都是通过建立tcp连接后,然后发送符合memcached约定的请求消息;在接收到memcached服务器应答消息后,也是按照memcached的应答消息约定进行解码(在Xmemcached客户端利用了flag字段实现将数据字节数组转化成应用层需要的类型)。
换句话说:Memcached缓存系统提供了服务端的实现(c语言),并约定了客户端与服务器进行通信的消息格式,更准确来说是字节流格式(通过tcp方式通信)。不同语言客户端,仅仅是这一规范的实现而已。当然Memcached已经提供了大部分语言的客户端实现,不过你也可以自己开发出一个客户端实现。
四、如何优化Xmemcached客户端代码,提高效率
从前面的源码分析可以看出,如果存入Memcached的是bean对象,需要实现Serializable接口以支持java对象序列化。据我了解,java自带的对象序列化,不仅序列化和反序化操作耗时,而且生成的字节数组也比较大。因此可以考虑换一种编解码技术,本人推荐使用fastjson,其不仅效率过,而且生成的json串体积小。其它优化措施,如果有想到,再补充
版权声明:本文为博主原创文章,未经博主允许不得转载。
分享到:
相关推荐
xmemcached-1.2.4的官方源码。 xmemcached XMemcached is a high performance, easy to use blocking multithreaded memcached client in java. It's nio based (using my opensource nio framework :yanf4j), ...
xmemcached1.3.5源码-附带自己写的RMI调用它的JMX服务,使用RMI调用JMX服务的详细过程,完整的eclipse工程,直接导入即可用。还用一些运行截图,很有用。 自己写的例子,类名是BaseExample 和RMITest.
基于java nio实现的高性能可扩展的memcached客户端。虽然Java的memcached库已经很多,但是这些Java开源memcached库并没有一个是基于NIO框架编写,因此并不能够充分发挥Java NIO的性能优势.... xmemcached的项目主页...
java的memcached客户端,支持一致性hash,支持动态增删服务器,客户端源码
Xmemcached是一款基于Java编写的高性能、高可用性的Memcached客户端库,由Kafka的创始人Jay Kreps开发。它提供了丰富的功能和优秀的性能,使得在Java应用中集成和使用Memcached变得更加简单。Xmemcached致力于提供...
Xmemcached是基于java nio实现的高性能可扩展的memcached客户端。它的主要特点: 高性能,稳定可靠,已经在众多公司的众多项目里得到应用。 功能完备:客户端分布式、权重、最新最完整的协议支持。 ...
XMemcached不仅支持memcached的所有协议,还具备一系列增强功能,使其成为开发者的首选工具之一。 #### 主要特性 1. **高性能**: - 基于Java NIO(非阻塞I/O),相较于传统的阻塞I/O模型,Java NIO能够更好地处理...
Xmemcached是一个高性能、轻量级的Java客户端库,专门用于连接和操作Memcached分布式内存缓存系统。这份手册不仅包含了基础的API使用方法,还涵盖了如何将Xmemcached与Spring等主流框架进行集成的详细配置信息。 **...
Xmemcached是一个高性能、线程安全的Java实现的分布式缓存系统,专为Memcached设计。这个库的主要目标是提供简单、快速、无阻塞的客户端API,以便于开发人员在Java应用程序中集成和利用Memcached的强大功能。下面将...
《Xmemcached用户指南:Java后端开发详解》 Xmemcached是一款高性能、易用的Java客户端库,专为Memcached缓存系统设计。本文档将深入探讨如何使用Xmemcached进行后端开发,帮助Java开发者更好地理解和利用这一强大...
xmemcached是Java领域中一个高效、稳定且易于使用的Memcached客户端库,它为开发者提供了便捷的接口来操作Memcached服务器。在2.4.6这个版本中,xmemcached继续优化了性能和稳定性,为开发分布式缓存应用提供了强大...
### Xmemcached用户指南知识点详解 #### 一、XMemcached简介 XMemcached是一款针对Java平台设计的高性能Memcached客户端。Memcached是一种分布式内存对象缓存系统,主要用于减轻数据库负担,提高动态Web应用程序的...
xmemcached是一款高性能、轻量级的Java客户端库,专为Memcached缓存系统设计,其1.4.3版本提供了丰富的功能和优化,旨在提升应用的性能和可扩展性。在本文中,我们将详细探讨xmemcached的核心特性和使用方法,帮助...
**xmemcached API 文档详解** xmemcached 是一个高性能、线程安全的 Java 客户端库,用于连接和操作 Memcached 分布式内存缓存系统。它提供了丰富的 API,使得开发人员能够轻松地在应用程序中集成 Memcached 功能,...
XMemcached是基于 java nio的Memcached客户端,java nio相比于传统阻塞 io 模型来说,有 效率高(特别在高并发下)和资源耗费相对较少的优点。传统阻塞 IO为了提高效率,需要 创建一定数量的连接形成连接池,而 nio...
xmemcached是一个高性能、易用且功能丰富的Java Memcached客户端库。它提供了全面的API,使得开发者能够方便地与Memcached缓存系统进行交互,从而提高Web应用的性能和响应速度。本文将深入探讨xmemcached的相关知识...
xmemcached-1.2.6.2
总的来说,memcache.spymemcached和xmemcached都是Java开发人员在使用Memcached时的重要工具,它们为Java应用程序提供了高效的缓存解决方案。了解并掌握这些客户端的特性和用法,对于提升应用程序性能和优化系统架构...
xmemcached-1.4.2最新版,可用。memcached java客户端
xmemcached使用的jar,需要的可以下载