新聞中心
公司網(wǎng)站使用了Memcached來做分布式緩存,最近有人反映Memcached客戶端占用CPU過高,懷疑是第三方客戶端性能不佳,進(jìn)而懷疑是文本協(xié)議的問題,要求部門自己開發(fā)Memcached的客戶端,使其支持二進(jìn)制協(xié)議。因?yàn)橹匦麻_發(fā)客戶端工作量比較大,同時(shí)在日常開發(fā)中,沒有聽說過Memcached客戶端遇到瓶頸。因此對(duì)此問題進(jìn)行了排查。結(jié)果發(fā)現(xiàn)主要是由于客戶端反序列化,類設(shè)計(jì)不合理造成的。把排查過程分享下,希望對(duì)其他人有所幫助。

創(chuàng)新互聯(lián)長(zhǎng)期為上千余家客戶提供的網(wǎng)站建設(shè)服務(wù),團(tuán)隊(duì)從業(yè)經(jīng)驗(yàn)10年,關(guān)注不同地域、不同群體,并針對(duì)不同對(duì)象提供差異化的產(chǎn)品和服務(wù);打造開放共贏平臺(tái),與合作伙伴共同營(yíng)造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為梁子湖企業(yè)提供專業(yè)的成都網(wǎng)站制作、成都做網(wǎng)站、外貿(mào)營(yíng)銷網(wǎng)站建設(shè),梁子湖網(wǎng)站改版等技術(shù)服務(wù)。擁有十多年豐富建站經(jīng)驗(yàn)和眾多成功案例,為您定制開發(fā)。
首先想到是:Memcached服務(wù)器端內(nèi)存占滿,在清理內(nèi)存中,造成客戶端socket連接不上,不斷發(fā)生異常。隨上服務(wù)器查看了Memcached的內(nèi)存占用率,連接數(shù)等,發(fā)現(xiàn)利用率均很低。暫時(shí)先排除服務(wù)器端問題。
其次想到可能是第三方在使用socket連接池時(shí),造成資源沒有關(guān)閉,或者死鎖。隨對(duì)第三方客戶端代碼粗略讀了一遍,并搜索相關(guān)文檔。未發(fā)現(xiàn)異常代碼。暫時(shí)先排除第三方客戶端問題。
最后想到會(huì)不會(huì)是開發(fā)人員在代碼編寫中出現(xiàn)了問題。隨對(duì)反映問題的兩個(gè)產(chǎn)品進(jìn)行了排查。發(fā)現(xiàn)了以下代碼。
- static Serializer ser = new Serializer(typeof(List
)); - //using JsonExSerializer;
- public static List
GetAllUserModule(int userId) - {
- string cache = CacheManager.Current.Get
(GetCacheKey(userId)); - if (!string.IsNullOrEmpty(cache))
- {
- return ser.Deserialize(cache) as List
; - }
- else
- {
- return null;
- }
- }
- public static List
SetAllUserModule(int userId, List modules) - {
- if (modules != null)
- {
- string cache = ser.Serialize(modules);
- CacheManager.Current.Add(GetCacheKey(userId), cache);
- }
- else
- {
- CacheManager.Current.Remove(GetCacheKey(userId));
- }
- return modules;
- }
代碼片段2:
- ///
- /// 聊天室房間
- ///
- [Serializable]
- public class Room
- {
- //房間有觀看人員數(shù)據(jù)
- List
_viewers = null; - List
_blackips = null; - List
_blackviewers = null; - List
_notice = null; - List
_speakers = null; - List
_content = null; - ///
- /// 添加新聊天者
- ///
- ///
返回新添加的聊天人員 - public Viewer AddViewer()
- {
- Viewer vi = new Viewer();
- //MaxViewerID += 1;
- //int id = MaxViewerID;
- int id = GetViewerID();
- vi.Name = GetViewerName("游客" + id);
- //vi.IP = System.Web.HttpContext.Current.Request.UserHostAddress;
- vi.IP = "127.0.0.1";
- vi.ViewID = id;
- Viewers.Add(vi);
- return vi;
- }
- ///
- /// 添加聊天內(nèi)容
- ///
- /// 聊天的內(nèi)容
- /// 發(fā)言人的id
- ///
返回新添加的對(duì)象 - public Content AddContent(string content, int viewid)
- {
- MaxContentID += 1;
- Content con = new Content(DateTime.Now, content, viewid, MaxContentID);
- Contents.Add(con);
- return con;
- }
- ......
- }
調(diào)用代碼為:
- Room room = LiveSys.Get(key);
- lock (room)
- {
- if (room.MaxContentID == 0)
- {
- //ChatContentOp cpo = new ChatContentOp();
- //room.MaxContentID = cpo.GetMaxContentID();
- room.MaxContentID = 300;
- }
- int viewerID = 123124123;
- room.AddContent(chatContent, viewerID);
- //判斷內(nèi)容是否大于100條。如果大于100條,刪除最近的100條以外的數(shù)據(jù)。
- System.IO.File.AppendAllText(@"d:\haha.txt", "最大數(shù)值:" +
- room.LimitContentCount + "###############聊天記錄數(shù):" + room.Contents.Count + "\r\n");
- if (room.Contents.Count > room.LimitContentCount)
- {
- room.Contents.RemoveRange(0, room.Contents.Count - room.LimitContentCount);
- }
- }
- LiveSys.Set(key, room);
代碼1存在的問題是:
Cache存儲(chǔ)的參數(shù)類型為object,沒有必要先進(jìn)行一次序列化,然后再進(jìn)行存儲(chǔ)。而序列化是很消耗CPU的。
代碼2問題:
代碼2實(shí)現(xiàn)的是一個(gè)在線聊天室,聊天室本身含有訪客,發(fā)言等內(nèi)容。在發(fā)言時(shí),對(duì)聊天室內(nèi)容進(jìn)行判斷,只顯示最近30條。新進(jìn)來訪客直接加到訪客別表中。表面上是沒什么問題的。但是細(xì)想之下有兩個(gè)問題:
1 聊天室類設(shè)計(jì)的比較復(fù)雜,每次從Memcached服務(wù)端取得數(shù)據(jù)后,都要進(jìn)行類型轉(zhuǎn)換。
2 沒有訪客清理機(jī)制。隨著訪客的不斷進(jìn)入,對(duì)象的體積會(huì)不斷增大。
對(duì)存疑部分編寫了代碼進(jìn)行測(cè)試。測(cè)試結(jié)果果然如推測(cè)所想。測(cè)試結(jié)果如下:
|
場(chǎng)景 |
寫入 |
讀取 |
大小 (單位) |
CPU | ||||
次數(shù) | 時(shí)間 | 平均 | 次數(shù) | 時(shí)間 | 平均 | |||
本地緩存 | 10000 | 0.03125 | 0 | 10000 | 0 | 0 | 1k | 0 |
MemClient | 10000 | 19.2656 | 0.001926 | 10000 | 22.75 | 0.002275 | 1k |
|
Json1k | 1000 | 2.8437 | 0.002843 | 1000 | 5.375 | 0.005375 | 1k |
|
Json8k | 1000 | 3.8593 | 0.003859 | 1000 | 29.0312 | 0.029031 | 8k |
|
直播1000人次 | 1000 | 38.9375 | 0.038937 | 1000 |
|
| 50k |
|
直播8000人次 | 100 | 18.25 | 0.1825 | 100 |
|
| 350k |
|
500k | 100 | 7.375 | 0.07375 | 100 | 7.09375 | 0.070937 | 500k |
|
|
場(chǎng)景 |
寫入 |
讀取 |
大小 (單位) |
CPU | ||||
次數(shù) | 時(shí)間 | 平均 | 次數(shù) | 時(shí)間 | 平均 | |||
本地緩存 | 10000 | 0.03125 | 3.125E-06 | 10000 | 0.015625 | 1.5625E-06 | 1k | 0 |
MemClient | 10000 | 19.78125 | 0.001978 | 10000 | 21.953125 | 0.002195 | 1k | |
Json1k | 1000 | 2.03125 | 0.002031 | 1000 | 6.078125 | 0.006078 | 1k | |
Json8k | 1000 | 2.765625 | 0.002765 | 1000 | 55.375 | 0.055375 | 8k | |
直播1000人次 | 1000 | 38.53125 | 0.038531 | 1000 | 50k | |||
直播8000人次 | 100 | 17.96875 | 0.179687 | 1000 | 350k | |||
500k | 100 | 7.5 | 0.075 | 100 | 6.5625 | 0.065625 | 500k | |
|
場(chǎng)景 |
寫入 |
讀取 |
大小 (單位) |
CPU | ||||
次數(shù) | 時(shí)間 | 平均 | 次數(shù) | 時(shí)間 | 平均 | |||
本地緩存 | 10000 | 0.015625 | 1.5625E-06 | 10000 | 0.015625 | 1.5625E-06 | 1k | 0 |
MemClient | 10000 | 18.015625 | 0.001801 | 10000 | 25.96875 | 0.002596 | 1k | 6% |
Json1k | 1000 | 1.15625 | 0.001156 | 1000 | 3.078125 | 0.003078 | 1k | 40% |
Json8k | 1000 | 1.859375 | 0.001859 | 1000 | 32.484375 | 0.032484 | 8k | 50% |
直播1000人次 | 1000 | 45.046875 | 0.045046 | 1000 | 50k | 30-40% | ||
直播8000人次 | 100 | 31.703125 | 0.317031 | 100 | 350k | 50% | ||
500k | 100 | 7.0625 | 0.070625 | 100 | 6.421875 | 0.064218 | 500k | 6% |
直播1000人次(當(dāng)天一共有1000人訪問,數(shù)據(jù)來源于運(yùn)營(yíng)檢測(cè)),留言內(nèi)容為30條時(shí),Room體積大概為:57K
直播1000人次(當(dāng)天一共有8000人訪問,數(shù)據(jù)來源于運(yùn)營(yíng)檢測(cè)),留言內(nèi)容為30條時(shí),Room體積大概為:350k
根據(jù)圖表可以看到以下情況:處理時(shí)間、CPU利用率和數(shù)據(jù)量大小,序列化,類復(fù)雜性都有關(guān)系。
序列化問題(類型轉(zhuǎn)換)對(duì)性能影響最為明顯(可在場(chǎng)景”json1k”、場(chǎng)景直播中看到)。在Json1k中,存儲(chǔ)對(duì)象和前幾個(gè)場(chǎng)景是相同的,處理時(shí)間也相差不大,較大區(qū)別是CPU利用率由5%左右增長(zhǎng)到40%左右(反序列化時(shí)尤為明顯)。在場(chǎng)景直播系統(tǒng)中,不存在序列化問題,但是其對(duì)象屬性中存在”訪客”, ”繁衍”等多個(gè)復(fù)雜對(duì)象,造成其在處理時(shí)需要處理過多的類型轉(zhuǎn)換,同時(shí)其體積不斷增大。
存儲(chǔ)對(duì)象的大小和處理時(shí)間存在一定關(guān)系,例如場(chǎng)景”500k”,其處理時(shí)間增長(zhǎng),但是其CPU利用率并未提高,其時(shí)間增長(zhǎng)是由于對(duì)象傳輸造成。
本地緩存在內(nèi)存中進(jìn)行尋址和類型轉(zhuǎn)換,涉及不到Socket連接,網(wǎng)絡(luò)傳輸,序列化操作,所以其處理相當(dāng)快。
就測(cè)試結(jié)果看:
本地緩存性能大約是分布式緩存性能的100倍左右。而出問題的聊天室除了CPU增高以外,其性能更比分布式緩存再降低40倍(直播1000人次)到200倍(直播8000人次)。綜合來看,聊天室的分布式緩存比本地緩存降了4000倍,甚至更多。
但是,還沒有完。
對(duì)于第二個(gè)問題,更改類設(shè)計(jì),清楚無效訪客,即可解決。
但是第一個(gè)問題,為什么用戶在存儲(chǔ)之前,先進(jìn)行json序列化呢?嗯,這是一個(gè)問題。
遂問之。
答曰,有些類直接使用第三方客戶端存儲(chǔ)時(shí),直接存儲(chǔ)報(bào)錯(cuò),所以先序列化為json類型,取值時(shí)再反序列化回來。
嗯,還有這事?
開發(fā)人員說了相關(guān)代碼。
- interface IUser
- {
- String UserId{ get; set;}
- String UserName{ get; set;}
- }
- [Serializable]
- class UserInfo : IUser
- {
- String UserId{ get; set;}
- String UserName{ get; set;}
- }
- [Serializable]
- class Game
- {
- IUser User{ get; set;}
- String UserName{ get; set;}
- }
他說:Game對(duì)象在直接使用MemcachedClient時(shí),是不能被二進(jìn)制序列化的,因?yàn)槠銾ser屬性類型為IUser,為一個(gè)接口。因此想了一個(gè)解決方法,即先將Game對(duì)象進(jìn)行 json序列化將其變?yōu)樽址?,然后將字符串存?chǔ)到Memcached。
原來是這樣。
接著又查看了MemcachedClient源代碼,其需要將對(duì)象進(jìn)行二進(jìn)制序列化,然后進(jìn)行存儲(chǔ)。接口屬性不能被序列化,遂又對(duì)序列化問題進(jìn)行了測(cè)試(見附件)。測(cè)試結(jié)果顯示上述代碼直接進(jìn)行二進(jìn)制序列化是可以的,同時(shí)直接使用第三方客戶端也是可以可行的。
問題出在哪?難道是沒有加[Serializable]。
一查果然:一個(gè)Serializable引發(fā)的血案。
記得有人說過,慎用分布式,能不用盡量不用。
一方面在性能上確實(shí)下降很多,分布式存儲(chǔ)主要性能消耗在以下幾個(gè)方面:協(xié)議解析,Socket連接,數(shù)據(jù)傳輸,序列化/類型轉(zhuǎn)換。
一方面在使用場(chǎng)景和類設(shè)計(jì)上要求也更加嚴(yán)格。個(gè)人認(rèn)為Memcached是不太適合存儲(chǔ)特別大的文件的。雖然有人說網(wǎng)上已經(jīng)有用來存儲(chǔ)視頻的。
還有幾個(gè)問題希望知道的朋友回答下:
1 有沒有.Net方面的Memcached客戶端支持二進(jìn)制協(xié)議和一致性的?
2 測(cè)試中發(fā)現(xiàn),當(dāng)Memcached設(shè)置緩存過小時(shí)(例如64M),當(dāng)其內(nèi)存使用已經(jīng)到62M時(shí),再進(jìn)行存儲(chǔ),新存儲(chǔ)的內(nèi)容再取出來就是空值,不知道是什么原因。
當(dāng)前名稱:關(guān)于Memcached客戶端CPU過高問題的排查
文章出自:http://m.5511xx.com/article/dhshghe.html


咨詢
建站咨詢
