【編者按】在 UPYUN Open Talk 廈門場,美拍架構平臺負責人洪小軍從計數器服務開始,深入地分享了大中型SNS系統設計經驗。在系統設計的基本原則一節他表示,系統設計必須以一致性、可用性、可擴展性為目標。在初期,機構需要做的是快速搭建系統上線;而隨著業務規模的增長,機構必須深入打造系統的可擴展性;到項目發展到中后期,開發人員往往需要處理各種各樣的問題,這個時候可用性則成了必須關注的重點。
以下為原文
一、 業務系統架構演化
談到業務系統的架構,首先要說計數器服務,雖然看起來是個小功能,如果將計數器整個歷程和深層次的東西掌握清楚,會發現很多東西是相通的。通過簡單的業務,挖掘背后的技術,是一個很好的學習過程。
計數器服務
SNS最重要的就是社交關系,以及用戶信息。在美拍你可以看到美拍評論,有評論數、點贊數,微博有評論數、轉發數、點贊數,這些都需要通過計數器來完成。
1. 實現計數器最粗暴的方式:數據庫直接count
一些產品在開始上線的時候,會用非常粗暴的方式——在數據庫中把數據count出來。計數器服務在SNS系統中屬于TOP級別的訪問大戶,甚至是TOP1,這個簡單粗暴的方式自然會面臨一些問題:因為沒有獨立的計數器,每次都需要從數據庫count,會出現大量慢查詢、慢請求問題。
2. 大部分公司常用的方案:獨立的計數器表
上面粗暴的計數器方式,沒有獨立的計數器,每次都要從數據庫count。而今大部分公司或系統會獨立維護計數器,建立計數器表,包括ID表和純計數表,每次變更都需要update,與此同時前面還有一個memacached,用來扛大量熱的請求。這個方案在前期沒有什么問題,但發。展到一段階段,會出現很多接口響應時間過長的問題。
回到SNS系統的特點,數據庫中會有大量的update/select操作。比如美拍,某一個明星發了一條信息,瞬間會有很多評論、點贊,計數表會頻繁的update。另外一種情況是,大量的信息是沒有被評論和轉發。SNS系統只要存放這些評論和轉發數等于0的數據,就會有大量的select,這就需要緩存的容量足夠大,將所有的數據裝進去。
SNS系統還有一個特點,就是長尾特性很明顯,數據分布上有一條很長的尾巴。比如最近三個月占90%,比如第4、5、6個月,占比是很均衡的,這就是長尾的特點,這對緩存提出很大的挑戰。假設你可能要保證緩存命中率90%,可能要花5G內存,如果到95%,就需要10G,如果要到99%,可能就要100G或200G,這就對容量提出很高的要求。 可進行的優化:
數據庫合并寫入,即程序段合并寫入請求,有起到一定效果,但是不會太明顯。
緩存金擴容盡可能存放更多數據,但由于SNS系統的長尾效應還是很難達到非常高的命中率。
3. 一定規模公司采用的方案:Redis內存存儲
二、 架設在內存基礎上的架構
內存緩存,從請求的訪問策略來看,從緩存讀取,cache不到,再從mysql讀取;命中率取決于它的容量。如果是內存存儲,命中率是100%。對于容量要求,很多地方會說存儲需要的容量比緩存大很多,但在具體的產品中并不完全是這樣。比如,在計數器服務中,采用存儲的方式,50%的美拍是沒有評論的,存儲只需要存50%有被評論過的就足夠了。但是采用內存緩存的方式,是沒有這個策略的,可能需要存很多沒有被評論過的信息。
1. Redis 內存存儲
Redis是全內存存儲,在SNS系統計數器中只需存存放所有計數器>0的數據 ,具有高速的讀取性能,保證接口的響應時間可控,這也是大多數公司采用的方案,也比較可控。
2. 長尾數據
隨著時間增長,會出現大量的長尾數據,尤其是SNS系統,就像微博發展4、5年前的數據特別多,全部寫入內存成本太高。長尾訪問,不需要用內存這么高的成本去支撐。
長尾問題解決方案:
做冷熱數據分離,將最近個月的數據放在內存,更早的宕到SSD上,成本比較低,這是一個折中方案;
通過修改redis,實現更緊湊的存儲。
三、 FEED服務
FEED業務指的是,好友的消息列表,美拍上的好友動態,微博就是首頁的列表。FEED常用推、拉兩種模式。
推模式:
推模式寫入消息的成本會比較高,相當于發一條美拍需要推給所有關注我美拍的人。推的數量取決于有多少人關注我。相反獲取信息的成本非常低,只需簡單的SELECT就可以取出來,推模式是基于空間去換時間的策略。
在這種情況下,需要考慮3個問題:
容量是否會成為瓶頸;
推送量大的情況下,會出現延遲問題;
加關注/減關注這類變更通知的成本非常高。
拉模式:
拉模式是和推模式相反的方式,輕量寫入。獲取消息,先將關注的列表取出來,再將關注的每個人反的美拍列表取出來,最終合并。這是用時間換空間,拉取需要很多計算,會影響到接口的響應時間,需要大量的計算,需要獲取大量的資源,這樣會造成需求的帶寬增多。
因此,在好友多、粉絲少的情況下,推的模式最好。好友少和粉絲多的情況,比較適合拉的方式。
現在的SNS系統大多都是好友多,粉絲又多。在這種情況下,一些業界公司采用了推拉結合的方法,比如Twitter。普通用戶(粉絲數少的用戶)采用推的方式,大V采用拉的方式。
在發布信息的時候會通過粉絲數及其他策略包括是否在線等狀況做推拉的判斷。微博,公開性的微博都是基于拉的方式,私密性的功能,都是基于推的方式。
FEED服務中的重點:
FEED服務中數據庫和緩存是兩個很重要的點。
在業務快速發展的階段,需要頻繁的增加新的產品屬性,每個屬性都需要在線變更,這樣會對在線用戶產生影響,同時也很增加成本。
常用的解決方法:索引和內容分離,各司其職。索引保證高效的查找,所有的查找都在索引里完成。索引可能會有不同級別、維度的索引。內容使用KV結構(V為二進制數據),便于數據變更。在這里內容需要盡可能緊湊的存儲。
數據庫定位
數據庫只是作為存儲,不應該有任何邏輯,因為系統追求的是高性能的讀取訪問。最多有V條件,不應有任何業務邏輯,業務邏輯應該放到程序端處理。
數據庫面臨很大的寫壓力,常用的是分庫分表的方式,但會出現兩個問題,大部分數據庫都有自身的IP,跟其他的表關聯,分庫分表就不能用智能IP,可能會造成IP沖突。
分庫分表策略
最常見的就是,每個庫里都有N張表,基于HASF分表。但到一定階段會出現一些問題:
特殊用戶查詢性能很高,這種用戶可能有幾百萬甚至上千萬的記錄,一旦穿透到數據庫,性能會很糟糕。
到一定規模后,會考慮進行硬件的升級,如果將所有的歷史數據存放SSD,很浪費。
為解決這個問題,很多公司采用了基于時間分片的方案。最好的方法是建立一個索引表,在索引表里存放每個月用戶有多少條數據。
四、 緩存:雪崩
說道緩存,先補充一個雪崩的概念。
假設系統有4,5個節點,每個緩存節點命中率是90%,也就是每個節點要承擔接近20%的訪問量,如果這個時候有1個節點壞了,這個請求會全部打到memacache,整個系統很可能就直接宕掉。這就是所謂的雪崩。
解決方案
1、在這個階段,程序端做兩節緩存,使得請求在某個節點壞掉了的時候,不會直接到memache,然后請求另外一個節點。
2、隨著業務增長,需要擴充。解決方法:
擴充節點到20個,但可能出現multiget hole問題,在這種情況下,導致性能下降。
搭建多集群。
五、 系統設計的基本原則
CAP原則:一致性、可用性、可擴展性
在SNS場景中,往往上面的三個點,只能選擇其中兩個,需要一定的折中。最主要的是擴展性和可用性。當然不是說一致性不重要,可以采用最終一致的方法做補充。
前期:快速搭建系統上線。
中期:重點解決可擴展性問題。通過硬件升級、單機性能優化,提高單機性能,以及采用SACLE分布式支持,可以解決擴展性問題。
SNS系統高可擴展性需要關注以下幾個點:
支持高并發讀取,首先第一點就是支持大量級的接口調用。其次還需要保證很低的響應時間。
支持峰值的寫入。最重要的一點是前后端分離,前端處于永遠可寫的狀態。(用戶成功發布內容后,顯示成功,實際并沒有完全成功,后面的事情交給后端去做)
做好分布式部署,包括一個機房內部多集群,最好是有條件做到多機房。多機房部署,要保證每個機房之間有可靠的消息同步,要避免跨機房讀調用。
(多機房如何將主要資源同步:SYSQL可采用master方式,Redis有條件的可采用master/salve方式)
中后期:很多系統需要解決可用性問題。可用性問題比前面的問題都更加有挑戰。
避免單點故障。
保證響應時間。
作者簡介:洪小軍,美拍架構平臺負責人。2014年底加入美圖,美圖架構平臺部門負責人,負責美圖后端整體技術架構設計工作,及其核心業務、基礎服務、平臺服務等的研發工作。