文档章节

flag -- 诡异的memcache标记

2J
 2J
发布于 06/13 09:20
字数 2228
阅读 176
收藏 1
点赞 0
评论 1

引子   

     打从去年一路北漂,进入无人货架行业,业务需求漫天飘,最近总算把工作都规划齐整。回望过去一年多的时间里,诸多东西值得整理,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);

 

 

结语

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

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

 

   成为一名优秀的程序员!

 

© 著作权归作者所有

共有 人打赏支持
2J

2J

粉丝 16
博文 10
码字总数 28472
作品 0
杭州
技术主管
加载中

评论(1)

滔哥
滔哥
我了个去,这个代码高亮真是那红薯R了那谁了。。。。
php操作memcache的使用测试总结

1.简介 memcache模块是一个高效的守护进程,提供用于内存缓存的过程式程序和面向对象的方便的接口,特别是对于设计动态web程序时减少对数据库的访问。 memcache也提供用于通信对话(session...

tiger_lee ⋅ 2014/02/19 ⋅ 0

Memcache学习总结2-Memcache的使用基本介绍

Memcache学习总结2-Memcache的使用基本介绍 上一次总结中我们已经安装部署好了Memcached,并且把PHP扩展Memcache也安装好了,这一节我们详细学习一下PHP扩展Memcache。Memcache客户端包含两组...

开元中国2015 ⋅ 2015/04/04 ⋅ 0

PHP缓存技术:memcache函数详解之一

Memcache函数库是在PECL(PHP Extension Community Library)中,主要作用是搭建大容量的内存数据的临时存放区域,在分布式的时候作用体现的非常明 显,网络营销培训否则不建议使用。 Memcache...

网络营销 ⋅ 2012/02/15 ⋅ 0

PHP中Memcache类函数详解

Memcache类函数列表如下: Memcache::add – 添加一个值,如果已经存在,则返回false Memcache::addServer – 添加一个可供使用的服务器地址 Memcache::close – 关闭一个Memcache对象 Memc...

Junn ⋅ 2015/01/06 ⋅ 0

php json_decode 返回值为null

今天工作中碰到一个很诡异的问题。从memcache中取出的json字符串无法解析。返回值为null。 我在做一个游戏的认证接口,客户端传入一串加密过的token,我解密以后去memcache中获取用户认证数据...

ClownFish ⋅ 2012/03/22 ⋅ 2

PHP中的Memcache详解

一、Memcache简介 Memcache是danga.com的一个项目,最早是为 LiveJournal 服务的,目前全世界不少人使用这个缓存项目来构建自己大负载的网站,来分担数据库的压力。它可以应对任意多个连接,...

沉淀岁月 ⋅ 2016/09/09 ⋅ 0

memcache java 客户端与c#客户端 数据不一致问题

使用 memcache 我在.net 里使用 enyim.caching 缓存。 在java 里使用 spymemcached 读取 不到,但是控制台都可以看到。 后来了解到,每个memcache 有一个flag,不同的客户端 不同。 我修改了...

2J ⋅ 05/28 ⋅ 0

Memcache所有方法及参数详解以及使用方法

参考http://www.php.net/manual/zh/function.Memcache-add.php Memcache::add - 添加一个值,如果已经存在,则返回false Memcache::addServer - 添加一个可供使用的服务器地址 Memcache::cl...

zchd ⋅ 2013/02/21 ⋅ 0

memcached 和 memcache 比较

1.相同点 (1)都是内存缓存,守护进程都是memcached,二者同名; (2)都无持久化特性。 2.不同点 (1)memcache 是完全在PHP框架内开发的,memecached是基于 libmemcached 开发的。从手册上...

酒肉穿肠过 ⋅ 2013/08/07 ⋅ 0

python memcached 初探(一)

环境:ubuntun 语言:python IDE:pycharm 安装memcached: sudo apt-get install memcached 检测安装是否成功: 输入memcached 默认就启动了,什么也不提示,如果没成功有错误提示 安装 pyth...

山下狮子 ⋅ 2014/05/20 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Linux中的端口大全

1 被LANA定义的端口 端口 名称 描述 1 tcpmux TCP 端口服务多路复用 5 rje 远程作业入口 7 echo Echo 服务 9 discard 用于连接测试的空服务 11 systat 用于列举连接了的端口的系统状态 13 d...

寰宇01 ⋅ 16分钟前 ⋅ 0

Confluence 6 如何备份存储文件和页面信息

备份的 ZIP 文件包含有 entities.xml,这个 XML 文件包含有 Confluence 的所有页面内容和存储附件的目录。 备份 Zip 文件结构 页面的附件是存储在附件存储目录中的,通过页面和附件 ID 进行识...

honeymose ⋅ 19分钟前 ⋅ 0

【每天一个JQuery特效】根据状态确定是否滑入或滑出被选元素

主要效果: 本文主要采用slideToggle()方法实现以一行代码同时实现以展开或收缩的方式显示或隐藏被选元素。 主要代码如下: <!DOCTYPE html><html><head><meta charset="UTF-8">...

Rhymo-Wu ⋅ 22分钟前 ⋅ 0

度量.net framework 迁移到.net core的工作量

把现有的.net framework程序迁移到.net core上,是一个非常复杂的工作,特别是一些API在两个平台上还不能同时支持。两个类库的差异性,通过人工很难识别全。好在微软的工程师们考虑到了我们顾...

李朝强 ⋅ 28分钟前 ⋅ 0

请不要在“微服务”的狂热中迷失自我!

微服务在过去几年一直是一个非常热门的话题(附录1)。何为“微服务的疯狂”,举个例子: 众所周知,Netflix在DevOps上的表现非常棒。Netfix可以做微服务。因此:如果我做微服务,我也将非常...

harries ⋅ 29分钟前 ⋅ 0

oAuth2 升级Spring Cloud Finchley.RELEASE踩坑分享

背景 6.19号,spring团队发布了期待已久的 Spring Cloud Finchley.RELEASE 版本。 重要变化: 基于Spring Boot 2.0.X 不兼容 Spring Boot 1.5.X 期间踩过几个坑,分享出来给大伙,主要是关于...

冷冷gg ⋅ 59分钟前 ⋅ 0

OSChina 周一乱弹 —— 理发师小姐姐的魔法

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @冰冰棒- :分享田馥甄的单曲《My Love》 《My Love》- 田馥甄 手机党少年们想听歌,请使劲儿戳(这里) @Li-Wang :哎,头发又长了。。。又要...

小小编辑 ⋅ 今天 ⋅ 8

Kafka1.0.X_消费者API详解2

偏移量由消费者管理 kafka Consumer Api还提供了自己存储offset的功能,将offset和data做到原子性,可以让消费具有Exactly Once 的语义,比kafka默认的At-least Once更强大 消费者从指定分区...

特拉仔 ⋅ 今天 ⋅ 0

NEO智能合约之发布和升级(二)

接NEO智能合约之发布和升级(一),我们接下来说说智能合约的升级功能。 一 准备工作 合约的升级需要在合约内预先设置好升级接口,以方便在升级时调用。接下来我们对NEO智能合约之发布和升级...

红烧飞鱼 ⋅ 今天 ⋅ 0

个人博客的运营模式能否学习TMALL天猫质量为上?

心情随笔|个人博客的运营模式能否学习TMALL天猫质量为上? 中国的互联网已经发展了很多年了,记得在十年前,个人博客十分流行,大量的人都在写博客,而且质量还不错,很多高质量的文章都是在...

原创小博客 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部