前言:從去年3月份入職到現在剛好一年半,在這一年半的時間里一直負責部門的資源服務開發與搭建,由於公司戰略的調整我負責的這個服務需要交接到別的部門。因為在負責服務的一年半中遇到太多太多坑,不過很幸運自己堅挺下來了,服務日訪問量也由剛接手時候的8千變成2.0億,業務方也擴大近一倍,今天我在這里把所有的經驗與大家分享,希望大家以后在做服務時候少走彎路。
一:基礎服務應該考慮的事項
1、基礎服務的定義
當接手一個新服務的時候我們就必須搞明白下面2件事:
- 這個服務的定位
- 這個服務承擔的職責
剛剛來58的時候在有次領導突然問我你能告訴我資源服務是什么嗎,當時我有點暈圈,半天沒有回答出來,因為我一直在根據業務方需求或者pm來開發自己玩去處於一個被動的角色,所以那次對話給我帶來的震驚挺大的。從那以后我就經常想我的服務是什么,后來就明白了它就是一個基於資源的庫存系統,既然是庫存系統那么它應該具有哪些能力呢,無非就是添加、消耗、返點、凍結、解凍、轉賬等功能,當我們明白這些功能后我們就清楚哪些是我們開發所承擔的能力,哪些不屬於我們承擔的能力,以至於后期我對我的服務進行大量解耦(比如一個用戶想獲取資源還進行判斷是不是會員,其實他並不屬於我們服務職責,那么類似這樣的業務需求我們就應該果斷拋給業務方)
2、基礎服務的文檔
我在做服務的初期並不存在文檔,但是慢慢發現文檔在整個服務中也處於一個非常重要的角色,因為新每個業務方需要接入服務的時候你不能每次都直接口述,那樣會耽誤太多精力,而且很多接入服務出現的問題沒有文檔的記錄也可能會丟失,這樣一來新接入的業務方可能會出現同樣的問題,為我們開發帶來大大的不便(文檔最好是准備2份,一份用wiki寫接入文檔,另一個是接口文檔)。下面總結下wiki文檔主要的內容
- 服務專業名字解釋
- 服務負責人以及聯系方式
- 服務配置說明(如版本號、沙箱ip等)
- 服務接口說明
- 服務調用demo
- 常見問題說明
3、服務的性能
性能是每個服務都需要關注的點,但是我們在設計服務初期就應該有一個性能指標,比如我們操作接口平均響應時長控制在30ms以內,查詢接口響應時長控制在10ms以內(這些指標可以根據業務方的需要來,比如一個下單功能整體要求是500ms可能分到我們提供的接口只有50ms),因為任何性能都是有一定標准的我們就圍繞這個標准去打造,性能優化指標可以參考我以前的一篇文章(https://www.cnblogs.com/LipeiNet/p/6379579.html)
4、服務的高可用
服務的高可用方案有很多比如,規范上線流程,灰度發布、服務限流、服務冗余(負載均衡)、數據冗余(備份)
4、服務的並發
並發基本大多服務都會面臨,一旦服務出現並發很可能造成了數據安全性出現問題,比如我們服務A用戶請求資源然后進行消耗,同一時間A用戶繼續請求資源進行消耗,那么導致的后果就是A消耗了2次資源但是只扣一次點,這樣一來服務就是出現問題,解決這個問題通常我們采用鎖,鎖主要分2種樂觀鎖和悲觀鎖。
悲觀鎖:認為所有的請求都會發生並發,所以悲觀鎖會對每一個請求加鎖,單點常用的悲觀鎖有synchronized和lock,具體這兩種鎖區別大家可以自己百度,當然另外如果服務是分布式的,那么就需要采用分布式鎖,常用的分布式鎖有基於redis和Zookeeper來實現的,我們這邊采用的是基於redis鎖,因為對性能要求比較高
樂觀鎖:認為所有的請求都不會發送並發,一般常用cas鎖來解決,就是先比后更的模式。如對數據加入版本號,獲取數據時候記錄版本號,如果更新時的版本號和獲取時不一致則拋出異常
5、服務的一致性
服務一致性我在這里來說明2種情況,一種是服務本身數據的一致性,另外是和調用方保持一致性.
調用方保持一致性:
我們服務提供冪等,當調用方出現超時或異常在次調用服務我們將會返回訂單重復提交,調用方可以根據返回值來判斷這個操作對自己業務而言是否是正確的.冪等我們服務采用的是永久性冪等,這樣做法其實有缺陷,因為量級太大會占用很大的數據庫資源,所以可以優化成對於添加采用永久性冪等,而一些消耗采用非永久性冪等,這樣一來就可以對冪等數據做歸檔,就會減少數據庫資源的利用。
服務本身一致性:
這個就是我們常說的一致性,我們現在並沒有完全解決這個問題,目前我對於跨單消耗我采用的是占位思想進行解決,也就是先會更新資源然后出現異常進行返點。並沒有采用最終一致性來解決,因為最終一致性會出現數據被多扣,比如我得資源第一次扣除失敗然后放入隊列,在隊列進行扣除,但是恰恰新的請求過來同樣會獲取這條資源進行扣除,這樣一來就出現數據安全問題,目前我們采用的是三次重試,所以嚴格來講我們服務的一致性並沒有完全解決.關於一致性問題在以后的文章會繼續和大家聊。
6、服務的接入方
當業務方需要調用我們服務的時候自己一定慎重搞不好就掉坑里了,下面我說2個例子
第一個例子:
有一個業務方告訴我們他們要做活動需要每天添加資源,然后我就問了他每天的量級,經過評估后我發現量級沒太大問題,但是當我要審批的時候發現有新的問題,因為活動期間添加資源會出現大量零碎的訂單,如果過期時間過久那么可能數據庫每種資源達到幾百或者上千條,那么帶來的后果就是我們在查詢或者消耗時候會非常慢,而且以前因為查詢過多訂單導致服務超時問題,所以后續我們就這問題溝通,他們要過期時間設置1個月,然后我在做壓力測試確認無誤后才審批。
第二個例子:
我們提供定制化接口給業務方,這個給我們帶來了特別多痛,我們提供一個消耗接口,但是這個業務方需要消耗詳情,我們同樣提供了,這樣就導致他的消耗和別人不一樣,做技術升級的時候還需要單獨考慮這個接口,因為我的忽略導致幾次沒有考慮清楚都出現線上事故,后來我們去問他們要消耗詳情干嘛,他說他業務其實不用需要把這些消耗詳情存入他們的庫,我們聽后覺得特別無語,這樣的數據直接走離線數據即可,這也是早期沒聊明白需求導致一個毒瘤接口。
綜上總結:
1、業務方的需求我們必須聊清楚,明白我們服務應該提供的能力,對於不合理的需求直接拒絕
2、拒絕提供定制化的接口,如果某個業務方需求影響整體同樣我們必須拒絕或者找到其他兼容方案
3、對於每次業務方的接入必須郵件寫的非常明白包括背景、量級、QPS等
二:基礎服務過程中case匯總
1、數據遷移和數據轉換引發事故
關於數據遷移和轉換引起的事故請參考這篇文章https://www.cnblogs.com/LipeiNet/p/7809567.html
2、線上操作數據庫表結構引發的事故
關於這個事故請查看這篇文章https://www.cnblogs.com/LipeiNet/p/9182454.html
3、重構引發的線上事故
重構我們一共出過兩次事故,一個代碼整合,一個是刪除已廢棄邏輯.
代碼整合帶來的問題:
背景:
剛進入公司的時候我看到項目中有大量代碼重復,就想着重構,把相同的代碼用函數的方式合並,最后經過審批后也這么干了,但是上線以后一線反饋自己添加的資源無法查詢,最后排查日志中發現在重構代碼中有一處代碼的資源訂單的開始生效日期是當前日期並不采用用戶傳入的日期。
刪除已廢棄邏輯帶有的問題:
背景:
由於我們服務的和外部服務解耦其中的一個數據庫字段被廢棄,所以我們在代碼中去除掉這個字段的邏輯信息,上線后業務方反饋資源消耗異常,排查后我們發現返回實體中去除字段應該是0但是我們去掉邏輯后變成null,這樣一來別的業務方判斷空指針異常
綜上總結:
1、項目重構前一定另外拉分支,修改項目的版本號,便於回滾
2、嚴格測試,用測試賬號測試線上接口保留返回值,然后同樣的操作用於被重構后的接口,比對連個返回值是否相同,如果不同,那么就需要弄明白是否對線上業務造成影響
4、慢查詢引發的線上事故
一次我上線了一個查詢接口,但是不久被反饋線上服務部分出現超時,經過排查后發現sql的字段沒有加索引。
總結:當我們上線新的服務時候必須經過沙箱嚴格測試包括性能指標。
5、內外部實體未解耦引發的線上事故
背景:
我們在新開發一個功能的時候在數據庫中增加了一個int類型的字段,而這個字段主要用於服務內部,並不會對外進行提供,但是在更新庫存的時候並沒有分離內外部實體,然后在更新的時候講這個字段值覆蓋成為0導致了線上的bug。
修復過程:
內外部實體徹底解耦,內部接口不采用任何外部接口進行傳輸
綜上總結:
設計階段:
設計階段明確內外部實體的職責,內外部實體不必保持一致,外部實體主要傳輸業務方需要的字段(具有業務含義)而內部實體需要和數據庫字段保持一致。
升級階段:
升級內部邏輯:
有時候因為某些原因我們需要進對內部服務升級,這個時候我們一定要注意是否在內部服務中采用了外部實體進行傳輸,如果有進行外部實體進行傳輸的就一定注意更改內部實體影響業務方,如果時間准許應該進行內外部實體進行解耦
某個業務方需求:
如果某一個業務方有特殊需求涉及屬性的更改,這個時候考慮這個需求是否合理,是否應該我們服務提供的能力,如果可以通過配置化規則來解決,如果解決不了然后升級服務版本,對返回值進行升級,但是內部一定要注意不要影響其他業務方調用
所有業務方需求:
更新pom版本升級服務,統計所有的調用方,讓調用方進行升級
6、持久層捕獲異常引發的線上事故
背景:
持久層捕獲異常並沒有拋出
綜上總結:
持久層中盡量不進行異常捕獲,如果需要捕獲接口中必須要有返回值
7、枚舉使用不當引發的線上事故
詳情請查看https://www.cnblogs.com/LipeiNet/p/9487900.html
8、限流把控不嚴引起業務方資源無法添加
背景:某個業務方用MQ異步調用我們的服務,但是他們是刷數據所以短時間內流量特別大,因為我們服務的限流這樣一來就出現大量的數據添加異常。
問題分析:主要出現這樣的問題原因有下面2個
1、業務方定時處理數據的時候沒能周知我們
2、我們並沒有把流量進行削峰
綜上總結:
1、在服務文檔上特別加上業務方定時跑數據時候提前郵件周知最大QPS,數據的量級以及操作時間
1、采用流量削峰,避免短時間的流量過來造成業務異常,主要做法將短時間多出的流量存儲在MQ端,如果出現一定量級采用報警