下面這些是我本身總結的,固然這一次圖沒有本身畫,都是百度搜的,如遇到版權問題請及時聯繫我刪除。node
這篇文章主要是說在進化的過程當中可能會遇到的問題以及如何去解救這些問題。mysql
1、客戶端緩存優化
一、添加CDN緩存
CDN 緩存也叫做網絡訪問的「第一跳」,用戶請求先到達的是互聯網網絡服務商的機房。在機房裏面部署 CDN 服務器,提供緩存服務。緩存了一些靜態資源。若是存在用戶請求的內容,直接經過CDN進行返回;沒有的話繼續向下請求web
二、正向代理緩存
正向代理緩存保存在客戶端,代理客戶端訪問互聯網,好比訪問谷歌,直接訪問不到,咱們就可使用一個代理服務器,將請求轉發給代理服務器,代理服務器可以訪問谷歌,這樣由代理去谷歌取到返回數據,再返回給咱們。redis
既然正向代理服務器能夠取得谷歌的數據,他就能夠緩存這些數據同時記錄下客戶端身份,當下次請求的時候經過客戶端身份驗證,就能夠直接找到這些緩存的數據了。算法
三、反向代理緩存
反向代理是在服務器這一端的,用戶經過互聯網鏈接到數據中心的時候,鏈接的一般是一個反向代理服務器,反向代理服務器根據用戶的請求,在本地的反向代理緩存中查找是否有用戶請求的數據,若是有就直接返回這個數據,若是沒有再把這個請求向下繼續轉發。sql
2、服務端優化
一、應用程序分佈式部署
分佈式部署的意思是同一個應用程序分佈在不一樣的服務器上,一塊對外提供服務。他們之間的等位相等。數據庫
(1)負載均衡算法訪問不一樣應用程序,避免單一應用程序的壓力
客戶端經過反向代理執行負載均衡算法去訪問不一樣的應用程序緩存
①隨機均衡算法 ②多權重負載 ③session 粘連ruby
(2)不一樣的應用程序實際上是同一個,如何解決session問題
注意,不一樣功能的應用程序也要解決session功能,服務器
第一種:粘性session
原理:粘性Session是指將用戶鎖定到某一個服務器上,好比,用戶第一次請求時,負載均衡器將用戶的請求轉發到了A服務器上,第二次訪問時,反向代理根據sessionID經過hash算法,轉發到A服務器上。
缺點:缺少容錯性,若是當前訪問的服務器發生故障,用戶被轉移到第二個服務器上時,他的session信息都將失效。
第二種:session同步
原理:任何一個服務器上的session發生改變(增刪改),會廣播給全部其它節點,無論其餘服務器需不須要session,以此來保證Session同步。
缺點:會對網絡負荷形成必定壓力,若是session量大的話可能會形成網絡堵塞,拖慢服務器性能。
第三種:持久化到緩存數據庫
原理:拿出一個緩存數據庫,存儲session信息,並設置相應的失效時間。每次訪問的時候無論是負載到了哪個服務器都會先從緩存數據庫裏面查詢session
二、應用程序拆分紅微服務
(1)微服務的註冊與發現Eureka
既然是微服務,就要有一箇中心去管理,有了中心以後,咱們就能夠將各類服務往裏面註冊使得各個服務能夠相互感知到。
Springcloud有一個Eureka註冊與發現中心,保存了各個微服務的名稱、IP地址、端口號等信息。這個時候,服務之間進行交流與交互。
通常註冊中心會採起集羣的策略,進行容錯;
(2)微服務的通訊
①Ribbon
本質是一個帶有負載均衡功能的http客戶端,在每次請求的時候會經過負載均衡算法Round Robin輪詢算法選擇一臺機器,均勻的把請求分發到各臺機器上。這是從每個微服務搭建成了分佈式的系統來考慮的。
實現原理:Ribbon會從 Eureka Client裏獲取到對應的服務註冊表,也就知道了全部的服務都部署在了哪些機器上,在監聽哪些端口號;而後Ribbon就可使用默認的Round Robin算法,從中選擇一臺機器。
②Feign
兩個不一樣的微服務就可使用Feign來通訊,Feign集成了Ribbon。底層使用的是動態代理的功能:
Feign的工做原理:
第一:對某個接口定義了@FeignClient註解,Feign就會針對這個接口建立一個動態代理;
第二:接着你要是調用那個接口,本質就是會調用 Feign建立的動態代理
第三:Feign的動態代理會根據你在接口上的@RequestMapping等註解,來動態構造出你要請求的服務的地址;
第四:Feign中的Ribbon拿到這個地址,經過負載均衡算法調用相應的微服務的相應方法;
(3)微服務的容錯機制Hystrix
這個功能的微服務即便是搭建了集羣可能也會出錯,從而形成服務雪崩;Springcloud提供了熔斷和服務降級的策略進行容錯;
①降級
若是調用的遠端服務出現問題(超時或異常),則返回一個結果,用於提示。通常都是返回一個默認值;
②熔斷
若是調用的遠端服務出現問題,則在一段時間以內直接返回提示信息(再也不調遠端的服務),一段時間後陸續調用遠端服務,若是再也不出現問題,則恢復正常調用遠端服務。
(4)微服務的網關Zuul
這麼多微服務部署在不一樣的服務器上地址是不同的,能夠經過網關Zuul把全部的服務接口統一塊兒來暴露出去給客戶用。在這裏咱們能夠過濾用戶的一些信息或者是作權限驗證等等工做;
(5)微服務的屬性配置SpringCloud Config
服務一多,改配置太麻煩了,須要有個東西來管理,最好還能在線修改配置,這時候分佈式配置中心(Spring Cloud Config)就出現了,它實現了將全部服務的配置文件都抽取到一個統一的地方
(6)微服務消息總線Springcloud Bus
想要更改配置的時候,服務可以知道而且熱更新配置,那麼就須要一個消息傳遞工具——消息總線(Spring Cloud Bus),經過這個總線向其餘服務傳遞消息
(7)微服務的鏈路追蹤
咱們想要知道各個服務之間的調用關係間接獲得服務之間的依賴,那麼就須要服務追蹤組件(zipkin ,SpringCloud Sleuth集成了zipkin)了。
三、微服務會出現的問題
(1)Session問題:解決方式同上
(2)大規模的增刪改查,如何抵抗住壓力
①採用消息隊列對用戶訪問進行削峯處理
②服務的熔斷和降級策略
(3)分佈式事務
好比:例如在下單場景下,庫存和訂單若是不在同一個節點上,涉及分佈式事務。
分佈式事務的最主要特色是須要跨節點進行通訊,解決辦法以下:
首先基於CAP理論,保證BASE特性。接下來看看如何解決:
①在設計微服務架構的時候儘量的保證微服務的獨立性。
②2pc協議模型
引入協調者(Coordinator)來協調參與者的行爲,並最終決定這些參與者是否要真正執行事務。
第一階段是表決階段,全部參與者都將本事務可否成功的信息反饋發給協調者;
第二階段是執行階段,協調者根據全部參與者的反饋,通知全部參與者,步調一致地在全部分支上提交或者回滾。
2pc的缺點:
(1)第一階段到第二階段有一個時間差,時間過久了以後,第一節點的某些參與者可能不能成功執行事務了,可是以前告訴協調者說我能夠成功發送,這就形成了錯誤。
(2)協調者在整個兩階段提交過程當中扮演着舉足輕重的做用,一旦協調者所在服務器宕機,那麼就會影響整個數據庫集羣的正常運行,好比在第二階段中,若是協調者由於故障不能正常發送事務提交或回滾通知,那麼參與者們將一直處於阻塞狀態,整個數據庫集羣將沒法提供服務。
(3)同步阻塞:兩階段提交執行過程當中,全部的參與者都須要遵從協調者的統一調度,期間處於阻塞狀態而不能從事其餘操做,這樣效率及其低下。
③基於消息隊列的最終一致性方案
消息一致性方案是經過消息中間件保證上、下游應用數據操做的一致性。基本思路是將本地操做和發送消息放在一個事務中,保證本地操做和消息發送要麼二者都成功或者都失敗。下游應用向消息系統訂閱該消息,收到消息後執行相應操做。
基本思路以下:
(1)將事務操做進行封裝
(2)上游業務(訂單服務)先執行事務操做,成功以後將數據發送到消息隊列
(3)下游服務(庫存服務)監聽消息隊列,而後去執行,執行成功或者是失敗都經過ACK告訴上游業務,上游業務根據這個ACK信息決定是否回滾或提交。
可是可能會出現異常的狀況:
(1)直接沒法到達消息隊列
網絡斷了,拋出異常,上游業務直接回滾便可。
(2)消息已經到達消息隊列,但返回的時候出現異常
MQ提供了確認ack機制,能夠用來確認消息是否有返回。所以咱們能夠在發送前在數據庫中先存一下消息,若是ack異常則進行重發
(3)消息送達後,消息服務本身掛了
先操做數據庫,而後再往消息隊列發送
(4)未送達消費者
消息隊列收到消息後,消費者去消費,此時消息隊列會處於"UNACK"的狀態,直到客戶端確認消息
(5)確認消息丟失
消息返回時假設確認消息丟失了,那麼消息隊列認爲消息沒有到達消費者會重發消息。
(6)消費者業務處理異常
消費者接受消息並處理,假設拋異常了,先重試,重試到必定的次數以後進行返回事務執行失敗。
(4)分佈式鎖
關於分佈式鎖的設計,從如下四個角度考慮:
第一:互斥性。在任意時刻,只有一個客戶端能持有鎖。
第二:不會發生死鎖。即便有一個客戶端在持有鎖的期間崩潰而沒有主動解鎖,也能保證後續其餘客戶端能加鎖。
第三:具備容錯性。只要大部分的節點正常運行,客戶端就能夠加鎖和解鎖。
第四:加鎖和解鎖必須是同一個客戶端,客戶端本身不能把別人加的鎖給解了。
常見的,分佈式鎖有三種實現方式:
第一:數據庫樂觀鎖;
利用表的惟一索引行級鎖進行加解鎖,加鎖:
加鎖:insert into methodLock(method_name,desc) values (‘method_name’,‘desc’)
解鎖:delete from methodLock where method_name ='method_nam
第二:基於Redis的分佈式鎖;
jedis.setnx(String key, String value, String nxxx, String expx, int time)
第三:基於ZooKeeper的分佈式鎖
zk 是一種提供配置管理、分佈式協同以及命名的中心化服務,用於集羣配置中心管理,服務的註冊監聽等。zookeeper 分佈式鎖的實現利用zookeeper 管理配置中心的watcher機制(觀察者模式),對競爭分佈式鎖的客戶端維護了一張臨時順序表。表中每一個節點表明一個客戶端。
3、緩存數據庫Redis
一、主從複製:實現高可用
一個緩存數據庫壓力太大,讀寫分離,經過哨兵機制監控各個節點和相互監督;
(1)哨兵模式原理
①哨兵如何實現相互監督的功能
第一:哨兵經過發佈訂閱__sentinel__:hello channel來實現這個功能。每一個哨兵每隔2s會向本身監控的全部主從Redis節點發送hello message,報告本身的IP、端口、運行ID、本身監控的Master節點IP、Master節點端口。
第二:全部主從Redis節點也會反饋這樣的信息
②哨兵如何故障檢測
第一:某個哨兵節點斷定master節點故障,他會投出一票S_DOWN,
第二:當有足夠多的sentinel節點斷定master節點故障都投出S_DOWN票時,master節點會被認爲是真正的下線了。
也就是基於多數投票原則
③哨兵模式如何實現故障恢復
故障恢復須要完成以下幾步操做:
第一:經過選主機制選擇新的Master節點替換掉原來的故障節點
第二:其餘的節點成爲Slave節點用於主從複製,也就是不變
第三:告知客戶端新的master節點地址信息,同時執行必要的腳原本通知系統管理員。
(2)選主機制
過程:
sentinel的選舉過程基本上是Raft協議的實現,即全部節點會隨機休眠一段時間,而後發起拉票,當某個節點得到的票數超過max(時,該節點就被推選爲leader節點。注意是哨兵去從節點裏面選。
到底選誰呢?
①根據指定的優先級選擇
管理員在啓動redis從節點的時候,指定了其優先級,哨兵會先從優先級高的從節點去選擇。
②根據數據更新程度選擇
優先級相同,全部slave節點複製數據的時候都會記錄複製偏移量,值越大說明與master節點的數據更一致。因此哨兵會選擇複製偏移量最大的節點。
③根據runid選擇:
到了這一步節點的孰優孰劣就沒什麼區別了,每一個節點啓動的時候都會有一個惟一的runId, 那麼咱們就選擇runid最小的節點好了。
二、集羣策略
(1)基本實現
Redis Cluster中,Sharding採用slot(槽)的概念,一共分紅16384個槽,分佈在不一樣的Redis數據庫中,對於每一個進入Redis的鍵值對,根據CRC16後16384取模hash映射,分配到這16384個slot中的某一箇中。
缺點
要保證16384個槽對應的node都正常工做,若是某個node發生故障,那它負責的slots也就失效,整個集羣將不能工做。
(2)更好的方案一致性哈希算法,解決擴容問題jedis:
Jedis裏面有一致性哈希算法,首先構建一個一致性哈希環的結構。一致性哈希環的大小是咱們計算機中無符號整型值的取值範圍,
將一個數據庫服務器虛擬成若干個虛擬節點,把這些虛擬節點的 hash 值放到環上去。在實踐中一般是把一個服務器節點虛擬成 200 個虛擬節點,而後把 200 個虛擬節點放到環上。用戶的key過來時,順時針的查找距離它最近的虛擬節點,找到虛擬節點之後,根據映射關係找到真正的物理節點。
三、Redis可能出現的問題
(1)緩存雪崩
原理:
緩存同一時間大面積的失效,從而致使全部請求都去查數據庫,致使數據庫CPU和內存負載太高,甚至宕機。
解決方案:
1)主從複製
2)根據一些關鍵數據進行自動降級
3)提早數據預熱
(2)緩存穿透
原理:
緩存穿透是指查詢一個一不存在的數據。例如:從緩存redis沒有命中,須要從mysql數據庫查詢,查不到數據則不寫入緩存,這將致使這個不存在的數據每次請求都要到數據庫去查詢,形成緩存穿透。
解決辦法:
1)布隆過濾器
當用戶想要查詢的時候,使用布隆過濾器發現不在集合中,就直接丟棄,再也不對持久層查詢。
2)緩存空對象
緩存中沒有就回去存儲層獲取,此時即便數據庫返回的空對象也將其緩存起來,同時會設置一個過時時間,以後再訪問這個數據將會從緩存中獲取
(3)緩存擊穿
緩存擊穿是指一個key很是熱點,在不停的扛着大併發,大併發集中對這一個點進行訪問,當這個key在失效的瞬間,持續的大併發就穿破緩存,直接請求數據庫,瞬間對數據庫的訪問壓力增大。
解決方案:在查詢緩存的時候和查詢數據庫的過程加鎖,只能第一個進來的請求進行執行,當第一個請求把該數據放進緩存中,接下來的訪問就會直接集中緩存,防止了緩存擊穿。
(4)Redis緩存與數據庫數據一致性
無論是先寫MySQL數據庫,再刪除Redis緩存;仍是先刪除緩存,再寫庫,都有可能出現數據不一致的狀況。舉一個例子:
1.若是刪除了緩存Redis,尚未來得及寫庫MySQL,另外一個線程就來讀取,發現緩存爲空,則去數據庫中讀取數據寫入緩存,此時緩存中爲髒數據。
2.若是先寫了庫,在刪除緩存前,寫庫的線程宕機了,沒有刪除掉緩存,則也會出現數據不一致狀況。
怎麼保證緩存一致性?:讀直接去緩存讀,沒有的話就讀數據庫,寫直接寫數據庫,而後失效緩存中對應的數據
第一種方案:延時雙刪策略+緩存超時設置
在寫庫先後都進行redis.del(key)操做,而且設定合理的超時時間。
具體的步驟就是:
1)先刪除緩存;
2)再寫數據庫;
3)休眠一段時間;
4)再次刪除緩存。
設置緩存過時時間
全部的寫操做以數據庫爲準,只要到達緩存過時時間,則後面的讀請求天然會從數據庫中讀取新值而後回填緩存。也就是看到寫請求就執行上面的策略。
第二種方案:異步更新緩存(基於訂閱binlog的同步機制)
MySQL binlog增量訂閱消費+消息隊列+增量數據更新到redis
一旦MySQL中產生了新的寫入、更新、刪除等操做,就能夠把binlog相關的消息經過消息隊列推送至Redis,Redis再根據binlog中的記錄,對Redis進行更新。
(5)redis的熱key問題如何解決
第一:熱Key的概念
所謂熱key問題就是,忽然有幾十萬的請求去訪問redis上的某個特定key。那麼,這樣會形成流量過於集中,達到物理網卡上限,從而致使這臺redis的服務器宕機。那接下來這個key的請求,就會直接懟到你的數據庫上,致使你的服務不可用。
第二:怎麼發現熱key
方法一:憑藉業務經驗,進行預估哪些是熱key 其實這個方法仍是挺有可行性的。好比某商品在作秒殺,那這個商品的key就能夠判斷出是熱key。缺點很明顯,並不是全部業務都能預估出哪些key是熱key。
方法二:在客戶端進行收集 這個方式就是在操做redis以前,加入一行代碼進行數據統計。那麼這個數據統計的方式有不少種,也能夠是給外部的通信系統發送一個通知信息。缺點就是對客戶端代碼形成入侵。
方法三:在Proxy層作收集 有些集羣架構是下面這樣的,Proxy能夠是Twemproxy,是統一的入口。能夠在Proxy層作收集上報,可是缺點很明顯,並不是全部的redis集羣架構都有proxy。
方法四:用redis自帶命令
(1)monitor命令,該命令能夠實時抓取出redis服務器接收到的命令,而後寫代碼統計出熱key是啥。固然,也有現成的分析工具能夠給你使用,好比redis-faina。可是該命令在高併發的條件下,有內存增暴增的隱患,還會下降redis的性能。
(2)hotkeys參數,redis 4.0.3提供了redis-cli的熱點key發現功能,執行redis-cli時加上–hotkeys選項便可。可是該參數在執行的時候,若是key比較多,執行起來比較慢。
方法五:本身抓包評估
Redis客戶端使用TCP協議與服務端進行交互,通訊協議採用的是RESP。本身寫程序監聽端口,按照RESP協議規則解析數據,進行分析。缺點就是開發成本高,維護困難,有丟包可能性。
以上五種方案,各有優缺點。根據本身業務場景進行抉擇便可。那麼發現熱key後,如何解決呢?
三:如何解決
目前業內的方案有兩種
(1)利用二級緩存 好比利用ehcache,或者一個HashMap均可以。在你發現熱key之後,把熱key加載到系統的JVM中。
針對這種熱key請求,會直接從jvm中取,而不會走到redis層。假設此時有十萬個針對同一個key的請求過來,若是沒有本地緩存,這十萬個請求就直接懟到同一臺redis上了。
如今假設,你的應用層有50臺機器,OK,你也有jvm緩存了。這十萬個請求平均分散開來,每一個機器有2000個請求,會從JVM中取到value值,而後返回數據。避免了十萬個請求懟到同一臺redis上的情形。
(2)備份熱key 這個方案也很簡單。不要讓key走到同一臺redis上不就好了。咱們把這個key,在多個redis上都存一份不就行了。接下來,有熱key請求進來的時候,咱們就在有備份的redis上隨機選取一臺,進行訪問取值,返回數據。
4、數據庫優化
一、主從複製
主從複製通常採用多主多從的方案
(1)多主條件下數據一致性問題
也就是兩臺主數據庫同時更新了數據,以誰的爲主
①一種方法是根據時間戳進行判斷
最後寫入的,也就是時間戳在後面的,覆蓋時間戳在前面的。
②還有一種衝突的解決方案是經過投票進行解決。
(2)主節點掛掉了怎麼辦?
多主多從模式中,幾臺主服務器相互監督觀察,只要對面的有更新本身也更新;
二、分庫分表
(1)垂直分庫分表
垂直分表意味着對這個表大部分增刪改查的操做須要跨庫,系統開銷太大,通常不使用。垂直分庫也會帶來事務等問題,解決辦法是2pc
(2)水平分庫分表
實現方案以下:
一、根據數值範圍
按照時間區間或ID區間來切分。例如:將userId爲1~9999的記錄分到第一個庫,10000~20000的分到第二個庫,以此類推。
優勢:
(1)單表大小可控
(2)自然便於水平擴展,後期若是想對整個分片集羣擴容時,只須要添加節點便可,無需對其餘分片的數據進行遷移
(3)使用分片字段進行範圍查找時,連續分片可快速定位分片進行快速查詢,有效避免跨分片查詢的問題。
缺點:
熱點數據可能較爲集中,形成壓力。
二、根據數值取模
例如:將 Customer 表根據 no字段切分到4個庫中,餘數爲0的放到第一個庫,餘數爲1的放到第二個庫,以此類推。
優勢:
數據分片相對比較均勻,不容易出現熱點和併發訪問的瓶頸
缺點:
(1)擴容比較麻煩,新增長一個數據庫時,須要從新hash
三、分庫分表出現的問題
(1)事務一致性問題(垂直分庫問題)
分佈式事務
當更新內容同時分佈在不一樣庫中,垂直分庫,不可避免會帶來跨庫事務問題。跨分片事務也是分佈式事務,沒有簡單的方案,通常可以使用"XA協議"和"兩階段提交"處理。
最終一致性
只要在容許的時間段內達到最終一致性便可,可採用事務補償的方式。也就是基於日誌,進行同步;
二、跨節點關聯查詢 join 問題(垂直分庫問題)
1)全局表
全局表,也可看作是"數據字典表",就是系統中全部模塊均可能依賴的一些表,爲了不跨庫join查詢,能夠將這類表在每一個數據庫中都保存一份。這些數據一般不多會進行修改,因此也不擔憂一致性的問題。
2)字段冗餘
利用空間換時間,爲了性能而避免join查詢。例如:訂單表保存userId時候,也將userName冗餘保存一份,這樣查詢訂單詳情時就不須要再去查詢"買家user表"了。
3)數據組裝,屢次請求
在系統層面,分兩次查詢,第一次查詢的結果集中找出關聯數據id,而後根據id發起第二次請求獲得關聯數據。最後將得到到的數據進行字段拼裝。
4)ER分片
關係型數據庫中,若是能夠先肯定表之間的關聯關係,並將那些存在關聯關係的表記錄存放在同一個分片上,
三、跨節點分頁、排序、函數問題(水平分庫問題)
(1)分頁問題
須要先在不一樣的分片節點中將數據進行排序並返回,而後將不一樣分片返回的結果集進行彙總和再次排序,最終返回給用戶。
(2)函數問題
在使用Max、Min、Sum、Count之類的函數進行計算的時候,也須要先在每一個分片上執行相應的函數,而後將各個分片的結果集進行彙總和再次計算,最終將結果返回。
四、分佈式ID問題(水平分庫問題)
在分庫分表的環境中,數據分佈在不一樣的分片上,不能再借助數據庫自增加特性直接生成,不然會形成不一樣分片上的數據表主鍵會重複。簡單介紹下使用和了解過的幾種 ID 生成算法。
(1)Twitter 的 Snowflake(又名「雪花算法」)
這種方案把64-bit分別劃分紅多段,分開來標示機器、時間等,以下圖所示:
這種分配方式能夠保證在任何一個IDC的任何一臺機器在任意毫秒內生成的ID都是不一樣的。根據這個算法的邏輯,只須要將這個算法用Java語言實現出來,封裝爲一個工具方法,那麼各個業務應用能夠直接使用該工具方法來獲取分佈式ID,只需保證每一個業務應用有本身的工做機器id便可,而不須要單獨去搭建一個獲取分佈式ID的應用。
(2)利用zookeeper生成惟一ID
zookeeper主要經過其znode數據版原本生成序列號,能夠生成32位和64位的數據版本號,客戶端可使用這個版本號來做爲惟一的序列號。
(3)Redis生成ID
這主要依賴於Redis是單線程的,因此也能夠用生成全局惟一的ID。能夠用Redis的原子操做 INCR和INCRBY來實現。
OK。有問題還望批評指正。
本文分享自微信公衆號 - 愚公要移山(fdd_sxu_nwpu)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。