引子   

     打从去年一路北漂,进入无人货架行业,业务需求漫天飘,最近总算把工作都规划齐整。回望过去一年多的时间里,诸多东西值得整理,memcache就是其中一个。

   看到java的工资高些,队伍中好些人都想学习java,美其名曰:技术多元化。奈何团队中并没有相关经验的人,也深知大家殷切的期盼,所以,只能先撸起袖子自己干,看看书、看看博客、看看视频,两个小项目就上线了,除memcache以外,过程还算顺利,于是就有了这篇文章。

正值高考,突然感怀,当年的失利,让自己更加坚强。

                        

  

背景

  因为目前大部分项目都是.net core ,使用了memcache做为缓存服务器,首先就是 spring boot 里集成 memcache(使用 spymemcached 客户端),集成过程就不说了,添加依赖,编写帮助类,通过 @Configuration 注入就可以了。

如果以为这样就完了,那就没有这个文章了,真正的故事才刚刚开始.....

问题

   配置完成后,就开始读取已经有缓存,然后就提示:Failed to decompress data,如下图,返回的内容就是null,但是在命令行能读出来。另外,我们缓存的都是string,不会存在序列化的问题(一开始还真怀疑过java与.net  string 序列化,好傻好天真)

因为一开始看上图是 warn,就没在意,于是开始了排除方法:

    1、java缓存,java 读取正常。

    2、java缓存,.net 读取正常。

3、直接控制添加, java 读取正常。

    4、更换java 客户端为xmemcached

    5、还尝试了很多.....甚至自己又部署几个memcache 环境

最后,得到一个结论:.net 缓存(使用的是 Enyim.Caching 客户端),java 无法正常获取。

    一个诡异的结论,咨询别人时,都说:memcache 与语言无关!

  

失落的解决方案

   尝试了很多次失败后,决定让他凉一凉。终究还是过不了内心的坎,感觉心中有一个东西,不得踏实,又不停的搜索,甚至还在阿里云里发了工单,一开始也怀疑是阿里云的服务器有问题(直接用的阿里的memcache),后来他们技术给我说了一堆

听不太明白的内容,大概是要用 string 开头的接口去读取。这时已经明白,不是读取不到,而是解码出错,返回null而已。

  再后来,就是一个叫flag 的参数引起了我的注意, 大意是说,不同客户端在缓存时,用了不同的flag 来标记,说什么 java 的是flag 32,.net 的是2之类的,只要修改.net 为32就可以了。 反正听起来就不靠谱,又到茫茫网络中去搜索.....

  又过了两天,感觉不能这么耗下去了,没有其他方案,想着,还是修改下 Enyim.Caching 源码试试看。接着 git clone 源码,很快定位到 flag 的地方 在 DefaultTranscoder.cs  74行左右,生成flag的代码如下

        public static uint TypeCodeToFlag(TypeCode code)
{
return ; //return (uint)((int)code | 0x0100); //修改前
}

     其中,TypeCode 是系统中数据类型对应一个 enum,源码如下,其中 String的值为 18,

namespace System
{
//
// Summary:
// Specifies the type of an object.
[ComVisible(true)]
public enum TypeCode
{
//
// Summary:
// A null reference.
Empty = ,
//
// Summary:
// A general type representing any reference or value type not explicitly represented
// by another TypeCode.
Object = ,
//
// Summary:
// A database null (column) value.
DBNull = ,
//
// Summary:
// A simple type representing Boolean values of true or false.
Boolean = ,
//
// Summary:
// An integral type representing unsigned 16-bit integers with values between 0
// and 65535. The set of possible values for the System.TypeCode.Char type corresponds
// to the Unicode character set.
Char = ,
//
// Summary:
// An integral type representing signed 8-bit integers with values between -128
// and 127.
SByte = ,
//
// Summary:
// An integral type representing unsigned 8-bit integers with values between 0 and
// 255.
Byte = ,
//
// Summary:
// An integral type representing signed 16-bit integers with values between -32768
// and 32767.
Int16 = ,
//
// Summary:
// An integral type representing unsigned 16-bit integers with values between 0
// and 65535.
UInt16 = ,
//
// Summary:
// An integral type representing signed 32-bit integers with values between -2147483648
// and 2147483647.
Int32 = ,
//
// Summary:
// An integral type representing unsigned 32-bit integers with values between 0
// and 4294967295.
UInt32 = ,
//
// Summary:
// An integral type representing signed 64-bit integers with values between -9223372036854775808
// and 9223372036854775807.
Int64 = ,
//
// Summary:
// An integral type representing unsigned 64-bit integers with values between 0
// and 18446744073709551615.
UInt64 = ,
//
// Summary:
// A floating point type representing values ranging from approximately 1.5 x 10
// -45 to 3.4 x 10 38 with a precision of 7 digits.
Single = ,
//
// Summary:
// A floating point type representing values ranging from approximately 5.0 x 10
// -324 to 1.7 x 10 308 with a precision of 15-16 digits.
Double = ,
//
// Summary:
// A simple type representing values ranging from 1.0 x 10 -28 to approximately
// 7.9 x 10 28 with 28-29 significant digits.
Decimal = ,
//
// Summary:
// A type representing a date and time value.
DateTime = ,
//
// Summary:
// A sealed class type representing Unicode character strings.
String =
}
}

  根据之前得到的结果,要把 .net 客户端的flag 设置成32,于是,直接返回32,代码生成上传,不试不知道,一试吓一跳,竟然正常了。java 能正常返回缓存内容了,如下图,正常打印了

  

刚开始真是高兴了足足10秒中,毕竟尝试了很多次失败,但转念一想,现在所有的项目,都得去引用自己编译的这个版本,以后如果 Enyim.Caching 升级了,我还得去重新下载、编译,所有项目又要重新引用,想想就后怕!

于是,第一次有了这样的感觉:问题解决了,但是很多失落!弄完回到家,看我一脸无趣,媳妇还安慰说:“今天没解决,明天再来,明天不行,后天再来,总会拨云见日的!”

升级版解决方案

  缺陷的解决方案,一直萦绕心头,挥之不去,于是,还是忍不住去查询新的方案,还特意发起了一个博问,不过就 dudu 回复了,虽然没有直接解决,也给了一些新的提示,并顺利的看到了 spymemcached 的源码。找到了

  解码的类 SerializingTranscoder.java ,对于 String 并未做处理,也没有解码的问题。 解码部分源码如下,可以看到,对于 String是直接调用  decodeString

public Object decode(CachedData d) {
byte[] data = d.getData();
Object rv = null;
if ((d.getFlags() & COMPRESSED) != ) {
data = decompress(d.getData());
}
int flags = d.getFlags() & SPECIAL_MASK;
if ((d.getFlags() & SERIALIZED) != && data != null) {
rv = deserialize(data);
} else if (flags != && data != null) {
switch (flags) {
case SPECIAL_BOOLEAN:
rv = Boolean.valueOf(tu.decodeBoolean(data));
break;
case SPECIAL_INT:
rv = Integer.valueOf(tu.decodeInt(data));
break;
case SPECIAL_LONG:
rv = Long.valueOf(tu.decodeLong(data));
break;
case SPECIAL_DATE:
rv = new Date(tu.decodeLong(data));
break;
case SPECIAL_BYTE:
rv = Byte.valueOf(tu.decodeByte(data));
break;
case SPECIAL_FLOAT:
rv = new Float(Float.intBitsToFloat(tu.decodeInt(data)));
break;
case SPECIAL_DOUBLE:
rv = new Double(Double.longBitsToDouble(tu.decodeLong(data)));
break;
case SPECIAL_BYTEARRAY:
rv = data;
break;
default:
getLogger().warn("Undecodeable with flags %x", flags);
}
} else {
rv = decodeString(data);
}
return rv;
}

decodeString 代码如下,可见并无特殊处理

  /**
* Decode the string with the current character set.
*/
protected String decodeString(byte[] data) {
String rv = null;
try {
if (data != null) {
rv = new String(data, charset);
}
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
return rv;
}

再细看 SerializingTranscoder.java 的处理逻辑,在解码之前,有压缩标志,以及 decompress() 方法, 这个方法在 BaseSerializingTranscoder.java 中,源代码如下,正好有,有一个 catch 会输出,最早看到的错误信息:Failed to decompress data

  getLogger().warn("Failed to decompress data", e); 找到了问题的发生地儿,离解决方案就不远了。 第一现场很重要。
  /**
* Get the object represented by the given serialized bytes.
*/
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);
rv=is.readObject();
is.close();
bis.close();
}
} catch (IOException e) {
getLogger().warn("Caught IOException decoding %d bytes of data",
in == null ? : in.length, e);
} catch (ClassNotFoundException e) {
getLogger().warn("Caught CNFE decoding %d bytes of data",
in == null ? : in.length, e);
} finally {
CloseUtil.close(is);
CloseUtil.close(bis);
}
return rv;
}

既然问题出在“解压”这里,那为什么我把 flag 设置成32就可以了呢,再看源码,判断是否解压的如下:

static final int COMPRESSED = 2;

 if ((d.getFlags() & COMPRESSED) != 0) {
  data = decompress(d.getData());
 } .net 里默认是 18 | 0x0100 = 274
 274 &  2 = 2  不等于0,会去解压,然后出错了。

 32 & 2  =0, 不解压,正常。

 这里其实验证了,flag与客户端无关。压缩标志与数据类型有关。

   问题已经明确了,只要程序不走解压就是正常的,并且,这些参数,都是类内部的状态,外面无法修改,那可以扩展吗?使用自己的解码类来实现,肯定是可以的,看 SerializingTranscoder 与 BaseSerializingTranscoder 的继承关系就知道,

再看  get 方法 memcachedClient.get(String key, Transcoder<T> tc),支持自定义  Transcoder, 接下来,问题就简单了,自定义一个 Transcoder 继承  BaseSerializingTranscoder 实现 Transcoder,不用解压,直接解码。

最后,其实,我只是在  SerializingTranscoder  基础上,把 static final int COMPRESSED = 0,就可以了,都不解压。 获取代码如下

HMSerializingTranscoder transcoder = new HMSerializingTranscoder();
return memcachedClient.get(key,transcoder);

结语

  分析到此,问题明了,方案明确,水到渠成,问题解决了。在不修改第三方源码的基础上,通过扩展解决了,也不用担心第三方升级的问题了,这样就比第一种别扭的方案舒服多了。

  第一次感受到阅读源码,与深究一个问题的带来的收获 -- 杠杠的

   成为一名优秀的程序员!

flag -- 诡异的memcache标记的更多相关文章

  1. bzoj 1171 大sz的游戏& 2892 强袭作战 (线段树+单调队列+永久性flag)

    大sz的游戏 Time Limit: 50 Sec  Memory Limit: 357 MBSubmit: 536  Solved: 143[Submit][Status][Discuss] Des ...

  2. (转)JavaMail中的Flag(邮件状态)

    本文转载自:http://blog.csdn.net/chjttony/article/details/6005594 标记邮件就是把邮件标记为已读,删除等操作,需要使用Flags类,它mail.ja ...

  3. memcached随笔练习

    实验环境: RHEL 6.5 (已关闭selinux,iptables) 首先部署LNMP环境,该步骤采用源码编译安装 安装Nginx-1.8.0 准备软件包:nginx-1.8.0.tar.gz 下 ...

  4. P4683 [IOI2008] Type Printer 打印机

    题意描述 [IOI2008] Type Printer 打印机 几百年前的 IOI 的题目还是很好的呀. 给你一个 诡异的 打印机,它只能用已有的字符来打印,而且必须每一个都用到.(这岂不是活字印刷术 ...

  5. 细说WebSocket - Node篇

    在上一篇提高到了 web 通信的各种方式,包括 轮询.长连接 以及各种 HTML5 中提到的手段.本文将详细描述 WebSocket协议 在 web通讯 中的实现. 一.WebSocket 协议 1. ...

  6. Django rest_framework 实用技巧

    前言: 最近工作中需要用到Django rest_framework框架做API, 边学边写,记录了一些实际工作中需要用到的功能,不是很全也不系统,以后需要什么功能可以在这查询. 后续还会更新其它的用 ...

  7. Java内存模型深度解析:重排序 --转

    原文地址:http://www.codeceo.com/article/java-memeory-2.html 数据依赖性 如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间 ...

  8. 接口测试第十二课(fidller过滤)(转)

    转自: 经常有人问我,如何只抓手机上某个应用的请求包?在使用fiddler抓手机包的过程中,fiddler会话框上瞬间就满屏了,因为它不仅抓到手机上的请求数据包,也抓到了PC端的网络请求包.这时候很难 ...

  9. 【思路】-URL重写

    URL重写  重写原理 过程分析 疑惑地方 lookfor app.Request.ApplicationPath如果有子目录的话 这个地方可能会起到作用,暂时不确定 bool flag = url. ...

随机推荐

  1. 企业级实时数据文件同步服务_【all】

    全网数据定时备份方案[cron + rsync] [更多参考]全网数据定时备份方案[cron + rsync] 全网数据实时备份方案[inotify,sersync] [更多参考]全网数据实时备份方案 ...

  2. 铁乐学Python_day06-整数和字符串小数据池

    python小数据池(内存地址) 今天来学习认识一下python中的小数据池. 我们都知道 ==是用来作比较,要是两个变量的数值相等, 用==比较返回的bool值会是True: a = 1000 b ...

  3. linux面试总结

    一.填空题:1. 在Linux系统中,以 文件 方式访问设备 .2. Linux内核引导时,从文件 /etc/fstab 中读取要加载的文件系统.3. Linux文件系统中每个文件用 i节点 来标识. ...

  4. springsource-tool-suite插件的在线安装

      1 首先,确定你现在使用的eclipse属于哪个版本? 查看自己的eclipse平台的版本(我的eclipse平台版本是4.3)       2 根据eclipse版本,选择插件的版本 官网:ht ...

  5. September 25th 2017 Week 39th Monday

    No man is rich enough to buy back his own past. 没有人富有到可以赎回自己的过去. Those rich are not willing to buy b ...

  6. ansible-playbook快速入门

    一.yaml语法: 1. yaml语法编写 1.1 同层级的字段通过相同缩进表示 1.2 map结构里面key/value用‘:’来分隔 1.3 key/value可以同行写,也可以换行写,换行写必须 ...

  7. docker 导入导出镜像

    docker容器导入导出有两种方法: 一种是使用save和load命令 使用例子如下: docker save ubuntu:load>/root/ubuntu.tar docker load& ...

  8. openresty + orange centos7 安装

    Orange Orange是一个基于OpenResty的API网关.除Nginx的基本功能外,它还可用于API监控.访问控制(鉴权.WAF).流量筛选.访问限速.AB测试.动态分流等.它有以下特性: ...

  9. ASP.NET MVC编程——路由

    框架自动生成的路由配置 上图中,路由配置文件为App_Start文件夹下的RouteConfig.cs. 代码如下: public class RouteConfig { public static ...

  10. 关于mvn install命令执行报错问题

    首先这个报错,通常要么是依赖问题,比如模块之间的依赖传递问题,通常报这种错误会在控制台提示. 或者是比如子工程分为test-entity.test-dao.test-service.test-web三 ...