<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    繼插件化后,熱補丁技術在2015年開始爆發,目前已經是非常熱門的Android開發技術。其中比較著名的有淘寶的Dexposed、支付寶的AndFix以及Qzone的超級熱補丁方案。微信對熱補丁技術的研究并不算早,大約開始于2015年6月。經過研究與嘗試現有的各個方案,我們發現它們都有著自身的一些局限性。微信最終采用不同于它們的技術方案,走出了自己的實踐演進之路。

    另外一方面,技術應當只是熱補丁方案中的一環。隨著對熱補丁的多次嘗試與應用,微信建立起自身的流程規范,同時也不斷的嘗試拓展它的應用場景。通過本文,我希望大家不僅能夠全面的了解各項熱補丁技術的優缺點,同時也能對它的應用場景有著更加全面的認識。在此基礎上,大家或許能更容易的決定是否在自己的項目中使用熱補丁技術,以及應當如何使用它。

    為什么需要熱補丁

    熱補?。鹤寫媚軌蛟跓o需重新安裝的情況實現更新,幫助應用快速建立動態修復能力。

    從上面的定義來看,熱補丁節省Android大量應用市場發布的時間。同時用戶也無需重新安裝,只要上線就能無感知的更新??雌饋砗苊篮茫@是否可以意味我們可以盡量使用補丁來代替發布呢?事實上,熱補丁技術當前依然存在它的局限性,主要表現在以下幾點:

    1. 補丁只能針對單一客戶端版本,隨著版本差異變大補丁體積也會增大;

    2. 補丁不能支持所有的修改,例如AndroidManifest;

    3. 補丁無論對代碼還是資源的更新成功率都無法達到100%。

    既然補丁技術無法完全代替升級,那它適合使用在哪些場景呢?

    一. 輕量而快速的升級

    熱補丁技術也可以理解為一個動態修改代碼與資源的通道,它適合于修改量較少的情況。以微信的多次發布為例,補丁大小均在300K以內,它相對于傳統的發布有著很大的優勢。

    以Android用戶的升級習慣,即使是相對活躍的微信也需要10天以上的時間去覆蓋50%的用戶。使用補丁技術,我們能做到1天覆蓋70%以上。這也是基于補丁體積較小,可以直接使用移動網絡下載更新。

    正因如此,補丁技術非常適合使用在灰度階段。在過去,我們需要在正式發布前保證所有嚴重的問題都已經得到修復,這通常需要我們經過三次以上的灰度過程,而且無法快速的驗證這些問題在同一批用戶的修復效果。利用熱補丁技術,我們可以快速對同一批用戶驗證修復效果,這大大縮短我們的發布流程。

    若發布版本出現問題或緊急漏洞,傳統方式需要單獨灰度驗證修改,然后重新發布新的版本。利用補丁技術,我們只需要先上線小部分用戶驗證修改的效果,最后再全量上線即可。但是此種發布對線上用戶影響較大, 我們需要謹慎而為。本著對用戶負責的態度, 發布補丁等同于發布版本 ,它也應該嚴格執行完整的測試與上線流程。

    總的來說,補丁技術可以降低開發成本,縮短開發周期,實現輕量而快速的升級。

    二. 遠端調試

    一入Android深似海,Android開發的另外一個痛是機型的碎片化。我們也許都會遇到"本地不復現","日志查不出","聯系用戶不鳥你"的煩惱。所以補丁機制非常適合使用在遠端調試上。即我們需要具備只特定用戶發送補丁的能力,這對我們查找問題非常有幫助。

    利用補丁技術,我們避免了騷擾用戶而默默的為用戶解決問題。當然這也需要非常嚴格的權限管理,以防惡意或隨意使用。

    三. 數據統計

    數據統計在微信中也占據著非常重要的位置,我們也非常希望將熱補丁與數據統計結合的更好。事實上,熱補丁無論在普通的數據統計還是ABTest都有著非常大的優勢。例如若我想對同一批用戶做兩種test, 傳統方式無法讓這批用戶去安裝兩個版本。使用補丁技術,我們可以方便的對同一批用戶更換補丁版本。在數據統計之路,如何與補丁技術結合的更好,更加精準的控制樣本人數與比例,這也是微信當前努力發展的一個方向。

    四. 其他

    事實上,Android官方也使用熱補丁技術實現Instant Run。它分為Hot Swap、Warm Swap與Cold Swap三種方式,大家可以參考 英文介紹 ,也可以看參考文章中的翻譯稿。最新的Instant App應該也是采用類似的原理,但是Google Play是不允許下發代碼的,這個海外App需要注意一下。

    微信熱補丁技術的演進之路

    在了解補丁技術可以與適合做什么之后,我們回到技術本身。由于 Dexposed 無法支持全平臺,并不適合應用到商業產品中。所以這里我們只簡單介紹Andfix、Qzone、微信幾套方案的實現,以及它們方案面臨著的問題,大家也可以參考資料中的 各大熱補丁方案分析和比較 一文。

    一. AndFix

    AndFix 采用native hook的方式,這套方案直接使用 dalvik_replaceMethod 替換class中方法的實現。由于它并沒有整體替換class, 而field在class中的相對地址在class加載時已確定,所以AndFix無法支持新增或者刪除filed的情況(通過替換 init 與clinit 只可以修改field的數值)。

    也正因如此,Andfix可以支持的補丁場景相對有限,僅僅可以使用它來修復特定問題。結合之前的發布流程,我們更希望補丁對開發者是不感知的,即他不需要清楚這個修改是對補丁版本還是正式發布版本(事實上我們也是使用git分支管理+cherry-pick方式)。另一方面,使用native替換將會面臨比較復雜的兼容性問題。

    相比其他方案,AndFix的最大優點在于立即生效。事實上,AndFix的實現與 Instant Run的熱插拔 有點類似,但是由于使用場景的限制,微信在最初期已排除使用這一方案。

    二. Qzone

    Qzone方案并沒有開源,但在github上的 Nuwa 采用了相同的方式。這個方案使用classloader的方式,能實現更加友好的類替換。而且這與我們加載Multidex的做法相似,能基本保證穩定性與兼容性。具體原理在這里不再細說,大家可以參考"安卓App熱補丁動態修復技術介紹"這篇文章。

    本方案為了解決 unexpected DEX problem 異常而采用插樁的方式,從而規避問題的出現。事實上,Android系統的這些檢查規則是非常有意義的,這會導致Qzone方案在Dalvik與Art都會產生一些問題。

    • Dalvik; 在dexopt過程,若class verify通過會寫入pre-verify標志,在經過optimize之后再寫入odex文件。這里的optimize主要包括inline以及quick指令優化等。

    若采用插樁導致所有類都非preverify,這導致verify與optimize操作會在加載類時觸發。這會有一定的性能損耗,微信分別采用插樁與不插樁兩種方式做過兩種測試,一是連續加載700個50行左右的類,一是統計微信整個啟動完成的耗時。

    平均每個類verify+optimize(跟類的大小有關系)的耗時并不長,而且這個耗時每個類只有一次。但由于啟動時會加載大量的類,在這個情況影響還是比較大。

    • Art; Art采用了新的方式,插樁對代碼的執行效率并沒有什么影響。但是若補丁中的類出現修改類變量或者方法,可能會導致出現內存地址錯亂的問題。為了解決這個問題我們需要將修改了變量、方法以及接口的類的父類以及調用這個類的所有類都加入到補丁包中。這可能會帶來補丁包大小的急劇增加。

    這里是因為在dex2oat時 fast* 已經將類能確定的各個地址寫死。如果運行時補丁包的地址出現改變,原始類去調用時就會出現地址錯亂。這里說的可能不夠詳細,事實上微信當時為了查清這兩個問題,也花費了一定的時間將Dalvik跟Art的流程基本搞透。若大家對這里感興趣,后續在單獨的文章詳細論述。

    總的來說,Qzone方案好處在于開發透明,簡單,這一套方案目前的應用成功率也是最高的, 但在補丁包大小與性能損耗上有一定的局限性。特別是無論我們是否真正應用補丁,都會因為插樁導致對程序運行時的性能產生影響。微信對于性能要求較高,所以我們也沒有采用這套方案。

    三. 微信熱補丁方案

    有沒有那么一種方案,能做到開發透明,但是卻沒有Qzone方案的缺陷呢?Instant Run的冷插拔與buck的 exopackage 或許能給我們靈感,它們的思想都是全量替換新的Dex。即我們完全使用了新的Dex,那樣既不出現Art地址錯亂的問題,在Dalvik也無須插樁。當然考慮到補丁包的體積,我們不能直接將新的Dex放在里面。但我們可以將新舊兩個Dex的差異放到補丁包中,最簡單我們可以采用BsDiff算法。

    簡單來說,在編譯時通過新舊兩個Dex生成差異patch.dex。在運行時,將差異patch.dex重新跟原始安裝包的舊Dex還原為新的Dex。這個過程可能比較耗費時間與內存,所以我們是單獨放在一個后臺進程:patch中。為了補丁包盡量的小,微信自研了DexDiff算法,它深度利用Dex的格式來減少差異的大小。它的粒度是Dex格式的每一項,可以充分利用原本Dex的信息,而BsDiff的粒度是文件,AndFix/Qzone的粒度為class。

    這塊后面我希望后面用單獨的文章來講述,這里先做一個鋪墊,大致的效果如下圖。在最極端的情況,由于利用了原本dex的信息完全替換一個13M的Dex,我們的補丁大小也僅僅只有6.6M。

    但是這套方案并非沒有缺點,它帶來的問題有兩個:

    1. 占用Rom體積;這邊大約是你所修改Dex大小的1.5倍( Dex壓縮成jar的大小加上 生成的dexopt文件大小)。

    2. 一個額外的合成過程;雖然我們單獨放在一個進程上處理,但是合成時間的長短與內存消耗也會影響最終的成功率(與修改Dex大小、補丁大小相關)。

    微信的熱補丁方案叫做Tinker,也算緬懷一下Dota中的地精修補匠,希望能做到無限刷新。

    限于篇幅,這里對Dex、library以及資源的更多技術細節并沒有詳細的論述,這里希望放在后面的單獨文章中。我們最后從整體比較一下這幾種方案:

    若不care性能損耗與補丁包大小,Qzone方案是最簡單且成功率最高的方案(沒有單獨的合成過程)。相對Tinker來說,它的占用Rom體積也更小。另一方面,Qzone與Tinker的成功率當前大約相差3%左右。

    事實上,一個完整的框架應該也是一個容易使用的框架。Tinker對補丁版本管理、進程管理、安全校驗等都有著很好的支持。同時我們也支持gradle與命名行兩種接入方式。希望在不久的將來,它可以很快的跟大家見面。

    微信的熱補丁應用現狀

    上一章節我們簡單比較了各個熱補丁的技術方案,它們解決了如何生成與加載補丁包的問題。但一個完善的熱補丁系統不應該僅限于此,它還需要包括以下幾個方面:

    • 網絡通道;這里要解決的問題是決定補丁以何種方式推送給哪部分的用戶。

    • 上線與后臺管理平臺;這里主要包括熱補丁的上線管理,歷史管理以及上報分析,報警監控等;

    一. 網絡通道現狀

    網絡通道負責的將補丁包交付給用戶,這個包括特定用戶與全量用戶兩種情況。事實上,微信當前針對熱補丁有以下三種通道更新:

    • pull通道; 在登陸/24小時等時機,通過pull方式查詢后臺是否有對應的補丁包更新,這也是我們最常用的方式;

    • 指定版本的push通道; 針對版本的通道,在緊急情況下,我們可以在一個小時內向所有用戶下發補丁包更新。

    • 指定特定用戶的push通道;對特定用戶或用戶組做遠程調試。

    事實上,對于大部分的應用來說,假設不實現push通道,CDN+pull通道實現起來還是較為容易。

    二. 上線與管理平臺現狀

    上線與管理平臺主要為了快速上線,管理歷史記錄,以及監控補丁的運行情況等(界面比較丑陋,因為我們木有美工啊)。

    事實上,微信發布熱補丁是非常慎重的。它整個發布流程與升級版本是保持一致的,也必須修改版本號、經過嚴格的完整測試流程等。我們也會通過灰度的方式上線,同時監控補丁版本的各個指標。這里的為了完整的監控補丁的情況,我們做的工作有:

    • 1分鐘粒度的每小時/每天的各版本累積用戶,及時監控補丁版本的人數與活躍;

    • 3分鐘粒度的Crash統計,基準版本與補丁版本的Crash每小時/每天的兩個維度對照;

    • 10分鐘粒度的補丁監控信息上報。

    三. 補丁成功率現狀

    應用成功率= 補丁版本人數/補丁發布前該版本人數

    由于可能存在基準或補丁版本用戶安裝了其他版本,所以本統計結果應略為偏低,但它能現實的反應補丁的線上覆蓋情況。

    使用Qzone方案,微信補丁在10天后的應用成功率大約在98.5%左右。使用Tinker大約只有95.5%左右,主要原因在于空間不足以及后臺進程被殺。在這里我們也在嘗試使用重試的方式以及降低合成的耗時與內存,從而提升成功率。

    熱補丁技術發展的很快,Android推出的Instant App也令人期待。但是在國內,似乎我們還是指望自己更靠譜一點。每一個的應用的需求都不太一致,這里大致講了一些微信的實踐經驗,希望對大家有幫助。

    未來工作

    隨著微信部門內從“單APP”向“多APP”演進,微信也正在邁入開源化的開發實踐。我們希望將各個功能組件化,從而做可以到快速復制與應用。微信的熱補丁框架“Tinker”當前也在經歷從微信分離,又合入到微信的過程。希望在不久的將來,我們也可以將“Tinker”以及微信中一些其他的組件開源出去。

    我們也希望可以找一些App作為內測,給我們提供寶貴的意見。若對微信的Tinker方案感興趣的用戶,可以單獨發消息或在文章末留言注明姓名、所在公司以及負責的App,我們希望挑選部分產品作為內測。

    參考文章

    大家可以點擊下方的閱讀原文,即可直接點擊跳轉。

    1. Dexposed github ( https://github.com/alibaba/dexposed )

    2. AndFix github ( https://github.com/alibaba/AndFix )

    3. Nuwa github ( https://github.com/jasonross/Nuwa )

    4. Qzone實現原理解析 ( https://mp.weixin.qq.com/s?__biz=MzI1MTA1MzM2Nw==&mid=400118620&idx=1&sn=b4fdd5055731290eef12ad0d17f39d4a )

    5. Instant Run英文原文 ( https://medium.com/google-developers/instant-run-how-does-it-work-294a1633367f#.c088qhdxu )

    6. Instant Run工作原理及用法中文翻譯稿 ( http://www.jianshu.com/p/2e23ba9ff14b )

    7. Buck exopackage 介紹 ( https://buckbuild.com/article/exopackage.html )

    8. 各大熱補丁方案分析和比較 (http://blog.zhaiyifan.cn/2015/11/20/HotPatchCompare/ )

    posted @ 2016-07-07 16:10 小馬歌 閱讀(226) | 評論 (0)編輯 收藏
     
         摘要: from:http://mp.weixin.qq.com/s?__biz=MzAwNDY1ODY2OQ==&mid=207243549&idx=1&sn=4ebe4beb8123f1b5ab58810ac8bc5994&scene=0#rd前言:在13年11月中旬時,因為基礎組件組人手緊張,Leo安排我和春哥去廣州輪崗支援。剛到廣州的時候,Ray讓我和春哥對Line...  閱讀全文
    posted @ 2016-07-07 16:09 小馬歌 閱讀(303) | 評論 (0)編輯 收藏
     

    from:http://www.ibm.com/developerworks/cn/web/1103_zhaoct_recommstudy1/


    “探索推薦引擎內部的秘密”系列將帶領讀者從淺入深的學習探索推薦引擎的機制,實現方法,其中還涉及一些基本的優化方法,例如聚類和分類的應用。同時在理論講解的基礎上,還會結合 Apache Mahout 介紹如何在大規模數據上實現各種推薦策略,進行策略優化,構建高效的推薦引擎的方法。本文作為這個系列的第一篇文章,將深入介紹推薦引擎的工作原理,和其中涉及的各種推薦機制,以及它們各自的優缺點和適用場景,幫助用戶清楚的了解和快速構建適合自己的推薦引擎。

    信息發現

    如今已經進入了一個數據爆炸的時代,隨著 Web 2.0 的發展, Web 已經變成數據分享的平臺,那么,如何讓人們在海量的數據中想要找到他們需要的信息將變得越來越難。

    在這樣的情形下,搜索引擎(Google,Bing,百度等等)成為大家快速找到目標信息的最好途徑。在用戶對自己需求相對明確的時候,用搜索引擎很方便的通過關鍵字搜索很快的找到自己需要的信息。但搜索引擎并不能完全滿足用戶對信息發現的需求,那是因為在很多情況下,用戶其實并不明確自己的需要,或者他們的需求很難用簡單的關鍵字來表述。又或者他們需要更加符合他們個人口味和喜好的結果,因此出現了推薦系統,與搜索引擎對應,大家也習慣稱它為推薦引擎。

    隨著推薦引擎的出現,用戶獲取信息的方式從簡單的目標明確的數據的搜索轉換到更高級更符合人們使用習慣的信息發現。

    如今,隨著推薦技術的不斷發展,推薦引擎已經在電子商務 (E-commerce,例如 Amazon,當當網 ) 和一些基于 social 的社會化站點 ( 包括音樂,電影和圖書分享,例如豆瓣,Mtime 等 ) 都取得很大的成功。這也進一步的說明了,Web2.0 環境下,在面對海量的數據,用戶需要這種更加智能的,更加了解他們需求,口味和喜好的信息發現機制。

    回頁首

    推薦引擎

    前面介紹了推薦引擎對于現在的 Web2.0 站點的重要意義,這一章我們將講講推薦引擎到底是怎么工作的。推薦引擎利用特殊的信息過濾技術,將不同的物品或內容推薦給可能對它們感興趣的用戶。

     


    圖 1. 推薦引擎工作原理圖
    圖 1. 推薦引擎工作原理圖 

    圖 1 給出了推薦引擎的工作原理圖,這里先將推薦引擎看作黑盒,它接受的輸入是推薦的數據源,一般情況下,推薦引擎所需要的數據源包括:

    • 要推薦物品或內容的元數據,例如關鍵字,基因描述等;
    • 系統用戶的基本信息,例如性別,年齡等
    • 用戶對物品或者信息的偏好,根據應用本身的不同,可能包括用戶對物品的評分,用戶查看物品的記錄,用戶的購買記錄等。其實這些用戶的偏好信息可以分為兩類:
    • 顯式的用戶反饋:這類是用戶在網站上自然瀏覽或者使用網站以外,顯式的提供反饋信息,例如用戶對物品的評分,或者對物品的評論。
    • 隱式的用戶反饋:這類是用戶在使用網站是產生的數據,隱式的反應了用戶對物品的喜好,例如用戶購買了某物品,用戶查看了某物品的信息等等。

    顯式的用戶反饋能準確的反應用戶對物品的真實喜好,但需要用戶付出額外的代價,而隱式的用戶行為,通過一些分析和處理,也能反映用戶的喜好,只是數據不是很精確,有些行為的分析存在較大的噪音。但只要選擇正確的行為特征,隱式的用戶反饋也能得到很好的效果,只是行為特征的選擇可能在不同的應用中有很大的不同,例如在電子商務的網站上,購買行為其實就是一個能很好表現用戶喜好的隱式反饋。

    推薦引擎根據不同的推薦機制可能用到數據源中的一部分,然后根據這些數據,分析出一定的規則或者直接對用戶對其他物品的喜好進行預測計算。這樣推薦引擎可以在用戶進入的時候給他推薦他可能感興趣的物品。

    推薦引擎的分類

    推薦引擎的分類可以根據很多指標,下面我們一一介紹一下:

    1. 推薦引擎是不是為不同的用戶推薦不同的數據

      根據這個指標,推薦引擎可以分為基于大眾行為的推薦引擎和個性化推薦引擎

      • 根據大眾行為的推薦引擎,對每個用戶都給出同樣的推薦,這些推薦可以是靜態的由系統管理員人工設定的,或者基于系統所有用戶的反饋統計計算出的當下比較流行的物品。
      • 個性化推薦引擎,對不同的用戶,根據他們的口味和喜好給出更加精確的推薦,這時,系統需要了解需推薦內容和用戶的特質,或者基于社會化網絡,通過找到與當前用戶相同喜好的用戶,實現推薦。

      這是一個最基本的推薦引擎分類,其實大部分人們討論的推薦引擎都是將個性化的推薦引擎,因為從根本上說,只有個性化的推薦引擎才是更加智能的信息發現過程。

    2. 根據推薦引擎的數據源

      其實這里講的是如何發現數據的相關性,因為大部分推薦引擎的工作原理還是基于物品或者用戶的相似集進行推薦。那么參考圖 1 給出的推薦系統原理圖,根據不同的數據源發現數據相關性的方法可以分為以下幾種:

      • 根據系統用戶的基本信息發現用戶的相關程度,這種被稱為基于人口統計學的推薦(Demographic-based Recommendation)
      • 根據推薦物品或內容的元數據,發現物品或者內容的相關性,這種被稱為基于內容的推薦(Content-based Recommendation)
      • 根據用戶對物品或者信息的偏好,發現物品或者內容本身的相關性,或者是發現用戶的相關性,這種被稱為基于協同過濾的推薦(Collaborative Filtering-based Recommendation)。
    3. 根據推薦模型的建立方式

      可以想象在海量物品和用戶的系統中,推薦引擎的計算量是相當大的,要實現實時的推薦務必需要建立一個推薦模型,關于推薦模型的建立方式可以分為以下幾種:

      • 基于物品和用戶本身的,這種推薦引擎將每個用戶和每個物品都當作獨立的實體,預測每個用戶對于每個物品的喜好程度,這些信息往往是用一個二維矩陣描述的。由于用戶感興趣的物品遠遠小于總物品的數目,這樣的模型導致大量的數據空置,即我們得到的二維矩陣往往是一個很大的稀疏矩陣。同時為了減小計算量,我們可以對物品和用戶進行聚類, 然后記錄和計算一類用戶對一類物品的喜好程度,但這樣的模型又會在推薦的準確性上有損失。
      • 基于關聯規則的推薦(Rule-based Recommendation):關聯規則的挖掘已經是數據挖掘中的一個經典的問題,主要是挖掘一些數據的依賴關系,典型的場景就是“購物籃問題”,通過關聯規則的挖掘,我們可以找到哪些物品經常被同時購買,或者用戶購買了一些物品后通常會購買哪些其他的物品,當我們挖掘出這些關聯規則之后,我們可以基于這些規則給用戶進行推薦。
      • 基于模型的推薦(Model-based Recommendation):這是一個典型的機器學習的問題,可以將已有的用戶喜好信息作為訓練樣本,訓練出一個預測用戶喜好的模型,這樣以后用戶在進入系統,可以基于此模型計算推薦。這種方法的問題在于如何將用戶實時或者近期的喜好信息反饋給訓練好的模型,從而提高推薦的準確度。

    其實在現在的推薦系統中,很少有只使用了一個推薦策略的推薦引擎,一般都是在不同的場景下使用不同的推薦策略從而達到最好的推薦效果,例如 Amazon 的推薦,它將基于用戶本身歷史購買數據的推薦,和基于用戶當前瀏覽的物品的推薦,以及基于大眾喜好的當下比較流行的物品都在不同的區域推薦給用戶,讓用戶可以從全方位的推薦中找到自己真正感興趣的物品。


    深入推薦機制

    這一章的篇幅,將詳細介紹各個推薦機制的工作原理,它們的優缺點以及應用場景。

    基于人口統計學的推薦

    基于人口統計學的推薦機制(Demographic-based Recommendation)是一種最易于實現的推薦方法,它只是簡單的根據系統用戶的基本信息發現用戶的相關程度,然后將相似用戶喜愛的其他物品推薦給當前用戶,圖 2 給出了這種推薦的工作原理。


    圖 2. 基于人口統計學的推薦機制的工作原理
    圖 2. 基于人口統計學的推薦機制的工作原理 

    從圖中可以很清楚的看到,首先,系統對每個用戶都有一個用戶 Profile 的建模,其中包括用戶的基本信息,例如用戶的年齡,性別等等;然后,系統會根據用戶的 Profile 計算用戶的相似度,可以看到用戶 A 的 Profile 和用戶 C 一樣,那么系統會認為用戶 A 和 C 是相似用戶,在推薦引擎中,可以稱他們是“鄰居”;最后,基于“鄰居”用戶群的喜好推薦給當前用戶一些物品,圖中將用戶 A 喜歡的物品 A 推薦給用戶 C。

    這種基于人口統計學的推薦機制的好處在于:

    1. 因為不使用當前用戶對物品的喜好歷史數據,所以對于新用戶來講沒有“冷啟動(Cold Start)”的問題。
    2. 這個方法不依賴于物品本身的數據,所以這個方法在不同物品的領域都可以使用,它是領域獨立的(domain-independent)。

    那么這個方法的缺點和問題是什么呢?這種基于用戶的基本信息對用戶進行分類的方法過于粗糙,尤其是對品味要求較高的領域,比如圖書,電影和音樂等領域,無法得到很好的推薦效果??赡茉谝恍╇娮由虅盏木W站中,這個方法可以給出一些簡單的推薦。另外一個局限是,這個方法可能涉及到一些與信息發現問題本身無關卻比較敏感的信息,比如用戶的年齡等,這些用戶信息不是很好獲取。

    基于內容的推薦

    基于內容的推薦是在推薦引擎出現之初應用最為廣泛的推薦機制,它的核心思想是根據推薦物品或內容的元數據,發現物品或者內容的相關性,然后基于用戶以往的喜好記錄,推薦給用戶相似的物品。圖 3 給出了基于內容推薦的基本原理。


    圖 3. 基于內容推薦機制的基本原理
    圖 3. 基于內容推薦機制的基本原理 

    圖 3 中給出了基于內容推薦的一個典型的例子,電影推薦系統,首先我們需要對電影的元數據有一個建模,這里只簡單的描述了一下電影的類型;然后通過電影的元數據發現電影間的相似度,因為類型都是“愛情,浪漫”電影 A 和 C 被認為是相似的電影(當然,只根據類型是不夠的,要得到更好的推薦,我們還可以考慮電影的導演,演員等等);最后實現推薦,對于用戶 A,他喜歡看電影 A,那么系統就可以給他推薦類似的電影 C。

    這種基于內容的推薦機制的好處在于它能很好的建模用戶的口味,能提供更加精確的推薦。但它也存在以下幾個問題:

    1. 需要對物品進行分析和建模,推薦的質量依賴于對物品模型的完整和全面程度。在現在的應用中我們可以觀察到關鍵詞和標簽(Tag)被認為是描述物品元數據的一種簡單有效的方法。
    2. 物品相似度的分析僅僅依賴于物品本身的特征,這里沒有考慮人對物品的態度。
    3. 因為需要基于用戶以往的喜好歷史做出推薦,所以對于新用戶有“冷啟動”的問題。

    雖然這個方法有很多不足和問題,但他還是成功的應用在一些電影,音樂,圖書的社交站點,有些站點還請專業的人員對物品進行基因編碼,比如潘多拉,在一份報告中說道,在潘多拉的推薦引擎中,每首歌有超過 100 個元數據特征,包括歌曲的風格,年份,演唱者等等。

    基于協同過濾的推薦

    隨著 Web2.0 的發展,Web 站點更加提倡用戶參與和用戶貢獻,因此基于協同過濾的推薦機制因運而生。它的原理很簡單,就是根據用戶對物品或者信息的偏好,發現物品或者內容本身的相關性,或者是發現用戶的相關性,然后再基于這些關聯性進行推薦。基于協同過濾的推薦可以分為三個子類:基于用戶的推薦(User-based Recommendation),基于項目的推薦(Item-based Recommendation)和基于模型的推薦(Model-based Recommendation)。下面我們一個一個詳細的介紹著三種協同過濾的推薦機制。

    基于用戶的協同過濾推薦

    基于用戶的協同過濾推薦的基本原理是,根據所有用戶對物品或者信息的偏好,發現與當前用戶口味和偏好相似的“鄰居”用戶群,在一般的應用中是采用計算“K- 鄰居”的算法;然后,基于這 K 個鄰居的歷史偏好信息,為當前用戶進行推薦。下圖 4 給出了原理圖。


    圖 4. 基于用戶的協同過濾推薦機制的基本原理
    圖 4. 基于用戶的協同過濾推薦機制的基本原理 

    上圖示意出基于用戶的協同過濾推薦機制的基本原理,假設用戶 A 喜歡物品 A,物品 C,用戶 B 喜歡物品 B,用戶 C 喜歡物品 A ,物品 C 和物品 D;從這些用戶的歷史喜好信息中,我們可以發現用戶 A 和用戶 C 的口味和偏好是比較類似的,同時用戶 C 還喜歡物品 D,那么我們可以推斷用戶 A 可能也喜歡物品 D,因此可以將物品 D 推薦給用戶 A。

    基于用戶的協同過濾推薦機制和基于人口統計學的推薦機制都是計算用戶的相似度,并基于“鄰居”用戶群計算推薦,但它們所不同的是如何計算用戶的相似度,基于人口統計學的機制只考慮用戶本身的特征,而基于用戶的協同過濾機制可是在用戶的歷史偏好的數據上計算用戶的相似度,它的基本假設是,喜歡類似物品的用戶可能有相同或者相似的口味和偏好。

    基于項目的協同過濾推薦

    基于項目的協同過濾推薦的基本原理也是類似的,只是說它使用所有用戶對物品或者信息的偏好,發現物品和物品之間的相似度,然后根據用戶的歷史偏好信息,將類似的物品推薦給用戶,圖 5 很好的詮釋了它的基本原理。

    假設用戶 A 喜歡物品 A 和物品 C,用戶 B 喜歡物品 A,物品 B 和物品 C,用戶 C 喜歡物品 A,從這些用戶的歷史喜好可以分析出物品 A 和物品 C 時比較類似的,喜歡物品 A 的人都喜歡物品 C,基于這個數據可以推斷用戶 C 很有可能也喜歡物品 C,所以系統會將物品 C 推薦給用戶 C。

    與上面講的類似,基于項目的協同過濾推薦和基于內容的推薦其實都是基于物品相似度預測推薦,只是相似度計算的方法不一樣,前者是從用戶歷史的偏好推斷,而后者是基于物品本身的屬性特征信息。


    圖 5. 基于項目的協同過濾推薦機制的基本原理
    圖 5. 基于項目的協同過濾推薦機制的基本原理 

    同時協同過濾,在基于用戶和基于項目兩個策略中應該如何選擇呢?其實基于項目的協同過濾推薦機制是 Amazon 在基于用戶的機制上改良的一種策略,因為在大部分的 Web 站點中,物品的個數是遠遠小于用戶的數量的,而且物品的個數和相似度相對比較穩定,同時基于項目的機制比基于用戶的實時性更好一些。但也不是所有的場景都是這樣的情況,可以設想一下在一些新聞推薦系統中,也許物品,也就是新聞的個數可能大于用戶的個數,而且新聞的更新程度也有很快,所以它的形似度依然不穩定。所以,其實可以看出,推薦策略的選擇其實和具體的應用場景有很大的關系。

    基于模型的協同過濾推薦

    基于模型的協同過濾推薦就是基于樣本的用戶喜好信息,訓練一個推薦模型,然后根據實時的用戶喜好的信息進行預測,計算推薦。

    基于協同過濾的推薦機制是現今應用最為廣泛的推薦機制,它有以下幾個顯著的優點:

    1. 它不需要對物品或者用戶進行嚴格的建模,而且不要求物品的描述是機器可理解的,所以這種方法也是領域無關的。
    2. 這種方法計算出來的推薦是開放的,可以共用他人的經驗,很好的支持用戶發現潛在的興趣偏好

    而它也存在以下幾個問題:

    1. 方法的核心是基于歷史數據,所以對新物品和新用戶都有“冷啟動”的問題。
    2. 推薦的效果依賴于用戶歷史偏好數據的多少和準確性。
    3. 在大部分的實現中,用戶歷史偏好是用稀疏矩陣進行存儲的,而稀疏矩陣上的計算有些明顯的問題,包括可能少部分人的錯誤偏好會對推薦的準確度有很大的影響等等。
    4. 對于一些特殊品味的用戶不能給予很好的推薦。
    5. 由于以歷史數據為基礎,抓取和建模用戶的偏好后,很難修改或者根據用戶的使用演變,從而導致這個方法不夠靈活。

    混合的推薦機制

    在現行的 Web 站點上的推薦往往都不是單純只采用了某一種推薦的機制和策略,他們往往是將多個方法混合在一起,從而達到更好的推薦效果。關于如何組合各個推薦機制,這里講幾種比較流行的組合方法。

    1. 加權的混合(Weighted Hybridization): 用線性公式(linear formula)將幾種不同的推薦按照一定權重組合起來,具體權重的值需要在測試數據集上反復實驗,從而達到最好的推薦效果。
    2. 切換的混合(Switching Hybridization):前面也講到,其實對于不同的情況(數據量,系統運行狀況,用戶和物品的數目等),推薦策略可能有很大的不同,那么切換的混合方式,就是允許在不同的情況下,選擇最為合適的推薦機制計算推薦。
    3. 分區的混合(Mixed Hybridization):采用多種推薦機制,并將不同的推薦結果分不同的區顯示給用戶。其實,Amazon,當當網等很多電子商務網站都是采用這樣的方式,用戶可以得到很全面的推薦,也更容易找到他們想要的東西。
    4. 分層的混合(Meta-Level Hybridization): 采用多種推薦機制,并將一個推薦機制的結果作為另一個的輸入,從而綜合各個推薦機制的優缺點,得到更加準確的推薦。

    推薦引擎的應用

    介紹完推薦引擎的基本原理,基本推薦機制,下面簡要分析幾個有代表性的推薦引擎的應用,這里選擇兩個領域:Amazon 作為電子商務的代表,豆瓣作為社交網絡的代表。

    推薦在電子商務中的應用 – Amazon

    Amazon 作為推薦引擎的鼻祖,它已經將推薦的思想滲透在應用的各個角落。Amazon 推薦的核心是通過數據挖掘算法和比較用戶的消費偏好于其他用戶進行對比,借以預測用戶可能感興趣的商品。對應于上面介紹的各種推薦機制,Amazon 采用的是分區的混合的機制,并將不同的推薦結果分不同的區顯示給用戶,圖 6 和圖 7 展示了用戶在 Amazon 上能得到的推薦。


    圖 6. Amazon 的推薦機制 - 首頁
    圖 6. Amazon 的推薦機制 - 首頁 

    圖 7. Amazon 的推薦機制 - 瀏覽物品
    圖 7. Amazon 的推薦機制 - 瀏覽物品 

    Amazon 利用可以記錄的所有用戶在站點上的行為,根據不同數據的特點對它們進行處理,并分成不同區為用戶推送推薦:

    • 今日推薦 (Today's Recommendation For You): 通常是根據用戶的近期的歷史購買或者查看記錄,并結合時下流行的物品給出一個折中的推薦。
    • 新產品的推薦 (New For You): 采用了基于內容的推薦機制 (Content-based Recommendation),將一些新到物品推薦給用戶。在方法選擇上由于新物品沒有大量的用戶喜好信息,所以基于內容的推薦能很好的解決這個“冷啟動”的問題。
    • 捆綁銷售 (Frequently Bought Together): 采用數據挖掘技術對用戶的購買行為進行分析,找到經常被一起或同一個人購買的物品集,進行捆綁銷售,這是一種典型的基于項目的協同過濾推薦機制。
    • 別人購買 / 瀏覽的商品 (Customers Who Bought/See This Item Also Bought/See): 這也是一個典型的基于項目的協同過濾推薦的應用,通過社會化機制用戶能更快更方便的找到自己感興趣的物品。

    值得一提的是,Amazon 在做推薦時,設計和用戶體驗也做得特別獨到:

    Amazon 利用有它大量歷史數據的優勢,量化推薦原因。

    • 基于社會化的推薦,Amazon 會給你事實的數據,讓用戶信服,例如:購買此物品的用戶百分之多少也購買了那個物品;
    • 基于物品本身的推薦,Amazon 也會列出推薦的理由,例如:因為你的購物框中有 ***,或者因為你購買過 ***,所以給你推薦類似的 ***。

    另外,Amazon 很多推薦是基于用戶的 profile 計算出來的,用戶的 profile 中記錄了用戶在 Amazon 上的行為,包括看了那些物品,買了那些物品,收藏夾和 wish list 里的物品等等,當然 Amazon 里還集成了評分等其他的用戶反饋的方式,它們都是 profile 的一部分,同時,Amazon 提供了讓用戶自主管理自己 profile 的功能,通過這種方式用戶可以更明確的告訴推薦引擎他的品味和意圖是什么。

    推薦在社交網站中的應用 – 豆瓣

    豆瓣是國內做的比較成功的社交網站,它以圖書,電影,音樂和同城活動為中心,形成一個多元化的社交網絡平臺,自然推薦的功能是必不可少的,下面我們看看豆瓣是如何推薦的。


    圖 8 . 豆瓣的推薦機制 - 豆瓣電影
    圖 8 . 豆瓣的推薦機制 - 豆瓣電影 

    當你在豆瓣電影中將一些你看過的或是感興趣的電影加入你看過和想看的列表里,并為它們做相應的評分,這時豆瓣的推薦引擎已經拿到你的一些偏好信息,那么它將給你展示如圖 8 的電影推薦。


    圖 9 . 豆瓣的推薦機制 - 基于用戶品味的推薦
    圖 9 . 豆瓣的推薦機制 - 基于用戶品味的推薦 

    豆瓣的推薦是通過“豆瓣猜”,為了讓用戶清楚這些推薦是如何來的,豆瓣還給出了“豆瓣猜”的一個簡要的介紹。

    你的個人推薦是根據你的收藏和評價自動得出的,每個人的推薦清單都不同。你的收藏和評價越多,豆瓣給你的推薦會越準確和豐富。
    每天推薦的內容可能會有變化。隨著豆瓣的長大,給你推薦的內容也會越來越準。

    這一點讓我們可以清晰明了的知道,豆瓣必然是基于社會化的協同過濾的推薦,這樣用戶越多,用戶的反饋越多,那么推薦的效果會越來越準確。

    相對于 Amazon 的用戶行為模型,豆瓣電影的模型更加簡單,就是“看過”和“想看”,這也讓他們的推薦更加專注于用戶的品味,畢竟買東西和看電影的動機還是有很大不同的。

    另外,豆瓣也有基于物品本身的推薦,當你查看一些電影的詳細信息的時候,他會給你推薦出“喜歡這個電影的人也喜歡的電影”, 如圖 10,這是一個基于協同過濾的應用。


    圖 10 . 豆瓣的推薦機制 - 基于電影本身的推薦
    圖 10 . 豆瓣的推薦機制 - 基于電影本身的推薦 

     

     

     

    總結

    在網絡數據爆炸的年代,如何讓用戶更快的找到想要的數據,如何讓用戶發現自己潛在的興趣和需求,無論是對于電子商務還是社會網絡的應用都是至關重要的。推薦引擎的出現,使得這個問題越來越被大家關注。但對大多數人來講,也許還在驚嘆它為什么總是能猜到你到底想要些什么。推薦引擎的魔力在于你不清楚在這個推薦背后,引擎到底記錄和推理了些什么。

    通過這篇綜述性的文章,你可以了解,其實推薦引擎只是默默的記錄和觀察你的一舉一動,然后再借由所有用戶產生的海量數據分析和發現其中的規律,進而慢慢的了解你,你的需求,你的習慣,并默默的無聲息的幫助你快速的解決你的問題,找到你想要的東西。

    其實,回頭想想,很多時候,推薦引擎比你更了解你自己。

    通過第一篇文章,相信大家對推薦引擎有一個清晰的第一印象,本系列的下一篇文章將深入介紹基于協同過濾的推薦策略。在現今的推薦技術和算法中,最被大家廣泛認可和采用的就是基于協同過濾的推薦方法。它以其方法模型簡單,數據依賴性低,數據方便采集,推薦效果較優等多個優點成為大眾眼里的推薦算法“No.1”。本文將帶你深入了解協同過濾的秘密,并給出基于 Apache Mahout 的協同過濾算法的高效實現。Apache Mahout 是 ASF 的一個較新的開源項目,它源于 Lucene,構建在 Hadoop 之上,關注海量數據上的機器學習經典算法的高效實現。

    感謝大家對本系列的關注和支持。

    posted @ 2016-07-04 15:48 小馬歌 閱讀(213) | 評論 (0)編輯 收藏
     

    這是關于 C1000K 序列文章的第二篇, 在前一篇文章 構建C1000K的服務器(1) – 基礎 中, 介紹了支持 C1000K 的 Linux 系統的內核參數調整和系統設置. 在本篇文章中, 將對一個真正的應用服務器做 C1000K 測試.

    Comet 服務器是一類邏輯相對簡單, 需要高并發連接的服務器. Comet 在網站系統中的應用非常廣泛, 可以見這篇日志的介紹: http://www.ideawu.net/blog/archives/737.html.

    HTTP 協議處理

    要開發一個支持百萬并發連接的 Comet 服務器, 我選擇 C/C++ 語言, 當然還有其它的選擇如 Erlang, Java 等. 對于一個只支持 long-polling Comet 服務器, 首先要具備解析 HTTP 協議的能力, 我選擇 libevent 來處理 HTTP 協議.

    通道和訂閱者管理

    服務器在啟動的時候, 就預先分配了 100 萬個通道對象的空間, 但訂閱者對象是按需分配的, 通過內存池方式. 100 萬個通道對象和程序的其它數據占用了 24MB 的內存.

    Benchmark

    啟動 icomet 服務器:

    ./icomet 

    服務器監聽了 100 個端口, 是為了測試方便, 原因見前一篇文章的分析: 構建C1000K的服務器(1) – 基礎.

    然后啟動 benchmark 客戶端:

    ./tools/benchmark 127.0.0.1 8100 

    benchmark 程序每創建十萬個連接, 就會暫停, 按回車后繼續. 通過 top/ps 查看 icomet 進程的內存占用. 最終, 得出如下數據.

    連接數進程VIRT進程RES
    039m24m
    100000302m288m
    200000579m565m
    5000001441m1427m
    10000002734m2720m

    可以看到, 每一個 Comet 連接大約占用了 2.7KB 的內存. 此時, 服務器空閑, 進程占用 CPU 為 0%.

    項目的代碼在: https://github.com/ideawu/icomet, 歡迎大家試用, 并反饋你的測試數據.

    Related posts:

    1. HTTP 長連接技術 Comet
    2. 構建C1000K的服務器(1) – 基礎
    3. iComet 的一個應用場景
    4. 長連接技術的應用
    posted @ 2016-05-26 11:17 小馬歌 閱讀(310) | 評論 (0)編輯 收藏
     
    from:http://www.ideawu.net/blog/archives/740.html?cp=2#comments

    著名的 C10K 問題提出的時候, 正是 2001 年, 到如今 12 年后的 2013 年, C10K 已經不是問題了, 任何一個普通的程序員, 都能利用手邊的語言和庫, 輕松地寫出 C10K 的服務器. 這既得益于軟件的進步, 也得益于硬件性能的提高.

    現在, 該是考慮 C1000K, 也就是百萬連接的問題的時候了. 像 Twitter, weibo, Facebook 這些網站, 它們的同時在線用戶有上千萬, 同時又希望消息能接近實時地推送給用戶, 這就需要服務器能維持和上千萬用戶的 TCP 網絡連接, 雖然可以使用成百上千臺服務器來支撐這么多用戶, 但如果每臺服務器能支持一百萬連接(C1000K), 那么只需要十臺服務器.

    有很多技術聲稱能解決 C1000K 問題, 例如 Erlang, Java NIO 等等, 不過, 我們應該首先弄明白, 什么因素限制了 C1000K 問題的解決. 主要是這幾點:

    1. 操作系統能否支持百萬連接?
    2. 操作系統維持百萬連接需要多少內存?
    3. 應用程序維持百萬連接需要多少內存?
    4. 百萬連接的吞吐量是否超過了網絡限制?

    下面來分別對這幾個問題進行分析.

    1. 操作系統能否支持百萬連接?

    對于絕大部分 Linux 操作系統, 默認情況下確實不支持 C1000K! 因為操作系統包含最大打開文件數(Max Open Files)限制, 分為系統全局的, 和進程級的限制.

    全局限制

    在 Linux 下執行:

    cat /proc/sys/fs/file-nr 

    會打印出類似下面的一行輸出:

    5100	0	101747 

    第三個數字 101747 就是當前系統的全局最大打開文件數(Max Open Files), 可以看到, 只有 10 萬, 所以, 在這臺服務器上無法支持 C1000K. 很多系統的這個數值更小, 為了修改這個數值, 用 root 權限修改 /etc/sysctl.conf 文件:

    fs.file-max = 1020000 net.ipv4.ip_conntrack_max = 1020000 net.ipv4.netfilter.ip_conntrack_max = 1020000 

    需要重啟系統服務生效:

    # Linux $ sudo sysctl -p /etc/sysctl.conf  # BSD $ sudo /etc/rc.d/sysctl reload 

    進程限制

    執行:

    ulimit -n 

    輸出:

    1024 

    說明當前 Linux 系統的每一個進程只能最多打開 1024 個文件. 為了支持 C1000K, 你同樣需要修改這個限制.

    臨時修改

    ulimit -n 1020000 

    不過, 如果你不是 root, 可能不能修改超過 1024, 會報錯:

    -bash: ulimit: open files: cannot modify limit: Operation not permitted 

    永久修改

    編輯 /etc/security/limits.conf 文件, 加入如下行:

    # /etc/security/limits.conf work         hard    nofile      1020000 work         soft    nofile      1020000 

    第一列的 work 表示 work 用戶, 你可以填 *, 或者 root. 然后保存退出, 重新登錄服務器.

    注意: Linux 內核源碼中有一個常量(NR_OPEN in /usr/include/linux/fs.h), 限制了最大打開文件數, 如 RHEL 5 是 1048576(2^20), 所以, 要想支持 C1000K, 你可能還需要重新編譯內核.

    2. 操作系統維持百萬連接需要多少內存?

    解決了操作系統的參數限制, 接下來就要看看內存的占用情況. 首先, 是操作系統本身維護這些連接的內存占用. 對于 Linux 操作系統, socket(fd) 是一個整數, 所以, 猜想操作系統管理一百萬個連接所占用的內存應該是 4M/8M, 再包括一些管理信息, 應該會是 100M 左右. 不過, 還有 socket 發送和接收緩沖區所占用的內存沒有分析. 為此, 我寫了最原始的 C 網絡程序來驗證:

    服務器

    #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <arpa/inet.h> #include <netinet/tcp.h> #include <sys/select.h>  #define MAX_PORTS 10  int main(int argc, char **argv){     struct sockaddr_in addr;     const char *ip = "0.0.0.0";     int opt = 1;     int bufsize;     socklen_t optlen;     int connections = 0;     int base_port = 7000;     if(argc > 2){         base_port = atoi(argv[1]);     }      int server_socks[MAX_PORTS];      for(int i=0; i<MAX_PORTS; i++){         int port = base_port + i;         bzero(&addr, sizeof(addr));         addr.sin_family = AF_INET;         addr.sin_port = htons((short)port);         inet_pton(AF_INET, ip, &addr.sin_addr);          int serv_sock;         if((serv_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1){             goto sock_err;         }         if(setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1){             goto sock_err;         }         if(bind(serv_sock, (struct sockaddr *)&addr, sizeof(addr)) == -1){             goto sock_err;         }         if(listen(serv_sock, 1024) == -1){             goto sock_err;         }          server_socks[i] = serv_sock;         printf("server listen on port: %d\n", port);     }      //optlen = sizeof(bufsize);     //getsockopt(serv_sock, SOL_SOCKET, SO_RCVBUF, &bufsize, &optlen);     //printf("default send/recv buf size: %d\n", bufsize);      while(1){         fd_set readset;         FD_ZERO(&readset);         int maxfd = 0;         for(int i=0; i<MAX_PORTS; i++){             FD_SET(server_socks[i], &readset);             if(server_socks[i] > maxfd){                 maxfd = server_socks[i];             }         }         int ret = select(maxfd + 1, &readset, NULL, NULL, NULL);         if(ret < 0){             if(errno == EINTR){                 continue;             }else{                 printf("select error! %s\n", strerror(errno));                 exit(0);             }         }          if(ret > 0){             for(int i=0; i<MAX_PORTS; i++){                 if(!FD_ISSET(server_socks[i], &readset)){                     continue;                 }                 socklen_t addrlen = sizeof(addr);                 int sock = accept(server_socks[i], (struct sockaddr *)&addr, &addrlen);                 if(sock == -1){                     goto sock_err;                 }                 connections ++;                 printf("connections: %d, fd: %d\n", connections, sock);             }         }     }      return 0; sock_err:     printf("error: %s\n", strerror(errno));     return 0; } 

    注意, 服務器監聽了 10 個端口, 這是為了測試方便. 因為只有一臺客戶端測試機, 最多只能跟同一個 IP 端口創建 30000 多個連接, 所以服務器監聽了 10 個端口, 這樣一臺測試機就可以和服務器之間創建 30 萬個連接了.

    客戶端

    #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <arpa/inet.h> #include <netinet/tcp.h>  int main(int argc, char **argv){     if(argc <=  2){         printf("Usage: %s ip port\n", argv[0]);         exit(0);     }      struct sockaddr_in addr;     const char *ip = argv[1];     int base_port = atoi(argv[2]);     int opt = 1;     int bufsize;     socklen_t optlen;     int connections = 0;      bzero(&addr, sizeof(addr));     addr.sin_family = AF_INET;     inet_pton(AF_INET, ip, &addr.sin_addr);      char tmp_data[10];     int index = 0;     while(1){         if(++index >= 10){             index = 0;         }         int port = base_port + index;         printf("connect to %s:%d\n", ip, port);          addr.sin_port = htons((short)port);          int sock;         if((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1){             goto sock_err;         }         if(connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1){             goto sock_err;         }          connections ++;         printf("connections: %d, fd: %d\n", connections, sock);          if(connections % 10000 == 9999){             printf("press Enter to continue: ");             getchar();         }         usleep(1 * 1000);         /*            bufsize = 5000;            setsockopt(serv_sock, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize));            setsockopt(serv_sock, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize));          */     }      return 0; sock_err:     printf("error: %s\n", strerror(errno));     return 0; } 

    我測試 10 萬個連接, 這些連接是空閑的, 什么數據也不發送也不接收. 這時, 進程只占用了不到 1MB 的內存. 但是, 通過程序退出前后的 free 命令對比, 發現操作系統用了 200M(大致)內存來維護這 10 萬個連接! 如果是百萬連接的話, 操作系統本身就要占用 2GB 的內存! 也即 2KB 每連接.

    可以修改

    /proc/sys/net/ipv4/tcp_wmem /proc/sys/net/ipv4/tcp_rmem 

    來控制 TCP 連接的發送和接收緩沖的大小(多謝 @egmkang).

    3. 應用程序維持百萬連接需要多少內存?

    通過上面的測試代碼, 可以發現, 應用程序維持百萬個空閑的連接, 只會占用操作系統的內存, 通過 ps 命令查看可知, 應用程序本身幾乎不占用內存.

    4. 百萬連接的吞吐量是否超過了網絡限制?

    假設百萬連接中有 20% 是活躍的, 每個連接每秒傳輸 1KB 的數據, 那么需要的網絡帶寬是 0.2M x 1KB/s x 8 = 1.6Gbps, 要求服務器至少是萬兆網卡(10Gbps).

    總結

    Linux 系統需要修改內核參數和系統配置, 才能支持 C1000K. C1000K 的應用要求服務器至少需要 2GB 內存, 如果應用本身還需要內存, 這個要求應該是至少 10GB 內存. 同時, 網卡應該至少是萬兆網卡.

    當然, 這僅僅是理論分析, 實際的應用需要更多的內存和 CPU 資源來處理業務數據.

    參考:

    http://www.cyberciti.biz/faq/linux-increase-the-maximum-number-of-open-files/
    http://www.lognormal.com/blog/2012/09/27/linux-tcpip-tuning/

    下一篇: 構建C1000K的服務器(2) – 實現

    Related posts:

    1. 要記得清除 sockaddr_in
    2. 構建C1000K的服務器(2) – 實現百萬連接的comet服務器
    3. 數據傳輸中的停止等待機制的實現
    4. Libevent 2 HTTP 客戶端示例
    5. 有趣的 main 函數參數
    Posted by ideawu at 2013-09-16 22:01:16 Tags: 
    posted @ 2016-05-26 11:16 小馬歌 閱讀(263) | 評論 (0)編輯 收藏
     
    from:http://www.ideawu.net/blog/archives/533.html

    "因為TCP端口號是16位無符號整數, 最大65535, 所以一臺服務器最多支持65536個TCP socket連接." - 一個非常經典的誤解! 即使是有多年網絡編程經驗的人, 也會持有這個錯誤結論.

    要戳破這個錯誤結論, 可以從理論和實踐兩方面來.

    理論

    系統通過一個四元組來唯一標識一條TCP連接. 這個四元組的結構是{local_ip, local_port, remote_ip, remote_port}, 對于IPv4, 系統理論上最多可以管理2^(32+16+32+16), 2的96次方個連接.

    因為對于同一臺服務器來說, 一般只有一個 local_ip, 那么, 同一臺服務器可以管理 2^(16+32+16) 個連接. 而一個服務(進程, 如 Nginx 進程)一般只監聽一個 local_port, 那么, 同一臺服務就可以管理 2^(32+16) 個連接. 而如果從一臺遠端機器(所謂的 client)來連接這臺服務器上的一個服務, 那么 local_ip, local_port, remote_ip 這3個變量是固定的, 那么, 就只能建立 2^16=65536 個連接了. 這就是經典的誤解的來源!

    如果不僅僅考慮TCP, 則是一個五元組, 加上協議號(TCP, UDP或者其它).

    實踐

    服務器綁定一個ip:port, 然后accept連接, 所有accept的連接使用的本地地址也是同樣的ip:port.

    擴展內容

    如果某個客戶端向同一個TCP端點(ip:port)發起主動連接, 那么每一條連接都必須使用不同的本地TCP端點, 如果客戶端只有一個IP則是使用不同的本地端口, 該端口的范圍在*nix系統上的一個例子是32768到61000, 可以通過如下命令查看:

    [root@benegg.com ~]# cat /proc/sys/net/ipv4/ip_local_port_range  32768   61000 

    也就是說, 一個客戶端連接同一個服務器的同一個ip:port(比如進行壓力測試), 最多可以發起30000個左右的連接.

    TCP客戶端(TCP的主動發起者)可以在同一ip:port上向不同的服務器發起主動連接, 只需在bind之前對socket設置SO_REUSEADDR選項.

    系統支持的最大打開文件描述符數(包括socket連接):

    [root@benegg.com ~]# cat /proc/sys/fs/file-max 580382 

    單個進程所能打開的最大文件描述符數:

    [root@benegg.com ~]# ulimit -n 1024 

    結論

    無論是對于服務器還是客戶端, 認為"一臺機器最多建立65536個TCP連接"是沒有根據的, 理論上遠遠超過這個值.

    另外, 對于client端, 操作系統會自動根據不同的遠端 ip:port, 決定是否重用本地端口.

    Related posts:

    1. 連連看游戲開發實踐(1) – 算法
    2. 對P2P應用不友好的NAT
    3. Tomcat網站server.xml設置
    4. P2P穿透NAT的思路
    5. SSDB 的雙主和多主配置
    Posted by ideawu at 2010-07-16 16:44:50
    posted @ 2016-05-26 11:15 小馬歌 閱讀(291) | 評論 (0)編輯 收藏
     

    在幾個月前改造dubbo時,netty4已經穩定很久了,一時手癢,按照netty3-rpc的源碼克隆了一套netty4,在修正了大量的包、類型不同之后,基本保持了netty3的風格,并發量小或者數據包很小時,一切都很ok, 在進行大并發測試時,結果和netty3完全不同,基本用慘不忍睹來形容。由于當時急于開發php客戶端,就把netty4-rpc當做一個失敗的組件存檔起來, 前幾天php-dubbo開發基本完成之后,返回過來思考netty4-rpc的問題,經過仔細分析數據包的解析過程,單步跟蹤源碼

    NettyCodecAdapter, TelnetCodec, ExchangeCoedec,發現ByteBuf的緩沖區為1024,當數據超過1024時,會調用多次Decoder.messageReceived函數,第一次分析dubbo的協議頭時,是正確的,第二次之后數據就錯誤了,然后dubbo內部緩沖區的數據越來越長,但是仍然分析不到一個完整的dubbo數據包

    因此去看netty4的源碼,發現AbstractNioByteChannel中有網絡數據接收的代碼時這么處理ByteBuf的

      ByteBuf byteBuf = null;
                int messages = 0;
                boolean close = false;
                try {
                    int totalReadAmount = 0;
                    boolean readPendingReset = false;
                    do {
                        byteBuf = allocHandle.allocate(allocator);
                        int writable = byteBuf.writableBytes();
                        int localReadAmount = doReadBytes(byteBuf);
                        if (localReadAmount <= 0) {
                            // not was read release the buffer
                            byteBuf.release();
                            close = localReadAmount < 0;
                            break;
                        }
                        if (!readPendingReset) {
                            readPendingReset = true;
                            setReadPending(false);
                        }
                        pipeline.fireChannelRead(byteBuf);
                        byteBuf = null;

    看見沒,內核是需要ByteBuf.release的,繼續通過byteBuf的一個實現PooledByteBuf分析源碼,原來是實現了一個基于簡單計數應用計數的循環使用的緩沖區,一旦計數變為1,該緩沖區被歸還到netty4內核,被后面的數據讀取線程重新使用

    而我們InternalDecoder的代碼為

          message = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.wrappedBuffer(
                        input.toByteBuffer());

    直接引用了ByteBuf.toByteBuffer,繼續查看源碼UnpooledHeapByteBuf, 其toByteBuffer實際是對內部數據的

    一個nio封裝而已,因此,使用上述函數時,導致dubbo的decode保存了一個某一個ByteBuffer的內部數據,但是雖有該

    buffer被歸還到netty4緩沖區中被循環引用,下一次可能被其他讀寫線程重新改寫數據,因此,高并發下當緩沖區被重復使用時,bytebuf將由于計數問題不斷被使用,而解碼器中缺傻傻等待。

    解決方案

    1.通過byteBuf的retain和release函數保證計數的有效性,通過程序例外或者緩沖區被使用完成時候歸還ByteBuf到netty4內核

    2.拷貝數據到dubbo的緩沖區中

    思考:

    netty3 是否也有該問題呢???

    posted @ 2016-05-17 15:25 小馬歌 閱讀(1508) | 評論 (1)編輯 收藏
     
         摘要: from:http://hittyt.iteye.com/blog/1691772Hessian反序列化問題眾所周知,Hessian框架提供的序列化方式,在性能上要優于Java自己的序列化方式。他將對象序列化,生成的字節數組的數量要相對于Java自帶的序列化方式要更簡潔。目前公司的一個項目中,有RPC調用的需要,這里我們使用了公司自己的開源RPC框架Dubbo作為遠程調用框架,進行業務方法的調用和...  閱讀全文
    posted @ 2016-05-17 15:24 小馬歌 閱讀(1345) | 評論 (0)編輯 收藏
     
    from:http://my.oschina.net/aruan/blog/351594

    同事劉陽使用dubbo服務器中配置mina作為網絡傳輸層,發現大并發情況下,解碼發生如下異常

    014-12-01 18:00:44,652 [DubboServerHandler-10.1.19.13:20880-thread-164] WARN  alibaba.dubbo.remoting.exchange.codec.ExchangeCodec (ExchangeCodec.java:596) -  [DUBBO] Fail to encode response: Response [id=8119, version=2.0.0, status=40, event=false, error=Fail to decode request due to: RpcInvocation [methodName=null, parameterTypes=null, arguments=null, attachments={input=242}, headers=null], result=null], send bad_response info instead, cause: null, dubbo version: 2.5.5, current host: 127.0.0.1
    java.lang.NullPointerException
        at com.alibaba.dubbo.rpc.protocol.dubbo.DubboCodec.encodeResponseData(DubboCodec.java:301)
        at com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec.encodeResponse(ExchangeCodec.java:560)
        at com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec.encode(ExchangeCodec.java:104)
        at com.alibaba.dubbo.rpc.protocol.dubbo.DubboCountCodec.encode(DubboCountCodec.java:39)
        at com.alibaba.dubbo.remoting.transport.mina.MinaCodecAdapter$InternalEncoder.encode(MinaCodecAdapter.java:79)
        at org.apache.mina.filter.codec.ProtocolCodecFilter.filterWrite(ProtocolCodecFilter.java:214)
        at org.apache.mina.common.support.AbstractIoFilterChain.callPreviousFilterWrite(AbstractIoFilterChain.java:361)
        at org.apache.mina.common.support.AbstractIoFilterChain.access$1300(AbstractIoFilterChain.java:53)
        at org.apache.mina.common.support.AbstractIoFilterChain$EntryImpl$1.filterWrite(AbstractIoFilterChain.java:659)
        at org.apache.mina.common.support.AbstractIoFilterChain$TailFilter.filterWrite(AbstractIoFilterChain.java:587)
        at org.apache.mina.common.support.AbstractIoFilterChain.callPreviousFilterWrite(AbstractIoFilterChain.java:361)
        at org.apache.mina.common.support.AbstractIoFilterChain.fireFilterWrite(AbstractIoFilterChain.java:355)
        at org.apache.mina.transport.socket.nio.SocketSessionImpl.write0(SocketSessionImpl.java:166)
        at org.apache.mina.common.support.BaseIoSession.write(BaseIoSession.java:177)
        at org.apache.mina.common.support.BaseIoSession.write(BaseIoSession.java:168)
        at com.alibaba.dubbo.remoting.transport.mina.MinaChannel.send(MinaChannel.java:95)
        at com.alibaba.dubbo.remoting.transport.AbstractPeer.send(AbstractPeer.java:51)
        at 

    經過對比netty3和netty4作為傳輸層,卻都沒有發現類似的問題。

    首先排除不是mina本身的問題,mina也沒有爆出有這個問題,初步判斷dubbo在使用mina時機制有問題

    經過對比發現

    1.netty是為每一個channel分配了一個NettyCodecAdapter, mina確實在服務器監聽前配置了MinaCodecAdapter

    2.也就是說,netty的每一個獨立的通道的Codec(encoder/decoder)是通道安全的

    3.mina的所有通道是共享相同的codec(encoder/decoder)的,因此,解碼器中的實例數據時非channel安全的

    因此解碼器中與netty相同的解碼器的緩沖數據算法在并發情況下將會產生數據覆蓋問題。

    4.解決方案

            1.配置acceptor的監聽器

    codecAdapter = new MinaCodecAdapter(getCodec(), getUrl(), this);
            acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(codecAdapter));

        acceptor.addListener(new IoServiceListener(){
                @Override
                public void serviceActivated(IoService service,
                        SocketAddress serviceAddress, IoHandler handler,
                        IoServiceConfig config) {
                }

                @Override
                public void serviceDeactivated(IoService service,
                        SocketAddress serviceAddress, IoHandler handler,
                        IoServiceConfig config) {
                }

                @Override
                public void sessionCreated(IoSession session) {
                    codecAdapter.sessionCreated(session);
                }

                @Override
                public void sessionDestroyed(IoSession session) {
                    codecAdapter.sessionDestroyed(session);
                }
            });

        2.監聽session的create和destroy事件,傳遞到decoder中,decoder中,通過session和buffer的鍵值對保存對不同通道的數據的緩存,

    private Map<IoSession, ChannelBuffer> buffers = new ConcurrentHashMap<IoSession, ChannelBuffer>();

            // ChannelBuffers.EMPTY_BUFFER;

            public void sessionCreated(IoSession session) {
                buffers.put(session, ChannelBuffers.EMPTY_BUFFER);
            }

            public void sessionDestroyed(IoSession session) {
                buffers.remove(session);
            }

        3.解碼時通過session獲得當前channel的數據

    ChannelBuffer buffer = buffers.get(session);
                if(buffer == null) return;


    經過測試,問題得以解決

    posted @ 2016-05-17 15:24 小馬歌 閱讀(1201) | 評論 (0)編輯 收藏
     

    其實這個不算是一個坑,阿里實現的字符串協議挺好的,但是由于我個人的強迫癥和在編寫php客戶端過程中對字符串的輸出和解析感覺很別扭,尤其是字符串數據很大時,必須一個字節一個字節的判斷處理,讓我很郁悶,明顯和我當年編寫匯編時的哪種精致不符。體現在兩個方面

    1.字符串的格式定義為 字母S或者R + 兩個字節的數據長度(MSB)+ utf8格式的字節數組,S表示最后一個塊,上面那個長度是unicode 

    字符串的長度,不是自己數組的長度,即"中國"這個詞,長度是2,utf8表示的字節數組確實6個字節

    2.我的php客戶端是用c混合c++編寫的php擴展,輸出字符串時首先調用libmbfl庫計算unicode字符串的長度,然后輸出utf8數據,因為在php中,我們默認采用utf8格式,字符串zval已經是utf8格式了,并且附帶一個utf8長度的整數,

    3.讀取應答分析字符串時,根據上面的長度并不知道該分配多少內存來接受整個字符串,因為長度和utf8的字節長度根本沒有關系,還有多個節時防止utf8字母被分配到多個不同的chunk上面

    4.因此,我們重新定義了一個字符串數據協議, E +4字節UTF8字節長度(MSB)+ utf8表示一個完整的字符串,4字節時,表示的數據有2^31-1個utf8字節,可以表示好幾百兆的漢字,夠用了,這樣我們可以使用c語言的memcpy函數進行快速數據拷貝和數據緩沖區分配了

    5.經過測試,大量字符串的數據傳輸性能能提高5%

    posted @ 2016-05-17 15:24 小馬歌 閱讀(286) | 評論 (0)編輯 收藏
    僅列出標題
    共95頁: First 上一頁 3 4 5 6 7 8 9 10 11 下一頁 Last 
     
    主站蜘蛛池模板: 亚洲色大情网站www| 亚洲毛片基地4455ww| 久久免费看黄a级毛片| 亚洲Av无码一区二区二三区| 在线观看亚洲免费| 国产乱妇高清无乱码免费| 亚洲人成电影在线天堂| 免费无码AV电影在线观看| 疯狂做受xxxx高潮视频免费| 亚洲大尺度无码专区尤物| 中文字幕影片免费在线观看| 精品国产呦系列在线观看免费| 亚洲欧洲精品视频在线观看| 国产大片91精品免费看3 | 中文字幕的电影免费网站| 亚洲黄色在线观看视频| 免费永久看黄在线观看app| 日韩电影免费在线观看| 亚洲av无码兔费综合| 久久久久亚洲AV成人无码| 日韩一级视频免费观看| 999久久久免费精品播放| 日本一区二区三区免费高清在线| 亚洲人成影院在线高清| 亚洲国产成人一区二区三区| 国产精品成人免费综合| 国产精彩免费视频| 日韩av无码免费播放| 黄色网址免费在线| 中文文字幕文字幕亚洲色| 亚洲bt加勒比一区二区| 亚洲男人的天堂一区二区| 最近2019中文字幕mv免费看| 久久爰www免费人成| 又大又硬又粗又黄的视频免费看| 亚洲最大无码中文字幕| 亚洲成人网在线观看| 亚洲成熟xxxxx电影| 国产精品亚洲产品一区二区三区| 日韩电影免费在线| 毛片免费观看网址|