畫一下你們系統的整體架構圖,說說各個服務在生產環境怎么部署的?
核心:服務框架、注冊中心、網關
即使你沒有用很多微服務架構里的東西,只要有上述三個東西,配合上寫一些文檔,接口文檔,分布式系統架構,其實就出來了,很多中小型公司,一個小型技術團隊,后端開發工程師總共就10多個人。
常見生產實踐問題:
關注分布式系統一些生產實踐的問題,應對你們公司的流量,各個服務、網關、注冊中心,都是如何部署的?
部署幾台機器,每台機器的配置是什么,每天整體的流量有多少,高峰期的請求量有多少,你的整體系統是否抗住了?
各個服務之間的超時、重試、冪等性?
中小型系統,拆分10-20個微服務。大型互聯網公司,一般幾百個,幾千個微服務。
中小型,一般2-3台機器足夠,把服務上線,服務發現優化到極致。
服務上線:注冊表多級緩存同步至1秒,拉取頻率降低至1秒。
服務心跳:1秒報1次。
故障發現:1秒檢查一次,2,3秒沒有認為沒有故障等。
服務注冊中心沒有壓力,服務注冊中心部署2台機器,每台4C8G,高可用,每秒輕松幾百請求,甚至上千。前提是數據庫SQL別寫的太爛。
網關機器配置稍微高一些,4C8G,一台扛每秒幾百個請求,部署3~4台,保證每台網關機器壓力較小,進一步保證可靠性。
為什么網關Zuul之前還要配置Nginx?
Nginx反向代理,軟負載,把請求均勻負載到多台Zuul的機器上。LVS和Nginx處於不同的負載維度,主要是運維工程師負責管理。
數據庫,MYSQL,16C32G,物理機最佳,平時扛每秒幾百請求,高峰期最多每秒扛三四千請求。
扛幾千請求時機器會負載很高,CPU,IO,網絡負載很高。DBA在優化一下。
你們系統每天有多大訪問量?每個服務高峰QPS多少?壓測過服務最大QPS嗎?
每天服務多少請求量,高峰每秒qps,在代碼里稍微加一些metrics代碼,對自己運行過程中各種請求量,每秒請求量,成功次數,失敗次數,在內存里直接做一些計數。
在負責的核心服務里,核心接口,開發一個簡單的metric統計機制,AtomicLong,保證原則性,並發數據統計准確。
每個接口被調用時,可以對每個接口每分鍾做一個metric統計,每個接口每天統計計數。
再通過Log4j, logback等日志組件,把次數直接打印到日志文件,統計出高峰期每秒系統被訪問的次數,每條每個接口訪問量。
響應延時:
計算一下每個接口從請求到執行完畢,需要耗費多長時間,算一下每個接口平均的請求延時,
TP99,TP95,TP90,TP50,TP99,99%的請求耗費的時間在100ms以內,但是1%的請求可能耗費的時間在100ms以上
TP99 = 100ms TP95 = 50ms,95%的請求耗費的時間多在50ms以內,但是5%的請求耗費的時間在50ms以上
壓測工具,java壓測工具,開源的可以用的,模擬出來同時有多少用戶發起多少請求,每秒發起1000請求能抗住嗎?每秒鍾發起2000請求能抗住嗎?
假設你的系統每秒鍾最多抗800請求,如果你的壓測工具每秒發起了1000個請求,此時會發現最多只有800個請求同時可以被處理,
剩余200個請求需要進行排隊被阻塞住了,表示你這個系統每秒鍾最多抗800個請求。
如果系統訪問量比現在增加10倍,你們考慮過系統的擴容方案嗎?
網關直接多部署10倍的機器即可,前面的Nginx做會負載均衡,把流量均勻分發給各個網關機器。
服務擴容,都很簡單的,多加機器,部署啟動,自動注冊到注冊中心去,此時其他服務會自動感知到你的服務多加了一些機器。
服務實例變多了10倍,此時幾十個服務實例,幾百個服務實例,對eureka機器會造成每秒幾百請求,沒問題,eureka機器,8核16G的配置,單機抗上千請求,很輕松。
數據庫本來是每秒幾百請求,10倍,每秒高峰期是三四千請求,橫向擴容很麻煩,
此時可以考慮給單個數據庫部署的機器提高配置,32核128G高配物理機,每秒鍾抗幾千請求問題不大。
總結: 最基本的操作就是擴容。
網關:橫向加機器。
注冊中心:縱向升配置。
數據庫:縱向升配置。
當然還有很多其他專門針對分布式,高並發的優化和操作,不過加機器都是最簡單直接的。
你們生產環境的服務是怎么配置超時和重試參數的?為什么要這樣配置?
背景:Spring Cloud生產優化,系統第一次啟動的時候,調用請求經常出現timeout。
原因:每個服務第一次被請求的時候,他會去初始化一個Ribbon的組件,初始化這些組件需要耗費一定的時間,所以很容易會導致超時。
解決方案:讓每個服務啟動的時候就直接初始化Ribbon相關的組件,避免第一次請求的時候初始化。
ribbon: eager-load: enabled: true zuul: ribbon: eager-load: enabled: true feign: hystrix: enabled: false
線上的服務,每個服務部署上線的時候,一般來說都需要配置相關的超時時間還有重試次數
訂單服務 -> 積分服務、庫存服務、倉促服務
訂單服務對於其他服務的調用,一般來說限制在多長時間就必須認為是超時了,如果超時之后如何進行重試
積分服務部署了兩台機器,機器1和機器2
訂單服務在一次請求積分服務的機器1的時候,超過1秒鍾,超時了;此時需要進行重試,對積分服務當前的這台機器1重試幾次?如果說機器1不行,是否可以重試一下積分服務的機器2?
ribbon: ConnectTimeout: 3000 ReadTimeout: 3000 OkToRetryOnAllOperations: true MaxAutoRetries: 1 MaxAutoRetriesNextServer: 1
中小型的系統,沒必要直接開啟hystrix,資源隔離、熔斷、降級,如果你沒有設計好一整套系統高可用的方案。
增加重試機制依賴:
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency>
如果出現服務請求重試,會不會出現類似重復下單的問題?
可能會。
訂單服務 -> 創建訂單 -> 庫存服務 -> 扣減庫存 -> wms服務 -> 通知發貨 -> 積分服務 -> 增加積分
場景:
訂單服務調用庫存服務的時候,因為網絡抖動,請求超時了,超過了秒鍾,此時訂單服務會重試,再次調用一下庫存服務,發送一模一樣的請求過去。
比如說,訂單服務第一次請求庫存服務,庫存服務其實是把扣減庫存的業務邏輯執行成功了,
只不過網絡問題,導致響應遲遲沒有返回給訂單服務,可能在1.2s之后返回了響應給訂單服務。
訂單服務就認為請求超時了,他就再次發送了一個一模一樣的請求給庫存服務,庫存服務可能會再次對庫存進行扣減。
對於核心接口的防重冪等性,你們是怎么設計的?怎么防止重復下單問題?
常見方案:
1. 數據庫唯一索引
2. 基於Redis實現冪等性防重
核心接口,冪等性都是自己保證,對應Create操作,通過DB唯一索引來保證;對於Update操作,建議在核心接口基於業務邏輯,配合Redis,來保證冪等性。
比如庫存,定制化的針對接口開發冪等性的機制,比如說一旦庫存扣減成功之后,就立馬要寫一條數據到redis里去,order_id_11356_stock_deduct,寫入redis中,如果寫入成功,就說明之前這個訂單的庫存扣減,沒人執行過。
但是如果此時有一些重試的請求過來了,調用了你的庫存扣減接口,他同時也進行了庫存的扣減,但是他用同樣的一個key,order_id_11356_stock_deduct,寫入redis中,此時會發現已經有人寫過key,key已經存在了。
此時你就應該直接對剛才的庫存扣減邏輯做一個反向的回滾邏輯,
update product_stock set stock = stock - 100,update product_stock set stock = stock + 100,反向邏輯,回滾自己,避免重復扣減庫存。
參考資料:
21天互聯網Java進階面試訓練營(分布式篇)-- 中華石杉
