Java之XMemcached使用及源码详解

来源:互联网 发布:侠义道2武功数据 编辑:程序博客网 时间:2024/06/05 15:38

前言

      本文主要讲述如何使用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 typeif ((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中存储的值。

0 0
原创粉丝点击