又拍網(wǎng)架構(gòu)中的分庫設(shè)計

又拍網(wǎng)是一個照片分享社區(qū)章姓,從2005年6月至今積累了260萬用戶,1.1億張照片识埋,目前的日訪問量為200多萬凡伊。5年的發(fā)展歷程里經(jīng)歷過許多起伏,也積累了一些經(jīng)驗窒舟,在這篇文章里系忙,我要介紹一些我們在技術(shù)上的積累。
又拍網(wǎng)和大多數(shù)Web2.0站點一樣惠豺,構(gòu)建于大量開源軟件之上银还,包括MySQLPHP洁墙、nginx蛹疯、Pythonmemcached热监、redis捺弦、SolrHadoopRabbitMQ等等孝扛。又拍網(wǎng)的服務(wù)器端開發(fā)語言主要是PHPPython列吼,其中PHP用于編寫Web邏輯(通過HTTP和用戶直接打交道), 而Python則主要用于開發(fā)內(nèi)部服務(wù)和后臺任務(wù)苦始。在客戶端則使用了大量的Javascript寞钥, 這里要感謝一下MooTools這個JS框架,它使得我們很享受前端開發(fā)過程陌选。 另外理郑,我們把圖片處理過程從PHP進程里獨立出來變成一個服務(wù)蹄溉。這個服務(wù)基于nginx,但是是作為nginx的一個模塊而開放REST API香浩。

開發(fā)語言
開發(fā)語言

圖1:開發(fā)語言
由于PHP的單線程模型,我們把耗時較久的運算和I/O操作從HTTP請求周期中分離出來臼勉, 交給由Python實現(xiàn)的任務(wù)進程來完成邻吭,以保證請求響應(yīng)速度。這些任務(wù)主要包括:郵件發(fā)送宴霸、數(shù)據(jù)索引囱晴、數(shù)據(jù)聚合和好友動態(tài)推送(稍候會有介紹)等等。通常這些任務(wù)由用戶觸發(fā)瓢谢,并且畸写,用戶的一個行為可能會觸發(fā)多種任務(wù)的執(zhí)行。 比如氓扛,用戶上傳了一張新的照片枯芬,我們需要更新索引,也需要向他的朋友推送一條新的動態(tài)采郎。PHP通過消息隊列(我們用的是RabbitMQ)來觸發(fā)任務(wù)執(zhí)行千所。

PHP和Python的協(xié)作
PHP和Python的協(xié)作

相關(guān)廠商內(nèi)容
1元體驗國際水準公有云,Windows Azure試用招募中
Windows Azure從開發(fā)到部署的自動化進程
微軟Windows Azure開啟機器學(xué)習(xí)之旅
基于開源軟件的Azure平臺大規(guī)模系統(tǒng)構(gòu)建
美圖公司運維/DBA職位北京&廈門火熱招聘中

相關(guān)贊助商

Windows Azure專區(qū)上線蒜埋,全面了解云服務(wù)
精彩呈現(xiàn)
淫痰!

圖2:PHP和Python的協(xié)作
數(shù)據(jù)庫一向是網(wǎng)站架構(gòu)中最具挑戰(zhàn)性的,瓶頸通常出現(xiàn)在這里整份。又拍網(wǎng)的照片數(shù)據(jù)量很大待错,數(shù)據(jù)庫也幾度出現(xiàn)嚴重的壓力問題。 因此烈评,這里我主要介紹一下又拍網(wǎng)在分庫設(shè)計這方面的一些嘗試火俄。
分庫設(shè)計
和很多使用MySQL的2.0站點一樣,又拍網(wǎng)的MySQL集群經(jīng)歷了從最初的一個主庫一個從庫讲冠、到一個主庫多個從庫烛占、 然后到多個主庫多個從庫的一個發(fā)展過程。

數(shù)據(jù)庫的進化過程
數(shù)據(jù)庫的進化過程

圖3:數(shù)據(jù)庫的進化過程
最初是由一臺主庫和一臺從庫組成沟启,當(dāng)時從庫只用作備份和容災(zāi)忆家,當(dāng)主庫出現(xiàn)故障時,從庫就手動變成主庫德迹,一般情況下芽卿,從庫不作讀寫操作(同步除外)。隨著壓力的增加胳搞,我們加上了memcached卸例,當(dāng)時只用其緩存單行數(shù)據(jù)称杨。 但是,單行數(shù)據(jù)的緩存并不能很好地解決壓力問題筷转,因為單行數(shù)據(jù)的查詢通常很快姑原。所以我們把一些實時性要求不高的Query放到從庫去執(zhí)行。后面又通過添加多個從庫來分流查詢壓力呜舒,不過隨著數(shù)據(jù)量的增加锭汛,主庫的寫壓力也越來越大。
在參考了一些相關(guān)產(chǎn)品和其它網(wǎng)站的做法后袭蝗,我們決定進行數(shù)據(jù)庫拆分唤殴。也就是將數(shù)據(jù)存放到不同的數(shù)據(jù)庫服務(wù)器中,一般可以按兩個緯度來拆分數(shù)據(jù):
垂直拆分:是指按功能模塊拆分到腥,比如可以將群組相關(guān)表和照片相關(guān)表存放在不同的數(shù)據(jù)庫中朵逝,這種方式多個數(shù)據(jù)庫之間的表結(jié)構(gòu)不同
水平拆分:而水平拆分是將同一個表的數(shù)據(jù)進行分塊保存到不同的數(shù)據(jù)庫中乡范,這些數(shù)據(jù)庫中的表結(jié)構(gòu)完全相同配名。
拆分方式
一般都會先進行垂直拆分,因為這種方式拆分方式實現(xiàn)起來比較簡單晋辆,根據(jù)表名訪問不同的數(shù)據(jù)庫就可以了段誊。但是垂直拆分方式并不能徹底解決所有壓力問題,另外栈拖,也要看應(yīng)用類型是否合適這種拆分方式连舍。如果合適的話,也能很好的起到分散數(shù)據(jù)庫壓力的作用涩哟。比如對于豆瓣我覺得比較適合采用垂直拆分索赏, 因為豆瓣的各核心業(yè)務(wù)/模塊(書籍、電影贴彼、音樂)相對獨立潜腻,數(shù)據(jù)的增加速度也比較平穩(wěn)。不同的是器仗,又拍網(wǎng)的核心業(yè)務(wù)對象是用戶上傳的照片融涣,而照片數(shù)據(jù)的增加速度隨著用戶量的增加越來越快。壓力基本上都在照片表上精钮,顯然垂直拆分并不能從根本上解決我們的問題威鹿,所以,我們采用水平拆分的方式轨香。
拆分規(guī)則
水平拆分實現(xiàn)起來相對復(fù)雜忽你,我們要先確定一個拆分規(guī)則,也就是按什么條件將數(shù)據(jù)進行切分臂容。 一般2.0網(wǎng)站都以用戶為中心科雳,數(shù)據(jù)基本都跟隨用戶根蟹,比如用戶的照片、朋友和評論等等糟秘。因此一個比較自然的選擇是根據(jù)用戶來切分简逮。每個用戶都對應(yīng)一個數(shù)據(jù)庫,訪問某個用戶的數(shù)據(jù)時尿赚, 我們要先確定他/她所對應(yīng)的數(shù)據(jù)庫散庶,然后連接到該數(shù)據(jù)庫進行實際的數(shù)據(jù)讀寫幕袱。
那么示罗,怎么樣對應(yīng)用戶和數(shù)據(jù)庫呢?我們有這些選擇:
按算法對應(yīng)
最簡單的算法是按用戶ID的奇偶性來對應(yīng),將奇數(shù)ID的用戶對應(yīng)到數(shù)據(jù)庫A泻蚊,而偶數(shù)ID的用戶則對應(yīng)到數(shù)據(jù)庫B。這個方法的最大問題是丑婿,只能分成兩個庫性雄。另一個算法是按用戶ID所在區(qū)間對應(yīng),比如ID在0-10000之間的用戶對應(yīng)到數(shù)據(jù)庫A羹奉, ID在10000-20000這個范圍的對應(yīng)到數(shù)據(jù)庫B秒旋,以此類推。按算法分實現(xiàn)起來比較方便诀拭,也比較高效迁筛,但是不能滿足后續(xù)的伸縮性要求,如果需要增加數(shù)據(jù)庫節(jié)點耕挨,必需調(diào)整算法或移動很大的數(shù)據(jù)集细卧, 比較難做到在不停止服務(wù)的前提下進行擴充數(shù)據(jù)庫節(jié)點。
按索引****/****映射表對應(yīng)
這種方法是指建立一個索引表筒占,保存每個用戶的ID和數(shù)據(jù)庫ID的對應(yīng)關(guān)系贪庙,每次讀寫用戶數(shù)據(jù)時先從這個表獲取對應(yīng)數(shù)據(jù)庫。新用戶注冊后翰苫,在所有可用的數(shù)據(jù)庫中隨機挑選一個為其建立索引止邮。這種方法比較靈活,有很好的伸縮性奏窑。一個缺點是增加了一次數(shù)據(jù)庫訪問导披,所以性能上沒有按算法對應(yīng)好。
比較之后埃唯,我們采用的是索引表的方式盛卡,我們愿意為其靈活性損失一些性能,更何況我們還有memcached筑凫, 因為索引數(shù)據(jù)基本不會改變的緣故滑沧,緩存命中率非常高并村。所以能很大程度上減少了性能損失。
數(shù)據(jù)訪問過程
數(shù)據(jù)訪問過程

圖4:數(shù)據(jù)訪問過程
索引表的方式能夠比較方便地添加數(shù)據(jù)庫節(jié)點滓技,在增加節(jié)點時哩牍,只要將其添加到可用數(shù)據(jù)庫列表里即可。 當(dāng)然如果需要平衡各個節(jié)點的壓力的話令漂,還是需要進行數(shù)據(jù)的遷移膝昆,但是這個時候的遷移是少量的,可以逐步進行叠必。要遷移用戶A的數(shù)據(jù)荚孵,首先要將其狀態(tài)置為遷移數(shù)據(jù)中,這個狀態(tài)的用戶不能進行寫操作纬朝,并在頁面上進行提示收叶。 然后將用戶A的數(shù)據(jù)全部復(fù)制到新增加的節(jié)點上后,更新映射表共苛,然后將用戶A的狀態(tài)置為正常判没,最后將原來對應(yīng)的數(shù)據(jù)庫上的數(shù)據(jù)刪除。這個過程通常會在臨晨進行隅茎,所以澄峰,所以很少會有用戶碰到遷移數(shù)據(jù)中的情況。
當(dāng)然辟犀,有些數(shù)據(jù)是不屬于某個用戶的俏竞,比如系統(tǒng)消息、配置等等堂竟,我們把這些數(shù)據(jù)保存在一個全局庫中魂毁。
問題
分庫會給你在應(yīng)用的開發(fā)和部署上都帶來很多麻煩。
不能執(zhí)行跨庫的關(guān)聯(lián)查詢
如果我們需要查詢的數(shù)據(jù)分布于不同的數(shù)據(jù)庫跃捣,我們沒辦法通過JOIN的方式查詢獲得漱牵。比如要獲得好友的最新照片,你不能保證所有好友的數(shù)據(jù)都在同一個數(shù)據(jù)庫里疚漆。一個解決辦法是通過多次查詢酣胀,再進行聚合的方式。我們需要盡量避免類似的需求娶聘。有些需求可以通過保存多份數(shù)據(jù)來解決闻镶,比如User-A和User-B的數(shù)據(jù)庫分別是DB-1和DB-2, 當(dāng)User-A評論了User-B的照片時丸升,我們會同時在DB-1和DB-2中保存這條評論信息铆农,我們首先在DB-2中的photo_comments表中插入一條新的記錄,然后在DB-1中的user_comments表中插入一條新的記錄狡耻。這兩個表的結(jié)構(gòu)如下圖所示墩剖。這樣我們可以通過查詢photo_comments表得到User-B的某張照片的所有評論猴凹, 也可以通過查詢user_comments表獲得User-A的所有評論。另外可以考慮使用全文檢索工具來解決某些需求岭皂, 我們使用Solr來提供全站標簽檢索和照片搜索服務(wù)郊霎。
評論表結(jié)構(gòu)
評論表結(jié)構(gòu)

圖5:評論表結(jié)構(gòu)
不能保證數(shù)據(jù)的一致****/****完整性
跨庫的數(shù)據(jù)沒有外鍵約束,也沒有事務(wù)保證爷绘。比如上面的評論照片的例子书劝, 很可能出現(xiàn)成功插入photo_comments表,但是插入user_comments表時卻出錯了土至。一個辦法是在兩個庫上都開啟事務(wù)购对,然后先插入photo_comments,再插入user_comments陶因, 然后提交兩個事務(wù)骡苞。這個辦法也不能完全保證這個操作的原子性。
所有查詢必須提供數(shù)據(jù)庫線索
比如要查看一張照片坑赡,僅憑一個照片ID是不夠的烙如,還必須提供上傳這張照片的用戶的ID(也就是數(shù)據(jù)庫線索)么抗,才能找到它實際的存放位置毅否。因此,我們必須重新設(shè)計很多URL地址蝇刀,而有些老的地址我們又必須保證其仍然有效螟加。我們把照片地址改成/photos/{username}/{photo_id}/的形式,然后對于系統(tǒng)升級前上傳的照片ID吞琐, 我們又增加一張映射表捆探,保存photo_id和user_id的對應(yīng)關(guān)系。當(dāng)訪問老的照片地址時站粟,我們通過查詢這張表獲得用戶信息, 然后再重定向到新的地址黍图。
自增****ID
如果要在節(jié)點數(shù)據(jù)庫上使用自增字段,那么我們就不能保證全局唯一奴烙。這倒不是很嚴重的問題助被,但是當(dāng)節(jié)點之間的數(shù)據(jù)發(fā)生關(guān)系時,就會使得問題變得比較麻煩切诀。我們可以再來看看上面提到的評論的例子揩环。如果photo_comments表中的comment_id的自增字段,當(dāng)我們在DB-2.photo_comments表插入新的評論時幅虑, 得到一個新的comment_id丰滑,假如值為101,而User-A的ID為1倒庵,那么我們還需要在DB-1.user_comments表中插入(1, 101 ...)褒墨。 User-A是個很活躍的用戶炫刷,他又評論了User-C的照片,而User-C的數(shù)據(jù)庫是DB-3郁妈。 很巧的是這條新評論的ID也是101柬唯,這種情況很用可能發(fā)生。那么我們又在DB-1.user_comments表中插入一行像這樣(1, 101 ...)的數(shù)據(jù)圃庭。 那么我們要怎么設(shè)置user_comments表的主鍵呢(標識一行數(shù)據(jù))锄奢?可以不設(shè)啊,不幸的是有的時候(框架剧腻、緩存等原因)必需設(shè)置拘央。那么可以以user_id、 comment_id和photo_id為組合主鍵书在,但是photo_id也有可能一樣(的確很巧)灰伟。看來只能再加上photo_owner_id了儒旬, 但是這個結(jié)果又讓我們實在有點無法接受栏账,太復(fù)雜的組合鍵在寫入時會帶來一定的性能影響,這樣的自然鍵看起來也很不自然栈源。所以挡爵,我們放棄了在節(jié)點上使用自增字段,想辦法讓這些ID變成全局唯一甚垦。為此增加了一個專門用來生成ID的數(shù)據(jù)庫茶鹃,這個庫中的表結(jié)構(gòu)都很簡單,只有一個自增字段id艰亮。 當(dāng)我們要插入新的評論時闭翩,我們先在ID庫的photo_comments表里插入一條空的記錄,以獲得一個唯一的評論ID迄埃。 當(dāng)然這些邏輯都已經(jīng)封裝在我們的框架里了疗韵,對于開發(fā)人員是透明的。 為什么不用其它方案呢侄非,比如一些支持incr操作的Key-Value數(shù)據(jù)庫蕉汪。我們還是比較放心把數(shù)據(jù)放在MySQL里。 另外彩库,我們會定期清理ID庫的數(shù)據(jù)肤无,以保證獲取新ID的效率。
實現(xiàn)
我們稱前面提到的一個數(shù)據(jù)庫節(jié)點為Shard骇钦,一個Shard由兩個臺物理服務(wù)器組成宛渐, 我們稱它們?yōu)镹ode-A和Node-B,Node-A和Node-B之間是配置成Master-Master相互復(fù)制的。 雖然是Master-Master的部署方式窥翩,但是同一時間我們還是只使用其中一個业岁,原因是復(fù)制的延遲問題, 當(dāng)然在Web應(yīng)用里寇蚊,我們可以在用戶會話里放置一個A或B來保證同一用戶一次會話里只訪問一個數(shù)據(jù)庫笔时, 這樣可以避免一些延遲問題。但是我們的Python任務(wù)是沒有任何狀態(tài)的仗岸,不能保證和PHP應(yīng)用讀寫相同的數(shù)據(jù)庫允耿。那么為什么不配置成Master-Slave呢?我們覺得只用一臺太浪費了扒怖,所以我們在每臺服務(wù)器上都創(chuàng)建多個邏輯數(shù)據(jù)庫较锡。 如下圖所示,在Node-A和Node-B上我們都建立了shard_001和shard_002兩個邏輯數(shù)據(jù)庫盗痒, Node-A上的shard_001和Node-B上的shard_001組成一個Shard蚂蕴,而同一時間只有一個邏輯數(shù)據(jù)庫處于Active狀態(tài)。 這個時候如果需要訪問Shard-001的數(shù)據(jù)時俯邓,我們連接的是Node-A上的shard_001骡楼, 而訪問Shard-002的數(shù)據(jù)則是連接Node-B上的shard_002。以這種交叉的方式將壓力分散到每臺物理服務(wù)器上稽鞭。 以Master-Master方式部署的另一個好處是鸟整,我們可以不停止服務(wù)的情況下進行表結(jié)構(gòu)升級, 升級前先停止復(fù)制川慌,升級Inactive的庫吃嘿,然后升級應(yīng)用祠乃,再將已經(jīng)升級好的數(shù)據(jù)庫切換成Active狀態(tài)梦重, 原來的Active數(shù)據(jù)庫切換成Inactive狀態(tài),然后升級它的表結(jié)構(gòu)亮瓷,最后恢復(fù)復(fù)制琴拧。 當(dāng)然這個步驟不一定適合所有升級過程,如果表結(jié)構(gòu)的更改會導(dǎo)致數(shù)據(jù)復(fù)制失敗嘱支,那么還是需要停止服務(wù)再升級的蚓胸。
Database Layout
Database Layout

圖6:數(shù)據(jù)庫布局
前面提到過添加服務(wù)器時,為了保證負載的平衡除师,我們需要遷移一部分數(shù)據(jù)到新的服務(wù)器上沛膳。為了避免短期內(nèi)遷移的必要,我們在實際部署的時候汛聚,每臺機器上部署了8個邏輯數(shù)據(jù)庫锹安, 添加服務(wù)器后,我們只要將這些邏輯數(shù)據(jù)庫遷移到新服務(wù)器就可以了。最好是每次添加一倍的服務(wù)器叹哭, 然后將每臺的1/2邏輯數(shù)據(jù)遷移到一臺新服務(wù)器上忍宋,這樣能很好的平衡負載。當(dāng)然风罩,最后到了每臺上只有一個邏輯庫時糠排,遷移就無法避免了,不過那應(yīng)該是比較久遠的事情了超升。
我們把分庫邏輯都封裝在我們的PHP框架里了入宦,開發(fā)人員基本上不需要被這些繁瑣的事情困擾。下面是使用我們的框架進行照片數(shù)據(jù)的讀寫的一些例子:
<?php $Photos = new ShardedDBTable('Photos', 'yp_photos', 'user_id', array( 'photo_id' => array('type' => 'long', 'primary' => true, 'global_auto_increment' => true), 'user_id' => array('type' => 'long'), 'title' => array('type' => 'string'), 'posted_date' => array('type' => 'date'), )); $photo = $Photos->new_object(array('user_id' => 1, 'title' => 'Workforme')); $photo->insert(); // 加載ID為10001的照片室琢,注意第一個參數(shù)為用戶ID $photo = $Photos->load(1, 10001); // 更改照片屬性 $photo->title = 'Database Sharding'; $photo->update(); // 刪除照片 $photo->delete(); // 獲取ID為1的用戶在2010-06-01之后上傳的照片 $photos = $Photos->fetch(array('user_id' => 1, 'posted_date__gt' => '2010-06-01'));?>
首先要定義一個ShardedDBTable對象云石,所有的API都是通過這個對象開放。第一個參數(shù)是對象類型名稱研乒, 如果這個名稱已經(jīng)存在汹忠,那么將返回之前定義的對象。你也可以通過get_table('Photos')這個函數(shù)來獲取之前定義的Table對象雹熬。 第二個參數(shù)是對應(yīng)的數(shù)據(jù)庫表名宽菜,而第三個參數(shù)是數(shù)據(jù)庫線索字段,你會發(fā)現(xiàn)在后面的所有API中全部需要指定這個字段的值竿报。 第四個參數(shù)是字段定義铅乡,其中photo_id字段的global_auto_increment屬性被置為true,這就是前面所說的全局自增ID烈菌, 只要指定了這個屬性阵幸,框架會處理好ID的事情。
如果我們要訪問全局庫中的數(shù)據(jù)芽世,我們需要定義一個DBTable對象挚赊。
<?php $Users = new DBTable('Users', 'yp_users', array( 'user_id' => array('type' => 'long', 'primary' => true, 'auto_increment' => true), 'username' => array('type' => 'string'), ));?>
DBTable是ShardedDBTable的父類,除了定義時參數(shù)有些不同(DBTable不需要指定數(shù)據(jù)庫線索字段)济瓢,它們提供一樣的API荠割。
緩存
我們的框架提供了緩存功能,對開發(fā)人員是透明的旺矾。
<?php $photo = $Photos->load(1, 10001);?>
比如上面的方法調(diào)用蔑鹦,框架先嘗試以Photos-1-10001為Key在緩存中查找,未找到的話再執(zhí)行數(shù)據(jù)庫查詢并放入緩存箕宙。當(dāng)更改照片屬性或刪除照片時嚎朽,框架負責(zé)從緩存中刪除該照片。這種單個對象的緩存實現(xiàn)起來比較簡單柬帕。稍微麻煩的是像下面這樣的列表查詢結(jié)果的緩存哟忍。
<?php $photos = $Photos->fetch(array('user_id' => 1, 'posted_date__gt' => '2010-06-01'));?>
我們把這個查詢分成兩步室囊,第一步先查出符合條件的照片ID,然后再根據(jù)照片ID分別查找具體的照片信息魁索。 這么做可以更好的利用緩存融撞。第一個查詢的緩存Key為Photos-list-{shard_key}-{md5(查詢條件SQL語句)}, Value是照片ID列表(逗號間隔)粗蔚。其中shard_key為user_id的值1尝偎。目前來看,列表緩存也不麻煩鹏控。 但是如果用戶修改了某張照片的上傳時間呢致扯,這個時候緩存中的數(shù)據(jù)就不一定符合條件了。所以当辐,我們需要一個機制來保證我們不會從緩存中得到過期的列表數(shù)據(jù)抖僵。我們?yōu)槊繌埍碓O(shè)置了一個revision,當(dāng)該表的數(shù)據(jù)發(fā)生變化時(調(diào)用insert/update/delete方法)缘揪, 我們就更新它的revision耍群,所以我們把列表的緩存Key改為Photos-list-{shard_key}-{md5(查詢條件SQL語句)}-{revision}, 這樣我們就不會再得到過期列表了找筝。
revision信息也是存放在緩存里的蹈垢,Key為Photos-revision。這樣做看起來不錯袖裕,但是好像列表緩存的利用率不會太高曹抬。因為我們是以整個數(shù)據(jù)類型的revision為緩存Key的后綴,顯然這個revision更新的非常頻繁急鳄,任何一個用戶修改或上傳了照片都會導(dǎo)致它的更新谤民,哪怕那個用戶根本不在我們要查詢的Shard里。要隔離用戶的動作對其他用戶的影響疾宏,我們可以通過縮小revision的作用范圍來達到這個目的张足。 所以revision的緩存Key變成Photos-{shard_key}-revision,這樣的話當(dāng)ID為1的用戶修改了他的照片信息時灾锯, 只會更新Photos-1-revision這個Key所對應(yīng)的revision兢榨。
因為全局庫沒有shard_key,所以修改了全局庫中的表的一行數(shù)據(jù)顺饮,還是會導(dǎo)致整個表的緩存失效。 但是大部分情況下凌那,數(shù)據(jù)都是有區(qū)域范圍的兼雄,比如我們的幫助論壇的主題帖子, 帖子屬于主題帽蝶。修改了其中一個主題的一個帖子赦肋,沒必要使所有主題的帖子緩存都失效。 所以我們在DBTable上增加了一個叫isolate_key的屬性。
<?php$GLOBALS['Posts'] = new DBTable('Posts', 'yp_posts', array( 'topic_id' => array('type' => 'long', 'primary' => true), 'post_id' => array('type' => 'long', 'primary' => true, 'auto_increment' => true), 'author_id' => array('type' => 'long'), 'content' => array('type' => 'string'), 'posted_at' => array('type' => 'datetime'), 'modified_at' => array('type' => 'datetime'), 'modified_by' => array('type' => 'long'), ), 'topic_id');?>
注意構(gòu)造函數(shù)的最后一個參數(shù)topic_id就是指以字段topic_id作為isolate_key佃乘,它的作用和shard_key一樣用于隔離revision的作用范圍囱井。
ShardedDBTable繼承自DBTable,所以也可以指定isolate_key趣避。 ShardedDBTable指定了isolate_key的話庞呕,能夠更大幅度縮小revision的作用范圍。 比如相冊和照片的關(guān)聯(lián)表yp_album_photos程帕,當(dāng)用戶往他的其中一個相冊里添加了新的照片時住练, 會導(dǎo)致其它相冊的照片列表緩存也失效。如果我指定這張表的isolate_key為album_id的話愁拭, 我們就把這種影響限制在了本相冊內(nèi)讲逛。
我們的緩存分為兩級,第一級只是一個PHP數(shù)組岭埠,有效范圍是Request盏混。而第二級是memcached。這么做的原因是惜论,很多數(shù)據(jù)在一個Request周期內(nèi)需要加載多次括饶,這樣可以減少memcached的網(wǎng)絡(luò)請求。另外我們的框架也會盡可能的發(fā)送memcached的gets命令來獲取數(shù)據(jù)来涨, 從而減少網(wǎng)絡(luò)請求图焰。
總結(jié)
這個架構(gòu)使得我們在很長一段時間內(nèi)都不必再為數(shù)據(jù)庫壓力所困擾。我們的設(shè)計很多地方參考了netlogflickr的實現(xiàn)蹦掐,因此非常感謝他們將一些實現(xiàn)細節(jié)發(fā)布出來技羔。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市卧抗,隨后出現(xiàn)的幾起案子藤滥,更是在濱河造成了極大的恐慌,老刑警劉巖社裆,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拙绊,死亡現(xiàn)場離奇詭異,居然都是意外死亡泳秀,警方通過查閱死者的電腦和手機标沪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嗜傅,“玉大人金句,你說我怎么就攤上這事÷类郑” “怎么了违寞?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵贞瞒,是天一觀的道長。 經(jīng)常有香客問我趁曼,道長军浆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任挡闰,我火速辦了婚禮乒融,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘尿这。我一直安慰自己簇抵,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布射众。 她就那樣靜靜地躺著碟摆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪叨橱。 梳的紋絲不亂的頭發(fā)上典蜕,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機與錄音罗洗,去河邊找鬼愉舔。 笑死,一個胖子當(dāng)著我的面吹牛伙菜,可吹牛的內(nèi)容都是我干的轩缤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼贩绕,長吁一口氣:“原來是場噩夢啊……” “哼火的!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起淑倾,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤馏鹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后娇哆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體湃累,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年碍讨,在試婚紗的時候發(fā)現(xiàn)自己被綠了治力。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡垄开,死狀恐怖琴许,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情溉躲,我是刑警寧澤榜田,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站锻梳,受9級特大地震影響箭券,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜疑枯,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一辩块、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧荆永,春花似錦废亭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至骂删,卻和暖如春掌动,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背宁玫。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工粗恢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人欧瘪。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓眷射,卻偏偏與公主長得像,于是被迫代替她去往敵國和親佛掖。 傳聞我的和親對象是個殘疾皇子妖碉,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354

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