Ⅰ 華為技術架構師分享:高並發場景下緩存處理的一些思路
在實際的開發當中,我們經常需要進行磁碟數據的讀取和搜索,因此經常會有出現從資料庫讀取數據的場景出現。但是當數據訪問量次數增大的時候,過多的磁碟讀取可能會最終成為整個系統的性能瓶頸,甚至是壓垮整個資料庫,導致系統卡死等嚴重問題。
常規的應用系統中,我們通常會在需要的時候對資料庫進行查找,因此系統的大致結構如下所示:
1.緩存和資料庫之間數據一致性問題
常用於緩存處理的機制我總結為了以下幾種:
首先來簡單說說Cache aside的這種方式:
Cache Aside模式
這種模式處理緩存通常都是先從資料庫緩存查詢,如果緩存沒有命中則從資料庫中進行查找。
這裡面會發生的三種情況如下:
緩存命中:
當查詢的時候發現緩存存在,那麼直接從緩存中提取。
緩存失效:
當緩存沒有數據的時候,則從database裡面讀取源數據,再加入到cache裡面去。
緩存更新:
當有新的寫操作去修改database裡面的數據時,需要在寫操作完成之後,讓cache裡面對應的數據失效。
關於這種模式下依然會存在缺陷。比如,一個是讀操作,但是沒有命中緩存,然後就到資料庫中取數據,此時來了一個寫操作,寫完資料庫後,讓緩存失效,然後,之前的那個讀操作再把老的數據放進去,所以,會造成臟數據。
Facebook的大牛們也曾經就緩存處理這個問題發表過相關的論文,鏈接如下:
分布式環境中要想完全的保證數據一致性是一件極為困難的事情,我們只能夠盡可能的減低這種數據不一致性問題產生的情況。
Read Through模式
Read Through模式是指應用程序始終從緩存中請求數據。 如果緩存沒有數據,則它負責使用底層提供程序插件從資料庫中檢索數據。 檢索數據後,緩存會自行更新並將數據返回給調用應用程序。使用Read Through 有一個好處。
我們總是使用key從緩存中檢索數據, 調用的應用程序不知道資料庫, 由存儲方來負責自己的緩存處理,這使代碼更具可讀性, 代碼更清晰。但是這也有相應的缺陷,開發人員需要給編寫相關的程序插件,增加了開發的難度性。
Write Through模式
Write Through模式和Read Through模式類似,當數據發生更新的時候,先去Cache裡面進行更新,如果命中了,則先更新緩存再由Cache方來更新database。如果沒有命中的話,就直接更新Cache裡面的數據。
2.緩存穿透問題
在高並發的場景中,緩存穿透是一個經常都會遇到的問題。
什麼是緩存穿透?
大量的請求在緩存中沒有查詢到指定的數據,因此需要從資料庫中進行查詢,造成緩存穿透。
會造成什麼後果?
大量的請求短時間內湧入到database中進行查詢會增加database的壓力,最終導致database無法承載客戶單請求的壓力,出現宕機卡死等現象。
常用的解決方案通常有以下幾類:
1.空值緩存
在某些特定的業務場景中,對於數據的查詢可能會是空的,沒有實際的存在,並且這類數據信息在短時間進行多次的反復查詢也不會有變化,那麼整個過程中,多次的請求資料庫操作會顯得有些多餘。
不妨可以將這些空值(沒有查詢結果的數據)對應的key存儲在緩存中,那麼第二次查找的時候就不需要再次請求到database那麼麻煩,只需要通過內存查詢即可。這樣的做法能夠大大減少對於database的訪問壓力。
2.布隆過濾器
通常對於database裡面的數據的key值可以預先存儲在布隆過濾器裡面去,然後先在布隆過濾器裡面進行過濾,如果發現布隆過濾器中沒有的話,就再去redis裡面進行查詢,如果redis中也沒有數據的話,再去database查詢。這樣可以避免不存在的數據信息也去往存儲庫中進行查詢情況。
什麼是緩存雪崩?
當緩存伺服器重啟或者大量緩存集中在某一個時間段失效,這樣在失效的時候,也會給後端系統(比如DB)帶來很大壓力。
如何避免緩存雪崩問題?
1.使用加鎖隊列來應付這種問題。當有多個請求湧入的時候,當緩存失效的時候加入一把分布式鎖,只允許搶鎖成功的請求去庫裡面讀取數據然後將其存入緩存中,再釋放鎖,讓後續的讀請求從緩存中取數據。但是這種做法有一定的弊端,過多的讀請求線程堵塞,將機器內存占滿,依然沒有能夠從根本上解決問題。
2.在並發場景發生前,先手動觸發請求,將緩存都存儲起來,以減少後期請求對database的第一次查詢的壓力。數據過期時間設置盡量分散開來,不要讓數據出現同一時間段出現緩存過期的情況。
3.從緩存可用性的角度來思考,避免緩存出現單點故障的問題,可以結合使用 主從+哨兵的模式來搭建緩存架構,但是這種模式搭建的緩存架構有個弊端,就是無法進行緩存分片,存儲緩存的數據量有限制,因此可以升級為Redis Cluster架構來進行優化處理。(需要結合企業實際的經濟實力,畢竟Redis Cluster的搭建需要更多的機器)
4.Ehcache本地緩存 + Hystrix限流&降級,避免Mysql被打死。
使用 Ehcache本地緩存的目的也是考慮在 Redis Cluster 完全不可用的時候,Ehcache本地緩存還能夠支撐一陣。
使用 Hystrix進行限流 & 降級 ,比如一秒來了5000個請求,我們可以設置假設只能有一秒 2000個請求能通過這個組件,那麼其他剩餘的 3000 請求就會走限流邏輯。
然後去調用我們自己開發的降級組件(降級),比如設置的一些默認值呀之類的。以此來保護最後的 MySQL 不會被大量的請求給打死。
Ⅱ 高性能高並發網站架構,教你搭建Redis5緩存集群
一、Redis集群介紹
Redis真的是一個優秀的技術,它是一種key-value形式的NoSQL內存資料庫,由ANSI C編寫,遵守BSD協議、支持網路、可基於內存亦可持久化的日誌型、Key-Value資料庫,並提供多種語言的API。 Redis最大的特性是它會將所有數據都放在內存中,所以讀寫速度性能非常好。Redis是基於內存進行操作的,性能較高,可以很好的在一定程度上解決網站一瞬間的並發量,例如商品搶購秒殺等活動。
網站承受高並發訪問壓力的同時,還需要從海量數據中查詢出滿足條件的數據,需要快速響應,前端發送請求、後端和mysql資料庫交互,進行sql查詢操作,讀寫比較慢,這時候引入Redis ,把從mysql 的數據緩存到Redis 中,下次讀取時候性能就會提高;當然,它也支持將內存中的數據以快照和日誌的形式持久化到硬碟,這樣即使在斷電、機器故障等異常情況發生時數據也不會丟失,Redis能從硬碟中恢復快照數據到內存中。
Redis 發布了穩定版本的 5.0 版本,放棄 Ruby的集群方式,改用 C語言編寫的 redis-cli的方式,是集群的構建方式復雜度大大降低。Redis-Cluster集群採用無中心結構,每個節點保存數據和整個集群狀態,每個節點都和其他所有節點連接。
為了保證數據的高可用性,加入了主從模式,一個主節點對應一個或多個從節點,主節點提供數據存取,從節點則是從主節點拉取數據備份,當這個主節點掛掉後,就會有這個從節點選取一個來充當主節點,從而保證集群不會掛掉。
redis-cluster投票:容錯,投票過程是集群中所有master參與,如果半數以上master節點與master節點通信超過(cluster-node-timeout),認為當前master節點掛掉。
集群中至少應該有奇數個節點,所以至少有三個節點,每個節點至少有一個備份節點,所以下面使用6節點(主節點、備份節點由redis-cluster集群確定)。6個節點分布在一台機器上,採用三主三從的模式。實際應用中,最好用多台機器,比如說6個節點分布到3台機器上,redis在建立集群時為自動的將主從節點進行不同機器的分配。
二、單機redis模式
下載源碼redis5.0並解壓編譯
wget http://download.redis.io/releases/redis-5.0.0.tar.gz
tar xzf redis-5.0.0.tar.gz
cd redis-5.0.0
make
redis前端啟動需要改成後台啟動.
修改redis.conf文件,將daemonize no -> daemonize yes
vim redis.conf
啟動redis
/www/server/redis/src/redis-server /www/server/redis/redis.conf
查看redis是否在運行
ps aux|grep redis
現在是單機redis模式完成。
三、redis集群模式:
1.創建6個Redis配置文件
cd /usr/local/
mkdir redis_cluster //創建集群目錄
cd redis_cluster
mkdir 7000 7001 7002 7003 7004 7005//分別代表6個節點
其對應埠 7000 7001 7002 70037004 7005
2.復制配置文件到各個目錄
cp /www/server/redis/redis.conf /usr/local/redis_cluster/7000/
cp /www/server/redis/redis.conf /usr/local/redis_cluster/7001/
cp /www/server/redis/redis.conf /usr/local/redis_cluster/7002/
cp /www/server/redis/redis.conf /usr/local/redis_cluster/7003/
cp /www/server/redis/redis.conf /usr/local/redis_cluster/7004/
cp /www/server/redis/redis.conf /usr/local/redis_cluster/7005/
3.分別修改配置文件
vim /usr/local/redis_cluster/7000/redis.conf
vim /usr/local/redis_cluster/7001/redis.conf
vim /usr/local/redis_cluster/7002/redis.conf
vim /usr/local/redis_cluster/7003/redis.conf
vim /usr/local/redis_cluster/7004/redis.conf
vim /usr/local/redis_cluster/7005/redis.conf
如下
port 7000 #埠
cluster-enabled yes #啟用集群模式
cluster-config-file nodes_7000.conf #集群的配置 配置文件首次啟動自動生成
cluster-node-timeout 5000 #超時時間 5秒
appendonly yes #aof日誌開啟 它會每次寫操作都記錄一條日誌
daemonize yes #後台運行
protected-mode no #非保護模式
pidfile /var/run/redis_7000.pid
//下面可以不寫
#若設置密碼,master和slave需同時配置下面兩個參數:
masterauth "jijiji" #連接master的密碼
requirepass "jijiji" #自己的密碼
cluster-config-file,port,pidfile對應數字
4.啟動節點
cd /www/server/redis/src/
./redis-server /usr/local/redis_cluster/7000/redis.conf
./redis-server /usr/local/redis_cluster/7001/redis.conf
./redis-server /usr/local/redis_cluster/7002/redis.conf
./redis-server /usr/local/redis_cluster/7003/redis.conf
./redis-server /usr/local/redis_cluster/7004/redis.conf
./redis-server /usr/local/redis_cluster/7005/redis.conf
查看redis運行
ps aux|grep redis
5.啟動集群
/www/server/redis/src/redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 --cluster-replicas 1
這里使用的命令是create,因為我們要創建一個新的集群。 該選項--cluster-replicas 1意味著我們希望每個創建的主伺服器都有一個從服。
輸入yes
至此,Reids5 集群搭建完成。
6.檢查Reids5集群狀態
可以執行redis-cli --cluster check host:port檢查集群狀態slots詳細分配。
redis-cli --cluster info 127.0.0.1:7000
7.停止Reids5集群
(1).因為Redis可以妥善處理SIGTERM信號,所以直接kill -9也是可以的,可以同時kill多個,然後再依次啟動。
kill -9 PID PID PID
(2).redis5 提供了關閉集群的工具,修改文件: /www/server/redis/utils/create-cluster/create-cluster
埠PROT設置為6999,NODES為6,工具會生成 7000-7005 六個節點 用於操作。
修改後,執行如下命令關閉集群:
/www/server/redis/utils/create-cluster/create-cluster stop
重新啟動集群:
/www/server/redis/utils/create-cluster/create-cluster start
8.幫助信息
執行redis-cli --cluster help,查看更多幫助信息
redis-cli --cluster help
吉海波
Ⅲ 課程介紹以及高並發高可用復雜系統中的緩存架構有哪些東西
按照國際有關組織的定義,設備在任一隨機時刻需要和開始執行任務時,處於可工作或可使用狀態的程度。通常用可用度(A0)表示,它把可靠性、維修性、測試性、保障性等等產品的設計特性綜合成為用戶所關心的使用參數。可用性的概率度量叫「可用度」。固有可用度AI=TBF/(TBF+MCT)其中:TBF為平均故障間隔時間(小時),MCT為平均修復時間(小時)。使用可用性A0=累計工作時間/(累計工作時間+累計不能工作時間)累計不能工作時間包括累計直接維修時間和累計維修保障延誤時間MLDT。故:A0=MTBF/(MTBF+MTTR+MLDT)可靠性的定義:產品在規定的條件下和規定的時間內,完成規定功能的能力。可靠性的概率度量叫可靠度。高可靠性是指該設備的可靠性水平高,例如平均無故障工作時間超過10000小時就比1000小時的高。可擴展性是指設備設計成為模塊結構,並且具有高可靠性水平,可以與新設計的功能模塊組合成新型裝備,具有良好的系統功能和高的可靠性、可用性水平。
Ⅳ 分布式緩存主要用在高並發環境下的作用
分布式緩存主要用在高並發環境下,減輕資料庫的壓力,提高系統的響應速度和並發吞吐。當大量的讀、寫請求湧向資料庫時,磁碟的處理速度與內存顯然不在一個量級,因此,在資料庫之前加一層緩存,能夠顯著提高系統的響應速度,並降低資料庫的壓力。作為傳統的關系型資料庫,MySQL提供完整的ACID操作,支持豐富的數據類型、強大的關聯查詢、where語句等,能夠非常客易地建立查詢索引,執行復雜的內連接、外連接、求和、排序、分組等操作,並且支持存儲過程、函數等功能,產品成熟度高,功能強大。但是,對於需要應對高並發訪問並且存儲海量數據的場景來說,出於對性能的考慮,不得不放棄很多傳統關系型資料庫原本強大的功能,犧牲了系統的易用性,並且使得系統的設計和管理變得更為復雜。這也使得在過去幾年中,流行著另一種新的存儲解決方案——NoSQL,它與傳統的關系型資料庫最大的差別在於,它不使用SQL作為查詢語言來查找數據,而採用key-value形式進行查找,提供了更高的查詢效率及吞吐,並且能夠更加方便地進行擴展,存儲海量數據,在數千個節點上進行分區,自動進行數據的復制和備份。在分布式系統中,消息作為應用間通信的一種方式,得到了十分廣泛的應用。消息可以被保存在隊列中,直到被接收者取出,由於消息發送者不需要同步等待消息接收者的響應,消息的非同步接收降低了系統集成的耦合度,提升了分布式系統協作的效率,使得系統能夠更快地響應用戶,提供更高的吞吐。
當系統處於峰值壓力時,分布式消息隊列還能夠作為緩沖,削峰填谷,緩解集群的壓力,避免整個系統被壓垮。垂直化的搜索引擎在分布式系統中是一個非常重要的角色,它既能夠滿足用戶對於全文檢索、模糊匹配的需求,解決資料庫like查詢效率低下的問題,又能夠解決分布式環境下,由於採用分庫分表,或者使用NoSQL資料庫,導致無法進行多表關聯或者進行復雜查詢的問題。
Ⅳ 如何解決高並發場景下,緩存冷啟動導致mysql負載過高,甚至瞬間被打死的問題
由於mysql是一個連接給一個線程,當並發高的時候,每秒需要幾百個甚至更多的線程,其中創建和銷毀線程還好說,大不了多耗費點內存,線程緩存命中率下降還有創建銷毀線程的性能增加問題---這個問題不是特別大,重點是mysql底層瞬間處理這幾百個線程提交的sql(有時候一個頁面會有10多條sql,cpu一次只能處理一條sql)會導致cpu的上下文切換,性能抖動,然後性能下降。
Ⅵ 高並發,寫入頻繁的評論系統有必要加緩存么
如果並發真到幾萬的話,緩存肯定是要加的。
具體加緩存的策略,看想要什麼效果,可以對查詢最頻繁的一類請求先加緩存。
保證mongo處於一個合理的負載。
Ⅶ 高並發三種解決方法
處理高並發的方法不止三種。
1:系統拆分
將一個系統拆分為多個子系統,用bbo來搞。然後每個系統連一個資料庫,這樣本來就一個庫,現在多個資料庫,這樣就可以抗高並發。
2:緩存,必須得用緩存
大部分的高並發場景,都是讀多寫少,那你完全可以在資料庫和緩存里都寫一份,然後讀的時候大量走緩存不就得了。畢竟人家redis輕輕鬆鬆單機幾萬的並發,沒問題的。所以可以考的慮考慮項目里,那些承載主要請求讀場景,怎麼用緩存來抗高並發。
3:MQ(消息隊列),必須得用MQ
可能還是會出現高並發寫的場景,比如說一個業務操作里要頻繁搞資料庫幾十次,增刪改增刪改,那高並發絕對搞掛系統,人家是緩存你要是用redis來承載寫那肯定不行,數據隨時就被LRU(淘汰掉最不經常使用的)了,數據格式還無比簡單,沒有事務支持。
所以該用mysql還得用mysql,用MQ,大量的寫請求灌入MQ里,排隊慢慢玩兒,後邊系統消費後慢慢寫,控制在mysql承載范圍之內。所以得考慮考慮你的項目里,那些承載復雜寫業務邏輯的場景里,如何用MQ來非同步寫,提升並發性。MQ單機抗幾萬並發也是可以的。
4:分庫分表
可能到了最後資料庫層面還是免不了抗高並發的要求,那麼就將一個資料庫拆分為多個庫,多個庫來抗更高的並發;然後將一個表拆分為多個表,每個表的數據量保持少一點,提高sql跑的性能。
5:讀寫分離
這個就是說大部分時候資料庫可能也是讀多寫少,沒必要所有請求都集中在一個庫上,可以搞個主從架構,主庫寫入,從庫讀取,搞一個讀寫分離。讀流量太多的時候,還可以加更多的從庫。
Ⅷ 高並發如何處理 和並發量是多少 還有緩存伺服器
數據要立即處理:(並發數*單連接平均傳輸數據=關口帶寬)+(減少IO頻率+低延+緩存並發情況數據=做緩存)+高性能伺服器
--數據--
Ⅸ 商城如何處理高並發,怎麼使用memcache做緩存
其實相於應用程序資料庫間塊內存區域些高頻訪問數據放其避免每都請求資料庫至於所用memcachedredis自程序hashmap塊區域共享且容易管理集群環境更便使用
些做直接數據序列化存redisstring或memcached些其做利用redis特數據結構存儲些關系例用sorted set存排行榜string用計數set做些倒排索引、用戶友關系類我覺些都稱緩存
Ⅹ php怎麼處理高並發
以下內容轉載自徐漢彬大牛的博客億級Web系統搭建——單機到分布式集群
當一個Web系統從日訪問量10萬逐步增長到1000萬,甚至超過1億的過程中,Web系統承受的壓力會越來越大,在這個過程中,我們會遇到很多的問題。為了解決這些性能壓力帶來問題,我們需要在Web系統架構層面搭建多個層次的緩存機制。在不同的壓力階段,我們會遇到不同的問題,通過搭建不同的服務和架構來解決。
Web負載均衡
Web負載均衡(Load Balancing),簡單地說就是給我們的伺服器集群分配「工作任務」,而採用恰當的分配方式,對於保護處於後端的Web伺服器來說,非常重要。
負載均衡的策略有很多,我們從簡單的講起哈。
1.HTTP重定向
當用戶發來請求的時候,Web伺服器通過修改HTTP響應頭中的Location標記來返回一個新的url,然後瀏覽器再繼續請求這個新url,實際上就是頁面重定向。通過重定向,來達到「負載均衡」的目標。例如,我們在下載PHP源碼包的時候,點擊下載鏈接時,為了解決不同國家和地域下載速度的問題,它會返回一個離我們近的下載地址。重定向的HTTP返回碼是302
這個重定向非常容易實現,並且可以自定義各種策略。但是,它在大規模訪問量下,性能不佳。而且,給用戶的體驗也不好,實際請求發生重定向,增加了網路延時。
2. 反向代理負載均衡
反向代理服務的核心工作主要是轉發HTTP請求,扮演了瀏覽器端和後台Web伺服器中轉的角色。因為它工作在HTTP層(應用層),也就是網路七層結構中的第七層,因此也被稱為「七層負載均衡」。可以做反向代理的軟體很多,比較常見的一種是Nginx。
Nginx是一種非常靈活的反向代理軟體,可以自由定製化轉發策略,分配伺服器流量的權重等。反向代理中,常見的一個問題,就是Web伺服器存儲的session數據,因為一般負載均衡的策略都是隨機分配請求的。同一個登錄用戶的請求,無法保證一定分配到相同的Web機器上,會導致無法找到session的問題。
解決方案主要有兩種:
1.配置反向代理的轉發規則,讓同一個用戶的請求一定落到同一台機器上(通過分析cookie),復雜的轉發規則將會消耗更多的CPU,也增加了代理伺服器的負擔。
2.將session這類的信息,專門用某個獨立服務來存儲,例如redis/memchache,這個方案是比較推薦的。
反向代理服務,也是可以開啟緩存的,如果開啟了,會增加反向代理的負擔,需要謹慎使用。這種負載均衡策略實現和部署非常簡單,而且性能表現也比較好。但是,它有「單點故障」的問題,如果掛了,會帶來很多的麻煩。而且,到了後期Web伺服器繼續增加,它本身可能成為系統的瓶頸。
3. IP負載均衡
IP負載均衡服務是工作在網路層(修改IP)和傳輸層(修改埠,第四層),比起工作在應用層(第七層)性能要高出非常多。原理是,他是對IP層的數據包的IP地址和埠信息進行修改,達到負載均衡的目的。這種方式,也被稱為「四層負載均衡」。常見的負載均衡方式,是LVS(Linux Virtual Server,Linux虛擬服務),通過IPVS(IP Virtual Server,IP虛擬服務)來實現。
在負載均衡伺服器收到客戶端的IP包的時候,會修改IP包的目標IP地址或埠,然後原封不動地投遞到內部網路中,數據包會流入到實際Web伺服器。實際伺服器處理完成後,又會將數據包投遞回給負載均衡伺服器,它再修改目標IP地址為用戶IP地址,最終回到客戶端。
上述的方式叫LVS-NAT,除此之外,還有LVS-RD(直接路由),LVS-TUN(IP隧道),三者之間都屬於LVS的方式,但是有一定的區別,篇幅問題,不贅敘。
IP負載均衡的性能要高出Nginx的反向代理很多,它只處理到傳輸層為止的數據包,並不做進一步的組包,然後直接轉發給實際伺服器。不過,它的配置和搭建比較復雜。
4. DNS負載均衡
DNS(Domain Name System)負責域名解析的服務,域名url實際上是伺服器的別名,實際映射是一個IP地址,解析過程,就是DNS完成域名到IP的映射。而一個域名是可以配置成對應多個IP的。因此,DNS也就可以作為負載均衡服務。
這種負載均衡策略,配置簡單,性能極佳。但是,不能自由定義規則,而且,變更被映射的IP或者機器故障時很麻煩,還存在DNS生效延遲的問題。
5. DNS/GSLB負載均衡
我們常用的CDN(Content Delivery Network,內容分發網路)實現方式,其實就是在同一個域名映射為多IP的基礎上更進一步,通過GSLB(Global Server Load Balance,全局負載均衡)按照指定規則映射域名的IP。一般情況下都是按照地理位置,將離用戶近的IP返回給用戶,減少網路傳輸中的路由節點之間的跳躍消耗。
「向上尋找」,實際過程是LDNS(Local DNS)先向根域名服務(Root Name Server)獲取到頂級根的Name Server(例如.com的),然後得到指定域名的授權DNS,然後再獲得實際伺服器IP。
CDN在Web系統中,一般情況下是用來解決大小較大的靜態資源(html/Js/Css/圖片等)的載入問題,讓這些比較依賴網路下載的內容,盡可能離用戶更近,提升用戶體驗。
例如,我訪問了一張imgcache.gtimg.cn上的圖片(騰訊的自建CDN,不使用qq.com域名的原因是防止http請求的時候,帶上了多餘的cookie信息),我獲得的IP是183.60.217.90。
這種方式,和前面的DNS負載均衡一樣,不僅性能極佳,而且支持配置多種策略。但是,搭建和維護成本非常高。互聯網一線公司,會自建CDN服務,中小型公司一般使用第三方提供的CDN。
Web系統的緩存機制的建立和優化
剛剛我們講完了Web系統的外部網路環境,現在我們開始關注我們Web系統自身的性能問題。我們的Web站點隨著訪問量的上升,會遇到很多的挑戰,解決這些問題不僅僅是擴容機器這么簡單,建立和使用合適的緩存機制才是根本。
最開始,我們的Web系統架構可能是這樣的,每個環節,都可能只有1台機器。
我們從最根本的數據存儲開始看哈。
一、 MySQL資料庫內部緩存使用
MySQL的緩存機制,就從先從MySQL內部開始,下面的內容將以最常見的InnoDB存儲引擎為主。
1. 建立恰當的索引
最簡單的是建立索引,索引在表數據比較大的時候,起到快速檢索數據的作用,但是成本也是有的。首先,佔用了一定的磁碟空間,其中組合索引最突出,使用需要謹慎,它產生的索引甚至會比源數據更大。其次,建立索引之後的數據insert/update/delete等操作,因為需要更新原來的索引,耗時會增加。當然,實際上我們的系統從總體來說,是以select查詢操作居多,因此,索引的使用仍然對系統性能有大幅提升的作用。
2. 資料庫連接線程池緩存
如果,每一個資料庫操作請求都需要創建和銷毀連接的話,對資料庫來說,無疑也是一種巨大的開銷。為了減少這類型的開銷,可以在MySQL中配置thread_cache_size來表示保留多少線程用於復用。線程不夠的時候,再創建,空閑過多的時候,則銷毀。
其實,還有更為激進一點的做法,使用pconnect(資料庫長連接),線程一旦創建在很長時間內都保持著。但是,在訪問量比較大,機器比較多的情況下,這種用法很可能會導致「資料庫連接數耗盡」,因為建立連接並不回收,最終達到資料庫的max_connections(最大連接數)。因此,長連接的用法通常需要在CGI和MySQL之間實現一個「連接池」服務,控制CGI機器「盲目」創建連接數。
建立資料庫連接池服務,有很多實現的方式,PHP的話,我推薦使用swoole(PHP的一個網路通訊拓展)來實現。
3. Innodb緩存設置(innodb_buffer_pool_size)
innodb_buffer_pool_size這是個用來保存索引和數據的內存緩存區,如果機器是MySQL獨占的機器,一般推薦為機器物理內存的80%。在取表數據的場景中,它可以減少磁碟IO。一般來說,這個值設置越大,cache命中率會越高。
4. 分庫/分表/分區。
MySQL資料庫表一般承受數據量在百萬級別,再往上增長,各項性能將會出現大幅度下降,因此,當我們預見數據量會超過這個量級的時候,建議進行分庫/分表/分區等操作。最好的做法,是服務在搭建之初就設計為分庫分表的存儲模式,從根本上杜絕中後期的風險。不過,會犧牲一些便利性,例如列表式的查詢,同時,也增加了維護的復雜度。不過,到了數據量千萬級別或者以上的時候,我們會發現,它們都是值得的。
二、 MySQL資料庫多台服務搭建
1台MySQL機器,實際上是高風險的單點,因為如果它掛了,我們Web服務就不可用了。而且,隨著Web系統訪問量繼續增加,終於有一天,我們發現1台MySQL伺服器無法支撐下去,我們開始需要使用更多的MySQL機器。當引入多台MySQL機器的時候,很多新的問題又將產生。
1. 建立MySQL主從,從庫作為備份
這種做法純粹為了解決「單點故障」的問題,在主庫出故障的時候,切換到從庫。不過,這種做法實際上有點浪費資源,因為從庫實際上被閑著了。
2. MySQL讀寫分離,主庫寫,從庫讀。
兩台資料庫做讀寫分離,主庫負責寫入類的操作,從庫負責讀的操作。並且,如果主庫發生故障,仍然不影響讀的操作,同時也可以將全部讀寫都臨時切換到從庫中(需要注意流量,可能會因為流量過大,把從庫也拖垮)。
3. 主主互備。
兩台MySQL之間互為彼此的從庫,同時又是主庫。這種方案,既做到了訪問量的壓力分流,同時也解決了「單點故障」問題。任何一台故障,都還有另外一套可供使用的服務。
不過,這種方案,只能用在兩台機器的場景。如果業務拓展還是很快的話,可以選擇將業務分離,建立多個主主互備。
三、 MySQL資料庫機器之間的數據同步
每當我們解決一個問題,新的問題必然誕生在舊的解決方案上。當我們有多台MySQL,在業務高峰期,很可能出現兩個庫之間的數據有延遲的場景。並且,網路和機器負載等,也會影響數據同步的延遲。我們曾經遇到過,在日訪問量接近1億的特殊場景下,出現,從庫數據需要很多天才能同步追上主庫的數據。這種場景下,從庫基本失去效用了。
於是,解決同步問題,就是我們下一步需要關注的點。
1. MySQL自帶多線程同步
MySQL5.6開始支持主庫和從庫數據同步,走多線程。但是,限制也是比較明顯的,只能以庫為單位。MySQL數據同步是通過binlog日誌,主庫寫入到binlog日誌的操作,是具有順序的,尤其當SQL操作中含有對於表結構的修改等操作,對於後續的SQL語句操作是有影響的。因此,從庫同步數據,必須走單進程。
2. 自己實現解析binlog,多線程寫入。
以資料庫的表為單位,解析binlog多張表同時做數據同步。這樣做的話,的確能夠加快數據同步的效率,但是,如果表和表之間存在結構關系或者數據依賴的話,則同樣存在寫入順序的問題。這種方式,可用於一些比較穩定並且相對獨立的數據表。
國內一線互聯網公司,大部分都是通過這種方式,來加快數據同步效率。還有更為激進的做法,是直接解析binlog,忽略以表為單位,直接寫入。但是這種做法,實現復雜,使用范圍就更受到限制,只能用於一些場景特殊的資料庫中(沒有表結構變更,表和表之間沒有數據依賴等特殊表)。
四、 在Web伺服器和資料庫之間建立緩存
實際上,解決大訪問量的問題,不能僅僅著眼於資料庫層面。根據「二八定律」,80%的請求只關注在20%的熱點數據上。因此,我們應該建立Web伺服器和資料庫之間的緩存機制。這種機制,可以用磁碟作為緩存,也可以用內存緩存的方式。通過它們,將大部分的熱點數據查詢,阻擋在資料庫之前。
1. 頁面靜態化
用戶訪問網站的某個頁面,頁面上的大部分內容在很長一段時間內,可能都是沒有變化的。例如一篇新聞報道,一旦發布幾乎是不會修改內容的。這樣的話,通過CGI生成的靜態html頁面緩存到Web伺服器的磁碟本地。除了第一次,是通過動態CGI查詢資料庫獲取之外,之後都直接將本地磁碟文件返回給用戶。
在Web系統規模比較小的時候,這種做法看似完美。但是,一旦Web系統規模變大,例如當我有100台的Web伺服器的時候。那樣這些磁碟文件,將會有100份,這個是資源浪費,也不好維護。這個時候有人會想,可以集中一台伺服器存起來,呵呵,不如看看下面一種緩存方式吧,它就是這樣做的。
2. 單台內存緩存
通過頁面靜態化的例子中,我們可以知道將「緩存」搭建在Web機器本機是不好維護的,會帶來更多問題(實際上,通過PHP的apc拓展,可通過Key/value操作Web伺服器的本機內存)。因此,我們選擇搭建的內存緩存服務,也必須是一個獨立的服務。
內存緩存的選擇,主要有redis/memcache。從性能上說,兩者差別不大,從功能豐富程度上說,Redis更勝一籌。
3. 內存緩存集群
當我們搭建單台內存緩存完畢,我們又會面臨單點故障的問題,因此,我們必須將它變成一個集群。簡單的做法,是給他增加一個slave作為備份機器。但是,如果請求量真的很多,我們發現cache命中率不高,需要更多的機器內存呢?因此,我們更建議將它配置成一個集群。例如,類似redis cluster。
Redis cluster集群內的Redis互為多組主從,同時每個節點都可以接受請求,在拓展集群的時候比較方便。客戶端可以向任意一個節點發送請求,如果是它的「負責」的內容,則直接返回內容。否則,查找實際負責Redis節點,然後將地址告知客戶端,客戶端重新請求。
對於使用緩存服務的客戶端來說,這一切是透明的。
內存緩存服務在切換的時候,是有一定風險的。從A集群切換到B集群的過程中,必須保證B集群提前做好「預熱」(B集群的內存中的熱點數據,應該盡量與A集群相同,否則,切換的一瞬間大量請求內容,在B集群的內存緩存中查找不到,流量直接沖擊後端的資料庫服務,很可能導致資料庫宕機)。
4. 減少資料庫「寫」
上面的機制,都實現減少資料庫的「讀」的操作,但是,寫的操作也是一個大的壓力。寫的操作,雖然無法減少,但是可以通過合並請求,來起到減輕壓力的效果。這個時候,我們就需要在內存緩存集群和資料庫集群之間,建立一個修改同步機制。
先將修改請求生效在cache中,讓外界查詢顯示正常,然後將這些sql修改放入到一個隊列中存儲起來,隊列滿或者每隔一段時間,合並為一個請求到資料庫中更新資料庫。
除了上述通過改變系統架構的方式提升寫的性能外,MySQL本身也可以通過配置參數innodb_flush_log_at_trx_commit來調整寫入磁碟的策略。如果機器成本允許,從硬體層面解決問題,可以選擇老一點的RAID(Rendant Arrays of independent Disks,磁碟列陣)或者比較新的SSD(Solid State Drives,固態硬碟)。
5. NoSQL存儲
不管資料庫的讀還是寫,當流量再進一步上漲,終會達到「人力有窮時」的場景。繼續加機器的成本比較高,並且不一定可以真正解決問題的時候。這個時候,部分核心數據,就可以考慮使用NoSQL的資料庫。NoSQL存儲,大部分都是採用key-value的方式,這里比較推薦使用上面介紹過Redis,Redis本身是一個內存cache,同時也可以當做一個存儲來使用,讓它直接將數據落地到磁碟。
這樣的話,我們就將資料庫中某些被頻繁讀寫的數據,分離出來,放在我們新搭建的Redis存儲集群中,又進一步減輕原來MySQL資料庫的壓力,同時因為Redis本身是個內存級別的Cache,讀寫的性能都會大幅度提升。
國內一線互聯網公司,架構上採用的解決方案很多是類似於上述方案,不過,使用的cache服務卻不一定是Redis,他們會有更豐富的其他選擇,甚至根據自身業務特點開發出自己的NoSQL服務。
6. 空節點查詢問題
當我們搭建完前面所說的全部服務,認為Web系統已經很強的時候。我們還是那句話,新的問題還是會來的。空節點查詢,是指那些資料庫中根本不存在的數據請求。例如,我請求查詢一個不存在人員信息,系統會從各級緩存逐級查找,最後查到到資料庫本身,然後才得出查找不到的結論,返回給前端。因為各級cache對它無效,這個請求是非常消耗系統資源的,而如果大量的空節點查詢,是可以沖擊到系統服務的。
在我曾經的工作經歷中,曾深受其害。因此,為了維護Web系統的穩定性,設計適當的空節點過濾機制,非常有必要。
我們當時採用的方式,就是設計一張簡單的記錄映射表。將存在的記錄存儲起來,放入到一台內存cache中,這樣的話,如果還有空節點查詢,則在緩存這一層就被阻擋了。
異地部署(地理分布式)
完成了上述架構建設之後,我們的系統是否就已經足夠強大了呢?答案當然是否定的哈,優化是無極限的。Web系統雖然表面上看,似乎比較強大了,但是給予用戶的體驗卻不一定是最好的。因為東北的同學,訪問深圳的一個網站服務,他還是會感到一些網路距離上的慢。這個時候,我們就需要做異地部署,讓Web系統離用戶更近。
一、 核心集中與節點分散
有玩過大型網游的同學都會知道,網游是有很多個區的,一般都是按照地域來分,例如廣東專區,北京專區。如果一個在廣東的玩家,去北京專區玩,那麼他會感覺明顯比在廣東專區卡。實際上,這些大區的名稱就已經說明了,它的伺服器所在地,所以,廣東的玩家去連接地處北京的伺服器,網路當然會比較慢。
當一個系統和服務足夠大的時候,就必須開始考慮異地部署的問題了。讓你的服務,盡可能離用戶更近。我們前面已經提到了Web的靜態資源,可以存放在CDN上,然後通過DNS/GSLB的方式,讓靜態資源的分散「全國各地」。但是,CDN只解決的靜態資源的問題,沒有解決後端龐大的系統服務還只集中在某個固定城市的問題。
這個時候,異地部署就開始了。異地部署一般遵循:核心集中,節點分散。
·核心集中:實際部署過程中,總有一部分的數據和服務存在不可部署多套,或者部署多套成本巨大。而對於這些服務和數據,就仍然維持一套,而部署地點選擇一個地域比較中心的地方,通過網路內部專線來和各個節點通訊。
·節點分散:將一些服務部署為多套,分布在各個城市節點,讓用戶請求盡可能選擇近的節點訪問服務。
例如,我們選擇在上海部署為核心節點,北京,深圳,武漢,上海為分散節點(上海自己本身也是一個分散節點)。我們的服務架構如圖:
需要補充一下的是,上圖中上海節點和核心節點是同處於一個機房的,其他分散節點各自獨立機房。
國內有很多大型網游,都是大致遵循上述架構。它們會把數據量不大的用戶核心賬號等放在核心節點,而大部分的網游數據,例如裝備、任務等數據和服務放在地區節點里。當然,核心節點和地域節點之間,也有緩存機制。
二、 節點容災和過載保護
節點容災是指,某個節點如果發生故障時,我們需要建立一個機制去保證服務仍然可用。毫無疑問,這里比較常見的容災方式,是切換到附近城市節點。假如系統的天津節點發生故障,那麼我們就將網路流量切換到附近的北京節點上。考慮到負載均衡,可能需要同時將流量切換到附近的幾個地域節點。另一方面,核心節點自身也是需要自己做好容災和備份的,核心節點一旦故障,就會影響全國服務。
過載保護,指的是一個節點已經達到最大容量,無法繼續接接受更多請求了,系統必須有一個保護的機制。一個服務已經滿負載,還繼續接受新的請求,結果很可能就是宕機,影響整個節點的服務,為了至少保障大部分用戶的正常使用,過載保護是必要的。
解決過載保護,一般2個方向:
·拒絕服務,檢測到滿負載之後,就不再接受新的連接請求。例如網游登入中的排隊。
·分流到其他節點。這種的話,系統實現更為復雜,又涉及到負載均衡的問題。
小結
Web系統會隨著訪問規模的增長,漸漸地從1台伺服器可以滿足需求,一直成長為「龐然大物」的大集群。而這個Web系統變大的過程,實際上就是我們解決問題的過程。在不同的階段,解決不同的問題,而新的問題又誕生在舊的解決方案之上。
系統的優化是沒有極限的,軟體和系統架構也一直在快速發展,新的方案解決了老的問題,同時也帶來新的挑戰。