天天看點

BAT網際網路大廠的後端主流技術棧是啥?

原文:tinyurl.com/uzlgatz

後端開發概述

何為後端開發?以一個網站為例,通常來說,前端研發注重頁面的展示,互動邏輯。而後端研發,則注重在發生在前端背後(backend)的邏輯上,例如給前端傳回資料,存儲資料。對于一個電商網站,一個簡單的下單動作,後端可能包括商品資料查詢,優惠資訊計算,庫存維護,使用者優惠券維護,訂單生成,商家通知觸發等等。在很多大公司前後端的配比是1:3甚至更高,因為一個複雜的業務系統,前端的展示僅僅是冰山一角,更複雜的業務邏輯都隐藏在後端。

通常來說,當使用者觸發某個行為後,用戶端會通過http/https請求,和我們的伺服器建立連接配接,發送請求,往往這個請求首先會被連結到負載均衡(LB)上,負載均衡根據配置,将請求轉發到内部的API服務上。這些API服務,根據不同的業務邏輯會請求其他服務(Service),這些服務各司其職,例如讀寫某 Mysql 表、讀寫緩存,甚至請求搜尋引擎來完成相應的任務。而API服務在完成相應的步驟後,也會将資料傳回給用戶端,用戶端根據前端邏輯完成相關的展示。

下面這個圖,簡單的展示了服務端研發可能使用服務組織方式和相關技術棧,後續會對所有技術棧和大廠使用場景一一簡述。

BAT網際網路大廠的後端主流技術棧是啥?

負載均衡 - Load Balance(LB)

負載均衡作為連接配接内外的門戶,在分布式系統中,有這非常核心的作用。下面的圖是負載均衡最直覺的呈現:

BAT網際網路大廠的後端主流技術棧是啥?

它将流量從外部轉發到内部系統,對于同樣的請求内容,不同時序的請求會被轉發到不同的服務執行個體上。對每個服務執行個體而言,它隻需要承擔系統總流量的 1/N 進而降低了單個服務的負載,提升了服務整體相應速度和系統穩定性。負載均衡器會保持跟蹤所有下遊服務的狀态,如果服務不可用,則會被從排程移除。

一個最常用的負載均衡就是Nignx反向代理,在上圖中,如果使用Nginx做負載均衡,最簡單的方法就是配置 upstream,例如下:

#配置負載均衡執行個體
upstream user_api { 
   server 10.0.6.108:7080; 
   server 10.0.0.85:8980; 
}

#配置轉發到 user_api upstream 
location /user { 
    proxy_pass http://user_api; 
}
           

複制

顯然,這份配置中要指定 user api 服務已經部署執行個體的 IP 位址和端口。如果沒有自動化的釋出方案,意味着每次開發新的API都需要在服務釋出好以後,更新 Nginx 配置,然後通過 reload nginx 的方式将API釋出上線。如果API不經常變動,這種做法也能支撐業務發展;但是如果公司業務快速發展,經常頻繁釋出API風險就會比較大了。在服務重新開機切換配置的過程中,可能導緻一些請求處理被丢棄,就連服務擴容和縮容(增加減少負載均衡執行個體),也要變更相應的nginx配置。

是以很多大廠,都會建設自己的 LB 平台,在這裡你可以配置需要暴露出去的 URL,提供服務的内部服務辨別,也會支援一些額外的配置,包括限流、熔斷參數等。而要做到這些,往往需要對 Nginx 原生的負載均衡能力做拓展,例如使用 dyups 子產品支援動态上下線配置;需要一個額外的管理平台,來管理所有對外API;需要服務注冊中心維護所有的服務對應的叢集和執行個體。

同時需要啟動一個 API Watch的線上常駐服務,負責監聽API配置變更和注冊中心每個服務執行個體的上下線變更,生成 dyups 子產品可以識别的 Nginx 配置,線上 load 到 Nginx 就可以完成服務動态上下線了。原理如下圖:

BAT網際網路大廠的後端主流技術棧是啥?

當然,這隻是一個最基本的功能和原理展示,大廠們往往根據不同的線上使用場景會有很多優化和系統設計的考量。

微服務生态

微服務 - 也被稱為微服務架構 - 一種将整個後端服務,按照領域、子產品分解為若幹獨立小應用的一種架構方式。微服務有如下特點

  • 服務可以單獨編寫、釋出、測試、部署,相比于所有功能集中于一體的單體服務,可維護性更強
  • 服務彼此之間依賴服務通信的方式松耦合
  • 按照業務領域來組織服務,每個團隊維護各自的服務

下圖直覺的闡述微服務的概念:

BAT網際網路大廠的後端主流技術棧是啥?

既然微服務體系是按照組織結構、功能子產品獨立進行開發、測試、部署的,那麼微服務架構就要解決因為獨立部署帶來一些問題,例如通訊協定,遠端調用(RPC),服務發現,服務治理等。有能力的大廠往往會有自己的架構來解決上面的問題,例如淘寶的HSF、阿裡開源的 dubbo,能力不足的小廠也可以從開源社群中選擇合适技術為我所用,“拼湊”出合理的解決方案,下面主要從開源社群選擇一些可用為我所用的技術來介紹。

Thrift

Thrift不僅僅是一個輕量的,高性能的遠端調用(RPC)通訊協定,也是一個優秀的RPC架構。Thrift 使用一種被稱為 IDL 的接口定義語言,來定義遠端調用的接口。使用官方提供的工具可以将IDL檔案生成微服務服務端(Server)和用戶端(Client)代碼。這裡 Server 端指提供服務的一方,而 Client 則指服務調用方,Client 通過 RPC 對 Server進行調用。

利用這份生成的代碼,就可以實作Client通過指定IP和端口的調用Server服務了。個人感覺 Thrift 最大的優勢是高性能,跨語言,以及足夠輕量。跨語言是很好的特性,因為一個大公司的不同部門,可能語言技術棧會有差異,使用 Thrift 可以屏蔽這種差異,讓彼此專注。

服務發現

上面提到,如果隻依賴 Thrift 我們可以實作通過指定IP端口的方式進行服務調用,但是顯然這是不可行的,我們需要 Client 動态感覺 Server 端服務的存在以及提供服務的所有執行個體。服務注冊中心就是解決這個問題而誕生的概念,可以簡單了解注冊中心就是一個儲存着服務狀态的”資料庫“,服務成功啟動後到注冊中心去注冊,并且保持和注冊中心的心跳以維持服務在注冊中心的最新狀态,當服務下線或者異常退出,服務可以主動通知注冊中心下線或者被注冊中心通過心跳失敗感覺到。

常見的服務注冊中心例如 Spring Cloud 架構中官方提供的 Eureka,Dubbo 預設使用的 Zookeeper。Spring Cloud 和 Dubbo 也對 Consul 增加了原生支援,這裡也主要介紹下Consul。具體對比可以參考[1]

Consul

Consul是基于GO語言開發的開源工具,主要面向分布式,服務化的系統提供服務注冊、服務發現和配置管理的功能。Consul的功能都很實用,其中包括:服務注冊/發現、健康檢查、Key/Value存儲、多資料中心和分布式一緻性保證等特性。Consul本身隻是一個二進制的可執行檔案,是以安裝和部署都非常簡單,隻需要從官網下載下傳後,在執行對應的啟動腳本即可。

如果使用 Spring Cloud 或者 Dubbo 等微服務架構,可以通過配置實作使用 Consul 作為服務注冊中心,服務啟動後,在Consul提供的Web界面就可以查到相應的服務。服務用戶端可以在第一次調用服務端前,通過Consul進行服務端執行個體的查詢然後按照查詢奧的服務執行個體進行遠端調用。

Consul 的 web界面:

BAT網際網路大廠的後端主流技術棧是啥?

微服務架構

開源社群中知名的微服務架構包括 Spring Cloud, Dubbo 等,這些架構配合生态中的一切其他元件可以解決例如服務注冊&發現、負載均衡、熔斷限流、調用鍊追蹤等絕大多數問題。不過當業務發展到一定階段,還會有更多問題要解決,例如服務調用鑒權等問題。

是以各個大廠幾乎都自研自己的微服務架構,但是基本的做法都是在開源社群選擇一部分,自己擴充一部分,例如通訊協定和RPC選擇Thrift,服務注冊中心選 Zookeeper/Consul,然後需要自研擴充的部分就是例如服務注冊,服務發現,負載均衡,統一監控,鑒權等公司特色的需求。

資料庫(Database)

對于一個小公司而言,可能會選擇把所有資料儲存在 Mysql 中,因為全部業務資料的容量可能也隻有百G。但是對于大廠,每天産生的資料可能都是T級别的,如果都儲存在Mysql中會有諸多問題,例如存儲成本、後續的修改查詢效率、高并發場景下存儲的極限能力等。

資料有它不同業務特性和使用場景,業務特性很好了解,例如我們不容忍交易資料發生丢失并且在很多操作它的場景,要求強一緻性;而使用者評論,則能容忍很小比例丢失,并且評論計數器和評論數目之前的如果出現微小差距,使用者也很難察覺到;而服務日志資料,則能容忍更大程度的丢失,隻要不影響開發Debug可能就不會有人追究。

資料不同的使用場景,也對存儲有不同方面的要求。例如同樣是使用者資料,使用者資料檢視自己的資料,一定要保證資料是使用者最新更新的,并且不能容忍出錯,哪怕是頁面相應速度略微慢一點;但是用在推薦場景作為使用者畫像作為模型輸入的時候,就能容忍這個資料不是最新的,但是要求資料通路速度要高,因為推薦場景往往對成千上萬個候選排序,畫像資料通路慢則直接拖累了整個推薦系統的效率。

Mysql

對于Web應用而言,關系型資料庫的選型中 Mysql 無疑是最佳選擇。它體積小、速度快、開源授權使得它擁有成本低,支援多種作業系統,有豐富的社群支援,支援多種語言連接配接和操作。

Mysql是儲存業務資料的理想之選,首先關系型資料庫在設計之初,從概念上就在支援業務資料模組化的概念,關系型資料庫能結構化的描述業務需求。Mysql 對ACID屬性的支援,也能滿足不同業務場景下對資料操作的不同訴求。但是在大廠背景下,Mysql也有它的限制。

首先,對于線上大表DDL幾乎不太可能,DDL指表結構變更之類的操作。對于一張動辄幾千萬資料的表,Alter Table 可能需要幾小時,除非提供備案方案,否則服務在這段時間将不可用。

其次,線上查詢要注意性能優化,避免慢SQL拖垮DB的場景。常見的性能問題包括查詢未命中索引而觸發全表掃描;使用了聚合查詢(group by)觸發全表掃描等

還有,大廠特别是ToC常見的大廠,每天産生的業務資料異常的大,Mysql存儲超過幾千萬性能會下降,是以需要使用分庫分表的方式來解決海量資料場景下的存儲問題。

Mycat

Mycat就是一個解決資料庫分庫分表等問題的資料庫中間件,也可以了解為是資料庫代理。在架構體系中是位于資料庫和應用層之間的一個元件,Mycat 實作了 Mysql 的原生協定,對于應用它感覺不到連接配接了 Mycat,因為從協定來講,兩者是一樣的。而Mycat将應用的請求轉發給它後面的資料庫,對應用屏蔽了分庫分表的細節。Mycat的三大功能:分表、讀寫分離、主從切換。

下圖通過 Mycat schema 配置和實際 DB 部署來簡要說明 Mycat 分庫分表功能的實作:

BAT網際網路大廠的後端主流技術棧是啥?

例如表 A,配置中它有兩個資料節點,分别是 datanode1 和 datanode2,含義是邏輯表 A實際對應了兩個實體存儲,分别是 db1 和 db2。對于應用而言,應用連接配接 Mycat 感覺到的時候邏輯表 A,而在底層 A 被拆分,存儲在兩個 db 中。

DRC

DRC 是 Data Replication Center 的縮寫,在使用Mysql作為核心存儲的場景下,我們可以使用Mysql原生的主備方案,實作同城災備。但是如果 Mysql 部署在跨國,跨洲的場景下,原生的災備方案就有諸多問題了,是以各大廠幾乎都有自己的DRC方案。

不過,雖然各自有不同的實作,但是原理和依賴的核心元件基本相同,本文從網際網路上找到餓了麼DRC元件闡述其原理。

BAT網際網路大廠的後端主流技術棧是啥?

本圖中,異地機房分别為北京機房和上海機房。本地機房(圖中為北京機房)會啟動一個 DRC Replicator,它和Master節點通信并在通信協定上模拟 Mysql Slave,Replicator将Master資料庫的binlog變更實時拉取到本地。然後把binlog解析,通過消息中間件将變更發送到異地機房(北京機房)。異地機房啟動一個DRC Applier的應用消費資料變更消息,然後把這個變更操作同步到本機房的Master上,就完成了異地資料同步的操作。圖中展示的是北京機房資料同步到上海機房的場景,實際反過來也是一樣。

DRC在設計和實踐中最常見的問題就是DB自增類型主鍵沖突,以及資料因為同步消息丢失而最後導緻的不一緻,前者可以通過強制使用ID生成器或者自增ID設定相同的增加值和不同的初始值等方式解決。而後者要麼采用一個規則同步最終資料,或者進行人為資料幹預。

緩存(Cache)

如果稍微深入研究Mysql的存儲原理,我們不難發現,資料是存儲在磁盤中的,雖然我們可以通過索引等資料結構,降低每次查找資料的響應時間,但是對于高并發的線上應用,直接查找資料庫依然很容易觸碰Mysql性能瓶頸,是以我們需要緩存來緩解DB查詢的壓力,當要查詢的資料命中緩存後,直接從緩存中擷取資料,進而降低DB的通路壓力。常見的緩存有兩種政策:

本地緩存:不需要序列化,速度快,緩存的數量與大小受限于本機記憶體,很多語言提供一些架構來支援記憶體緩存,例如 guava cache,spring預設內建的 Ehcache。

分布式緩存:需要序列化,速度相較于本地緩存較慢,但是理論上緩存的數量與容量無限制(因為分布式緩存機器可以不斷擴充),常見的分布式緩存包括 Redis 和 Memcache。本文主要介紹下 Redis。

Redis

Redis 是基于記憶體的緩存中間件,正因為基于記憶體,是以具有非常快的相應速度。支援豐富的資料結構,例如 string、hashes、list、set、sorted set等等,應用非常廣泛的應用。

常見的緩存讀寫政策包括Cache-Aside,Read Through,Write Through,具體可以參考[2],不過文中缺少一種Cache 讀寫方案,這也是很多高并發線上應用比較常用的一種方式。

Write Behind Caching模式

Write Behind Caching 這種模式通常是先将資料寫入到緩存裡面,然後再異步地将DB更新的資料寫入到cache中,這樣的設計既可以直接的減少我們對于資料的database裡面的直接通路,降低壓力,同時對于database的多次修改可以進行合并操作,極大的提升了系統的承載能力。這個模式下,應用在讀資料的時候,不感覺DB,隻感覺Cache,優勢在于簡化了設計,缺點在于強依賴Cache,如果Cache出現問題例如當機,則讀會失效。

Redis 叢集方案

伴随着需要緩存的資料量增加和高可用的依賴,大廠的Redis都是需要叢集化方式部署的。一方面通過主從模式提升了系統的高可用,另一方面通過叢集模式将系統演化為可用無限擴容的模式。

Redis 從 3.0 版本開始,原生的支援了叢集模式,通過 Sentinel 叢集實作動态的主從模式[3];原生的叢集模式,将所有的資料劃分到 16384 slots中,而叢集中的每個節點,會配置設定若幹 slots。然而原生叢集的方案,雖然簡化了叢集的設計,但是卻增加了用戶端的負擔,需要用戶端對Moved/ASK [4] 事件做封裝處理,同時需要維護路由表提高通路效率,增加了用戶端設計的複雜度。

是以大廠往往不會選擇 Redis 原生的叢集化方案,而是使用基于Proxy的叢集化方案,業界比較知名開源 Proxy 有 Twemproxy 和 Codis [5],本文簡要介紹下 Codis,實際上很多知名大廠的 Proxy 都來源于Codis。

BAT網際網路大廠的後端主流技術棧是啥?

Codis 引入了Group的概念,每個Group包括 1 個 Redis Master 及至少1個 Redis Slave,可以認為每個Group是一個系統分片(Shard),與 Redis-cluster 一樣,Codis 也采用預分片的機制,整個叢集分成 1024 個 slots,通過一緻性雜湊演算法,将Key映射到某個 slot,再通過維護在 Zookeeper 裡的分片路由表,将Key的請求轉發到對應的Group上。

Codis 提供了一套營運監控界面,運維人員可通過 Dashboard “自助式”地進行主從切換。

而對于應用而言,通路 codis 和通路原生的 Redis 并沒有任何差別,節點的動态上下線,slot 配置設定的變更都在 Proxy 層完美的對應用屏蔽了。

KV-DB

前文講述了 Mysql 和 Redis,或許對于大多數公司,這兩類存儲已經足夠。前者用于儲存業務資料,後者用于集中式緩存。但是對于大廠,還有若幹場景上面兩種存儲無法滿足:例如推薦系統線上預測場景,需要将使用者畫像、商品畫像、商家畫像、使用者商戶交叉畫像線上加載預測上下文,特征處理後給到模型做預測打分。ToC的網際網路産品的注冊使用者很可能過億,是以使用者畫像總存儲很可能百G甚至T。

如果是這樣規模的資料,Mysql的讀性能肯定扛不住線上預測場景;Redis 是記憶體緩存,存儲昂貴,同時在容災恢複時候,Redis需要将AOF或者RDB資料載入記憶體後才能提供服務,資料量過大需要很長的恢複時間。是以需要另外一種存儲能解決這個問題。

幾乎所有大廠都有屬于自己的KV-DB,例如360開源的Pika,餓了麼通過購買Tikv封裝而成Ekv,位元組跳動的 Abase。Pika 和 Tikv在存儲底層都使用了RocksDB作為資料存儲,而RocksDB它是将資料存儲在硬碟上的,Pika 和Tikv在上層建構的都是叢集化方案,主從模式等,基于記憶體的一緻性Cache等。下圖是Pika架構圖:

BAT網際網路大廠的後端主流技術棧是啥?

消息隊列(MQ)

伴随着業務的複雜,我們往往會遇到這個場景,一個資料操作後,需要觸發下遊若幹個子操作。例如外賣場景,使用者下訂單成功,要通知商家使用者訂單,要物流平台對訂單進行排程和派單,要觸發一些後置的風控邏輯對訂單合法性進行校驗等。如果是同步的設計,需要在訂單完成後對後續的操作一一進行API調用,這樣的做法讓訂單流程依賴更多外部服務,提升了業務複雜度,降低了服務的穩定性,是以我們需要消息中間件來解耦操作。依賴的服務依賴下單消息,而不是在下單結束後,通過接口調用的方式觸發。

我們可以把消息隊列(MQ)比作是一個存放消息的容器,Producer 負責生産消息,将消息發送到MQ,Consumer取出消息供自己使用。如圖所示:

BAT網際網路大廠的後端主流技術棧是啥?

消息隊列是分布式系統中重要的元件,使用消息隊列主要是為了通過異步處理降低系統耦合性,除此之外,還可以依賴消息隊列提高系統性能和削峰填谷。

關于削峰填谷這裡我舉一個應用場景 —— 搶購。搶購是很多網站促銷的重要場景,呈現方式往往是通過倒計時的方式,在某個固定時間放出限量的優惠産品。開放時間到來時,會有大量使用者觸發購買動作,我們知道下訂單往往是一個耗時比較久的操作,需要對庫存,營銷資訊,使用者等多方面資料做查詢、校驗、計算操作,同時為了控制超賣,可能會引入鎖機制。如果處理不好搶購的瞬時流量,可能會打垮系統。一種優化思路是可以将瞬間的購買請求轉發到消息隊列中,再由消息隊列的消費者消費消息,進行後續的訂單操作,進而對系統進行流量削峰。

從大的應用場景上,我們可以将消息隊列的應用拆分成兩類,一類是業務場景,例如上文提到查到使用者訂單消息,這類場景的吞吐量未必很大,但是需要消息中間件具有一些更進階的易于業務使用的特性,例如支援消息持久化,延遲消息等。另外一類是大資料場景,該類場景對吞吐量有極高的訴求,例如使用者行為搜集(User Behavior Tracking)等。這裡隻介紹兩種上面場景适合的消息隊列中間件。

RocketMQ

RocketMQ是一款分布式、隊列模型的消息中間件,是由阿裡巴巴設計的,經過多次雙十一流量洪峰的洗禮,讓它更有光環效應;RocketMQ是純java編寫,基于通信架構Netty這也讓它擁有更好的社群基礎和可改造的空間。相比于Kafka,RocketMQ支援很多業務友好的特性,具有以下特點:

支援嚴格的消息順序,支援Topic與Queue兩種模式,億級消息堆積能力,比較友好的分布式特性,同時支援Push與Pull方式消費消息

Kafka

Kafka 的特點其實很明顯,就是僅僅提供較少的核心功能,但是提供超高的吞吐量,毫秒級的延遲,極高的可用性以及可靠性,而且分布式可以任意擴充。同時 kafka 最好是支撐較少的 topic 數量即可,保證其超高吞吐量。kafka 唯一的一點劣勢是有可能消息重複消費,那麼對資料準确性會造成極其輕微的影響,在大資料領域中以及日志采集中,這點輕微影響可以忽略。

對象存儲

對象存儲是面向對象/檔案的、海量的網際網路存儲,它也可以直接被稱為“雲存儲”。對象盡管是檔案,它是已被封裝的檔案,也就是說,在對象存儲系統裡,你不能直接打開/修改檔案,但可以像ftp一樣上傳檔案,下載下傳檔案等。另外對象存儲沒有像檔案系統那樣有一個很多層級的檔案結構,而是隻有一個“桶”(bucket)的概念(也就是存儲空間),“桶”裡面全部都是對象,是一種非常扁平化的存儲方式。其最大的特點就是它的對象名稱就是一個域名位址,一旦對象被設定為“公開”,所有網民都可以通路到它;它的擁有者還可以通過REST API的方式通路其中的對象。是以,對象存儲最主流的使用場景,就是存儲網站、移動app等網際網路/移動網際網路應用的靜态内容(視訊、圖檔、檔案、軟體安裝包等)

不過自建對象存儲成本很高,很多中等規模的廠,都會選擇商業化對象存儲方案,例如七牛雲,阿裡OSS等,用來降低研發和維護成本。

Elastic Search

無論是線上應用還是管理背景,都有模糊搜尋的需求,例如使用者搜尋感興趣的文章,評論,視訊。管理者對命中一些關鍵詞的内容進行批量下架等處理。這種模糊搜尋,如果使用Mysql原生的查詢,效率是非常低的,能想到的樸素做法可能先将使用者Query進行分詞處理,然後用分詞的結果依次送出到Mysql做某字段的Contains條件查詢。Contains操作會對表進行全掃描,如果有千萬資料,效率難以想象。

針對這樣的場景,反向索引是非常理想的方案,反向索引(Inverted Index)是實作“單詞-文檔矩陣”的一種具體存儲形式,通過反向索引,可以根據單詞快速擷取包含這個單詞的文檔清單。

BAT網際網路大廠的後端主流技術棧是啥?

如果需要索引的資料不多,索引更新頻率也不高,簡單的做法可以使用 Apache Lucene 建構一個非常輕量的反向索引,将需要倒排的資料,通過 Lucene 提供的API 灌到索引檔案中,而該索引檔案可以在後續的搜尋服務中被 Lucene 加載,提供查詢服務。

但是大廠場景往往是擁有海量需要索引的資料,同時要支援線上建構索引檔案和災備能力,在開源社群中 Elastic Search 就是非常好的選擇之一,ES 底層也是基于 Lucene,但是它提供分布式的文檔存儲引擎,分布式的搜尋引擎和分析引擎,支援PB級資料。