网上有种说法是:Memcache是这个项目的名称,而memcached是它服务器端的主程序文件名。我又查了Memcache的官网http://memcached.org/,home页一直引用的是Memcached。姑且不论该叫什么名称合适,在这里统一称呼为Memcached,仅代表我的个人习惯。
前两个客户端的使用,这里不做详述。
<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(); } }
接下来详细追踪下这两个方法的源码
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源码。
同样的,先列出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中存储的值。
除了上述通过java代码与memcached交互外,我们还可以直接通过命令方式与其交互。步骤如下:
1.先打开cmd窗口
2.通过telnet连上memcached服务器,命令如下:
telnet 127.0.0.1 11211
3.若连接成功,可直接输入命令与memcached服务器交互。
格式如下
<command name> <key> <flags> <exptime> <bytes> <data block>参数说明如下:
<command name> set/add/replace
<key> 查找关键字
<flags> 客户机使用它存储关于键值对的额外信息
<exptime> 该数据的存活时间,0表示永远
<bytes> 存储字节数
<data block> 存储的数据块(可直接理解为key-value结构中的value)
各存储命令特别说明:
实例演示:
set myname 0 0 9 super man STORED
get key [key1]...
获取一个或多个键值,键之间以空格分隔。
实例演示:
get myname VALUE myname 0 9 super man END
除了上述常用命令外,还有gets、cas、stats等命令,大家有兴趣的可以去学习下。
类比下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已经提供了大部分语言的客户端实现,不过你也可以自己开发出一个客户端实现。
从前面的源码分析可以看出,如果存入Memcached的是bean对象,需要实现Serializable接口以支持java对象序列化。据我了解,java自带的对象序列化,不仅序列化和反序化操作耗时,而且生成的字节数组也比较大。因此可以考虑换一种编解码技术,本人推荐使用fastjson,其不仅效率过,而且生成的json串体积小。其它优化措施,如果有想到,再补充
原文地址:http://blog.csdn.net/tang9140/article/details/43445511