日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

又拍网架构-又一个用到python的网站

發布時間:2024/4/13 python 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 又拍网架构-又一个用到python的网站 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

2019獨角獸企業重金招聘Python工程師標準>>>

又拍網是一個照片分享社區,從2005年6月至今積累了260萬用戶,1.1億張照片,目前的日訪問量為200多萬。5年的發展歷程里經歷過許多起伏,也積累了一些經驗,在這篇文章會介紹一些在技術上的積累。

又拍網和大多數Web2.0站點一樣,構建于大量開源軟件之上,包括MySQL、PHP、nginx、Python、memcached、redis、Solr、Hadoop和RabbitMQ等等。又拍網的服務器端開發語言主要是PHP和Python,其中PHP用于編寫Web邏輯(通過HTTP和用戶直接打交道), 而Python則主要用于開發內部服務和后臺任務。在客戶端則使用了大量的Javascript, 這里要感謝一下MooTools這個JS框架,它使得我們很享受前端開發過程。 另外,我們把圖片處理過程從PHP進程里獨立出來變成一個服務。這個服務基于nginx,但是是作為nginx的一個模塊而開放REST API。

圖1:開發語言

由于PHP的單線程模型,我們把耗時較久的運算和I/O操作從HTTP請求周期中分離出來, 交給由Python實現的任務進程來完成,以保證請求響應速度。這些任務主要包括:郵件發送、數據索引、數據聚合和好友動態推送(稍候會有介紹)等等。通常這些任務由用戶觸發,并且,用戶的一個行為可能會觸發多種任務的執行。 比如,用戶上傳了一張新的照片,我們需要更新索引,也需要向他的朋友推送一條新的動態。PHP通過消息隊列(我們用的是RabbitMQ)來觸發任務執行。

圖2:PHP和Python的協作

數據庫一向是網站架構中最具挑戰性的,瓶頸通常出現在這里。又拍網的照片數據量很大,數據庫也幾度出現嚴重的壓力問題。 因此,這里我主要介紹一下又拍網在分庫設計這方面的一些嘗試。

分庫設計
和很多使用MySQL的2.0站點一樣,又拍網的MySQL集群經歷了從最初的一個主庫一個從庫、到一個主庫多個從庫、 然后到多個主庫多個從庫的一個發展過程。

最初是由一臺主庫和一臺從庫組成,當時從庫只用作備份和容災,當主庫出現故障時,從庫就手動變成主庫,一般情況下,從庫不作讀寫操作(同步除外)。隨著壓力的增加,我們加上了memcached,當時只用其緩存單行數據。 但是,單行數據的緩存并不能很好地解決壓力問題,因為單行數據的查詢通常很快。所以我們把一些實時性要求不高的Query放到從庫去執行。后面又通過添加多個從庫來分流查詢壓力,不過隨著數據量的增加,主庫的寫壓力也越來越大。

在參考了一些相關產品和其它網站的做法后,我們決定進行數據庫拆分。也就是將數據存放到不同的數據庫服務器中,一般可以按兩個緯度來拆分數據:

垂直拆分:是指按功能模塊拆分,比如可以將群組相關表和照片相關表存放在不同的數據庫中,這種方式多個數據庫之間的表結構不同。

水平拆分:而水平拆分是將同一個表的數據進行分塊保存到不同的數據庫中,這些數據庫中的表結構完全相同。

拆分方式
一般都會先進行垂直拆分,因為這種方式拆分方式實現起來比較簡單,根據表名訪問不同的數據庫就可以了。但是垂直拆分方式并不能徹底解決所有壓力問題,另外,也要看應用類型是否合適這種拆分方式。如果合適的話,也能很好的起到分散數據庫壓力的作用。比如對于豆瓣我覺得比較適合采用垂直拆分, 因為豆瓣的各核心業務/模塊(書籍、電影、音樂)相對獨立,數據的增加速度也比較平穩。不同的是,又拍網的核心業務對象是用戶上傳的照片,而照片數據的增加速度隨著用戶量的增加越來越快。壓力基本上都在照片表上,顯然垂直拆分并不能從根本上解決我們的問題,所以,我們采用水平拆分的方式。

拆分規則
水平拆分實現起來相對復雜,我們要先確定一個拆分規則,也就是按什么條件將數據進行切分。 一般2.0網站都以用戶為中心,數據基本都跟隨用戶,比如用戶的照片、朋友和評論等等。因此一個比較自然的選擇是根據用戶來切分。每個用戶都對應一個數據庫,訪問某個用戶的數據時, 我們要先確定他/她所對應的數據庫,然后連接到該數據庫進行實際的數據讀寫。

那么,怎么樣對應用戶和數據庫呢?我們有這些選擇:

· 按算法對應

最簡單的算法是按用戶ID的奇偶性來對應,將奇數ID的用戶對應到數據庫A,而偶數ID的用戶則對應到數據庫B。這個方法的最大問題是,只能分成兩個庫。另一個算法是按用戶ID所在區間對應,比如ID在0-10000之間的用戶對應到數據庫A, ID在10000-20000這個范圍的對應到數據庫B,以此類推。按算法分實現起來比較方便,也比較高效,但是不能滿足后續的伸縮性要求,如果需要增加數據庫節點,必需調整算法或移動很大的數據集, 比較難做到在不停止服務的前提下進行擴充數據庫節點。

· 按索引/映射表對應

這種方法是指建立一個索引表,保存每個用戶的ID和數據庫ID的對應關系,每次讀寫用戶數據時先從這個表獲取對應數據庫。新用戶注冊后,在所有可用的數據庫中隨機挑選一個為其建立索引。這種方法比較靈活,有很好的伸縮性。一個缺點是增加了一次數據庫訪問,所以性能上沒有按算法對應好。

比較之后,我們采用的是索引表的方式,我們愿意為其靈活性損失一些性能,更何況我們還有memcached, 因為索引數據基本不會改變的緣故,緩存命中率非常高。所以能很大程度上減少了性能損失。

圖4:數據訪問過程

索引表的方式能夠比較方便地添加數據庫節點,在增加節點時,只要將其添加到可用數據庫列表里即可。 當然如果需要平衡各個節點的壓力的話,還是需要進行數據的遷移,但是這個時候的遷移是少量的,可以逐步進行。要遷移用戶A的數據,首先要將其狀態置為遷移數據中,這個狀態的用戶不能進行寫操作,并在頁面上進行提示。 然后將用戶A的數據全部復制到新增加的節點上后,更新映射表,然后將用戶A的狀態置為正常,最后將原來對應的數據庫上的數據刪除。這個過程通常會在臨晨進行,所以,所以很少會有用戶碰到遷移數據中的情況。

當然,有些數據是不屬于某個用戶的,比如系統消息、配置等等,我們把這些數據保存在一個全局庫中。

問題
分庫會給你在應用的開發和部署上都帶來很多麻煩。

· 不能執行跨庫的關聯查詢

如果我們需要查詢的數據分布于不同的數據庫,我們沒辦法通過JOIN的方式查詢獲得。比如要獲得好友的最新照片,你不能保證所有好友的數據都在同一個數據庫里。一個解決辦法是通過多次查詢,再進行聚合的方式。我們需要盡量避免類似的需求。有些需求可以通過保存多份數據來解決,比如User-A和User-B的數據庫分別是DB-1和DB-2, 當User-A評論了User-B的照片時,我們會同時在DB-1和DB-2中保存這條評論信息,我們首先在DB-2中的photo_comments表中插入一條新的記錄,然后在DB-1中的user_comments表中插入一條新的記錄。這兩個表的結構如下圖所示。這樣我們可以通過查詢photo_comments表得到User-B的某張照片的所有評論, 也可以通過查詢user_comments表獲得User-A的所有評論。另外可以考慮使用全文檢索工具來解決某些需求, 我們使用Solr來提供全站標簽檢索和照片搜索服務。

圖5:評論表結構

· 不能保證數據的一致/完整性

跨庫的數據沒有外鍵約束,也沒有事務保證。比如上面的評論照片的例子, 很可能出現成功插入photo_comments表,但是插入user_comments表時卻出錯了。一個辦法是在兩個庫上都開啟事務,然后先插入photo_comments,再插入user_comments, 然后提交兩個事務。這個辦法也不能完全保證這個操作的原子性。

· 所有查詢必須提供數據庫線索

比如要查看一張照片,僅憑一個照片ID是不夠的,還必須提供上傳這張照片的用戶的ID(也就是數據庫線索),才能找到它實際的存放位置。因此,我們必須重新設計很多URL地址,而有些老的地址我們又必須保證其仍然有效。我們把照片地址改成/photos/{username}/{photo_id}/的形式,然后對于系統升級前上傳的照片ID, 我們又增加一張映射表,保存photo_id和user_id的對應關系。當訪問老的照片地址時,我們通過查詢這張表獲得用戶信息, 然后再重定向到新的地址。

· 自增ID

如果要在節點數據庫上使用自增字段,那么我們就不能保證全局唯一。這倒不是很嚴重的問題,但是當節點之間的數據發生關系時,就會使得問題變得比較麻煩。我們可以再來看看上面提到的評論的例子。如果photo_comments表中的comment_id的自增字段,當我們在DB-2.photo_comments表插入新的評論時, 得到一個新的comment_id,假如值為101,而User-A的ID為1,那么我們還需要在DB-1.user_comments表中插入(1, 101 …)。 User-A是個很活躍的用戶,他又評論了User-C的照片,而User-C的數據庫是DB-3。 很巧的是這條新評論的ID也是101,這種情況很用可能發生。那么我們又在DB-1.user_comments表中插入一行像這樣(1, 101 …)的數據。 那么我們要怎么設置user_comments表的主鍵呢(標識一行數據)?可以不設啊,不幸的是有的時候(框架、緩存等原因)必需設置。那么可以以user_id、 comment_id和photo_id為組合主鍵,但是photo_id也有可能一樣(的確很巧)。看來只能再加上photo_owner_id了, 但是這個結果又讓我們實在有點無法接受,太復雜的組合鍵在寫入時會帶來一定的性能影響,這樣的自然鍵看起來也很不自然。所以,我們放棄了在節點上使用自增字段,想辦法讓這些ID變成全局唯一。為此增加了一個專門用來生成ID的數據庫,這個庫中的表結構都很簡單,只有一個自增字段id。 當我們要插入新的評論時,我們先在ID庫的photo_comments表里插入一條空的記錄,以獲得一個唯一的評論ID。 當然這些邏輯都已經封裝在我們的框架里了,對于開發人員是透明的。 為什么不用其它方案呢,比如一些支持incr操作的Key-Value數據庫。我們還是比較放心把數據放在MySQL里。 另外,我們會定期清理ID庫的數據,以保證獲取新ID的效率。

實現
我們稱前面提到的一個數據庫節點為Shard,一個Shard由兩個臺物理服務器組成, 我們稱它們為Node-A和Node-B,Node-A和Node-B之間是配置成Master-Master相互復制的。 雖然是Master-Master的部署方式,但是同一時間我們還是只使用其中一個,原因是復制的延遲問題, 當然在Web應用里,我們可以在用戶會話里放置一個A或B來保證同一用戶一次會話里只訪問一個數據庫, 這樣可以避免一些延遲問題。但是我們的Python任務是沒有任何狀態的,不能保證和PHP應用讀寫相同的數據庫。那么為什么不配置成Master-Slave呢?我們覺得只用一臺太浪費了,所以我們在每臺服務器上都創建多個邏輯數據庫。 如下圖所示,在Node-A和Node-B上我們都建立了shard_001和shard_002兩個邏輯數據庫, Node-A上的shard_001和Node-B上的shard_001組成一個Shard,而同一時間只有一個邏輯數據庫處于Active狀態。 這個時候如果需要訪問Shard-001的數據時,我們連接的是Node-A上的shard_001, 而訪問Shard-002的數據則是連接Node-B上的shard_002。以這種交叉的方式將壓力分散到每臺物理服務器上。 以Master-Master方式部署的另一個好處是,我們可以不停止服務的情況下進行表結構升級, 升級前先停止復制,升級Inactive的庫,然后升級應用,再將已經升級好的數據庫切換成Active狀態, 原來的Active數據庫切換成Inactive狀態,然后升級它的表結構,最后恢復復制。 當然這個步驟不一定適合所有升級過程,如果表結構的更改會導致數據復制失敗,那么還是需要停止服務再升級的。

圖6:數據庫布局

前面提到過添加服務器時,為了保證負載的平衡,我們需要遷移一部分數據到新的服務器上。為了避免短期內遷移的必要,我們在實際部署的時候,每臺機器上部署了8個邏輯數據庫, 添加服務器后,我們只要將這些邏輯數據庫遷移到新服務器就可以了。最好是每次添加一倍的服務器, 然后將每臺的1/2邏輯數據遷移到一臺新服務器上,這樣能很好的平衡負載。當然,最后到了每臺上只有一個邏輯庫時,遷移就無法避免了,不過那應該是比較久遠的事情了。

我們把分庫邏輯都封裝在我們的PHP框架里了,開發人員基本上不需要被這些繁瑣的事情困擾。下面是使用我們的框架進行照片數據的讀寫的一些例子:

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的照片,注意第一個參數為用戶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都是通過這個對象開放。第一個參數是對象類型名稱, 如果這個名稱已經存在,那么將返回之前定義的對象。你也可以通過get_table(‘Photos’)這個函數來獲取之前定義的Table對象。 第二個參數是對應的數據庫表名,而第三個參數是數據庫線索字段,你會發現在后面的所有API中全部需要指定這個字段的值。 第四個參數是字段定義,其中photo_id字段的global_auto_increment屬性被置為true,這就是前面所說的全局自增ID, 只要指定了這個屬性,框架會處理好ID的事情。

如果我們要訪問全局庫中的數據,我們需要定義一個DBTable對象。

array(‘type’ => ‘long’, ‘primary’ => true, ‘auto_increment’ => true),
‘username’ => array(‘type’ => ’string’),
));
?>
DBTable是ShardedDBTable的父類,除了定義時參數有些不同(DBTable不需要指定數據庫線索字段),它們提供一樣的API。

緩存
我們的框架提供了緩存功能,對開發人員是透明的。

load(1, 10001);
?>
比如上面的方法調用,框架先嘗試以Photos-1-10001為Key在緩存中查找,未找到的話再執行數據庫查詢并放入緩存。當更改照片屬性或刪除照片時,框架負責從緩存中刪除該照片。這種單個對象的緩存實現起來比較簡單。稍微麻煩的是像下面這樣的列表查詢結果的緩存。

fetch(array(‘user_id’ => 1, ‘posted_date__gt’ => ‘2010-06-01′));
?>
我們把這個查詢分成兩步,第一步先查出符合條件的照片ID,然后再根據照片ID分別查找具體的照片信息。 這么做可以更好的利用緩存。第一個查詢的緩存Key為Photos-list-{shard_key}-{md5(查詢條件SQL語句)}, Value是照片ID列表(逗號間隔)。其中shard_key為user_id的值1。目前來看,列表緩存也不麻煩。 但是如果用戶修改了某張照片的上傳時間呢,這個時候緩存中的數據就不一定符合條件了。所以,我們需要一個機制來保證我們不會從緩存中得到過期的列表數據。我們為每張表設置了一個revision,當該表的數據發生變化時(調用insert/update/delete方法), 我們就更新它的revision,所以我們把列表的緩存Key改為Photos-list-{shard_key}-{md5(查詢條件SQL語句)}-{revision}, 這樣我們就不會再得到過期列表了。

revision信息也是存放在緩存里的,Key為Photos-revision。這樣做看起來不錯,但是好像列表緩存的利用率不會太高。因為我們是以整個數據類型的revision為緩存Key的后綴,顯然這個revision更新的非常頻繁,任何一個用戶修改或上傳了照片都會導致它的更新,哪怕那個用戶根本不在我們要查詢的Shard里。要隔離用戶的動作對其他用戶的影響,我們可以通過縮小revision的作用范圍來達到這個目的。 所以revision的緩存Key變成Photos-{shard_key}-revision,這樣的話當ID為1的用戶修改了他的照片信息時, 只會更新Photos-1-revision這個Key所對應的revision。

因為全局庫沒有shard_key,所以修改了全局庫中的表的一行數據,還是會導致整個表的緩存失效。 但是大部分情況下,數據都是有區域范圍的,比如我們的幫助論壇的主題帖子, 帖子屬于主題。修改了其中一個主題的一個帖子,沒必要使所有主題的帖子緩存都失效。 所以我們在DBTable上增加了一個叫isolate_key的屬性。

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’);
?>
注意構造函數的最后一個參數topic_id就是指以字段topic_id作為isolate_key,它的作用和shard_key一樣用于隔離revision的作用范圍。

ShardedDBTable繼承自DBTable,所以也可以指定isolate_key。 ShardedDBTable指定了isolate_key的話,能夠更大幅度縮小revision的作用范圍。 比如相冊和照片的關聯表yp_album_photos,當用戶往他的其中一個相冊里添加了新的照片時, 會導致其它相冊的照片列表緩存也失效。如果我指定這張表的isolate_key為album_id的話, 我們就把這種影響限制在了本相冊內。

我們的緩存分為兩級,第一級只是一個PHP數組,有效范圍是Request。而第二級是memcached。這么做的原因是,很多數據在一個Request周期內需要加載多次,這樣可以減少memcached的網絡請求。另外我們的框架也會盡可能的發送memcached的gets命令來獲取數據, 從而減少網絡請求。

總結
這個架構使得我們在很長一段時間內都不必再為數據庫壓力所困擾。我們的設計很多地方參考了netlog和flickr的實現,因此非常感謝他們將一些實現細節發布出來。

關于作者:

周兆兆(Zola,不是你熟知的那個),又拍網架構師。6年IT從業經驗,不太專注于某項技術,對很多技術都感興趣。

————————————————————
作為國內最大的圖片服務提供商之一,Yupoo! 的 Alexa 排名大約在 5300 左右。同時收集到的一些數據如下:
帶寬:4000M/S (參考)
服務器數量:60 臺左右
Web服務器:Lighttpd, Apache, nginx
應用服務器:Tomcat
其他:Python, Java, MogileFS 、ImageMagick 等
首先看一下網站的架構圖:

該架構圖給出了很好的概覽(點擊可以查看在 Yupoo! 上的大圖和原圖,請注意該圖版權信息)。
關于 Squid 與 Tomcat
Squid 與 Tomcat 似乎在 Web 2.0 站點的架構中較少看到。我首先是對 Squid 有點疑問,對此阿華的解釋是”目前暫時還沒找到效率比 Squid 高的緩存系統,原來命中率的確很差,后來在 Squid 前又裝了層 Lighttpd, 基于 url 做 hash, 同一個圖片始終會到同一臺 squid 去,所以命中率徹底提高了”
對于應用服務器層的 Tomcat,現在 Yupoo! 技術人員也在逐漸用其他輕量級的東西替代,而 YPWS/YPFS 現在已經用 Python 進行開發了。

名次解釋:
YPWS–Yupoo Web Server YPWS 是用 Python開發的一個小型 Web 服務器,提供基本的 Web 服務外,可以增加針對用戶、圖片、外鏈網站顯示的邏輯判斷,可以安裝于任何有空閑資源的服務器中,遇到性能瓶頸時方便橫向擴展。
YPFS–Yupoo File System 與 YPWS 類似,YPFS 也是基于這個 Web 服務器上開發的圖片上傳服務器。

【Updated: 有網友留言質疑 Python 的效率,Yupoo 老大劉平陽在 del.icio.us 上寫到 “YPWS用Python自己寫的,每臺機器每秒可以處理294個請求, 現在壓力幾乎都在10%以下”】

圖片處理層
接下來的 Image Process Server 負責處理用戶上傳的圖片。使用的軟件包也是 ImageMagick,在上次存儲升級的同時,對于銳化的比率也調整過了(我個人感覺,效果的確好了很多)。”Magickd“ 是圖像處理的一個遠程接口服務,可以安裝在任何有空閑 CPU資源的機器上,類似 Memcached的服務方式。
我們知道 Flickr 的縮略圖功能原來是用 ImageMagick 軟件包的,后來被雅虎收購后出于版權原因而不用了(?);EXIF 與 IPTC Flicke 是用 Perl 抽取的,我是非常建議 Yupoo! 針對 EXIF 做些文章,這也是潛在產生受益的一個重點。
圖片存儲層
原來 Yupoo! 的存儲采用了磁盤陣列柜,基于 NFS 方式的,隨著數據量的增大,”Yupoo! 開發部從07年6月份就開始著手研究一套大容量的、能滿足 Yupoo! 今后發展需要的、安全可靠的存儲系統“,看來 Yupoo! 系統比較有信心,也是滿懷期待的,畢竟這要支撐以 TB 計算的海量圖片的存儲和管理。我們知道,一張圖片除了原圖外,還有不同尺寸的,這些圖片統一存儲在 MogileFS 中。
對于其他部分,常見的 Web 2.0 網站必須軟件都能看到,如 MySQL、Memcached 、Lighttpd 等。Yupoo! 一方面采用不少相對比較成熟的開源軟件,一方面也在自行開發定制適合自己的架構組件。這也是一個 Web 2.0 公司所必需要走的一個途徑。

來源:http://www.bopor.com/?p=652

轉載于:https://my.oschina.net/longhtml/blog/156338

總結

以上是生活随笔為你收集整理的又拍网架构-又一个用到python的网站的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。