關(guān)于php的共享內(nèi)存的使用和研究之外部存儲(chǔ)

上文:關(guān)于php的共享內(nèi)存的使用和研究之由起
下文:
關(guān)于php的共享內(nèi)存的使用和研究之深入剖析swoole table

上文中提到了針對(duì)php的共享內(nèi)存方案的嘗試烂叔,最終發(fā)現(xiàn)它并不適用于我的場景筒狠,如果想要兼容多進(jìn)程或多線程并發(fā)讀寫的情況下可靠哨免,一定要有適當(dāng)?shù)臋C(jī)制來保證資源的唯一性磨总。

加鎖肯定是想到的第一選擇井联,對(duì)于每次共享內(nèi)存的時(shí)候瓮栗,先獲取一個(gè)鎖,只有獲取成功了之后才允許進(jìn)行讀和寫胀屿,這應(yīng)該是最簡單的方案,但是同步鎖對(duì)性能的損耗也是比較大的包雀。APC的user data cache的存儲(chǔ)機(jī)制對(duì)數(shù)據(jù)要求嚴(yán)格正確宿崭,鎖比較多,它的效率與本地的memcache相當(dāng)才写。既然這樣葡兑,不如把眼光投向業(yè)界,看看大牛們使用什么樣的方案來解決這個(gè)問題赞草。

YAC

原文鏈接:http://www.laruence.com/2013/03/18/2846.html

laurance為了解決如下的兩個(gè)問題讹堤,設(shè)計(jì)出了這個(gè)cache:

  • 想讓PHP進(jìn)程之間共享一些簡單的數(shù)據(jù)
  • 希望非常高效的緩存一些頁面

同時(shí)也是基于如下的經(jīng)驗(yàn)假設(shè):

  • 對(duì)于一個(gè)應(yīng)用來說, 同名的Cache鍵, 對(duì)應(yīng)的Value, 大小幾乎相當(dāng).
  • 不同的鍵名的個(gè)數(shù)是有限的.
  • Cache的讀的次數(shù), 遠(yuǎn)遠(yuǎn)大于寫的次數(shù).
  • Cache不是數(shù)據(jù)庫, 即使Cache失效也不會(huì)帶來致命錯(cuò)誤.

實(shí)現(xiàn)這個(gè)cache,關(guān)鍵是無鎖化的設(shè)計(jì). 根據(jù)laurance的說法房资,他解決讀鎖的方式是通過不加鎖的讀蜕劝,然后CRC校驗(yàn)。
看了一下他代碼的實(shí)現(xiàn)轰异,是對(duì)key中存儲(chǔ)的固定size的值進(jìn)行了CRC的計(jì)算岖沛,然后把key中附帶存儲(chǔ)的crc信息和內(nèi)容計(jì)算出來的crc信息進(jìn)行校驗(yàn)。如果校驗(yàn)成功了搭独,那么認(rèn)為查詢成功婴削,如果校驗(yàn)失敗了,那么認(rèn)為查詢失敗牙肝。其實(shí)本質(zhì)上這是一種使用CPU來換鎖的方式唉俗,大部分的服務(wù)器是多核的,一旦加鎖配椭,對(duì)CPU是很大的浪費(fèi)虫溜。下面這張圖形象的表明了這一點(diǎn):

yac_lock.png

這個(gè)是個(gè)不錯(cuò)的trick,能夠解決的問題就是在多個(gè)進(jìn)程頻繁的寫入的時(shí)候股缸,可能導(dǎo)致的讀出錯(cuò)不會(huì)帶來錯(cuò)誤的結(jié)果衡楞,因?yàn)橐坏ヽrc校驗(yàn)不通過,那么讀出來的結(jié)果就是失效了敦姻。這顯然比上一篇文章中的共享內(nèi)存的讀的方式要高明一些瘾境。不過根據(jù)我的觀察,使用共享內(nèi)存的方式镰惦,由于一直是向后不停的寫入迷守,出現(xiàn)被覆蓋的概率幾乎沒有,而laurance這里之所以要校驗(yàn)旺入,則是因?yàn)樗麜?huì)進(jìn)行內(nèi)存的回收和循環(huán)寫入兑凿,這點(diǎn)在下文中會(huì)繼續(xù)說明凯力。

現(xiàn)在重點(diǎn)說說這個(gè)YAC的寫入的問題,首先啟動(dòng)的時(shí)候key空間大小確定礼华,可以通過配置來調(diào)整分配給存儲(chǔ)key的大小沮协,從而擴(kuò)展key的個(gè)數(shù)。4M基本上相當(dāng)于32768個(gè)Cache值卓嫂。首先第一個(gè)很重要的點(diǎn)就是如何設(shè)計(jì)哈希方法來避免寫入沖突,這里他使用的是雙散列法的
MurmurHash.

對(duì)于小于4M的內(nèi)存塊的操作由于key不同聘殖,根據(jù)哈希出來的起始位置也不同晨雳。不同key之間沖突的概率,等同于哈希算法沖突的概率奸腺,這個(gè)還是比較低的餐禁。對(duì)于大的內(nèi)存塊,這里使用了segment->pos指針來控制內(nèi)存的分塊突照。和共享內(nèi)存擴(kuò)展的實(shí)現(xiàn)方式還是比較類似了帮非,反正就是一個(gè)pos指針,找得到就update讹蘑,找不到就向后寫末盔。

那么如果發(fā)生沖突呢,laurance給出了一個(gè)例子:

比如A進(jìn)程申請了40字節(jié), B進(jìn)程申請了60字節(jié), 但是Pos只增加了60字節(jié). 這個(gè)時(shí)候有如下幾種情況:

  1. A寫完了數(shù)據(jù), 返回成功, 但是B進(jìn)程又寫完了數(shù)據(jù)返回成功, 最終B進(jìn)程的Cache種上了, 而A進(jìn)程的被踢出了.
  2. B進(jìn)程寫完了數(shù)據(jù), 返回成功, A進(jìn)程又寫完了數(shù)據(jù)返回成功, 最終A進(jìn)程的Cache種上了, B進(jìn)程的被踢出.
  3. A進(jìn)程寫一半, B進(jìn)程寫一半, 然后A進(jìn)程又寫一半, B進(jìn)程又寫一半, 都返回成功, 但最終, 緩存都失效.

可見, 最嚴(yán)重的錯(cuò)誤, 就是A和B的緩存都失效, 但是Yac不會(huì)把錯(cuò)誤數(shù)據(jù)返回給用戶, 當(dāng)下一次來查詢Cache的時(shí)候, 因?yàn)榇嬖赾rc校驗(yàn), 所以都miss.

看到這兒終于明白了座慰,并沒有解決多進(jìn)程寫的問題陨舱,多進(jìn)程的寫還是可能會(huì)有沖突,不僅僅是單key的沖突版仔,不同key之間也可能會(huì)有沖突游盲。但是沖突了不怕,會(huì)通過校驗(yàn)的方式確保client端能夠判斷出來自己沖突了蛮粮,這點(diǎn)對(duì)應(yīng)用程序確實(shí)非常重要益缎,因?yàn)檫@不是cache error,而僅僅是cache miss而已然想,這兩種情況的嚴(yán)重程度和處理機(jī)制的確完全不同莺奔。

另外還有一個(gè)亮點(diǎn)是內(nèi)存的循環(huán)分配,如果一個(gè)內(nèi)存塊用完了又沾,那么可以重置pos弊仪,從而從頭開始分配,有了這種機(jī)制杖刷,即使并發(fā)寫導(dǎo)致pos一直后移励饵,也不會(huì)出現(xiàn)內(nèi)存耗盡的情況了,這確實(shí)是個(gè)不錯(cuò)的特性滑燃。

總結(jié)來看役听,yac兩個(gè)不錯(cuò)的特性:

  • 讀的CRC校驗(yàn),保證最嚴(yán)重是cache miss
  • 寫的pos重置,保證內(nèi)存不被寫滿

但是針對(duì)我的使用場景典予,多并發(fā)情況下的同key的多寫多讀甜滨,它并沒有很好的解決這個(gè)問題,而是比較適用于低頻的用戶數(shù)據(jù)的緩存瘤袖,比如登陸用戶的頭像衣摩、昵稱這類信息。拉取頻次不高捂敌,miss了也能向后端請求艾扮。所以怎么辦呢,只能繼續(xù)進(jìn)行求索占婉。

ps:laurance在文章開頭群嘲了一下APC的性能泡嘴,相當(dāng)于本地的memcache,結(jié)果文末貼出的性能對(duì)比逆济,yac完全比不上apc酌予。。有點(diǎn)莫名

Swoole table

接下來說說這兩年在社區(qū)里面比較火奖慌,最近剛剛發(fā)布了內(nèi)置協(xié)程2.0版本的swoole. github地址:https://github.com/swoole/swoole-src

swoole table是swoole中的一個(gè)基于共享內(nèi)存和鎖實(shí)現(xiàn)的超高性能的并發(fā)數(shù)據(jù)結(jié)構(gòu)抛虫,用來解決多進(jìn)程、多線程數(shù)據(jù)共享和同步加鎖的問題简僧。這不就是我們苦苦尋覓的解決方案么莱褒?

先來看一下swoole table的常見的使用方式,首先它支持三種基本的類型:

  • swoole_table::TYPE_INT 整形字段
  • swoole_table::TYPE_FLOAT 浮點(diǎn)字段
  • swoole_table::TYPE_STRING 字符串字段

如果想要在各個(gè)進(jìn)程之間共享高性能的本地?cái)?shù)據(jù)涎劈,那么使用的范例如下:

// 新建swoole table广凸,并且指定類型
$table = new swoole_table(1024);
$table->column('id', swoole_table::TYPE_INT, 4);       //1,2,4,8
$table->column('name', swoole_table::TYPE_STRING, 64);
$table->column('num', swoole_table::TYPE_FLOAT);
$table->create();

// 新建swoole的server
$serv = new swoole_server('127.0.0.1', 9501);
//將table保存在serv對(duì)象上
$serv->table = $table;
$serv->on('receive', function ($serv, $fd, $from_id, $data) {
    // 使用swoole table存儲(chǔ)全局的數(shù)據(jù)
    $ret = $serv->table->set($key, array('from_id' => $data, 'fd' => $fd, 'data' => $data));
});

// 這里需要注意的就是一定要在server啟動(dòng)之前創(chuàng)建swoole table,從而保證它能夠被全局共享
$serv->start();

如果只是你自己的一個(gè)進(jìn)程在不同的請求之間共享高性能的本地?cái)?shù)據(jù)蛛枚,那么使用的范例如下:

class LocalSwooleTable {
    private static $_swooleTable;// 靜態(tài)變量谅海,單個(gè)進(jìn)程內(nèi)共享

    const SWOOLE_TABLE_SET_FAILED = -1001;
    const SWOOLE_TABLE_GET_FAILED = -1002;

    // swoole table初始化
    private function __construct() {
        //預(yù)估數(shù)據(jù)量 100個(gè)服務(wù),每個(gè)長度30 需要3000個(gè)字節(jié),這里申請64k
        self::$_swooleTable = new \swoole_table(65536);
        self::$_swooleTable->column('ip',\swoole_table::TYPE_STRING, 64);
        self::$_swooleTable->column('port',\swoole_table::TYPE_INT, 4);
        self::$_swooleTable->column('timestamp',\swoole_table::TYPE_INT, 4);
        self::$_swooleTable->column('bTcp',\swoole_table::TYPE_INT, 4);
        self::$_swooleTable->create();
    }

    // 獲取單個(gè)swoole的實(shí)例
    public static function getInstance() {
        if(self::$_swooleTable) {
            return self::$_swooleTable;
        }
        else {
            new LocalSwooleTable();
            return self::$_swooleTable;
        }
    }
}
// 獲取唯一的實(shí)例
$swooleTableIns = LocalSwooleTable::getInstance();
$key = "sample";
$routeInfo['timestamp'] = time();
$routeInfo['ip'] = '10.25.22.33';
$routeInfo['port'] = 1000;
$routeInfo['bTcp'] = 1;

// 設(shè)置swoole table中的內(nèi)容
$flag = $swooleTableIns->set($key,$routeInfo);

// 獲取swoole table中的內(nèi)容
$routeInfo = $swooleTableIns->get($key);

本來,第一種方式應(yīng)該是我們最好的選擇蹦浦,但是因?yàn)槲覀兪褂昧薚SF框架(或者任何不是自己從頭裸寫swoole的框架)扭吁,都不會(huì)把創(chuàng)建server這一步暴露到業(yè)務(wù)代碼中,這就給我們使用全局的swoole的table帶來了很大的難度盲镶。換句話說侥袜,知道好用,但是就是業(yè)務(wù)用起來非常的不方便溉贿,不具備業(yè)務(wù)擴(kuò)展性枫吧。

所以無奈之下,我們還是選取了第二種方案宇色,從性能上面來講的話九杂,確實(shí)是有提升的颁湖,不好的地方就是存儲(chǔ)資源浪費(fèi)了一些,每個(gè)進(jìn)程都用了專屬自己的swoole table例隆,這當(dāng)然是無奈之舉甥捺。還是希望能夠之后通過一些改造,把全局的swoole table這種能力能夠開放出來镀层。

屏幕快照 2017-01-24 下午5.28.17.png

基本上訪問一次是0.03ms镰禾,這個(gè)性能還是比較突出的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末唱逢,一起剝皮案震驚了整個(gè)濱河市羡微,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌惶我,老刑警劉巖居凶,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锁摔,死亡現(xiàn)場離奇詭異,居然都是意外死亡凛辣,警方通過查閱死者的電腦和手機(jī)毅哗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門听怕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人虑绵,你說我怎么就攤上這事尿瞭。” “怎么了翅睛?”我有些...
    開封第一講書人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵声搁,是天一觀的道長。 經(jīng)常有香客問我捕发,道長疏旨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任扎酷,我火速辦了婚禮檐涝,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘法挨。我一直安慰自己谁榜,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開白布凡纳。 她就那樣靜靜地躺著窃植,像睡著了一般。 火紅的嫁衣襯著肌膚如雪荐糜。 梳的紋絲不亂的頭發(fā)上撕瞧,一...
    開封第一講書人閱讀 50,096評(píng)論 1 291
  • 那天陵叽,我揣著相機(jī)與錄音,去河邊找鬼丛版。 笑死巩掺,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的页畦。 我是一名探鬼主播胖替,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼豫缨!你這毒婦竟也來了独令?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤好芭,失蹤者是張志新(化名)和其女友劉穎燃箭,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體舍败,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡招狸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了邻薯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片裙戏。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖厕诡,靈堂內(nèi)的尸體忽然破棺而出累榜,到底是詐尸還是另有隱情,我是刑警寧澤灵嫌,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布壹罚,位于F島的核電站,受9級(jí)特大地震影響寿羞,放射性物質(zhì)發(fā)生泄漏渔嚷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一稠曼、第九天 我趴在偏房一處隱蔽的房頂上張望形病。 院中可真熱鬧,春花似錦霞幅、人聲如沸漠吻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽途乃。三九已至,卻和暖如春扔傅,著一層夾襖步出監(jiān)牢的瞬間耍共,已是汗流浹背烫饼。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留试读,地道東北人杠纵。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像钩骇,于是被迫代替她去往敵國和親比藻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容