Thursday, February 18, 2010

Tokyo Tyran/Memcached compatiblity issue

Recently I was playing with using Tokyo Tyrant with Spy Memcached client. And discovered that according to Tokyo Tyrant docs: "flags", "exptime", and "cas unique" parameters are ignored. And this is causing Spy memcached client to not be able operate with serialized objects correctly. basically whenever I save serialized object there I get String object in return.
After some hacking I managed to come up with this little class that stores "flag" data into byte array itself:

import net.spy.memcached.CachedData;
import net.spy.memcached.transcoders.SerializingTranscoder;

/**
* TTSerializingTranscoder makes spymemcached client work correctly with TokyoTyrant by working around fact that
* Tokyo Tyrant is not storing put metadata as defined in memcached protocol
*/
public class TTSerializingTranscoder extends SerializingTranscoder {

@Override
public Object decode(CachedData d) {
byte[] result = d.getData();
byte[] data = new byte[result.length - 4];
byte[] flag = new byte[4];
System.arraycopy(result, 0, flag, 0, 4);
System.arraycopy(result, 4, data, 0, result.length - 4);
int flags = byteArrayToInt(flag);
return super.decode(new CachedData(flags, data, getMaxSize()));
}

public CachedData encode(Object o) {
final CachedData res = super.encode(o);
byte[] b = res.getData();
byte[] data = new byte[b.length + 4];
final int flags = res.getFlags();
System.arraycopy(intToByteArray(flags), 0, data, 0, 4);
System.arraycopy(b, 0, data, 4, b.length);
return new CachedData(flags, data, getMaxSize());
}

public static byte[] intToByteArray(int value) {
return new byte[]{
(byte) (value >>> 24),
(byte) (value >>> 16),
(byte) (value >>> 8),
(byte) value};
}


public static int byteArrayToInt(byte[] b) {
return (b[0] << 24)
+ ((b[1] & 0xFF) << 16)
+ ((b[2] & 0xFF) << 8)
+ (b[3] & 0xFF);
}
}


to use this class I had to extend DefaultConnectionFactory (see code below) and pass it to MemcachedClient constructor (new MemcachedClient(new SpyConnectionFactory(TTSerializingTranscoder.class)));)



import net.spy.memcached.DefaultConnectionFactory;
import net.spy.memcached.transcoders.SerializingTranscoder;
import net.spy.memcached.transcoders.Transcoder;

/**
*
*/
public class SpyConnectionFactory extends DefaultConnectionFactory {
private Class transcoderClass = SerializingTranscoder.class;

public SpyConnectionFactory() {
}

public SpyConnectionFactory(Class transcoderClass) {
this.transcoderClass = transcoderClass;
}

@Override
public Transcoder<Object> getDefaultTranscoder() {
try {
//noinspection unchecked
return transcoderClass.newInstance();
} catch (Exception e) {
throw new RuntimeException("Failed to create transcoder for " + transcoderClass, e);
}
}

public Class getTranscoderClass() {
return transcoderClass;
}

public void setTranscoderClass(Class transcoderClass) {
this.transcoderClass = transcoderClass;
}
}

No comments:

Post a Comment

Followers