flag -- 诡异的memcache标记
引子
? ??打從去年一路北漂,進入無人貨架行業,業務需求漫天飄,最近總算把工作都規劃齊整。回望過去一年多的時間里,諸多東西值得整理,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);?
?
結語
分析到此,問題明了,方案明確,水到渠成,問題解決了。在不修改第三方源碼的基礎上,通過擴展解決了,也不用擔心第三方升級的問題了,這樣就比第一種別扭的方案舒服多了。
第一次感受到閱讀源碼,與深究一個問題的帶來的收獲 -- 杠杠的
?
?成為一名優秀的程序員!
?
版權聲明:作者:J2
編輯:妞妞
妞妞主頁
出處:http://www.cnblogs.com/jijunjian/
本文版權歸作者和博客園共有,歡迎轉載,大家好,才是真的好!
總結
以上是生活随笔為你收集整理的flag -- 诡异的memcache标记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql进阶练习
- 下一篇: 功能表单之树形选择字段类型的高级使用——