天天看點

flag -- 詭異的memcache标記引子   背景問題失落的解決方案更新版解決方案結語

引子   

     打從去年一路北漂,進入無人貨架行業,業務需求漫天飄,最近總算把工作都規劃齊整。回望過去一年多的時間裡,諸多東西值得整理,memcache就是其中一個。

   看到java的工資高些,隊伍中好些人都想學習java,美其名曰:技術多元化。奈何團隊中并沒有相關經驗的人,也深知大家殷切的期盼,是以,隻能先撸起袖子自己幹,看看書、看看部落格、看看視訊,兩個小項目就上線了,除memcache以外,過程還算順利,于是就有了這篇文章。

       正值聯考,突然感懷,當年的失利,讓自己更加堅強。

                                 

flag -- 詭異的memcache标記引子   背景問題失落的解決方案更新版解決方案結語

  

背景

  因為目前大部分項目都是.net core ,使用了memcache做為緩存伺服器,首先就是 spring boot 裡內建 memcache(使用 spymemcached 用戶端),內建過程就不說了,添加依賴,編寫幫助類,通過 @Configuration 注入就可以了。

      如果以為這樣就完了,那就沒有這個文章了,真正的故事才剛剛開始.....

問題

   配置完成後,就開始讀取已經有緩存,然後就提示:Failed to decompress data,如下圖,傳回的内容就是null,但是在指令行能讀出來。另外,我們緩存的都是string,不會存在序列化的問題(一開始還真懷疑過java與.net  string 序列化,好傻好天真)

flag -- 詭異的memcache标記引子   背景問題失落的解決方案更新版解決方案結語

        因為一開始看上圖是 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,

flag -- 詭異的memcache标記引子   背景問題失落的解決方案更新版解決方案結語
flag -- 詭異的memcache标記引子   背景問題失落的解決方案更新版解決方案結語
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 能正常傳回緩存内容了,如下圖,正常列印了

flag -- 詭異的memcache标記引子   背景問題失落的解決方案更新版解決方案結語

       剛開始真是高興了足足10秒中,畢竟嘗試了很多次失敗,但轉念一想,現在所有的項目,都得去引用自己編譯的這個版本,以後如果 Enyim.Caching 更新了,我還得去重新下載下傳、編譯,所有項目又要重新引用,想想就後怕!

       于是,第一次有了這樣的感覺:問題解決了,但是很多失落!弄完回到家,看我一臉無趣,媳婦還安慰說:“今天沒解決,明天再來,明天不行,後天再來,總會撥雲見日的!”

更新版解決方案

  缺陷的解決方案,一直萦繞心頭,揮之不去,于是,還是忍不住去查詢新的方案,還特意發起了一個博問,不過就 dudu 回複了,雖然沒有直接解決,也給了一些新的提示,并順利的看到了 spymemcached 的源碼。找到了

  解碼的類 SerializingTranscoder.java ,對于 String 并未做處理,也沒有解碼的問題。 解碼部分源碼如下,可以看到,對于 String是直接調用  decodeString

flag -- 詭異的memcache标記引子   背景問題失落的解決方案更新版解決方案結語
flag -- 詭異的memcache标記引子   背景問題失落的解決方案更新版解決方案結語
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;
  }      

     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); 找到了問題的發生地兒,離解決方案就不遠了。 第一現場很重要。

      
flag -- 詭異的memcache标記引子   背景問題失落的解決方案更新版解決方案結語
flag -- 詭異的memcache标記引子   背景問題失落的解決方案更新版解決方案結語
/**
   * 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;
  }      

      既然問題出在“解壓”這裡,那為什麼我把 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²

flag -- 詭異的memcache标記引子   背景問題失落的解決方案更新版解決方案結語
flag -- 詭異的memcache标記引子   背景問題失落的解決方案更新版解決方案結語

編輯:

妞妞 妞妞首頁

出處:http://www.cnblogs.com/jijunjian/

本文版權歸作者和部落格園共有,歡迎轉載,大家好,才是真的好!

#top