引子   

     打从去年一路北漂,进入无人货架行业,业务需求漫天飘,最近总算把工作都规划齐整。回望过去一年多的时间里,诸多东西值得整理,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 32;//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 = 0,//// Summary://     A general type representing any reference or value type not explicitly represented//     by another TypeCode.Object = 1,//// Summary://     A database null (column) value.DBNull = 2,//// Summary://     A simple type representing Boolean values of true or false.Boolean = 3,//// 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 = 4,//// Summary://     An integral type representing signed 8-bit integers with values between -128//     and 127.SByte = 5,//// Summary://     An integral type representing unsigned 8-bit integers with values between 0 and//     255.Byte = 6,//// Summary://     An integral type representing signed 16-bit integers with values between -32768//     and 32767.Int16 = 7,//// Summary://     An integral type representing unsigned 16-bit integers with values between 0//     and 65535.UInt16 = 8,//// Summary://     An integral type representing signed 32-bit integers with values between -2147483648//     and 2147483647.Int32 = 9,//// Summary://     An integral type representing unsigned 32-bit integers with values between 0//     and 4294967295.UInt32 = 10,//// Summary://     An integral type representing signed 64-bit integers with values between -9223372036854775808//     and 9223372036854775807.Int64 = 11,//// Summary://     An integral type representing unsigned 64-bit integers with values between 0//     and 18446744073709551615.UInt64 = 12,//// 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 = 13,//// 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 = 14,//// 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 = 15,//// Summary://     A type representing a date and time value.DateTime = 16,//// Summary://     A sealed class type representing Unicode character strings.String = 18}
}

View Code

  根据之前得到的结果,要把 .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) != 0) {data = decompress(d.getData());}int flags = d.getFlags() & SPECIAL_MASK;if ((d.getFlags() & SERIALIZED) != 0 && data != null) {rv = deserialize(data);} else if (flags != 0 && 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;}

View Code

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 ? 0 : in.length, e);} catch (ClassNotFoundException e) {getLogger().warn("Caught CNFE decoding %d bytes of data",in == null ? 0 : in.length, e);} finally {CloseUtil.close(is);CloseUtil.close(bis);}return rv;}

View Code

既然问题出在“解压”这里,那为什么我把 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);

结语

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

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

   成为一名优秀的程序员!

版权声明:
作者:J²


编辑:妞妞
妞妞主页
出处:http://www.cnblogs.com/jijunjian/
本文版权归作者和博客园共有,欢迎转载,大家好,才是真的好!

flag -- 诡异的memcache标记相关推荐

  1. memcache函数整理

    连接 mixed Memcache::pconnect(string $host[, int $port[, int $timeout]]) bool Memcache::connect(string ...

  2. 成功解决absl.flags._exceptions.UnrecognizedFlagError: Unknown command line flag 'data_format'

    成功解决absl.flags._exceptions.UnrecognizedFlagError: Unknown command line flag 'data_format' 目录 解决问题 解决 ...

  3. 图片 标记 软件_如何设计软件功能标记

    图片 标记 软件 A previous company had a problem: our deploys were thousands of lines in size, took nearly ...

  4. memcached随笔练习

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

  5. 《Linux命令行与shell脚本大全》笔记

    初识Linux Shell 什么是Linux Linux可划分为以下四部分: Linux内核 GNU工具 图形化桌面环境 应用软件 深入探究Linux内核 内核主要负责以下四种功能: 系统内存管理 软 ...

  6. nginx源码的安装与磁盘分区

    源码包的安装与磁盘分区 文章目录 源码包的安装与磁盘分区 1. ` nginx ` 编译安装 1.1 [` ngink ` 的网络地址](http://nginx.org/en/download.ht ...

  7. java中实现具有传递性吗_Java中volatile关键字详解,jvm内存模型,原子性、可见性、有序性...

    一.Java内存模型 想要理解volatile为什么能确保可见性,就要先理解Java中的内存模型是什么样的. Java内存模型规定了所有的变量都存储在主内存中.每条线程中还有自己的工作内存,线程的工作 ...

  8. Java学习总结:52(Java网络编程)

    Java网络编程 开发第一个网络程序 java.net包提供了网络编程有关的开发工具类,在此包中有一下两个主要的核心操作类. ServerSocket类:是一个封装支持的TCP协议的操作类,主要工作在 ...

  9. SRWebSocket源码浅析(下)

    接上文) 四. 接着来讲讲数据的读和写: 当建立连接成功后,就会循环调用这么一个方法: //读取http头部 - (void)_readHTTPHeader; { if (_receivedHTTPH ...

最新文章

  1. DeepMind推出首个商业产品,30秒内准确诊断眼疾!
  2. R语言编程艺术(1)快速入门
  3. spring30: 事务
  4. android cts 编译,使用 Android studio 分析运行 CTS 用例
  5. 释放Linux磁盘空间方法
  6. Swift中文教程(三)--流程控制
  7. 高考成绩接近满分,却被清华北大拒绝,被称“中国最帅科学家”
  8. centos7/rhel7重置root密码 (rd.break和init方法)
  9. GBDT算法之流失预警模型
  10. txt文件的编码结构
  11. 虚拟机中无法使用鼠标滚轮(罗技鼠标)
  12. C#实现所有CRC8,CRC16,CRC32校验算法
  13. 拓嘉辰丰:把握活动规则,玩转拼多多万人团
  14. c语言程序运行一会死机,为什么函数执行完了,还能导致系统死机?
  15. 一个3位数字.COM的域名一般多少钱??
  16. IP协议与IP地址那些事
  17. 《炬丰科技-半导体工艺》 蓝宝石上的非极性氮化镓紫外光电探测器
  18. P93-好玩游戏的物品清单
  19. 圆周分孔计算公式表图_在圆上分孔怎么计?
  20. 国产操作系统,路在何方?

热门文章

  1. 数组排序(选择排序和冒泡排序)
  2. 单点登录总结(域名内与跨域名)
  3. guava Lists.transform 踩过的坑
  4. C#~异步编程再续~await与async引起的w3wp.exe崩溃-问题友好的解决
  5. Material Design学习之 Snackbars(详细分析,Toast的加强版)
  6. eclipse执行单元测试报CreateProcess error=87的解决方法
  7. PHP函数 -字符串函数
  8. javascript创建对象的几种方式 .
  9. java-HashSet源码学习
  10. 有哪些非关系型数据库