使用HAProxy、PHP、Redis和MySQL支撐每週10億請求
在公司的發展中,保證伺服器的可擴充套件性對於擴大企業的市場需要具有重要作用,因此,這對架構師提出了一定的要求。Octivi聯合創始人兼軟體架構師Antoni Orfin將向你介紹一個非常簡單的架構,使用HAProxy、PHP、Redis和MySQL就能支撐每週10億請求。同時,你還能瞭解專案未來的橫向擴充套件途徑及常見的模式。
狀態
- 伺服器
- 3個應用程式節點
- 2個MySQL+1個備份
- 2個Redis
- 應用程式
- 應用程式每週處理10億請求
- 峰值700請求/秒的單Symfony2例項(平均工作日約550請求/秒)
- 平均響應時間30毫秒
- Varnish,每秒請求超過1.2萬次(壓力測試過程中獲得)
- 資料儲存
- Redis儲存了1.6億記錄,資料體積大約100GB,同時它是我們的主要資料儲存
- MySQL儲存了3億記錄,資料體積大約300GB,通常情況下它作為三級快取層
平臺
- 監視:
- Icinga
- Collectd
- 應用程式
- HAProxy + Keepalived
- Varnish
- PHP(PHP-FPM)+ Symfony2 Framework
- 資料儲存
- MySQL(主從配置),使用HAProxy做負載均衡
- Redis (主從配置)
背景
大約1年前,一個朋友找到我並提出了一個苛刻的要求:它們是一個飛速發展的電子商務初創公司,而當時已經準備向國際發展。介於那個時候他們仍然是一個創業公司,初始解決方案必須符合所謂的成本效益,因此也就無法在伺服器上投入更多的資金。遺留系統使用了標準的LAMP堆疊,因此他們擁有一個強力的PHP開發團隊。如果必須引入新技術的話,那麼這些技術必須足夠簡單,不會存在太多架構上的複雜性;那麼,他們當下的技術團隊就可以對應用進行長期的維護。
為了滿足他們擴充套件到下一個市場的需求,架構師必須使用可擴充套件理念進行設計。首先,我們審視了他們的基礎設施:
老系統使用了單模組化設計思路,底層是一些基於PHP的Web應用程式。這個初創公司有許多所謂的前端網站,它們大多都使用了獨立的資料庫,並共享了一些支撐業務邏輯的通用程式碼。毫不客氣的說,長期維護這種應用程式絕對是一個噩夢:因為隨著業務的發展,有些程式碼必須被重寫,這樣的話,修改某個網站將不可避免導致業務邏輯上的不一致,這樣一來,他們不得不在所有Web應用程式上做相同的修改。
通常情況下,這該歸結於專案管理問題,管理員必須對橫跨多個程式碼庫的那些程式碼負責。基於這個觀點,整改第一步就是提取核心的業務關鍵功能,並將之拆分為獨立的服務(這也是本文的一個重點部分),也就是所謂的面向服務架構,在整個系統內遵循“separation of concern”原則。每個服務只負責一個業務邏輯,同時也要明確更高等級的業務功能。舉個形象的例子也就是,這個系統可能是個搜尋引擎、一個銷售系統等。
前端網站通過REST API與服務互動,響應則基於JSON格式。為了簡單起見,我們沒有選擇SOAP,一個開發者比較無愛的協議,因為誰都不願意解析一堆的XML。
提取一些不會經常處理的服務,比如身份驗證和會話管理。這是非常必要的一個環節,因為它們的處理等級比較高。前端網站負責這個部分,只有它們可以識別使用者。這樣一來我們可以保持服務的足夠簡單,在處理擴充套件和程式碼相關問題時都具有巨大的優勢,可謂各司其職,完美無缺。
帶來的好處:
- 獨立子系統(服務)可以便捷的在不同團隊中開發,開發者互不干涉,效率理所當然提升。
- 身份驗證和會話不會通過它們來管理,因此它們造成的擴充套件問題不翼而飛。
- 業務邏輯被區分,不同的前端網站不會再存在功能冗餘。
- 顯著地提高了服務的可用性。
共生的缺點:
為系統管理員帶來更大的工作量。鑑於服務都使用了獨立的基礎設施,這將給管理員帶來更多需要關注的地方。
很難保持向後相容。在一年的維護之後,API方法中發生了數不盡的變化。因此問題發生了,它們必將破壞向後相容,因為每個網站的程式碼都可能發生變化,還可能存在許多技術人員同時修改一個網站的情況……然而,一年後,所有方法匹配的仍然是專案開始時建立的文件。
應用程式層
著眼請求工作流,第一層是應用程式。HAProxy負載均衡器、Varnish和Symfony2應用程式都在這一層。來自前端網站的請求首先會傳遞給HAProxy,隨後負載均衡器將把他分給不同的節點。
應用程式節點配置
- Xeon E5-1620@3.60GHz,64GB RAM,SATA
- Varnish
- Apache2
- PHP 5.4.X(PHP-FPM),使用APC位元組碼快取
我們購買了3個這樣的伺服器,N+1冗餘配置的active-active模式,備份伺服器同樣處理請求。因為效能不是首要因素,我們為每個節點配置獨立的Varnish以降低快取hit,同時也避免了單點故障(SPOF)。在這個專案中,我們更重視可用性。因為一個前端網站伺服器中使用了Apache 2,我們保留了這個堆疊。這樣一來,管理員不會困擾於太多新加入的技術。
Symfony2應用程式
應用程式本身基於Symfony2建立,這是一個PHP全堆疊框架,提供了大量加速開發的元件。作為基於複雜框架的典型REST服務可能受到很多人質疑,這裡為你細說:
- 對 PHP/Symfony 開發者友好。客戶端IT團隊由PHP開發者組成,新增新技術將意味必須招聘新的開發者,因為業務系統必須做長時間的維護。
- 清晰的專案結構。 PHP/Symfony雖然從來都不是必需品,但卻是許多專案的預設選擇。引入新的開發者將非常方便,因為對他們來說程式碼非常友好。
- 許多現成的元件。遵循DRY思想……沒有人願意花力氣去做重複的工作,我們也不例外。我們使用了大量的Symfony2 Console Component,這個框架非常有利於做CLI命令,以及應用程式效能分析(debug工具欄)、記錄器等。
在選用Symfony2之前,我們做了大量的效能測試以保證應用程式可以支撐計劃流量。我們制定了概念驗證,並使用JMeter執行,我們得到了讓人滿意的結果——每秒700請求時響應時間可以控制在50毫秒。這些測試給了我們足夠的信心,讓我們堅信,即使Symfony2這樣複雜的框架也可以得到理想的效能。
應用程式分析與監控
我們使用Symfony2工具來監視應用程式,在收集指定方法執行時間上表現的非常不錯,特別是那些與第三方網路服務互動的操作。這樣一來,我們可以發現架構中潛在的弱點,找出應用程式中最耗時的部分。
冗長的日誌同樣是不可缺少的一部分,我們使用PHP Monolog庫把這些日誌處理成優雅的log-lines,便於開發者和管理員理解。這裡需要注意的是儘可能多地新增細節,越詳細越好,我們使用了不同的日誌等級:
- Debug,可能會發生的事情。比如,請求資訊在呼叫前會傳送給一個外部Web服務;事情發生後從API呼叫響應。
- Error,當錯誤發生時請求流並未被終止,比如第三方API的錯誤響應。
- Critical,應用程式崩潰的瞬間。
因此,你可以清晰地瞭解Error和Critical資訊。而在開發/測試環境中,Debug資訊同樣被記錄。同時,日誌被儲存在不同的檔案中,也就是Monolog庫下的“channels”。系統中有一個主日誌檔案,記錄了所有應用程式級錯誤,以及各個channel的短日誌,從單獨的檔案中記錄了來自各個channel的詳細日誌。
擴充套件性
擴充套件平臺的應用程式層並不困難,HAProxy效能並不會在短時間耗盡,唯一需要考慮的就是如何冗餘以避免單點故障。因此,當下需要做的只是新增下一個應用程式節點。
資料層
我們使用Redis和MySQL儲存所有的資料,MySQL更多作為三級快取層,而Redis則是系統的主要資料儲存。
Redis
在系統設計時,我們基於以下幾點來選擇滿足計劃需求的資料庫:
- 在儲存大量資料時不會影響效能,大約2.5億記錄
- 通常情況下多是基於特定資源的簡單GET請求,沒有查詢及複雜的SELECT操作
- 在單請求時儘可能多的獲得資源以降低延時
在經過一些調查後,我們決定使用Redis
- 大部分我們執行的操作都具有 O(1)或O(N)複雜性, N是需要檢索鍵的數量,這意味著keyspace大小並不會影響效能。
- 通常情況下會使用MGET命令列同時檢索100個以上的鍵,這樣可以儘可能的避免網路延時,而不是在迴圈中做多重GET操作。
我們當下擁有兩個Redis伺服器,使用主從複製模式。這兩個節點的配置相同,都是Xeon E5-2650v2@2.60GHz,128GB,SSD。記憶體限制被設定為100GB,通常情況下使用率都是100%。
在應用程式並沒有耗盡單個Redis伺服器的所有資源時,從節點主要作作備份使用,用以保證高有效性。如果主節點當機,我們可以快速的將應用程式切換到從節點。在維護和伺服器遷移時,複製同樣被執行——轉換一個伺服器非常簡單。
你可能會猜想當Redis資源被一直耗盡時的情景,所有的鍵都是持久化型別,大約佔90% keyspace,剩餘資源被全部被用於TTL過期快取。當下,keyspace已經被分為兩個部分:一個是TTL集(快取),另一個則是用於持久化資料。感謝“volatile-lru”最大化記憶體設定的可行性,最不經常使用快取鍵會被移除。如此一來,系統就可以一直保持單Redis例項同時執行兩個操作——主儲存和通用快取。
使用這個模式必須一直監視“期滿”鍵的數量:
db.redis1:6379> info keyspace # Keyspace db0:keys=16XXXXXXX,expires=11XXXXXX,avg_ttl=0
“期滿”鍵數量越接近0情況越危險,這個時候管理員就需要考慮適當的分片或者是增加記憶體。
我們如何進行監控?這裡使用Icinga check,儀表盤會顯示數字是否會達到臨界點,我們還使用了Redis來視覺化“丟失鍵”的比率。
在一年後,我們已經愛上了Redis,它從未讓我們失望,這一年系統從未發生任何當機情況。
MySQL
在Redis之外,我們還使用了傳統RDBMS——MySQL。但是區別於他人,我們通常使用它作為三級快取層。我們使用MySQL儲存一些不會經常使用物件以降低Redis的資源使用率,因此它們被放到了硬碟上。這裡沒有什麼可說道的地方,我們只是儘可能地讓其保持簡單。我們使用了兩個MySQL伺服器,配置是Xeon E5-1620@3.60GHz,64GB RAM,SSD。兩個伺服器使用本地、非同步的主-主複製。此外,我們使用一個單獨的從節點作為備份。
MySQL的高可用性
在應用程式中,資料庫永遠是最難的瓶頸。當前,這裡還不需要考慮橫向擴充套件操作,我們多是縱向擴充套件Redis和MySQL伺服器。當下這個策略還存在一定的發展空間,Redis執行在一個126GB記憶體的伺服器上,擴充套件到256GB也並不困難。當然,這樣的伺服器也存在劣勢,比如快照,又或是是簡單的啟動——Redis伺服器啟動需要很長的時間。
在縱向擴充套件失效後進行的必然是橫向擴充套件,值得高興的是,專案開始時我們就為資料準備了一個易於分片的結構:
在Redis中,我們為記錄使用了4個“heavy”型別。基於資料型別,它們可以分片到4個伺服器上。我們避免使用雜湊分片,而是選擇基於記錄型別分片。這種情況下,我們仍然可以執行MGET,它始終在一種型別鍵上執行。
在MySQL上,結構化的表格非常易於向另一臺伺服器上遷移——同樣基於記錄型別(表格)。當然,一旦基於記錄型別的分片不再奏效,我們將轉移至雜湊。
學到的知識
- 不要隨便共享你的資料庫給別人用。有一次,一個前端網站希望將會話處理切換到Redis,所以就直接連線了一個,它耗盡了Redis的快取空間,以至於應用程式再也不能儲存下一個快取鍵了。所有的快取應該在 MySQL 的結果集佔用大量開銷時才進行儲存。
- 日誌越詳細越好。如果log-lines中沒有足夠的資訊,快速Debug問題定位將成為難點。如此一來,你不得不等待一個又一個問題發生,直到找到根結所在。
- 架構中使用複雜的框架並不意味著低效能。許多人驚訝我們使用全堆疊框架來支撐如此流量應用程式,其祕訣在於更聰明的使用工具,否則即使是Node.js也可能變得很慢。選擇一個提供良好開發環境的技術,沒有人期望使用一堆不友好的工具,這將降低開發團隊士氣。
相關文章
- 使用HAProxy、PHP、Redis和MySQL支撐10億請求每週架構細節PHPRedisMySql架構
- 每秒7億次請求,阿里新一代資料庫如何支撐?阿里資料庫
- PHP curl 請求使用教程PHP
- PHP中使用cURL實現Get和Post請求PHP
- php模擬請求(偽造來源和請求ip)PHP
- 支撐微博億級社交平臺,小白也能玩轉Redis叢集(實戰篇)Redis
- 支撐微博億級社交平臺,小白也能玩轉Redis叢集(原理篇)Redis
- PHP傳送POST和GET請求PHP
- MySQL 資料庫 到底能支撐多少表?MySql資料庫
- PHP curl 請求PHP
- Laravel 請求週期Laravel
- Python + Django 如何支撐了 7 億月活使用者的 Instagram?PythonDjango
- 中國股市如何判斷支撐和壓力
- 【轉】怎麼用PHP傳送HTTP請求(POST請求、GET請求)?PHPHTTP
- 谷歌翻譯使用php curl請求介面文件谷歌PHP
- Redis穩定性之戰:AOF日誌支撐資料持久化Redis持久化
- MySQL HAProxyMySql
- php請求API介面方法PHPAPI
- cURL實現傳送Get和Post請求(PHP)PHP
- 每週分享第 10 期
- 分析如何支撐高併發?
- PHP 回顧之 Web 請求PHPWeb
- 37. 請求的生命週期
- 我如何使用每月100美金伺服器支撐5天內5百萬使用者伺服器
- 筆記:MAC使用brew配置nginx、php、mysql、php-fpm、redis筆記MacNginxPHPMySqlRedis
- PHP使用redisPHPRedis
- Servlet中請求重定向和請求轉發和includeServlet
- 使用docker安裝mysql和redisDockerMySqlRedis
- 如何支撐微服務架構落地微服務架構
- 開心網10億賣身:社交收入佔比縮至一成 靠2款遊戲支撐遊戲
- PHP AJAX JSONP實現跨域請求使用例項PHPJSON跨域
- php 支援jsonp跨域請求PHPJSON跨域
- PgSQL·應用案例·驚天效能!單RDSPostgreSQL例項支撐2000億SQL
- 優雅地使用GET和POST請求方法
- 使用fidder進行post和get請求
- 歐洲伽利略導航系統遭遇大規模故障 使用者尋求美國 GPS 等後備支撐
- 請求重定向和請求轉發的區別
- Android Http請求框架一:Get 和 Post 請求AndroidHTTP框架