【重磅!進 BAT 必讀,大廠對分佈式線上問題真實拷問連環炮!(已更新一波)】

大廠要求

  • 技術廣度
  • 項目經驗
  • 生產經驗
  • 技術深度
  • 系統設計

Dubbo vs Spring Cloud

Eureka: 服務註冊
Feign:服務調用
Ribbon:負載均衡
Zuul/Spring Cloud Gateway: 網關,灰度發佈、統一限流(每秒 1W 個請求)、統一熔斷、統一降級、統一緩存、統一受權認證
Hystrix:鏈路追蹤
Stream:java

Dubbo 底層運行原理

消費者

動態代理:Proxy
負載均衡: Cluster,負載均衡,故障轉移
註冊中心:Registry
通訊協議:Protocol,http,rmi,dubbo
信息交換:Exchange,Request 和 Response
網絡通訊: Transport ,netty, mina
序列化:封裝好的請求如何序列化成二進制數組,經過 netty /mina 發送出去node

提供者

網絡通訊 : Transport 基於 Netty /Mina 實現的 Server
信息交換: Exchange, Response
通訊協議:Protocol
動態代理:Proxymysql

Dubbo 底層網絡通訊

Dubbo 高可擴展性

  1. 核心組件所有接口化,組件和組件之間的調用,必須所有依託於接口,去動態找配置的實現類,若是沒有配置就用他本身默認的
  2. 提供一種本身實現的組件的配置的方式,好比本身實現了某個組件,配置一下,運行的時候直接找你配置的組件,做爲實現類 ,不用本身默認組件

設計 RPC 框架

核心就是Dubbo裏的動態代理裏面的代碼邏輯,網絡通訊、代理機制、負載均衡react

Spring Cloud 底層架構原理

  • Eureka

做用:服務註冊與發現、心跳與故障
二級緩存,優化併發衝突,讀多寫少
nginx

  • Feign

對接口打了一個註解,針對這個註解標註的接口生成動態代理,而後針對Feign的動態代理去調用他的方法的時候,會在底層生成 http協議格式的請求
先使用 Ribbon 去本地的 Eureka 註冊表的緩存裏獲取出來對方機器的列表 ,進行負載均衡,選擇一臺機器出來 ,而後使用HttpClient 框架組件對那臺機器發送 HTTP請求面試

  • Zuul

配置一下不一樣的請求和服務的對應關係,你請求到了網關,他直接查找匹配的服務,而後直接把請求轉發給那個服務的某臺機器,Ribbon 從 Eureka 本地的緩存列表裏獲取一臺機器,負載均衡,把請求直接用 HTTP 通訊框架發送到指定機器上去。redis

Dubbo VS Spring Cloud

  • Dubbo ,RPC性能比 HTTP性能更好,併發能力更強。Dubbo一次請求 10ms,Spring Cloud 耗費 20ms
  • Dubbox 之前定位是單純的服務框架,Spring Cloud是全家桶概念、提供分佈式配置中心、受權認證、服務調用鏈路追蹤、資源隔離、熔斷降級、請求 QPS 監控

契約測試、消息中間件封裝、Zk封裝算法

  • Spring Cloud Alibaba, 技術相互融合

註冊中心調研

  • Eureka
  • ZooKeeper
  • Consul
  • Nacos

Eureka 集羣架構原理


peer-to-peer,部署一個集羣,可是集羣裏每一個機器的地方是對等的,各個服務能夠向任何一個 Eureka實例服務註冊和服務發現,集羣裏任何一個 Eureka 實例接收到寫請求以後,會自動同步給其餘全部的 Eureka 實例sql

ZooKeeper 集羣架構原理


Leader + Follower 2 種角色,只有Leader 能夠負責寫(服務註冊),他能夠把數據同步給 Follower ,讀的時候 Leader /Follower 均可以讀mongodb

CAP

C 一致性,A 可用性,P 分區容錯性
ZooKeeper 採用 CP,選舉期間服務不可用

Eureka 採用 AP和最終一致性

服務註冊發現時效性

  • ZooKeeper 時效性更好,註冊或者掛了,通常秒級就能感知到
  • Eureka 默認配置很是糟糕,服務發現感知要幾十秒,甚至分鐘級別,上線一個新的服務實例,到其餘人能夠發現他,極端狀況下要 1 分鐘的時間,Ribbon 同時也須要間隔時間纔會更新它本身的緩存。

服務故障,間隔 60 秒纔去檢查心跳,發現這個服務上一次心跳是 60 秒以前,隔 60 秒取檢查心跳,超過 90 秒認爲沒有心跳,纔會認爲他死了,已經 2 分鐘過去了,30 秒纔會更新緩存,30 秒,其餘服務纔會來拉最新的註冊表

容量

  • zk 不適合大規模的服務實例,由於服務上下線的時候,須要瞬間推送數據通知到全部的其餘服務實例,因此一旦服務規模太大,到了幾千個服務實例的時候,會致使網絡帶寬會大量佔用
  • eureka也很難支撐大規模的服務實例,由於每一個 eureka實例都要接受全部的請求,實例多了壓力太大,扛不住,也很難到幾千服務實例

註冊中心高可用

eureka 集羣部署

服務發現過慢

  • eureka 裏配置 ReadWrite 同步到 ReadOnly 緩存的間隔時間

eureka.server.responseCacheUpdateIntervalMs = 3000

  • 服務裏配置拉取註冊中心配置的間隔時間

eureka.client.registryFetchIntervalSeconds = 3000

  • 服務裏配置心跳間隔的時間

eureka.client.leaseRenewalIntervalInSeconds: 30

  • eureka裏線程檢查心跳的間隔時間

eureka.server.evictionIntervalTimerInMs = 6000

  • eureka裏配置心跳過時時間,這條記錄就會從註冊表中刪掉,而後ReadWrite緩存就會淘汰掉

eureka.instance.leaseExpirationDurationInSeconds: 6
優化成12 秒內能夠感覺到節點上線、下線

  • 關閉 eureka 的自我保護 (當忽然大面積服務都沒心跳了,eureka 會保護註冊表不下線這些服務,源碼層不少bug)

eureka.server.enableSelfPreservation: false

生產環境中的註冊中心

  • eureka 配置 8核 16G,16 核 32G,每臺機器每秒鐘的請求支撐幾千絕對沒問題,能夠支撐上千個服務
  • 時效性優化

網關

  • 動態路由:新開發某個服務,動態把請求路徑和服務的映射關係熱加載到網關裏去,服務增減機器,網關自動熱感應
  • 灰度發佈:
  • 鑑權認證
  • 性能監控:每一個API 接口的耗時、成功率、QPS
  • 系統日誌
  • 數據緩存
  • 限流熔斷:控制 1 秒鐘 1000 個請求

網關調研

  • Kong

Nginx裏面的一個基於 lua 寫的模塊,實現了網關的功能

  • Zuul

Spring Cloud

  • OpenResty

Nginx + lua

  • 自研網關

Servlet + Netty來作網關

中小型公司:
SpringCloud體系用Zuul,若是是Dubbo,有的採用Kong;或者 nginx+負載均衡
大廠:
自研

Zuul(Servlet,Java)高併發能力不強,基於Tomcat部署把網關跑起來,Java語言開發,能夠把控源碼作二次開發
Nginx(Kong,Nginx+Lua)抗高併發能力很強,精通Nginx源碼很難,很難從Nginx內核層面去作一些二次開發和源碼定製

網關對服務動態路由

讀取數據庫(Redis ,ZooKeeper, 阿波羅)路由配置以及定時任務刷新

網關優化

每秒 10W 請求 ,一個 zuul 在 8核 16 上能夠抗住 幾千+ 請求,幾十臺 zuul 能夠抗住 10W+
16 核 32G 的,一臺抗住小几萬,幾臺 zuul 就能夠抗住 10W+

自研服務註冊中心

eureka :peer to peer 每臺機器都是高併發請求,有瓶頸
zookeeper: 上下線全量通知其餘服務,網絡帶寬被打滿,有瓶頸

分佈式註冊中心,分片存儲服務註冊表,橫向擴容, 天天機器均攤高併發請求,各個服務按需主動拉取 ,避免反向通知網卡被打滿,Master-Slave 高可用性,Master寫到Slave後纔算寫成功,強一致性

網關灰度發佈

eureka.instance.meta-data-map.version: new
RibbonFilterContextHolder

生產環境部署

註冊中心

中小型公司 20~30 個服務,註冊中心 2 ~3 臺機器(4 核 8G),每秒抗上千請求
註冊中心優化服務註冊和發現配置
註冊表多級緩存同步 1 秒,註冊表拉取頻率 1 秒
服務心跳 1 秒上報一次,服務故障發現 1 秒,發現 2 秒內服務沒上報心跳,就故障

服務

每秒併發在1000 之內,每一個服務部署 2 臺機器,每臺機器 4 核 8G,每臺機器抗幾百請求一點問題都沒
大部分系統高峯期每秒幾百請求,低峯期每秒幾十請求,幾個請求

網關係統

4 核 8G,一臺機器抗每秒幾百請求,部署 3~4 臺

數據庫

16 核 32G,物理機,高峯期每秒幾千(三四千的時候,網絡負載比較高,CPU 使用率比較高,I/O 負載比較高)請求問題不大,

QPS

metrics 機制,利用 AtomicLong 算出核心接口每分鐘調用多少次(除以 60 就是高峯期每秒訪問次數)以及天天被調用總次數
TP99 = 100ms , 99%的接口耗時在 100ms之內,1%的接口耗時在100ms以上
平均響應延時 = (每次調用的耗時 + 歷史總耗時 )/ 當前請求的總次數
最大 QPS:
假設最大 QPS 爲 800 ,當壓測工具每秒發起 1000 個請求的時候,只有 800個能夠同時被處理,200 個在排隊被阻塞住

系統訪問量增長 10 倍

  • 網關多部署 10 倍機器
  • 服務多加機器
  • Eureka 換成 8 核 16G (抗住上千請求很輕鬆)
  • 數據庫換成32 核 128G (每秒抗住幾千請求問題不大)

超時和重試

  • 第一次請求的時候,會初始化 Ribbon 組件,須要耗費必定的時間 ,因此很容易致使超時。(改進:讓每一個服務啓動的時候直接初始化 Ribbon 組件 )

ribbon.eager-load.enabled: true , zuul.ribbon.eager-load.enabled: true

  • 關閉 hystrix (性能)

feign.hystrix.enabled:false

  • 設置超時時間

connectTimeout:1000
ReadTimeout:1000
OKToRetryOnAllOperations:true
MaxAutoRetries:1
MaxAutoRetriesNextServer:1

防重冪等性

  • 插入操做冪等性

數據庫創建惟一索引

  • 更新操做冪等性

在少數核心接口內,根據業務邏輯作冪等性,業務執行成功後,生成一個惟一的key(order_id_stock_deduct)到redis裏,下一次進來的時候,若是發現重複key,則執行反向操做

分佈式事務

XA、TCC、可靠消息最終一致性方案、最大努力通知方案、Sega

TCC

Try-Confirm-Cancel 每一個人先走 try ,有人失敗了就會讓你們都走 Cancel, 若是Try都成功了,會讓你們調用Confirm
庫存接口拆分紅 1. 扣減庫存接口 2.回滾扣減庫存接口 由分佈式事務框架通知已經執行成功的服務的回滾接口,保證回滾掉。數據要麼所有成功、要麼所有失敗。

技術選型

  • TCC 框架有 ByteTCC ,Himly (我的),seata (阿里開源)
  • 可靠消息最終一致性方案 基於ActiveMq封裝可靠消息服務、基於RabbitMQ本身開發一個可靠消息服務,RocketMQ提供分佈式事務支持,實現了可靠消息服務功能

TCC 核心架構

  • TC TM RM


  1. TM 請求 TC 要開啓一個新的分佈式事務,TC 生成一個 XID
  2. XID 經過服務調用傳遞下去
  3. RM 註冊本地事務做爲一個這次分佈式事務的一個分支事務到 TC 上
  4. TM 發現所有執行成功,則通知 TC 進行全局提交,此時 TC 通知每一個分支事務,能夠提交了

若是 TM 發現有錯誤(某個分支事務未執行成功,把異常一層一層拋到最外層),則通知 TC 進行回滾,TC 會通知全部分支事務進行回滾

TCC 高併發場景

  • 每一個服務都須要跟 TC 這個角色進行頻繁的網絡通訊,帶來開銷,不引入分佈式事務可能 100ms,引入了可能200ms
  • 上報分支事務狀態給 TC,其中seata-server 基於文件存儲,會更耗費網絡請求
  • TC ,seata-server 也要支持擴容,部署多臺機器 ,TC 背後的數據庫要分庫分表

RocketMQ對事務的支持

MQ:抗高併發 、削峯、解耦

本身實現 RocketMQ 對事務的支持

分佈式鎖

解決庫存爲-5 的問題

分佈式鎖解決庫存問題

Redis 分佈式鎖

Redisson 框架
redisson.lock("product_1_stock")

product_1_stock:{

"87ff-dsfsfs-121212-sfsfsfs :1":1

}
生存時間:30s
watchdog,redisson 框架後臺執行一段邏輯,每隔10s去檢查一下這個鎖是否還被當前客戶端持有,若是是的話,從新刷新一下 key的生存時間爲 30s
其餘客戶端嘗試加鎖,這個時候發現「product_1_stock」 這個 key已經存在了,裏面顯示被別的客戶端加鎖了,此時他就會陷入一個無限循環,阻塞住本身,不能幹任何事情,必須在這裏等待。

第一個客戶端加鎖成功,此時有 2 種狀況,1)這個客戶端操做完畢以後,主動釋放鎖(將 UUID 線程 ID 對應的值 -1 ,當加鎖次數未 0 的時候,刪除 key ,不然刷新 key的生存週期 30s) 2)若是這個客戶端宕機了,那麼和這個客戶端的 redisson框架以前啓動的後臺 watchdog線程就沒了,此時最多30s,這個key-value就消失了,自動釋放了宕機客戶端以前持有的鎖

集羣故障會致使鎖失效?
會,除非是修改 redis和 redisson框架源碼,二次開發,加鎖必須是 master 和 slave 同時寫成功了,纔算是加鎖成功。

ZooKeeper 分佈式鎖

curator,基於 zk 實現了一整套高級功能

ZooKeeper 羊羣效應

若是幾十個客戶端同時爭搶一個鎖,此時會致使任何一個客戶端釋放鎖的時候 ,zk要反向通知幾十個客戶端,幾十個客戶端又要發送請求到 zk 去嘗試建立鎖,幾十我的要加鎖,你們都是亂糟糟的,無序的。形成不少不必的請求和網絡開銷。其實只須要下一個順序節點收到通知去加鎖

ZooKeeper 腦裂

由於ZooKeeper與客戶端 A 之間網絡發生問題,ZooKeeper 誤認爲客戶端 A 掛掉了,此時刪掉客戶端 A 建立的臨時節點,此時 A 還在運行,B 客戶端監聽到了 A 客戶端的節點被刪掉,覺得獲取到鎖,加鎖成功,此時分佈式鎖就失效了。

分佈式系統,主控節點有一個 Master,此時由於網絡故障,致使其餘人覺得這個 Master 不可用了,其餘節點出現了別的Master,致使集羣裏有 2 個Master同時在運行。
解決方案:修改curator框架源碼,加一些複雜協調機制

Redis VS ZooKeeper 分佈式鎖

從分佈式系統協調語義而言,ZooKeeper 作分佈式鎖(選舉、監聽)更好一些,由於Redis 實際上是緩存,可是Redis能抗高併發,高併發場景更好一些。
ZooKeeper 自己不適合部署大規模集羣,他自己適用的場景就是部署三五臺機器,不是承載高併發請求的,僅僅用做分佈式系統協調的。

分佈式鎖高併發優化

Redis 單機抗住幾萬(1,2W)併發輕鬆
優化策略:分段加鎖
好比:蘋果庫存有 10000 個,此時在數據庫中建立 10 個庫存字段,stock_01,stock_02..stock_10 ,每一個庫存字段裏放 1000 個庫存,此時這個庫存的分佈式鎖,對應 10 個 key, product_1_stock_01, product_1_stock_02,product_1_stock_03 ,product_1_stock_10,請求過來以後,從 10個 key隨機選擇一個key,去加鎖。每秒 1W 個請求過來,此時他們會對 10 個庫存分段 key 加鎖,每一個 key就 1000 個請求,每臺服務器也就 1000 個請求而已。
萬一說每一個庫存分段僅僅剩餘 10 個庫存了,此時我下訂單買 20 個蘋果,合併扣減庫存。對 product_1_stock_5 加鎖了,此時庫存只有 10 個,不夠買 20 個蘋果,能夠嘗試去 product_1_stock_1,再查詢他的庫存可能 有 30 個,此時下單就能夠,鎖定庫存的時候,對 product_1_stock_5鎖定 10 個庫存,對product_1_stock_1鎖定 10 個庫存,一共鎖定 20 個庫存。

淘寶京東庫存扣減方案

大公司通常有分佈式 K-V 存儲,tair,redis,mongodb,高併發,每秒幾萬幾十萬都沒問題,甚至每秒百萬。
實時庫存數據放 K-V 存儲裏去,你在操做庫存的時 候,直接扣減(不用先查庫再扣減),若是你發現扣減以後是負數的話,此時就認爲庫存超賣了,回滾剛纔的扣減,返回提示給用戶,庫存不足,不能下訂單了。對 K-V 庫存每次修改寫到 MQ 裏,異步同步到數據庫,至關於異步雙寫,用分佈式 K-V 抗高併發,作好一致性方案。

消息隊列連環炮

  1. 項目裏怎麼樣使用 MQ 的?

訂單系統每次下一個新的訂單的時候,會發送一條 MQ 消息,後臺有個庫存系統負責獲取了消息而後更新庫存。

  1. 爲何要使用消息隊列?
  2. 消息隊列有什麼優勢和缺點?
  3. kafka,activemq,rabbitmq,rocketmq 都有什麼去唄?
  4. 如何保證消息隊列高可用?
  5. 如何保證消息不被重複消費?
  6. 如何保證消息的可靠性傳輸?
  7. 如何保證消息的順序性?
  8. 寫一個消息隊列架構設計?

面試官思路:
從作過的某一個點切入,而後層層展開深刻考察,一個接一個問,直到把這個技術點刨根問底,問到最底層。

消息隊列技術選型

解決的問題:

  1. 解耦
  2. 異步
  3. 削峯

不用 MQ 系統耦合場景

  1. A 系統產生了一個比較關鍵的數據,不少系統須要 A 系統將數據發過來,強耦合(B,C,D,E 系統可能參數不同、一會須要一會不須要數據,A 系統要不斷修改代碼維護)
  2. A 系統還要考慮 B、C、D、E 系統是否掛了,是否訪問超時?是否重試?

使用 MQ 系統解耦場景

  1. 維護這個代碼,不須要考慮人家是否調用成功,失敗超時
  2. 若是新系統須要數據,直接從 MQ 裏消費便可,若是某個系統不須要這條數據就取消對 MQ 消息的消費便可。

總結:經過一個 MQ 的發佈訂閱消息模型(Pub/Sub), 系統 A 跟其餘系統就完全解耦了。

不用 MQ 同步高延遲請求場景

通常互聯網類的企業,對用戶的直接操做,通常要求每一個請求都必須在 200ms之內,對用戶幾乎是無感知的。

使用 MQ 進行異步化以後的接口性能優化


提升高延時接口

沒有用 MQ 時高峯期系統被打死的場景


高峯期每秒 5000 個請求,每秒對 MySQL 執行 5000 條 SQL(通常MySQL每秒 2000 個請求差很少了),若是MySQL被打死,而後整個系統就崩潰,用戶就沒辦法使用系統了。可是高峯期過了以後,每秒鐘可能就 50 個請求,對整個系統沒有任何壓力。

使用 MQ 進行削峯的場景


5000 個請求寫入到 MQ 裏面,系統 A 每秒鐘最多隻能處理 2000 個請求(MySQL 每秒鐘最多處理 2000 個請求),系統 A 從 MQ 裏慢慢拉取請求,每秒鐘拉取 2000 個請求。MQ,每秒鐘 5000 個請求進來,結果只有 2000 個請求出去,結果致使在高峯期(21小時),可能有幾十萬甚至幾百萬的請求積壓在 MQ 中,這個是正常的,由於過了高峯期以後,每秒鐘就 50 個請求,可是系統 A 仍是會按照每秒 2000 個該請求的速度去處理。只要高峯期一過,系統 A 就會快速的將積壓的消息給解決掉。(算一筆帳,每秒積壓在 MQ 裏消息有 3000 條,一分鐘就會積壓 18W 條消息,一個小時就會積壓 1000 萬條消息。等高峯期一過,差很少須要 1 個多小時就能夠把 1000W 條積壓的消息給處理掉)

架構中引入 MQ 後存在的問題

  1. 系統可用性下降

MQ 可能掛掉,致使整個系統崩潰

  1. 系統複雜性變高

可能發重複消息,致使插入重複數據;消息丟了;消息順序亂了; 系統 B,C,D 掛了,致使 MQ 消息積累,磁盤滿了;

  1. 一致性問題

原本應該A,B,C,D 都執行成功了再返回,結果A,B,C 執行成功 D 失敗

Kafka、ActiveMQ、RabbitMQ、RocketMQ 有什麼優缺點

特性 ActiveMQ RabbitMQ RocketMQ Kafka
單機吞吐量 萬級(一秒1W~2W 左右請求) 萬級 十萬級 十萬級
時效性 ms級 微秒級,這個是rabbitmq一大特色,延遲最低的 ms級 ms級之內
可用性 高,基於主從架構高可用性 高,基於主從架構高可用性 很是高,分佈式架構 很是高,Kafka是分佈式的,一個數據多個副本,少數機器宕機,不會丟失數據,不會致使不可用
消息可靠性 有較低機率丟失數據 通過參數優化配置,能夠作到 0 丟失 通過參數優化配置,消息能夠作到 0 丟失
優劣勢總結 優勢:很是成熟,功能強大,在業內大量的公司和項目都有應用。缺點:偶爾會有較低機率丟失消息,而如今社區以及國內應用愈來愈少,官方社區對ActiveMQ 5.x維護愈來愈少,並且確實主要是基於解耦和異步來用,較少在大規模吞吐的場景中使用 優勢:erlang語言開發,性能極其好,延時很低,管理界面很是棒,社區活躍 缺點:RabbitMQ確實吞吐量會低一些(單機幾萬),這個是由於他的實現機制比較重。並且 erlang 開發,國內有幾個實力作 erlang源碼級別的研究和定製?缺少掌控,依賴開源社區的維護和修復bug。並且 RabbitMQ集羣動態擴展會很麻煩,其實主要是 erlang語言自己帶來的問題,很難讀源碼,很難定製和掌控 優勢:接口簡單易用,阿里保障,日處理消息上百億之多,能夠作到大規模吞吐,性能也很是好,分佈式擴展也很方便,社區維護還能夠,可靠性和可用性都 OK,還能夠支撐大規模的topic數量,支持複雜 MQ 業務場景,源碼是 java,方便公司定製和掌控 缺點:社區活躍通常,接口不是按照標準的 JMS 規範走的,有些系統遷移須要修改大量代碼,阿里出臺的技術,有可能這個技術被拋棄。 優勢:提供較少的核心功能,可是提升超高的吞吐量,ms級的延遲,極高的可用性以及可靠性,並且分佈式能夠任意擴展,Kafka最好是支撐較少的topic數量來保證極高的吞吐量。缺點:有可能消息重複消費,會對數據準確性形成影響,大數據領域中以及日誌採集,這點影響能夠忽略,自然適合大數據實時計算以及日誌收集

建議:中小型公司 RabbitMQ 大公司:RocketMQ 大數據實時計算:Kafka

消息隊列高可用

RabbtitMQ 高可用

RabbitMQ有三種模式:單機模式 、普通集羣模式、鏡像集羣模式

  • 單機模式

demo級

  • 普通集羣模式(非高可用)

隊列的元數據存在於多個實例中,可是消息不存在多個實例中,每次

多臺機器上啓動多個 rabbitmq 實例,每一個機器啓動一個。
優勢:能夠多個機器消費消息,可 以提升消費的吞吐量
缺點:可能會在 rabbitmq 內部產生大量的數據傳輸 ;可用性基本沒保障,queue 所在機器宕機,就沒辦法消費了
沒有高可用性可言

  • 鏡像集羣模式(高可用,非分佈式)


隊列的元數據和消息都會存在於多個實例中,每次寫消息到 queue的時候,都會自動把消息到多個實例的 queue 裏進行消息同步。也就 是每一個節點上都有這個 queue 的一個完整鏡像(這個 queue的所有數據)。任何一個節點宕機了,其餘節點還包含這個 queue的完整數據,其餘 consumer 均可以到其餘活着的節點上去消費數據都是 OK 的。缺點:不是分佈式的,若是這個 queue的數據量很大,大到這個機器上的容量沒法容納 。
開啓鏡像集羣模式方法: 管理控制檯,Admin頁面下,新增一個鏡像集羣模式的策略,指定的時候能夠要求數據同步到全部節點,也能夠要求同步到指定數量的節點,而後你再次建立 queue 的時候 ,應用這個策略,就 會自動將數據同步到其餘的節點上去。

  • Kafka 高可用架構

broker進程就是kafka在每臺機器上啓動的本身的一個進程。每臺機器+機器上的broker進程,就能夠認爲是 kafka集羣中的一個節點。
你建立一個 topic,這個topic能夠劃分爲多個 partition,每一個 partition 能夠存在於不一樣的 broker 上,每一個 partition就存放一部分數據。
這就是自然的分佈式消息隊列,也就是說一個 topic的數據,是分散放在 多個機器上的,每一個機器就放一部分數據。
分佈式的真正含義是每一個節點只放一部分數據,而不是完整數據(完整數據就是HA、集羣機制)
Kafka 0.8版本以前是沒有 HA 機制的,任何一個 broker 宕機了,那麼就缺失一部分數據。
Kafka 0.8之後,提供了 HA 機制,就是 replica 副本機制。每一個 partition的數據都會同步到其餘機器上,造成本身的多個 replica 副本。而後全部 replica 會選舉一個 leader。那麼生產者、消費者都會和這個 leader 打交道,而後其餘 replica 就是 follow。寫的時候,leader 負責把數據同步到全部 follower上去,讀的時候就直接讀 leader 上的數據便可。若是某個 broker宕機了,恰好也是 partition的leader,那麼此時會選舉一個新的 leader出來,你們繼續讀寫那個新的 leader便可,這個就 是所謂的高可用性。
leader和follower的同步機制:
寫數據的時候,生產者就寫 leader,而後 leader將數據落地寫本地磁盤,接着其餘 follower 本身主動從 leader來pull數據。一旦全部 follower同步好數據了,就會發送 ack給 leader,leader收到全部 follower的 ack以後,就會返回寫成功的消息給生產者。
消費的時候,只會從 leader去讀,可是隻有一個消息已經被全部 follower都同步成功返回 ack的時候,這個消息纔會被消費者讀到。

消息隊列重複數據

MQ 只能保證消息不丟,不能保證重複發送

Kafka 消費端可能出現的重複消費問題


每條消息都有一個 offset 表明 了這個消息的順序的序號,按照數據進入 kafka的順序,kafka會給每條數據分配一個 offset,表明了這個是數據的序號,消費者從 kafka去消費的時候,按照這個順序去消費,消費者會去提交 offset,就是告訴 kafka已經消費到 offset=153這條數據了 ;zk裏面就記錄了消費者當前消費到了 offset =幾的那條消息;假如此時消費者系統被重啓,重啓以後,消費者會找kafka,讓kafka把上次我消費到的那個地方後面的數據繼續給我傳遞過來。
重複消息緣由:(主要發生在消費者重啓後)
消費者不是說消費完一條數據就立馬提交 offset的,而是定時按期提交一次 offset。消費者若是再準備提交 offset,可是還沒提交 offset的時候,消費者進程重啓了,那麼此時已經消費過的消息的 offset並無提交,kafka也就不知道你已經消費了 offset= 153那條數據,這個時候kafka會給你發offset=152,153,154的數據,此時 offset = 152,153的消息重複消費了

保證 MQ 重複消費冪等性


冪等:一個數據或者一個請求,給你重複來屢次,你得確保對應的數據是不會改變的,不能出錯。
思路:

  1. 拿數據要寫庫,首先檢查下主鍵,若是有數據,則不插入,進行一次update
  2. 若是是寫 redis,就沒問題,反正每次都是 set ,自然冪等性
  3. 生產者發送消息的時候帶上一個全局惟一的id,消費者拿到消息後,先根據這個id去 redis裏查一下,以前有沒消費過,沒有消費過就處理,而且寫入這個 id 到 redis,若是消費過了,則不處理。
  4. 基於數據庫的惟一鍵

保證 MQ 消息不丟

MQ 傳遞很是核心的消息,好比:廣告計費系統,用戶點擊一次廣告,扣費一塊錢,若是扣費的時候消息丟了,則會不斷少錢,聚沙成塔,對公司是一個很大的損失。

RabbitMQ可能存在的數據丟失問題

  1. 生產者寫消息的過程當中,消息都沒有到 rabbitmq,在網絡傳輸過程當中就丟了。或者消息到了 rabbitmq,可是人家內部出錯了沒保存下來。
  2. RabbitMQ 接收到消息以後先暫存在主機的內存裏,結果消費者還沒來得及消費,RabbitMQ本身掛掉了,就致使暫存在內存裏的數據給搞丟了。
  3. 消費者消費到了這個消費,可是還沒來得及處理,本身就掛掉了,RabbitMQ 覺得這個消費者已經處理完了。

問題 1解決方案:
事務機制:(通常不採用,同步的,生產者發送消息會同步阻塞卡住等待你是成功仍是失敗。會致使生產者發送消息的吞吐量降下來)

channel.txSelect
try {
    //發送消息
} catch(Exception e){
    channel.txRollback;
    //再次重試發送這條消息
} 
    channel.txCommit;

confirm機制:(通常採用這種機制,異步的模式,不會阻塞,吞吐量會比較高)

  1. 先把 channel 設置成 confirm 模式
  2. 發送一個消息到 rabbitmq
  3. 發送完消息後就不用管了
  4. rabbitmq 若是接收到了這條消息,就會回調你生產者本地的一個接口,通知你說這條消息我已經收到了
  5. rabbitmq 若是在接收消息的時候報錯了,就會回調你的接口,告訴你這個消息接收失敗了,你能夠再次重發。
public void ack(String messageId){

}

public void nack(String messageId){
    //再次重發一次這個消息
}

問題 2 解決方案:
持久化到磁盤

  1. 建立queue的時候將其設置爲持久化的,這樣就能夠保證 rabbitmq持久化queue的元數據,可是不會持久化queue裏的數據
  2. 發送消息的時候將 deliveryMode 設置爲 2,將消息設置爲持久化的,此時 rabbitmq就會將消息持久化到磁盤上去。必須同時設置 2 個持久化才行。
  3. 持久化能夠跟生產者那邊的 confirm機制配合起來,只有消息被持久化到磁盤以後,纔會通知生產者 ack了 ,因此哪怕是在持久化到磁盤以前 ,rabbitmq掛了,數據丟了,生產者收不到 ack,你也能夠本身重發。

缺點:可能會有一點點丟失數據的可能,消息恰好寫到了 rabbitmq中,可是還沒來得及持久化到磁盤上,結果不巧, rabbitmq掛了,會致使內存裏的一點點數據會丟失。

問題 3 解決方案:
緣由:消費者打開了 autoAck機制(消費到一條消息,還在處理中,還沒處理完,此時消費者自動 autoAck了,通知 rabbitmq說這條消息已經消費了,此時不巧,消費者系統宕機了,那條消息丟失了,還沒處理完,並且 rabbitmq還覺得這個消息已經處理掉了)
解決方案:關閉 autoAck,本身處理完了一條消息後,再發送 ack給 rabbitmq,若是此時還沒處理完就宕機了,此時rabbitmq沒收到你發的ack消息,而後 rabbitmq 就會將這條消息從新分配給其餘的消費者去處理。

Kafka 可能存在的數據丟失問題

  1. 消費端弄丟數據

緣由:消費者消費到那條消息後,自動提交了 offset,kafka覺得你已經消費好了這條消息,結果消費者掛了,這條消息就丟了。
例子:消費者消費到數據後寫到一個內存 queue裏緩存下,消息自動提交 offset,重啓了系統,結果會致使內存 queue 裏還沒來得及處理的數據丟失。
解決方法:kafka會自動提交 offset,那麼只要關閉自動提交 offset,在處理完以後本身手動提交,能夠保證數據不會丟。可是此時確實仍是會重複消費,好比恰好處理完,還沒提交 offset,結果本身掛了,此時確定會重複消費一次 ,作好冪等便可。

  1. Kafka 丟掉消息

緣由:kafka 某個 broker 宕機,而後從新選舉 partition 的 leader時,此時其餘的 follower 恰好還有一些數據沒有同步,結果此時 leader掛了,而後選舉某個 follower成 leader以後,就丟掉了以前leader裏未同步的數據。
例子:kafka的leader機器宕機,將 follower 切換爲 leader以後,發現數據丟了
解決方案:(保證 kafka broker端在 leader發生故障,或者leader切換時,數據不會丟)

  1. 給 topic設置 replication.factor ,這個值必須大於 1,保證每一個 partition 必須至少有 2 個副本
  2. 在 kafka 服務端設置 min.insync.replicas 參數,這個值必須大於 1,這個是要求一個 leader至少感知到有至少一個follower還跟本身保持聯繫,沒掉隊,這樣才能確保 leader掛了還有一個follower,保證至少一個 follower能和leader保持正常的數據同步。
  3. 在 producer 端設置 acks =all,這個是要求每條數據,必須是寫入全部 replica 以後,才能認爲是寫成功了。不然會生產者會一直重試,此時設置 retries = MAX(很大的重試的值),要求一旦寫入失敗,就卡在這裏(避免消息丟失)
  4. kafka 生產者丟消息

按 2 的方案設置了 ack =all,必定不會丟。它會要求 leader 接收到消息,全部的 follower 都同步 到了消息以後,才認爲本次寫成功。若是沒知足這個條件,生產者會無限次重試 。

消息隊列順序性

背景:mysql binlog 同步的系統,在mysql裏增刪改一條數據,對應出來了增刪改 3 條binlog,接着這 3 條binlog發送到 MQ 裏面,到消費出來依次執行,起碼是要保證順序的吧,否則順序變成了 刪除、修改、增長。日同步數據達到上億,mysql->mysql,好比大數據 team,須要同步一個mysql庫,來對公司的業務系統的數據作各類複雜的操做。
場景:

  1. rabbitmq,一個queue,多個consumer,這不明顯亂了
  2. kafka,一個topic,一個partition,一個consumer,內部多線程,這不也亂了

RabbitMQ 消息順序錯亂

RabbitMQ 如何保證消息順序性

須要保證順序的數據放到同一個queue裏

Kafka 消息順序錯亂


寫入一個 partition中的數據必定是有順序的。
生產者在寫的時候,能夠指定一個 key,好比訂單id做爲key,那麼訂單相關的數據,必定會被分發到一個 partition中區,此時這個 partition中的數據必定是有順序的。Kafka 中一個 partition 只能被一個消費者消費。消費者從partition中取出數據的時候 ,必定是有順序的。

Kafka 保證消息順序性


若是消費者單線程消費+處理,若是處理比較耗時,處理一條消息是幾十ms,一秒鐘只能處理幾十條數據,這個吞吐量過低了。確定要用多線程去併發處理,壓測消費者4 核 8G 單機,32 條線程,最高每秒能夠處理上千條消息

消息隊列延遲以及過時失效

消費端出了問題,不消費了或者消費極其慢。接着坑爹了,你的消息隊列集羣的磁盤都快寫滿了 ,都沒人消費,怎麼辦?積壓了幾個小時,rabbitmq設置了消息過時時間後就沒了,怎麼辦?
例如:每次消費以後都要寫 mysql,結果mysql掛了,消費端 hang 不動了。
消費者本地依賴的一個東西掛了,致使消費者掛了。
長時間沒處理消費,致使 mq 寫滿了。
場景:幾千萬條數據再 MQ 裏積壓了七八個小時

快速處理積壓的消息

一個消費者一秒是 1000 條,一秒 3 個消費者是 3000 條,一分鐘是 18W 條,1000 多 W 條須要一個小時恢復。

步驟:

  1. 先修復 consumer 的問題,確保其恢復消費速度,而後將現有的 consumer 都停掉
  2. 新建一個topic,partition是原來的 10 倍,臨時創建好原先 10 倍或者 20 倍的 queue 數量
  3. 而後寫一個臨時的分發數據的 consumer 程序,這個程序部署上去消費積壓的數據,消費以後不作耗時的處理,直接均勻輪詢寫入臨時創建好的 10 倍數量的 queue
  4. 接着臨時徵用 10 倍的機器來部署 consumer,每一批 consumer 消費一個臨時 queue 的數據
  5. 這種作法至關 因而臨時將 queue 資源和 consumer 資源擴大 10 倍,以正常 10 倍速度
  6. 等快速消費完積壓數據以後,恢復原先部署架構 ,從新用原先的 consumer機器消費消息

原來 3 個消費者須要 1 個小時能夠搞定,如今 30 個臨時消費者須要 10 分鐘就能夠搞定。

若是用的 rabbitmq,而且設置了過時時間,若是此消費在 queue裏積壓超過必定的時間會被 rabbitmq清理掉,數據直接搞丟。
這個時候開始寫程序,將丟失的那批 數據查出來,而後從新灌入mq裏面,把白天丟的數據補回來。

若是消息積壓mq,長時間沒被處理掉,致使mq快寫完滿了,你臨時寫一個程序,接入數據來消費,寫到一個臨時的mq裏,再讓其餘消費者慢慢消費 或者消費一個丟棄一個,都不要了,快速消費掉全部的消息,而後晚上補數據。

如何設計消息隊列中間件架構

  1. mq要支持可伸縮性,快速擴容。設計一個分佈式的 MQ,broker->topic->partition,每一個 partition 放一個機器,就存一部分數據。若是如今資源不夠,給 topic 增長 partition ,而後作數據遷移,增長機器。
  2. mq數據落磁盤,避免進程掛了數據丟了,順序寫,這樣就沒有磁盤隨機讀寫的尋址開銷,磁盤順序讀寫的性能是很高的,這個就是 kafka的思路。
  3. mq高可用性。多副本->leader & follower-> broker 掛了從新選舉 leader 對外提供服務
  4. 支持數據 0 丟失。

分佈式搜索引擎

es,
lucene底層的原理是倒排索引

分佈式緩存

緩存好處:高性能 + 高併發

高性能(經常使用)


數據庫查詢耗費了800ms,其餘用戶對同一個數據再次查詢 ,假設該數據在10分鐘之內沒有變化過,而且 10 分鐘以內有 1000 個用戶 都查詢了同一數據,10 分鐘以內,那 1000 個用戶,每一個人查詢這個數據都感受很慢 800ms
好比 :某個商品信息,在 一天以內都不會改變,可是這個商品每次查詢一次都要耗費2s,一天以內被瀏覽 100W次
mysql 單機也就 2000qps,緩存單機輕鬆幾萬幾十萬qps,單機 承載併發量是 mysql 單機的幾十倍。

高併發


在中午高峯期,有 100W 個用戶訪問系統 A,每秒有 4000 個請求去查詢數據庫,數據庫承載每秒 4000 個請求會宕機,加上緩存後,能夠 3000 個請求走緩存 ,1000 個請求走數據庫。
緩存是走內存的,內存自然能夠支撐4w/s的請求,數據庫(基於磁盤)通常建議併發請求不要超過 2000/s

緩存不良後果

  1. 緩存與數據庫雙寫不一致
  2. 緩存雪崩
  3. 緩存穿透
  4. 緩存併發競爭

Redis 線程模型

redis 單線程 ,memcached 多線程
redis 是單線程 nio 異步線程模型

Redis 和 Memcached 區別

  1. Redis 支持服務器端的數據操做:Redis比Memcached來講,擁有更多的數據結構和並支持更豐富的數據操做,一般在Memcached裏,你須要將數據拿到客戶端來進行相似的修改再set回去。這大大增長了網絡 IO 的次數和數據體積。在Redis中,這些複雜的操做一般和通常的GET/SET同樣高效。因此,若是須要緩存能支持更復雜的結構和操做,那麼Redis會是不錯的選擇
  2. 集羣模式:memcached 沒有原生的集羣模式,須要依靠客戶端來實現往集羣中分片寫入數據,可是 Redis 目前是原生支持 cluster模式的

Redis 單線程模型

一個線程+一個隊列

redis 基於 reactor 模式開發了網絡事件處理器,這個處理器叫作文件事件處理器,file event handler,這個文件事件處理器是單線程的,因此redis 是單線程的模型,採用 io多路複用機制同時監聽多個 socket,根據socket上的事件來選擇對應的事件處理器來處理這個事件。
文件事件處理器包含:多個 socket,io多路複用程序,文件事件分派器,事件處理器(命令請求處理器、命令回覆處理器、鏈接應答處理器)
文件事件處理器是單線程的,經過 io 多路複用機制監聽多個 socket,實現高性能和線程模型簡單性
被監聽的 socket 準備好執行 accept,read,write,close等操做的時候,會產生對應的文件事件,調用以前關聯好的時間處理器處理
多個 socket併發操做,產生不一樣的文件事件,i/o多路複用會監聽多個socket,將這些 socket放入一個隊列中排隊。時間分派器從隊列中取出socket給對應事件處理器。
一個socket時間處理完後,時間分派器才能從隊列中拿到下一個socket,給對應事件處理器來處理。

文件事件:
AE_READABLE 對應 socket變得可讀(客戶端對redis執行 write操做)
AE_WRITABLE 對應 socket 變得可寫(客戶端對 redis執行 read操做)
I/O 多路複用能夠同時監聽AE_REABLE和 AE_WRITABLE ,若是同時達到則優先處理 AE_REABLE 時間
文件事件處理器:
鏈接應答處理器 對應 客戶端要鏈接 redis
命令請求處理器 對應 客戶端寫數據到 redis
命令回覆處理器 對應 客戶端從 redis 讀數據

流程:

  1. redis 初始化時,會將鏈接應答處理器跟 AE_READABLE事件關聯
  2. 客戶端對 redis 發起鏈接,產生一個 AE_READABLE 事件
  3. 鏈接應答處理器處理客戶端 AE_READABLE 事件,建立客戶端對應的 socket,同時將這個 socket的 AE_READABLE 事件和命令請求處理器關聯
  4. 客戶端對 redis 發起讀請求,會在 socket上產生一個 AE_READABLE 事件
  5. 綁定 AE_READABLE 事件的命令請求處理器會從 socket 中讀取請求相關數據,執行對應操做,當執行完畢後,將 socket的 AE_WRITABLE 事件跟命令回覆處理器關聯
  6. 當客戶端這邊準備好讀取響應時,會在 socket上產生一個AE_WRITABLE事件
  7. 綁定 AE_WRITABLE 事件的命令回覆處理器將準備好的響應數據寫入 socket,供客戶端來讀取
  8. 命令回覆處理器寫完後,刪掉 socket的 AE_WRITABLE 事件和命令回覆處理器的綁定關係

Redis 單線程模型效率高

一秒鐘能夠處理幾萬個請求

  1. 非阻塞 I/O 多路複用機制(不處理事件,只輪詢請求壓入隊列)
  2. 純內存操做(操做只有幾微秒)
  3. 單線程反而 避免了多線程頻繁上下文切換的問題

Redis 數據類型

  • string

普通的 set,get kv緩存

  • hash

類型 map結構,好比一個對象(沒有嵌套對象)緩存到 redis裏面,而後讀寫緩存的時候,能夠直接操做hash的字段(好比把 age 改爲 21,其餘的不變)
key=150
value = {

"id":150,
"name":"zhangsan",
"age":20

}

  • list

有序列表 ,元素能夠重複
能夠經過 list 存儲一些列表型數據結構,相似粉絲列表,文章評論列表。
例如:微信大 V的粉絲,能夠以 list 的格式放在 redis 裏去緩存
key=某大 V value=[zhangsan,lisi,wangwu]
好比 lrange 能夠從某個元素開始讀取多少個元素,能夠基於 list 實現分頁查詢功能,基於 redis實現高性能分頁,相似微博下來不斷分頁東西。
能夠搞個簡單的消息隊列,從 list頭懟進去(lpush),list尾巴出來 (brpop)

  • set

無序集合,自動去重
須要對一些數據快速全局去重,(固然也能夠基於 HashSet,可是單機)
基於 set 玩差集、並集、交集的操做。好比:2 我的的粉絲列表整一個交集,看看 2 我的的共同好友是誰?
把 2 個大 V 的粉絲都放在 2 個 set中,對 2 個 set作交集(sinter)

  • sorted set

排序的 set,去重可是能夠排序,寫進去的時候給一個分數,自動根據分數排序

排行榜:

  1. 將每一個用戶以及其對應的分數寫入進去

zadd board score username

  1. zrevrange board 0 99 能夠獲取排名前 100 的用戶
  2. zrank board username 能夠看到用戶在排行榜裏的排名

例如:
zadd board 85 zhangsan
zadd board 72 wangwu
zadd board 96 lis
zadd board 62 zhaoliu

自動排序爲:
96 lisi
85 zhangsan
72 wangwu
62 zhaoliu

獲取排名前 3 的用戶 : zrevrange board 0 3
96 lisi
85 zhangsan
72 wangwu

查看zhaoliu的排行 :zrank board zhaoliu 返回 4

Redis 過時策略

內存是寶貴的,磁盤是廉價的
給key設置過時時間後,redis對這批key是按期刪除+惰性刪除
按期刪除:
redis 默認每隔 100ms隨機抽取一些設置了過時時間的 key,檢查其是否過時了,若是過時就刪除。
注意:redis是每隔100ms隨機抽取一些 key來檢查和刪除,而不是遍歷全部的設置過時時間的key(不然CPU 負載會很高,消耗在檢查過時 key 上)
惰性刪除:
獲取某個key的時候, redis 會檢查一下,這個key若是設置了過時時間那麼是否過時,若是過時了則刪除。
若是按期刪除漏掉了許多過時key,而後你也沒及時去查,也沒走惰性刪除,若是大量過時的key堆積在內存裏,致使 redis 內存塊耗盡,則走內存淘汰機制。

內存淘汰策略:

  1. noeviction:當內存不足以容納新寫入數據時,新寫入操做直接報錯(沒人用)
  2. allkeys-lru: 當內存不足以容納新寫入數據時,在鍵空間中,移除最近最少使用的key(最經常使用)
  3. allkeys-random: 當內存不足以容納新寫入數據時,在鍵空間中,隨機移除某個 key,(沒人用)
  4. volatile-lru:當內存不足以容納新寫入數據時,在設置了過時時間的鍵空間中,移除最近最少使用的key(不合適)
  5. volatile-ttl:當內存不足以容納新寫入數據時,在設置了過時時間的鍵空間中,有更早過時時間的 key 優先移除(不合適)

LRU 算法:

package com.mousycoder.mycode;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @version 1.0
 * @author: mousycoder
 * @date: 2019/10/31 17:55
 */
public class LRUCache<K,V> extends LinkedHashMap<K,V> {

    private final int CACHE_SIZE;

    public LRUCache( int cacheSize) {
        super((int)Math.ceil(cacheSize / 0.75) + 1 ,0.75f,true);
        this.CACHE_SIZE = cacheSize;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > CACHE_SIZE;
    }

    public static void main(String[] args) {
        LRUCache<Integer,Integer> lruCache = new LRUCache<>(10);
        for (int i = 0; i < 15; i++) {
            lruCache.put(i,i);
        }

        Integer integer1 = lruCache.get(0);
        for (Integer integer : lruCache.keySet()) {
            System.out.println(integer);
        }

    }
}

Redis 高併發和高可用

緩存架構(多級緩存架構、熱點緩存)
redis 高併發瓶頸在單機,讀寫分離,通常是支撐讀高併發,寫請求少,也就 一秒一兩千,大量請求讀,一秒鐘二十萬次。

主從架構


一主多從,主負責寫,將數據同步複製到其餘 slave節點,從節點負責讀,全部讀的請求所有走從節點。主要是解決讀高併發。、
主從架構->讀寫分離->支撐10W+讀QPS架構

Redis Replication


master->slave 複製,是異步的
核心機制:

  1. redis 採用異步方式複製數據到 slave 節點
  2. 一個 master node是能夠配置多個 slave node的
  3. slave node也能夠鏈接其餘的 slave node
  4. slave node 作複製的時候,是不會 block master node的正常工做
  5. slave node 在作複製的時候,也不會 block對本身的查詢操做,它會用舊的數據集來提供服務。可是複製完成時,須要刪除舊數據集,加載新的數據集,這個時候就會暫停對外服務了。
  6. slave node 主要用來進行橫向擴容,作讀寫分離,擴容 slave node 能夠提升讀的吞吐量

master持久化對主從架構的意義:
若是開啓了主從架構,必定要開啓 master node的持久化,否則 master宕機重啓數據是空的,一經複製,slave的數據也丟了

主從複製原理:

第一次啓動或者斷開重連狀況:

  1. 當啓動一個 slave node的時候,它會發送一個 PSYNC 命令給 master node
  2. master 會觸發一次 full resynchronization (若是不是第一次鏈接,master 只會複製給 slave 部分缺乏的數據,從backlog裏找)
  3. master會啓動一個後臺線程,開始生成一份 RDB 快照( bgsave,也能夠直接在內存中建立),同時將從客戶端收到的全部寫命令緩存在內存中。RDB 文件生成完畢以後,master會將這個RDB發送給slave,slave會先寫入本地磁盤,而後再從本地磁盤加載到內存中。而後 master會將內存中緩存的寫命令發送給 slave,slave也會同步這些數據(slave若是跟 master網絡故障,斷開鏈接,會自動重連,master若是發現有多個 slave 來從新鏈接,僅僅只會啓動一個 RDB save 操做,用一份數據服務全部 slave node)

正常狀況下:
master 來一條數據,就異步給 slave

Redis高可用性

整年 99.99%的時間,都是出於可用的狀態,那麼就能夠稱爲高可用性
redis 高可用架構叫故障轉移,failover,也能夠叫作主備切換,切換的時間不可用,可是總體高可用。
sentinal node(哨兵)

Sentinal

做用:

  1. 集羣監控,負責監控 redis master 和 slave進程是否正常
  2. 消息通知,若是某個 redis 實例有故障,那麼哨兵負責發送消息做爲報警通知給管理員
  3. 故障轉移,若是 master 掛掉,會自動轉移到 slave
  4. 配置中心,若是故障轉移了,通知 client 客戶端新的 master地址

兩節點哨兵集羣


quorum = 1 (表明哨兵最低個數能夠嘗試故障轉移,選舉執行的哨兵)
master 宕機,只有 S2 存活,由於 quorum =1 能夠嘗試故障轉移,可是沒達到 majority =2 (最低容許執行故障轉移的哨兵存活數)的標準,沒法執行故障轉移

三節點哨兵集羣(經典)


若是 M1 宕機了,S2,S3 認爲 master宕機,選舉一個執行故障轉移,由於 3 個哨兵的 majority = 2,因此能夠執行故障轉移

Redis 主從 + 哨兵

丟數據:

  1. master內存中數據異步同步到 slave master 就掛掉了,丟掉了 master 內存中的數據

  1. 腦裂,某個 master 所在機器忽然脫離了正常的網絡,其餘 slave機器不能鏈接,可是實際上 master還在運行,哨兵認爲 master 宕機,選舉 slave爲master,此時集羣裏有 2 個 master, client還沒來得及切換到新的master,還繼續寫在舊的 master上,數據丟了,此時舊的 master再次恢復,被被做爲一個 slave 掛到新的 master 上,本身的數據被清空 (腦裂,大腦一分爲 2,同時指揮同一我的)

解決方案:

  1. min-slaves-max-lag 10 (至少一個 slave同步的延遲不能超過 10s) 減小異步複製的數據丟失,發現slave複製數據和 ack延時過長,拒絕寫入,減小同步數據損失。讓client作降級寫到本地磁盤裏和限流,或者先暫存到消息隊列,而後從新發回 master
  2. min-slaves-to-write 1 減小腦裂帶來的數據丟失,最多損失 10 s數據,假設master 不能繼續給 slave發送數據,而且 slave 10s沒給本身的 ack消息,直接拒絕客戶端寫請求,同時 client作降寫到本地磁盤、限流,或者先暫存到消息隊列,而後從新發回 master

哨兵

sdown 主觀宕機,哨兵以爲一個 master 宕機(ping 超過了 is-master-down-after-milliseconds毫秒數)
odown 客觀宕機,quorum數量的哨兵都以爲 master宕機
哨兵互相感知經過 redis的 pub/sub系統,每隔 2 秒往同一個 channel裏發消息(本身的 host,ip,runid),其餘哨兵能夠消費這個消息
以及同步交換master的監控信息。
哨兵確保其餘slave修改master信息爲新選舉的master
當一個 master被認爲 odown && marjority哨兵都贊成,那麼某個哨兵會執行主備切換,選舉一個slave成爲master(考慮 1. 跟master斷開鏈接的時長 2. slave 優先級 3.複製 offset 4. runid)
選舉算法:

  1. 若是slave跟master斷開鏈接已經超過 down-after-milliseconds * 10 + master宕機時間,則放棄
  2. 按 slave 優先級排序 ,slave-priority 越小越靠前
  3. replica offset ,哪一個slave複製越多的數據,越靠前
  4. runid 越小,越靠前

quorum 數量哨兵認爲odown->選舉一個哨兵切換->得到 majority哨兵的受權(quorum < majority 須要 majority個哨兵受權,quorum >= majority 須要 quorum 哨兵受權)
第一個選舉出來的哨兵切換失敗了,其餘哨兵等待 failover-time以後,從新拿confiuration epoch作爲新的version 切換,保證拿到最新配置,用於 configuration傳播(經過 pu/sub消息機制,其餘哨兵對比 version 新舊更新 master配置)

Redis 優化方案

高併發:主從架構
高容量:Redis集羣,支持每秒幾十萬的讀寫併發
高可用:主從+哨兵

Redis 持久化

持久化的意義在於數據備份(到其餘服務器)+故障恢復(遇到災難,機房斷電,電纜被切)

RDB 對 Redis 中的數據執行週期性的持久化

緩存雪崩

方案:

事前:保證 redis 集羣高可用性 (主從+哨兵或 redis cluster),避免全盤崩潰
事中:本地 ehcache 緩存 + hystrix 限流(保護數據庫) & 降級,避免 MySQL被打死
過後: redis持久化,快速恢復緩存數據,繼續分流高併發請求

限制組件每秒就 2000 個請求經過限流組件進入數據庫,剩餘的 3000 個請求走降級,返回一些默認 的值,或者友情提示
好處 :

  1. 數據庫絕對不會死,確保了每秒只會過去 2000 個請求
  2. 只要數據庫不死,對於用戶來講 2/5的請求能夠被處理
  3. 系統沒死,用戶多點幾回可能就刷出來了

緩存穿透


4000 個請求黑客攻擊請求數據庫裏沒有的數據
解決方案:把黑客查數據庫中不存在的數據的值,寫到緩存中,好比: set -999 UNKNOWN

分庫分表

相關文章
相關標籤/搜索