最全的微服務知識科普

微信公衆號:內核小王子 關注可瞭解更多關於數據庫,JVM內核相關的知識; 若是你有任何疑問也能夠加我pigpdong[^1]java

微服務

好處:實現跨團隊的解藕,實現更高的併發(目前單機只能實現c10k)不用在拷貝代碼,基礎服務能夠公用,更好的支持服務治理,可以更好的兼容雲計算平臺。mysql

RPC

rpc:向調用本地方法同樣調用遠程函數linux

客戶端:通常利用動態代理生成一個接口的實現類,在這個實現類裏經過網絡把接口名稱,參數,方法序列化後傳出去,而後控制同步調用仍是異步調用,異步調用須要設置一個回調函數,客戶端還須要維護負載均衡,超時處理,鏈接池管理等,鏈接池維護了和多個server的鏈接,靠此作負載均衡,當某個服務器宕機後去除該鏈接。請求上下文維護了請求ID和回調函數,超時的請求當回覆報文到達後因爲找不到請求上下文就會丟棄。git

服務端:維護鏈接,網絡收到請求後反序列化得到方法名稱,接口名稱,參數名稱後經過反射進行調用,而後將結果在傳回客戶端。ajax

序列化的方式:一種是隻序列化字段的值,反序列化的時候從新構建對象在把值設置進去,另一種方式直接將整個對象的結構序列化成二進制,前者節省空間,後者反序列化速度快,目前的序列化框架也是在反序列化時間和佔用空間之間權衡。有點相似哈夫曼編碼,或者數據庫怎麼存儲一行一行的數據。redis

註冊中心

通常有三種模式,f5作集中式代理,客戶端嵌入式代理例如dubbo,還有一種是綜合上面兩種,多個客戶端共用一個代理,代理做爲一個獨立進程部署在和客戶端服務器同一臺物理機上,servicemesh就是這種模式。算法

zookeeper 不適合作註冊中心的緣由:zookeeper爲了一致性犧牲了可用性,可是註冊中心實際上對一致性要求並不高,不一致產生的後果也就是某個服務下線了可是客戶端並不知道,可是客戶端經過重試其餘節點就能夠了,另外當發生網絡分區的時候,若是超過半數節點掛了,zookeeper就不可用,可是實際上他應該仍然能夠對他所在機房的節點提供註冊服務的,例如三個機房分別放了2臺2臺1臺,若是各個機房之間網絡斷了,可是機房內部上通的,可是這樣註冊中心不可用即便內部節點也不能服務了。zookeeper並非嚴格的一致性,他支持讀寫分離,其餘節點收到寫請求會轉發給master節點,而其餘節點能夠支持讀請求,當數據尚未從主節點複製過來的時候讀到的多是過時的數據。sql

配置中心

配置中心的需求:保證高可用,實時通知,灰度發佈,權限控制,一鍵回滾,環境隔離(開發 測試 生產)目前的開源實現:nacos disconf apollo。docker

disconf:scan模塊掃描註解和監聽器,store模塊將遠程獲取到的配置存儲到本地,本地一個job檢測配置是否有變化,有變化就通知監聽器,fetch模塊從遠程經過http獲取配置,watch模塊監聽zookeeper上節點的變化,有變化就會調用fetch進行獲取.shell

apollo:四個模塊:portal 做爲一個管理後臺,提供管理員操做的入口。 有獨立的數據庫。 adminservice 提供配置的修改和發佈服務的底層服務,和 configservice 公用一個數據庫configdb,每次修改配置就會往數據庫裏插入一條記錄releasemessage,configservice 用一個定時任務去掃描數據庫是否有新的releasemessage,有的話就通知客戶端,而客戶端採用定時輪詢的方式去查詢 configservice 是否有新消息,這裏採用 deferredresult 異步執行。eruka爲adminservice和configservice提供了註冊發現的服務。客戶端獲取到配置文件後也會寫入磁盤。

任務調度

  • 1.執行器也就是應用自己,任務單元也就是具體執行任務的線程 可以主動註冊調度器中,並在啓動的時候進行更新,例如刪除已經清空的任務
  • 2.調度中心支持集羣部署避免單點,能夠選舉一個主節點其餘爲slave
  • 3.支持負載均衡算法爲每一個任務隨機選擇執行器,可以支持失敗重試,將執行很慢或者失去鏈接的執行器移除
  • 4.支持控制任務併發,例如是否容許一個任務沒執行完這個任務又被調度
  • 5.支持任務依賴,例如一個任務沒執行完另外一個任務不能執行,或者自動執行另一個任務
  • 6.支持任務分片,將一個任務根據參數分片到不一樣的執行器上一塊兒執行。
  • 7.能夠取消一個任務
  • 8.已經支持glue模式,能夠不用發佈就執行一個任務單元

分佈式鎖

  • 1)redis setnx裏面已經有參數能夠支持分佈式鎖,可是最好能把鎖的擁有方存到value裏,釋放的時候作比較,否則可能釋放錯鎖,也就是會出現A釋放了B的鎖。
  • 2)zk採用建立臨時節點,其餘建立失敗的線程監聽鎖的狀態。
SET resource_name my_random_value NX PX 30000
複製代碼

統一監控:

  • 1)收集日誌並分析,日誌也能夠和rpc鏈路進行關聯,也能夠對日誌進行降噪或者壓縮存儲
  • 2)提供api的方式以及攔截器模式,能夠基於javaagent作到無嵌入
  • 3)實現opentracing鏈路追蹤
  • 4)能夠基於disruptor ringbuffer的生產消費者模式
  • 5)海量數據的存儲,elasticsearch
  • 6)報表生成,監控指標設置
  • 7)各個節點進行收集,消息上傳到服務端統一處理
  • 8)監控指標:rpc鏈路,數據庫,cpu指標等,http狀態,各類中間件
  • 9)日誌收集能夠經過直接在日誌框架上加攔截器,或者用flink+kafka收集

緩存

先清空緩存仍是先更新數據庫?

若是是更新緩存而不是刪除緩存:則無論哪一種方式都會形成緩存和數據庫不一致,若是是刪除緩存:則先刪除緩存在更新數據庫,若是更新數據庫失敗了也沒有太大影響,緩存被清了從新加載便可。可是也要考慮到緩存穿透的問題,若是這個時候大流量進來是否會壓垮數據庫?

以上是考慮到分佈式事務中一個成功一個失敗的狀況,可是這種機率畢竟是小的,能夠用在併發量不是很高可是對數據一致性要求很高的狀況,若是併發很高建議先更新數據庫後清空緩存。

若是先清空緩存,後更新數據庫,在尚未更新到數據庫的狀況下另一個事務去查詢,發現緩存沒命中就去數據庫取,而後又寫入緩存,以後上一個事務的數據庫更新,這樣就致使了緩存和數據庫不一致,若是先更新數據庫在清空緩存,更新完數據庫後緩存還沒更新,這個時候來讀取緩存是舊的值,也出現不一致,可是最終清空緩存後會一致。不過這種方式也會產生永久不一致,可是機率很小,例如一個讀請求,沒有命中緩存,這個時候可能另外一個線程恰好清空緩存,而後他就去數據裏面取,可是又有一個線程在他讀完數據庫後將數據庫改成另一個值,這樣那個讀請求寫入到緩存的數據就是髒數據了。

redis採用單線程模型,對只有io操做來講性能很好,可是redis也提供了計算功能,如排序聚合,cpu在計算的時候全部的io操做都是阻塞的。

memecached先申請一塊內存將其分割成大小不等的若干內存塊以存儲不一樣大小的鍵值對。這種方式效率高可是可能產生空間浪費。而redis只是單純的包裝了下malloc和free.

redis提供了兩種方式持久化數據,一種方式是把某一時刻全部的數據都寫入磁盤,另一種方式經過增量日誌的形式

memecache提供了cas來保證數據一致性,redis提供了事務,將一連串指令一塊兒執行或者回滾

memechache只能經過一致性哈希來進行集羣,而redis提供了集羣功能,客戶端作路由選擇那個master節點,master節點能夠有多個slave節點作爲備用和讀。

redis 中的字符串沒有采用c語言裏的結構,額外加上了空閒內存和已佔用內存,這樣讀取的時候因爲已經知道char數組大小,因此能夠直接取出,避免遍歷操做,當字符串變大或縮小的時候能夠避免從新分配內存,能夠用到空閒空間,也就是redis會預分配一個空間。 另外redis裏的哈希,用了兩個table存儲,主要爲了擴容,也就是rehash,這樣當擴容的時候雙方就能夠互換,redis採用漸近式擴容,也就是每一次操做都執行兩個哈希表,當新增的時候只在新表。set數據結構能夠用來存儲總的點贊次數,而zset是一個有序鏈表,爲了加快查詢用跳錶進行存儲。

如何防止緩存雪崩:緩存要高可用,能夠設置多級緩存,如何預防緩存穿透:設置不一樣的失效時間

消息隊列

如何保證消息的順序:嚴格的一致,只能一個生產者,發送到一個broker上,而後只有一個隊列一個消費者,可是這種模式不少弊端,一個地方異常將阻塞整個流程,rocketmq將這個問題交給應用層處理,也就是發送端本身選擇發送到哪一個隊列,例如同一個訂單的消息發送到同一個隊列。可是算法在其中一個隊列異常的時候也會有問題。

如何保證消息不重複:只要網絡上傳輸確定會有這種問題,因此最好應用層可以支持冪等,或者用一張去重表,存儲每個處理過的消息id

發送消息流程

  • 1.先獲取topic對應的路由信息(路由信息會從namesrv返回,在客戶端緩存,返回這個topic對應哪幾個broker以及每一個broker上有多少個隊列)
  • 2.若是沒有獲取到,可能沒有topic,須要自動建立,自動建立是客戶端發信息個namesrv,namesrv在去請求broker,broker建立好後返回
  • 3.根據路由策略獲取一個queue(從全部的queue中根據對應的路由策略獲取queue,而後在判斷這個queue對應的broker是否健康,健康就返回) 這個地方就能夠作到broker的高可用
  • 4.因此 咱們發現消息是發給哪一個broker的哪一個queue是在客戶端發送的時候決定的,不是在生成commitlog以後在派發的,這樣咱們就能夠指定都某一個固定queue了
  • 5.消息發送的時候會構建發送請求,裏面包含了消息體和隊列信息,topic信息等,消息體裏面會增長一個消息ID,
  • 6.若是消息重試屢次後仍是失敗就會進入死信隊列,一個固定的topic

消息存儲

每一個commitlog大小爲1G,第二個文件的起始偏移量就是1G的byte大小,當根據一個偏移量獲取對應哪一個文件的時候,根據偏移量對1G取餘就能夠,這些commitlog文件經過一個 文件隊列維護,每次寫文件返回隊列的最後一個文件,而後須要加鎖,建立完文件後會進行預熱,預熱的時候會在每個內存頁4kb裏面寫一個byte0。讓系統會對緩存頁緩存防止真正寫入的時候發生缺頁,mmap的機制是隻會記錄一個虛擬地址,當缺頁的纔會去獲取物理內存的地址,建立文件有兩種方式,一種是FileChannel.map獲取MappedByteBuffer 另一種是使用堆外內存池,而後flush

消息的消費

一個隊列只能被一個客戶端消費,當有多個隊列,只有一個客戶端的時候,這個客戶端須要去4個隊列上消費,當只有一個隊列的時候只會有一個客戶端能夠收到消息,因此通常狀況下須要客戶端數量和隊列數量一致,客戶端通常會保存每一個隊列消費的位置,由於這個隊列只會有一個客戶端消費,因此這個客戶端每次消費都會記錄下隊列的offset,broker端也會記錄同一個grouo消費的offset

MappedByteBuffer 的原理是老的read是先將數據從文件系統讀取到操做系統內核緩存,而後在將數據拷貝到用戶態的內存供應用使用,而使用mmap能夠將文件的數據或者某一段數據映射到虛擬內存,這個時候並無進行數據讀取,當用戶訪問虛擬內存的地址的時候會觸發缺頁異常,這個時候會從底層文件系統直接將數據讀取到用戶態內存,而MappedByteBuffer經過FileChannel的map方法進行映射的時候會返回一個虛擬地址,而MappedByteBuffer就是經過這個虛擬地址配合UnSafe獲取字節數據,而操做系統在觸發缺頁異常的時候會去文件系統讀取數據加載到內存,這個時候通常會進行預讀取,通常爲4KB,當系統下次訪問數據的時候就不會發生缺頁異常,由於數據已經在內存裏了,爲了讓MappedByteBuffer讀取文件的速度更高,咱們能夠對MappedByteBuffer所映射的文件進行預熱,例如將每一個pagecache寫一個數據,這樣在真正寫數據的時候就不會發生缺頁了。

分庫分表

通常三種方式:在dao層和orm層利用mybatis攔截器,基於jdbc層進行攔截重寫JDBC接口作加強,基於數據庫代理。

jdbc代理,實現datasource,connection,preparestatement,druid解析sql,生成執行計劃,利用resultset對結果集進行合併(group by order max sum)

分表策略,通常是哈希,要保證分庫和分表的算法徹底沒有關聯,否則會數據分佈不均勻。

數據擴容的時候能夠經過配置中心動態的修改寫入策略,如何一開始能夠先讀老表,數據同時寫入新表和老表,等數據遷移完成後,在讀新表並雙寫,以後在讀新表寫新表。

惟一id

數據庫自增id,一次取多個,單機限制,另外數據庫自增id內部也用了個鎖,只是在sql執行結束即便事務沒提交也會釋放鎖。

雪花算法變種 : 15位時間戳,4位自增序列,2位區分訂單類型,7位機器ID,2位分庫後綴,2位分表後綴 共32位

利用zookeeper的順序節點獲取自增ID

分佈式事務

兩階段提交:事務管理器,資源管理器,一階段準備,二階段提交 (XA方案對業務無侵入,由數據庫廠商提供支持,可是性能不好)

事物補償

TCC :也是兩階段,第一階段 嘗試鎖定資源 第二階段確認或者回滾

設計規範

  • 業務操做分紅兩部,例如轉帳:嘗試階段爲凍結餘額,第二階段提交爲 從凍結餘額扣款,回滾爲解凍
  • 事務協調器記錄主事務日誌和分支事務日誌,支持在任意一步發生異常後進行補償或者逆向補償保證最終一致性
  • 併發控制,下降鎖的粒度提升併發,保證兩個事務間不須要加排他鎖,例如熱點帳戶的轉帳操做,因爲第一階段進行了凍結,因此後面的扣減餘額不一樣事務之間沒有影響。
  • 容許空回滾:可能一階段的嘗試操做發生超時,而後二階段發起回滾,回滾的時候要判斷一階段是否進行過操做,若是一階段沒有收到請求,回滾操做直接返回成功。
  • 避免一階段操做懸掛:可能一階段超時,二階段回滾後,一階段的請求到達,這時候要拒絕一階段的嘗試操做。
  • 冪等控制,因爲第一階段和第二階段的操做可能都會執行屢次,另外操做接口最好能提供狀態查詢接口供後臺的補償任務正常執行

框架事務(seata)

一階段 框架會攔截業務sql,根據語句執行前結果生成 undolog , 根據語句執行後對結果生成 redolog , 根據數據庫表名加主鍵生成行鎖

二階段 若是事務正常結束,將刪除 undolog redolog 行鎖,若是事務將回滾,則執行 undolog sql , 刪除中間數據 在執行 undolog 的時候會校驗髒寫,也就是有沒有其餘事務已經修改了這行記錄,也就是用 redolog 作對比,若是出現髒寫只能人工修數據 (二階段的清理工做能夠異步執行)

開啓事務的時候會向tc申請一個全局的事務id,這個事務id會經過rpc框架的攔截器傳入到被調用端,而後放入threadlocal,被調用方在執行sql的時候會去檢查一下是否在一個全局事務裏。

默認的隔離級別爲讀未提交,由於事務一階段已經本地事務提交而全局事務並無完成後續可能會回滾,其餘事務能夠看到這個這個狀態,提供的讀已提交的方式是經過 for update,當解析到該語句的時候會檢查是否存在行鎖衝突,若是存在衝突就等待直到釋放。

  • 1.tm 向 tc 發起開啓一個全局事務,生成一個全局惟一的 xid
  • 2.xid 在微服務調用鏈上進行傳遞
  • 3.rm 向 tc 註冊分支事務
  • 4.tm 向 tc 發起全局提交或者回滾決議
  • 5.tc 向 rm 發起回滾或提交請求

一致性消息隊列:先發送半消息,若是成功了在執行本地事務,本地事務成功就提交半消息,本地事務失敗就回滾半消息,若是消息隊列長期沒有收到確認或者回滾能夠反查本地事務的狀態,消費端收到消息後,執行消費端業務,若是執行失敗能夠從新獲取,執行成功發送消費成功的確認。

MYCAT

CAP

C 一致性 A 可用性 P 分區容忍性 能夠簡單地這樣理解:MySQL 單機是C 主從同步複製 CP 主從異步複製 AP

Zookeeper 選擇了P,可是既沒有實現C也沒有實現A 而是選擇最終一致性,能夠在多個節點上讀取,可是隻容許一個節點接受寫請求,其餘節點接收的寫請求會轉發給主節點,只要過半節點返回成功就會提交,若是一個客戶端鏈接的正好是沒有被提交的follower節點,那麼這個節點上讀取到的數據就是舊的,這樣就出現了數據的不一致,因此沒有徹底實現C,因爲須要過半節點返回成功才提交,若是超過半數返回失敗或者不返回,那麼zookeeper將出現不可用,因此也沒有徹底實現A

固然衡量一個系統是CP仍是AP,能夠根據他犧牲A更多仍是犧牲C更多,而ZK其實就是犧牲了A來知足C,當超過集羣半數的節點宕機後,系統將不可用,這也是不建議使用zk作註冊中心的緣由

CAP理論只是描述了在分佈式環境中一致性,可用性,分區容忍不能同時知足,並無讓咱們必定要三選二,因爲網絡分區在分佈式環境下是不可避免的,因此爲了追求高可用,每每咱們會犧牲強一執行,採用弱一致性和最終一致性的方案 也就是著名的BASE理論,而base理論實際上是針對傳統關係型數據的ACID而言的,而ACID的提出是基於單節點下的,而在分佈式環境下,如何協調數據一致性,也就是在數據的隔離級別上作出取捨,而即便是單機的關係型數據庫也爲了提升性能,也就是可用性,定義了隔離級別,去打破ACID裏面的強一致性C,固然數據庫也是爲業務服務的,某些業務或者說大部分業務都沒有強一致性的需求。

秒殺的處理

  • 動靜分離:ajax 不刷新頁面,緩存,cdn
  • 發現熱點數據:業務流程上變通讓熱點業務隔離出來,也經過鏈路監控獲取一段時間的熱點數據
  • 隔離:業務隔離,數據庫隔離
  • 兜底方案:服務降級,限流
  • 流量削峯: 排隊,過濾無效請求,答題或者驗證碼,消息隊列
  • 減庫存:(下單減庫存用戶不付款須要回滾,付款減庫存最終可能庫存不足須要退款,下單後佔庫存一段時間後在回滾) 正常電商第三種,秒殺採用第一種,不超賣的控制不用放在應用層,直接在sql層加where語句進行判斷,可是mysql針對同一行記錄也就是同一個商品的減庫存,確定會高併發下爭取行鎖,這將致使數據庫的tps降低(死鎖檢測會遍歷全部須要等待鎖的鏈接這個操做很是耗cpu),從而影響其餘商品的銷售,因此咱們能夠將請求在應用層進行排隊,若是份額較少能夠直接捨棄,另外一種方案是在數據庫層排隊,這種方案須要採用mysql的補丁

docker

namespace

docker在建立容器進程的時候能夠指定一組namespace參數,這樣容器就只能看到當前namespace所限定的資源,文件,設備,網絡。用戶,配置信息,而對於宿主機和其餘不相關的程序就看不到了,PID namespace讓進程只看到當前namespace內的進程,Mount namespace讓進程只看到當前namespace內的掛載點信息,Network namespace讓進程只看到當前namespace內的網卡和配置信息,

cgroup

全名 linux control group,用來限制一個進程組可以使用的資源上限,如CPU,內存,網絡等,另外Cgroup還可以對進程設置優先級和將進程掛起和恢復,cgroup對用戶暴露的接口是一個文件系統,/sys/fs/cgroup下 這個目錄下面有 cpuset,memery等文件,每個能夠被管理的資源都會有一個文件,如何對一個進程設置資源訪問上限呢?在/sys/fs/cgroup目錄下新建一個文件夾,系統會默認建立上面一系列文件,而後docker容器啓動後,將進程ID寫入taskid文件中,在根據docker啓動時候傳人的參數修改對應的資源文件

chroot

經過chroot來更改change root file system更改進程的根目錄到掛載的位置,通常會經過chroot掛載一個完整的linux的文件系統,可是不包括linux內核,這樣當咱們交付一個docker鏡像的時候不只包含須要運行的程序還包括這個程序依賴運行的這個環境,由於咱們打包了整個依賴的linux文件系統,對一個應用來講,操做系統纔是他所依賴的最完整的依賴庫

增量層

docker在鏡像的設計中引入層的概念,也就是用戶在製做docker鏡像中的每一次修改都是在原來的rootfs上新增一層roofs,以後經過一種聯合文件系統union fs的技術進行合併,合併的過程當中若是兩個rootfs中有相同的文件則會用最外層的文件覆蓋原來的文件來進行去重操做,舉個例子,咱們從鏡像中心pull一個mysql的鏡像到本地,當咱們經過這個鏡像建立一個容器的時候,就在這個鏡像原有的層上新加了一個增roofs,這個文件系統只保留增量修改,包括文件的新增刪除,修改,這個增量層會藉助union fs和原有層一塊兒掛載到同一個目錄,這個增長的層能夠讀寫,原有的其餘層只能讀,這樣保證了全部對docker鏡像的操做都是增量,以後用戶能夠commit這個鏡像將對這個鏡像的修改生成一個新的鏡像,新的鏡像就包含了原有的層和新增的層,只有最原始的層纔是一個完整的linux fs, 那麼既然只讀層不容許修改,那麼我怎麼刪除只讀層的文件呢,這個時候只須要在讀寫層也就是最外層生成一個whiteout文件來遮擋原來的文件就能夠了。

發佈與部署

目前的大部分公司採用下面的部署方式

  • 1.建立pileline 指定項目名稱和對應的tag,以及依賴工程,一個pipeline指一個完整的項目生命週期(開發提交代碼到代碼倉庫,打包,部署到開發環境,自動化測試,部署到測試環境,部署到生產環境)
  • 2.根據項目名稱和tag去gitlab上拉取最新的代碼(利用java裏的Runtime執行shell腳本)
  • 3.利用maven進行打包,這個時候能夠爲maven建立一個單獨的workspace(shell腳本)
  • 4.根據預先寫好的docfile,拷貝maven打的包生成鏡像,並上傳鏡像 (shell腳本)
  • 5.經過k8s的api在測試環境發佈升級
  • 6.經過灰度等方案發布到生產環境

微信公衆號:內核小王子 關注可瞭解更多關於數據庫,JVM內核相關的知識; 若是你有任何疑問也能夠加我pigpdong[^1]

歷史文章:

JAVA和操做系統交互細節

經過MySQL存儲原理來分析排序和鎖

網絡內核之TCP是如何發送和接收消息的

相關文章
相關標籤/搜索