自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

開發(fā) | 再見了,公司的“爛系統(tǒng)”

開發(fā) 前端 開發(fā)工具
本篇文章作者給大家分享一個復(fù)雜系統(tǒng)的拆分改造實踐!

[[391481]]

圖片來自 Pexels 

為什么要拆分?

先看一段對話:

 

從上面對話可以看出拆分的理由:

  • 應(yīng)用間耦合嚴(yán)重。系統(tǒng)內(nèi)各個應(yīng)用之間不通,同樣一個功能在各個應(yīng)用中都有實現(xiàn),后果就是改一處功能,需要同時改系統(tǒng)中的所有應(yīng)用。
  • 這種情況多存在于歷史較長的系統(tǒng),因各種原因,系統(tǒng)內(nèi)的各個應(yīng)用都形成了自己的業(yè)務(wù)小閉環(huán)。
  • 業(yè)務(wù)擴展性差。數(shù)據(jù)模型從設(shè)計之初就只支持某一類的業(yè)務(wù),來了新類型的業(yè)務(wù)后又得重新寫代碼實現(xiàn),結(jié)果就是項目延期,大大影響業(yè)務(wù)的接入速度。
  • 代碼老舊,難以維護。各種隨意的 if else、寫死邏輯散落在應(yīng)用的各個角落,處處是坑,開發(fā)維護起來戰(zhàn)戰(zhàn)兢兢。
  • 系統(tǒng)擴展性差。系統(tǒng)支撐現(xiàn)有業(yè)務(wù)已是顫顫巍巍,不論是應(yīng)用還是DB都已經(jīng)無法承受業(yè)務(wù)快速發(fā)展帶來的壓力。
  • 新坑越挖越多,惡性循環(huán)。不改變的話,最終的結(jié)果就是把系統(tǒng)做死了。

[[391482]] 

拆前準(zhǔn)備什么?

多維度把握業(yè)務(wù)復(fù)雜度

一個老生常談的問題,系統(tǒng)與業(yè)務(wù)的關(guān)系?

 

我們最期望的理想情況是第一種關(guān)系(車輛與人),業(yè)務(wù)覺得不合適,可以馬上換一輛新的。

但現(xiàn)實的情況是更像心臟起搏器與人之間的關(guān)系,不是說換就能換。一個系統(tǒng)接的業(yè)務(wù)越多,耦合越緊密。

如果在沒有真正把握住業(yè)務(wù)復(fù)雜度之前貿(mào)然行動,最終的結(jié)局就是把心臟帶飛。

如何把握住業(yè)務(wù)復(fù)雜度?需要多維度的思考、實踐。

一個是技術(shù)層面,通過與 PD 以及開發(fā)的討論,熟悉現(xiàn)有各個應(yīng)用的領(lǐng)域模型,以及優(yōu)缺點,這種討論只能讓人有個大概,更多的細(xì)節(jié)如代碼、架構(gòu)等需要通過做需求、改造、優(yōu)化這些實踐來掌握。

各個應(yīng)用熟悉之后,需要從系統(tǒng)層面來構(gòu)思,我們想打造平臺型的產(chǎn)品,那么最重要也是最難的一點就是功能集中管控,打破各個應(yīng)用的業(yè)務(wù)小閉環(huán),統(tǒng)一收攏。

這個決心更多的是開發(fā)、產(chǎn)品、業(yè)務(wù)方、各個團隊之間達(dá)成的共識,“按照業(yè)務(wù)或者客戶需求組織資源”。

此外也要與業(yè)務(wù)方保持功能溝通、計劃溝通,確保應(yīng)用拆分出來后符合使用需求、擴展需求,獲取他們的支持。

定義邊界,原則:高內(nèi)聚,低耦合,單一職責(zé)

業(yè)務(wù)復(fù)雜度把握后,需要開始定義各個應(yīng)用的服務(wù)邊界。怎么才算是好的邊界?像葫蘆娃兄弟一樣的應(yīng)用就是好的!

舉個例子,葫蘆娃兄弟(應(yīng)用)間的技能是相互獨立的,遵循單一職責(zé)原則,比如水娃只能噴水,火娃只會噴火,隱形娃不會噴水噴火但能隱身。

更為關(guān)鍵的是,葫蘆娃兄弟最終可以合體為金剛葫蘆娃,即這些應(yīng)用雖然功能彼此獨立,但又相互打通,最后合體在一起就成了我們的平臺。

[[391484]] 

這里很多人會有疑惑,拆分粒度怎么控制?很難有一個明確的結(jié)論,只能說是結(jié)合業(yè)務(wù)場景、目標(biāo)、進(jìn)度的一個折中。

但總體的原則是先從一個大的服務(wù)邊界開始,不要太細(xì),因為隨著架構(gòu)、業(yè)務(wù)的演進(jìn),應(yīng)用自然而然會再次拆分,讓正確的事情自然發(fā)生才最合理。

確定拆分后的應(yīng)用目標(biāo)

一旦系統(tǒng)的宏觀應(yīng)用拆分圖出來后,就要落實到某一具體的應(yīng)用拆分上了。

首先要確定的就是某一應(yīng)用拆分后的目標(biāo)。拆分優(yōu)化是沒有底的,可能越做越深,越做越?jīng)]結(jié)果,繼而又影響自己和團隊的士氣。

比如說可以定這期的目標(biāo)就是將 DB、應(yīng)用分拆出去,數(shù)據(jù)模型的重新設(shè)計可以在第二期。

確定當(dāng)前要拆分應(yīng)用的架構(gòu)狀態(tài)

例如,代碼情況、依賴狀況,并推演可能的各種異常。

動手前的思考成本遠(yuǎn)遠(yuǎn)低于動手后遇到問題的解決成本。應(yīng)用拆分最怕的是中途說“他*的,這塊不能動,原來當(dāng)時這樣設(shè)計是有原因的,得想別的路子!”

這時的壓力可想而知,整個節(jié)奏不符合預(yù)期后,很可能會接二連三遇到同樣的問題,這時不僅同事們士氣下降,自己也會喪失信心,繼而可能導(dǎo)致拆分失敗。

給自己留個錦囊,“有備無患”

錦囊就四個字“有備無患”,可以貼在桌面或者手機上。

在以后具體實施過程中,多思考下“方案是否有多種可以選擇?復(fù)雜問題能否拆解?實際操作時是否有預(yù)案?”,應(yīng)用拆分在具體實踐過程中比拼得就是細(xì)致二字,多一份方案,多一份預(yù)案,不僅能提升成功概率,更給自己信心。

放松心情,緩解壓力

收拾下心情,開干!

改造實踐

DB 拆分實踐

DB 拆分在整個應(yīng)用拆分環(huán)節(jié)里最復(fù)雜,分為垂直拆分和水平拆分兩種場景,我們都遇到了。垂直拆分是將庫里的各個表拆分到合適的數(shù)據(jù)庫中。

比如一個庫中既有消息表,又有人員組織結(jié)構(gòu)表,那么將這兩個表拆分到獨立的數(shù)據(jù)庫中更合適。

水平拆分:以消息表為例好了,單表突破了千萬行記錄,查詢效率較低,這時候就要將其分庫分表。

 

①主鍵 id 接入全局 id 發(fā)生器

DB 拆分的第一件事情就是使用全局id發(fā)生器來生成各個表的主鍵 id。為什么?

舉個例子,假如我們有一張表,兩個字段 id 和 token,id 是自增主鍵生成,要以 token 維度來分庫分表,這時繼續(xù)使用自增主鍵會出現(xiàn)問題。

 

正向遷移擴容中,通過自增的主鍵,到了新的分庫分表里一定是唯一的,但是,我們要考慮遷移失敗的場景,如下圖所示,新的表里假設(shè)已經(jīng)插入了一條新的記錄,主鍵 id 也是 2。

 

這個時候假設(shè)開始回滾,需要將兩張表的數(shù)據(jù)合并成一張表(逆向回流),就會產(chǎn)生主鍵沖突!

因此在遷移之前,先要用全局唯一 id 發(fā)生器生成的 id 來替代主鍵自增 id。

這里有幾種全局唯一 id 生成方法可以選擇:

  • Snowflake:非全局遞增。
  • MySQL 新建一張表用來專門生成全局唯一 id(利用 auto_increment 功能)(全局遞增)。
  • 有人說只有一張表怎么保證高可用?那兩張表好了(在兩個不同 db),一張表產(chǎn)生奇數(shù),一張表產(chǎn)生偶數(shù)。或者是 n 張表,每張表的負(fù)責(zé)的步長區(qū)間不同(非全局遞增)
  • ……

我們使用的是阿里巴巴內(nèi)部的 tddl-sequence(MySQL+內(nèi)存),保證全局唯一但非遞增。

在使用上遇到一些坑:

  • 對按主鍵 id 排序的 SQL 要提前改造。因為 id 已經(jīng)不保證遞增,可能會出現(xiàn)亂序場景,這時候可以改造為按 gmt_create 排序。
  • 報主鍵沖突問題。這里往往是代碼改造不徹底或者改錯造成的,比如忘記給某一 insert sql 的 id 添加 #{},導(dǎo)致繼續(xù)使用自增,從而造成沖突。

 

②建新表&遷移數(shù)據(jù)&binlog 同步

新表字符集建議是 utf8mb4,支持表情符。新表建好后索引不要漏掉,否則可能會導(dǎo)致慢 SQL!

從經(jīng)驗來看索引被漏掉時有發(fā)生,建議事先列計劃的時候?qū)⑦@些要點記下,后面逐條檢查。

使用全量同步工具或者自己寫 job 來進(jìn)行全量遷移;全量數(shù)據(jù)遷移務(wù)必要在業(yè)務(wù)低峰期時操作,并根據(jù)系統(tǒng)情況調(diào)整并發(fā)數(shù)。

增量同步:全量遷移完成后可使用 binlog 增量同步工具來追數(shù)據(jù),比如阿里內(nèi)部使用精衛(wèi),其他企業(yè)可能有自己的增量系統(tǒng),或者使用阿里開源的。

  1. cannal/otter:https://github.com/alibaba/canal?spm=5176.100239.blogcont11356.10.5eNr98 
  2. https://github.com/alibaba/otter/wiki/QuickStart?spm=5176.100239.blogcont11356.21.UYMQ17 

增量同步起始獲取的 binlog 位點必須在全量遷移之前,否則會丟數(shù)據(jù),比如我中午 12 點整開始全量同步,13 點整全量遷移完畢,那么增量同步的 binlog 的位點一定要選在 12 點之前。

位點在前會不會導(dǎo)致重復(fù)記錄?不會!線上的 MySQL binlog 是 row 模式,如一個 delete 語句刪除了 100 條記錄,binlog 記錄的不是一條 delete 的邏輯 SQL,而是會有 100 條 binlog 記錄。

insert 語句插入一條記錄,如果主鍵沖突,插入不進(jìn)去。

③聯(lián)表查詢 SQL 改造

現(xiàn)在主鍵已經(jīng)接入全局唯一 id,新的庫表、索引已經(jīng)建立,且數(shù)據(jù)也在實時追平,現(xiàn)在可以開始切庫了嗎?no!

考慮以下非常簡單的聯(lián)表查詢 SQL,如果將 B 表拆分到另一個庫里的話,這個 SQL 怎么辦?畢竟跨庫聯(lián)表查詢是不支持的!

 

因此,在切庫之前,需要將系統(tǒng)中上百個聯(lián)表查詢的 SQL 改造完畢。如何改造呢?

業(yè)務(wù)避免:業(yè)務(wù)上松耦合后技術(shù)才能松耦合,繼而避免聯(lián)表 SQL。但短期內(nèi)不現(xiàn)實,需要時間沉淀。

全局表:每個應(yīng)用的庫里都冗余一份表,缺點:等于沒有拆分,而且很多場景不現(xiàn)實,表結(jié)構(gòu)變更麻煩。

冗余字段:就像訂單表一樣,冗余商品 id 字段,但是我們需要冗余的字段太多,而且要考慮字段變更后數(shù)據(jù)更新問題。

內(nèi)存拼接:通過 RPC 調(diào)用來獲取另一張表的數(shù)據(jù),然后再內(nèi)存拼接。

  • 適合 job 類的 SQL,或改造后 RPC 查詢量較少的 SQL。
  • 不適合大數(shù)據(jù)量的實時查詢 SQL。

假設(shè) 10000 個 ID,分頁 RPC 查詢,每次查 100 個,需要 5ms,共需要 500ms,RT 太高。

本地緩存另一張表的數(shù)據(jù),適合數(shù)據(jù)變化不大、數(shù)據(jù)量查詢大、接口性能穩(wěn)定性要求高的 SQL。

 

④切庫方案設(shè)計與實現(xiàn)(兩種方案)

以上步驟準(zhǔn)備完成后,就開始進(jìn)入真正的切庫環(huán)節(jié),這里提供兩種方案,我們在不同的場景下都有使用。

DB 停寫方案,如下圖:

 

優(yōu)點:快,成本低。

缺點:

  • 如果要回滾得聯(lián)系 DBA 執(zhí)行線上停寫操作,風(fēng)險高,因為有可能在業(yè)務(wù)高峰期回滾。
  • 只有一處地方校驗,出問題的概率高,回滾的概率高。

舉個例子,如果面對的是比較復(fù)雜的業(yè)務(wù)遷移,那么很可能發(fā)生如下情況導(dǎo)致回滾:

  • SQL 聯(lián)表查詢改造不完全。
  • SQL 聯(lián)表查詢改錯&性能問題。
  • 索引漏加導(dǎo)致性能問題。

字符集問題:此外,binlog 逆向回流很可能發(fā)生字符集問題(utf8mb4 到 gbk),導(dǎo)致回流失敗。

這些 binlog 同步工具為了保證強最終一致性,一旦某條記錄回流失敗,就卡住不同步,繼而導(dǎo)致新老表的數(shù)據(jù)不同步,繼而無法回滾!

雙寫方案,如下圖:

 

第 2 步“打開雙寫開關(guān),先寫老表 A 再寫新表 B”,這時候確保寫 B 表時 try catch 住,異常要用很明確的標(biāo)識打出來,方便排查問題。

第 2 步雙寫持續(xù)短暫時間后(比如半分鐘后),可以關(guān)閉 binlog 同步任務(wù)。

優(yōu)點:

  • 將復(fù)雜任務(wù)分解為一系列可測小任務(wù),步步為贏。
  • 線上不停服,回滾容易。
  • 字符集問題影響小。

缺點:

  • 流程步驟多,周期長。
  • 雙寫造成 RT 增加。

⑤開關(guān)要寫好

不管什么切庫方案,開關(guān)少不了,這里開關(guān)的初始值一定要設(shè)置為 null!

如果隨便設(shè)置一個默認(rèn)值,比如“讀老表 A”,假設(shè)我們已經(jīng)進(jìn)行到讀新表 B 的環(huán)節(jié)了。

這時重啟了應(yīng)用,在應(yīng)用啟動的一瞬間,最新的“讀新表 B”的開關(guān)推送等可能沒有推送過來,這個時候就可能使用默認(rèn)值,繼而造成臟數(shù)據(jù)!

拆分后一致性怎么保證?

以前很多表都在一個數(shù)據(jù)庫內(nèi),使用事務(wù)非常方便,現(xiàn)在拆分出去了,如何保證一致性?

如下:

  • 分布式事務(wù),性能較差,幾乎不考慮。
  • 消息機制補償(如何用消息系統(tǒng)避免分布式事務(wù)?)
  • 定時任務(wù)補償用得較多,實現(xiàn)最終一致,分為加數(shù)據(jù)補償,刪數(shù)據(jù)補償兩種。

應(yīng)用拆分后穩(wěn)定性怎么保證?

一句話:懷疑第三方,防備使用方,做好自己!

 

①懷疑第三方

防御式編程,制定好各種降級策略;比如緩存主備、推拉結(jié)合、本地緩存……

遵循快速失敗原則,一定要設(shè)置超時時間,并異常捕獲。

強依賴轉(zhuǎn)弱依賴,旁支邏輯異步化:我們對某一個核心應(yīng)用的旁支邏輯異步化后,響應(yīng)時間幾乎縮短了 1/3,且后面中間件、其它應(yīng)用等都出現(xiàn)過抖動情況,而核心鏈路一切正常。

適當(dāng)保護第三方,慎重選擇重試機制。

②防備使用方

設(shè)計一個好的接口,避免誤用:

  • 遵循接口最少暴露原則:很多同學(xué)搭建完新應(yīng)用后會隨手暴露很多接口,而這些接口由于沒人使用而缺乏維護,很容易給以后挖坑。聽到過不只一次對話,“你怎么用我這個接口啊,當(dāng)時隨便寫的,性能很差的”。
  • 不要讓使用方做接口可以做的事情:比如你只暴露一個 getMsgById 接口,別人如果想批量調(diào)用的話,可能就直接 for 循環(huán) RPC 調(diào)用,如果提供 getMsgListByIdList 接口就不會出現(xiàn)這種情況了。
  • 避免長時間執(zhí)行的接口:特別是一些老系統(tǒng),一個接口背后對應(yīng)的可能是 for 循環(huán) select DB 的場景。

容量限制:

  • 按應(yīng)用優(yōu)先級進(jìn)行流控:不僅有總流量限流,還要區(qū)分應(yīng)用,比如核心應(yīng)用的配額肯定比非核心應(yīng)用配額高。
  • 業(yè)務(wù)容量控制:有些時候不僅僅是系統(tǒng)層面的限制,業(yè)務(wù)層面也需要限制。舉個例子,對 Saas 化的一些系統(tǒng)來說,“你這個租戶最多 1w 人使用”。

③做好自己

a)單一職責(zé)

b)及時清理歷史坑:例:例如我們改造時候發(fā)現(xiàn)一年前留下的坑,去掉后整個集群 CPU 使用率下降 1/3。

c)運維 SOP 化:說實話,線上出現(xiàn)問題,如果沒有預(yù)案,再怎么處理都會超時。

曾經(jīng)遇到過一次 DB 故障導(dǎo)致臟數(shù)據(jù)問題,最終只能硬著頭皮寫代碼來清理臟數(shù)據(jù),但是時間很長,只能眼睜睜看著故障不斷升級。

經(jīng)歷過這個事情后,我們馬上設(shè)想出現(xiàn)臟數(shù)據(jù)的各種場景,然后上線了三個清理臟數(shù)據(jù)的 job,以防其它不可預(yù)知的產(chǎn)生臟數(shù)據(jù)的故障場景,以后只要遇到出現(xiàn)臟數(shù)據(jù)的故障,直接觸發(fā)這三個清理 job,先恢復(fù)再排查。

d)資源使用可預(yù)測:應(yīng)用的 CPU、內(nèi)存、網(wǎng)絡(luò)、磁盤心中有數(shù)。

  • 正則匹配耗 CPU
  • 耗性能的 job 優(yōu)化、降級、下線(循環(huán)調(diào)用 RPC 或SQL)
  • 慢 SQL 優(yōu)化、降級、限流
  • Tair/Redis、DB 調(diào)用量要可預(yù)測
  • 例:Tair、DB

舉個例子:某一個接口類似于秒殺功能,QPS 非常高(如下圖所示),請求先到 tair,如果找不到會回源到 DB,當(dāng)請求突增時候,甚至?xí)|發(fā) Tair/Redis 這層緩存的限流。

此外由于緩存在一開始是沒數(shù)據(jù)的,請求會穿透到 DB,從而擊垮 DB。

這里的核心問題就是 Tair/Redis 這層資源的使用不可預(yù)測,因為依賴于接口的 QPS,怎么讓請求變得可預(yù)測呢? 

如果我們再增加一層本地緩存(Guava,比如超時時間設(shè)置為 1 秒),保證單機對一個 key 只有一個請求回源,那樣對 Tair/Redis 這層資源的使用就可以預(yù)知了。

假設(shè)有 500 臺 client,對一個 key 來說,一瞬間最多 500 個請求穿透到 Tair/Redis,以此類推到 DB。

 

再舉個例子:比如 client 有 500 臺,對某 key 一瞬間最多有 500 個請求穿透到 DB,如果 key 有 10 個,那么請求最多可能有 5000 個到 DB,恰好這些 SQL 的 RT 有些高,怎么保護 DB 的資源?

可以通過一個定時程序不斷將數(shù)據(jù)從 DB 刷到緩存。這里就將不可控的 5000 個 QPS 的 DB 訪問變?yōu)榭煽氐膫€位數(shù) QPS 的 DB 訪問。

 

總結(jié)

①做好準(zhǔn)備面對壓力!

②復(fù)雜問題要拆解為多步驟,每一步可測試可回滾!

這是應(yīng)用拆分過程中的最有價值的實踐經(jīng)驗!

③墨菲定律:你所擔(dān)心的事情一定會發(fā)生,而且會很快發(fā)生,所以準(zhǔn)備好你的 SOP(標(biāo)準(zhǔn)化解決方案)!

某個周五和組里同事吃飯時討論到某一個功能存在風(fēng)險,約定在下周解決,結(jié)果周一剛上班該功能就出現(xiàn)故障了。

以前講小概率不可能發(fā)生,但是概率再小也是有值的,比如 P=0.00001%,互聯(lián)網(wǎng)環(huán)境下,請求量足夠大,小概率事件就真發(fā)生了。

④借假修真

這個詞看上去有點玄乎,顧名思義,就是在借著一些事情,來提升另外一種能力,前者稱為假,后者稱為真。

在任何一個單位,對核心系統(tǒng)進(jìn)行大規(guī)模拆分改造的機會很少,因此一旦你承擔(dān)起責(zé)任,就毫不猶豫地全力以赴吧!

不要被過程的曲折所嚇倒,心智的磨礪,才是本真。

作者:zhanlijun

編輯:陶家龍

出處:cnblogs.com/LBSer/p/6195309.html

 

責(zé)任編輯:武曉燕 來源: 博客園
相關(guān)推薦

2021-07-21 07:11:21

TeamviewerWindowsMac

2019-05-09 10:48:46

無人駕駛人工智能配送機器人

2020-11-18 13:24:02

廣告彈窗網(wǎng)信互聯(lián)網(wǎng)

2020-12-30 07:49:32

KubernetesJava Spring Clo

2020-12-29 14:29:27

Windows 10Windows微軟

2018-06-04 14:41:36

互聯(lián)網(wǎng)數(shù)據(jù)移動

2013-08-13 14:22:33

開發(fā)者微軟Windows Pho

2013-12-20 09:59:34

小米閃購模式雷軍

2022-06-15 17:55:43

IE瀏覽器Windows微軟

2013-03-19 11:28:01

Windows 7 R

2020-04-06 16:52:01

else關(guān)鍵字編程語言

2023-02-26 00:17:45

2015-10-10 11:08:36

控制面板Windows 10微軟

2021-09-13 09:35:48

AI 數(shù)據(jù)人工智能

2022-04-22 09:02:24

XshelliTerm2工具

2021-05-22 06:57:53

IE瀏覽器微軟

2021-03-10 13:57:55

Edge微軟瀏覽器

2017-12-15 21:09:20

Chrome開發(fā)者瀏覽器

2016-12-27 15:13:12

系統(tǒng)

2021-05-19 14:33:46

微軟Windows 10Windows
點贊
收藏

51CTO技術(shù)棧公眾號