亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

微服務(wù)一直以來是服務(wù)治理的基本盤之一,落地到云原生上,往往是每個(gè) K8s pods 部署一個(gè)服務(wù),獨(dú)立迭代、獨(dú)立運(yùn)維。

但是在快速部署的時(shí)候,有時(shí)候,我們可能需要一些宏服務(wù)的優(yōu)勢。有沒有一種方法,能夠 “既要又要” 呢?本文基于 tRPC-Go 服務(wù),提出并最終實(shí)踐了一種經(jīng)驗(yàn)證可行的方法。

一、微服務(wù)的優(yōu)劣

微服務(wù)是云原生的大潮流,它的優(yōu)勢非常明顯:

  • 微服務(wù)大大降低了模塊間的耦合。當(dāng)某個(gè)模塊 / 微服務(wù)需要變更時(shí),只需要調(diào)整這個(gè)微服務(wù)即可,其他服務(wù)無感知;
  • 微服務(wù)化使得模塊的更新能夠平滑過渡,避免了停機(jī)更新的問題,也適合大型團(tuán)隊(duì)或多個(gè)團(tuán)隊(duì)間合作構(gòu)建;
  • 微服務(wù)模塊的輸入 / 輸出定義很明確,非常適合融合 DDD 理念進(jìn)行設(shè)計(jì);
  • 問題排查時(shí),能夠快速定位出現(xiàn)問題的模塊,對運(yùn)維也很友好。

然而微服務(wù)也存在劣勢:

  • 當(dāng)系統(tǒng)趨向復(fù)雜時(shí),隨著微服務(wù)的拆分、功能的繁雜和細(xì)化,微服務(wù)越來越多,一窺系統(tǒng)全貌的難度越來越大;
  • 模塊間通信通過 RPC 實(shí)現(xiàn),RPC 帶來了時(shí)間和網(wǎng)絡(luò)流量的開銷;
  • 依賴于完備的服務(wù)治理體系,對小團(tuán)隊(duì)而言,部署成本較高;
  • 多租戶隔離部署時(shí),運(yùn)維難度也成倍增加。

二、遇到的問題

我們是心悅俱樂部首頁 Feeds 流推薦系統(tǒng)的開發(fā)團(tuán)隊(duì)。但我們推薦系統(tǒng)也接入了其他業(yè)務(wù),比如我們在接入游戲知幾項(xiàng)目的一個(gè)功能后,全量發(fā)布前的壓測中發(fā)現(xiàn) CPU 開銷大到難以接受。

1.分析

我們的系統(tǒng)是簡單按照 “業(yè)務(wù) → 分流 → 重排 → 精排 → 召回” 的推薦系統(tǒng)微服務(wù)化部署,沒有做編排化:

微服務(wù)不香了?單體化改造為我們節(jié)省上萬核CPU

觀察壓測數(shù)據(jù),我們會(huì)發(fā)現(xiàn),在分流層前后的服務(wù),網(wǎng)絡(luò)開銷非常大:

微服務(wù)不香了?單體化改造為我們節(jié)省上萬核CPU

分流服務(wù)是推薦系統(tǒng)的總?cè)肟?,它沒有很強(qiáng)的業(yè)務(wù)屬性,而是在整個(gè)推薦系統(tǒng)的前面、在業(yè)務(wù)數(shù)據(jù)的基礎(chǔ)上,加入 A/B Test 參數(shù),供整個(gè)推薦系統(tǒng)使用。所以它對于業(yè)務(wù)負(fù)載基本是透傳的。

很明顯,業(yè)務(wù)服務(wù)發(fā)給推薦系統(tǒng)的數(shù)據(jù)流量非常大,而作為透明傳輸業(yè)務(wù)數(shù)據(jù)的分流服務(wù),入?yún)⑿枰葱蛄谢?,出參需要重新序列化,這些都是無謂的算力消耗。

從分流服務(wù)的火焰圖上也可見一斑——作為主要邏輯的查詢實(shí)驗(yàn)參數(shù),僅占了不到 10% 的 CPU,剩下的 CPU 都花在 gc、序列化反序列化、RPC 上面:

微服務(wù)不香了?單體化改造為我們節(jié)省上萬核CPU

三、解決方案

從代碼上看,占流量大頭的數(shù)據(jù)結(jié)構(gòu),在整個(gè)調(diào)用鏈路上都是一致的,我們自然想到,省去網(wǎng)絡(luò)開銷,直接在內(nèi)存里存取該多好啊。

其他內(nèi)部團(tuán)隊(duì)其實(shí)也曾經(jīng)提出一個(gè) “單體大應(yīng)用融合落地方案”,給了我們很大啟發(fā)。不過,文檔里面只是提出了將所有的微服務(wù)合并在一個(gè) pod 中進(jìn)行部署,服務(wù)間調(diào)用依然是 RPC 而不是內(nèi)存調(diào)用。

實(shí)際上我們觀察一下 tRPC 的 RPC 調(diào)用方法,可以看到所有的 RPC 調(diào)用,對 Go 業(yè)務(wù)代碼來看是以一個(gè) Go interface 的形式給出的;而實(shí)現(xiàn)方實(shí)現(xiàn)對外提供服務(wù)的方式,從業(yè)務(wù)層面也只是實(shí)現(xiàn)相應(yīng)的 server interface 就可以。也就是說,服務(wù)的 client 端和 server 端,看到和實(shí)現(xiàn)的,都只是普通的 Go 函數(shù)。在此思路上,我們團(tuán)隊(duì)的同學(xué)在該文檔的基礎(chǔ)上,提出了一個(gè)將 RPC "mock" 成本地函數(shù)調(diào)用的方案,并由我落地驗(yàn)證了。

本文旨在向讀者詳細(xì)說明基于 tRPC 的微服務(wù)單體化方案的一種實(shí)現(xiàn)方法。代碼改造還是有必要的,但我們的目標(biāo)是盡可能減少代碼改造量,避免入侵業(yè)務(wù)。

1.RPC 背景

以我們的重排服務(wù)為例,重排服務(wù)需要實(shí)現(xiàn)這樣的一個(gè) PB:

service FeedsRerank {

rpc GetFeedList (GetFeedRequest) returns (GetFeedReply) {}

}

通過 tRPC 命令行工具 build 之后,會(huì)生成一個(gè)xxx.trpc.go文件,其中包含 service 接口:

type FeedsRerankService interface {

GetFeedList(ctx context.Context, req *GetFeedRequest) (*GetFeedReply, error)

}

作為服務(wù)端,需要實(shí)現(xiàn)這個(gè)接口,并在 mAIn 函數(shù)中調(diào)用 RegisterFeedsRerankService 注冊實(shí)現(xiàn), tRPC 會(huì)自動(dòng)對接框架和代碼實(shí)現(xiàn)。

同時(shí)還會(huì)生成另外一個(gè) client 接口:

type FeedsRerankClientProxy interface {

GetFeedList(ctx context.Context, req *GetFeedRequest, opts ...client.Option) (*GetFeedReply, error)

}

一般而言,任意一個(gè) client 要調(diào)用重排服務(wù)的話,只需要 client := pb.NewFeedsRerankClientProxy(),然后就可以直接調(diào)用 GetFeedList 方法了,tRPC 幫調(diào)用方隱藏了底層 RPC 細(xì)節(jié)。對調(diào)用方而言,這就只是一個(gè)函數(shù)而已。對,函數(shù)?。?!

2.代碼改造

1)Client 側(cè)

我們的思路是:作為 rerank 這個(gè)微服務(wù),要將自己的入口映射到某處;而 client 方不要自行 new 下游的 proxy,而是從這個(gè)地方統(tǒng)一取(我們把這個(gè)叫做 proxy API),這樣我們就可以實(shí)現(xiàn)了。用 Go 的語言來描述, 調(diào)用方看到的只是一個(gè) interface, 那我們就在內(nèi)存把被調(diào)用方的代碼按照這個(gè) interface 進(jìn)行實(shí)現(xiàn), 然后想辦法讓 client 端直接用上這個(gè)實(shí)現(xiàn),就可以了!

考慮到絕大部分的 trpc proxy 都只是使用默認(rèn)參數(shù)進(jìn)行初始化即開箱即用,因此我們就將這些都統(tǒng)一收攏起來,構(gòu)建了一個(gè)獲取各種 client proxy 的 repo 倉庫(比如就簡單命名為 "api"),clent 方從這個(gè)倉庫的 getter 函數(shù)中獲取自己需要的 client,如:

rerank := api.FeedsRerank()

rsp, err := rerank.GetFeedList(ctx, req)

// .....

2)Server 側(cè)

Server 是提供服務(wù)的一側(cè),每個(gè)微服務(wù),首先要把自己的業(yè)務(wù)代碼完全抽出來,不要放在 main 包中——這個(gè)改造并不難。各微服務(wù)的業(yè)務(wù)邏輯,可以抽取出來稱為 service 包,對外暴露一個(gè) Register 函數(shù),這個(gè)函數(shù)的入?yún)⒅邪?trpc-go/service.Server 類型,用于調(diào)用 tRPC 服務(wù)注冊函數(shù),如重排服務(wù):

pb.RegisterFeedsRerankService(server, rerankImpl)

這是原本就有的常規(guī)操作。但是除此之外,還需要調(diào)用前文的 proxy API,將自己的實(shí)現(xiàn) mock 一下。需要注意的是,tRPC 的 client proxy 函數(shù)參數(shù),相比 server 側(cè)實(shí)現(xiàn)的方法,多了一個(gè) opts ...client.Option 參數(shù)。不過絕大多數(shù)情況下,我們忽略這些參數(shù)就好了。

還是以重排為例,簡單用以下代碼 mock 一下自身:

type rerankProxy struct {

impl *rerankImpl

}

func (r *rerankProxy) GetFeedList(

ctx context.Context, req *pb.GetFeedRequest, opts ...client.Option,

) (*pb.GetFeedReply, error) {

rsp := &pb.GetFeedRequest{}

err := r.impl.GetFeedList(req, rsp)

return rsp, err

}

func (impl *rerankImpl) mockProxy() {

r := &rerankProxy{impl: impl}

proxyAPI.RegisterFeedsRerank(p)

}

可以看到, 除了通過 rerankImpl 類型實(shí)現(xiàn)了作為 server 端的 FeedsRerankService 接口之外, 也通過 rerankProxy 類型實(shí)現(xiàn)了 client 端的 FeedsRerankClientProxy 接口。這樣,當(dāng)上游調(diào)用時(shí), 統(tǒng)一從 proxy API 中獲取 proxy 接口實(shí)現(xiàn), 在微服務(wù)場景下,那么就是一個(gè)正常的 RPC 調(diào)用;但是在單體場景下,不知不覺地就只是一個(gè)內(nèi)存的調(diào)用了。

3)main 包

我們在原有邏輯中,每一個(gè)微服務(wù)的邏輯都寫在 main 包中。支持單體化的改造之后,每一個(gè)微服務(wù)的邏輯都應(yīng)挪到一個(gè)非 main 包中,并且微服務(wù)依賴的各種組件盡量使用注入,而不是由微服務(wù)內(nèi)部初始化。包括微服務(wù)所依賴的 client proxy 接口。

4)Proxy API 實(shí)現(xiàn)

前文提到的 Proxy API 的實(shí)現(xiàn)原理很簡單,各 client proxy 只需要默認(rèn)調(diào)用 NewXxx 函數(shù)初始化即可(比如對應(yīng)前文的 NewFeedsRerankClientProxy),得益于 tRPC 的懶初始化機(jī)制,這些 Proxy 創(chuàng)建了之后,只要不去調(diào)用它,那么即便配置里不包含相關(guān)的 client 配置,就不會(huì)報(bào)錯(cuò)。因此,雖然在 Proxy API 中初始化了多個(gè) Proxy,也不會(huì)對具體到某個(gè)微服務(wù)造成影響。

至于 mock 動(dòng)作,則通過 RegisterXxxx 函數(shù)(比如前文的 RegisterFeedsRerank)實(shí)現(xiàn)。具體落到細(xì)節(jié)處,也只不過是一個(gè)個(gè)的私有成員變量而已。

Proxy API 的代碼大致框架如下:

package proxyapi

type API interface {

FeedsRerank() pb.NewFeedsRerankClientProxy

RegisterFeedsRerank(p pb.NewFeedsRerankClientProxy)

}

func DefaultAPI() API {

return defaultAPIImpl

}

type apiImpl struct {

internalFeedsRerankClientProxy pb.FeedsRerankClientProxy

internalXxxxClientProxy pb.XxxxClientProxy // 作為實(shí)例, 其他的微服務(wù)模式類似, 下同

// ...

}

var _ API = (*apiImpl)(nil)

var defaultAPIImpl = new()

func new() *apiImpl {

return &apiImpl{

internalFeedsRerankClientProxy: pb.NewFeedsRerankClientProxy(), // trpc 的默認(rèn) client 初始化邏輯

internalXxxxClientProxy: pb.NewXxxxClientProxy(),

// ...

}

}

func (a *apiImpl) FeedsRerank() pb.NewFeedsRerankClientProxy {

return a.internalFeedsRerankClientProxy

}

func (a *apiImpl) RegisterFeedsRerank(p pb.NewFeedsRerankClientProxy) {

if p != nil {

a.internalFeedsRerankClientProxy = p

}

}

// ...

上面的重復(fù)邏輯挺多,為了減少無意義的重復(fù)代碼開發(fā),我們在代碼中編寫了 shell 腳本,并且通過 go generate 來生成上述代碼。

四、部署改造

按照前文所述,我們用一個(gè)單體大應(yīng)用,包含了五個(gè)微服務(wù)。那么我們在部署的時(shí)候,要如何配置呢?

1.服務(wù)配置

首先,我們要決定這個(gè)單體應(yīng)用對外暴露的微服務(wù)接口有哪些。如果你需要暴露多個(gè)微服務(wù)入口,那么就需要在啟動(dòng)時(shí)傳入的 trpc_go.yaml 中配置對應(yīng)的多個(gè)微服務(wù)注冊和監(jiān)聽地址。

我們的場景比較簡單,因?yàn)檎麄€(gè)推薦系統(tǒng)是一個(gè)單鏈?zhǔn)秸{(diào)用,所以我們只需要對外暴露業(yè)務(wù)層的服務(wù)即可。注冊也直接注冊到原有的業(yè)務(wù)層對應(yīng)的北極星節(jié)點(diǎn)上。

那么剩下的幾個(gè)微服務(wù)呢?每一個(gè)微服務(wù)可是都調(diào)用了 tRPC 的 RegisterXxxx 函數(shù)哦?請讀者放心,tRPC register 的時(shí)候,如果查不到對應(yīng)的配置入口,那么 tRPC 也只是什么都不做而已,不會(huì)導(dǎo)致進(jìn)程的 panic。

2.配置配置

在啟動(dòng)時(shí)傳入的 trpc_go.yaml 文件中,我們還需要添加各微服務(wù)所需要的配置入口。這個(gè)時(shí)候,我們就需要將每一個(gè)微服務(wù)所需的所有配置,都配置上。需要注意的是,如果之前不同的微服務(wù)采用了同樣的配置名,卻實(shí)現(xiàn)了不同的功能,那么在代碼改造的時(shí)候需要修改一下,要不然在此處會(huì)發(fā)生沖突。

五、收益

1.降本增效

進(jìn)行單體化改造之前,推薦系統(tǒng)五個(gè)服務(wù),在我們預(yù)定的容量下,預(yù)估需要接近 18,000 核。經(jīng)過單體優(yōu)化之后,在沒有修改任何邏輯的前提下,就將這個(gè)數(shù)字降到 7000,優(yōu)化掉了足有 61%??梢?RPC 給我們系統(tǒng)帶來的開銷有多大。

此外我們后續(xù)又做了不少算法和業(yè)務(wù)層面的優(yōu)化,又降到了 1000 核的水平,主要是緩存優(yōu)化、前置計(jì)算和閑時(shí)算力的優(yōu)化。

該方案雖然實(shí)現(xiàn)了一個(gè)單體化的大服務(wù),但是完全不妨礙其他租戶的業(yè)務(wù)采用微服務(wù)化的部署??梢哉f,我們在開發(fā)階段依然是用微服務(wù)模式開發(fā),并且在不同租戶下采用了不同的部署模式??芍^是在低改造量前提下實(shí)現(xiàn)了 “既要又要”。

2.擴(kuò)展思考

當(dāng)然,單體化之后的服務(wù),在運(yùn)維層面自然會(huì)帶來宏服務(wù)的缺點(diǎn),比如說運(yùn)維困難,模塊迭代不靈活等等。這個(gè)時(shí)候就需要我們?nèi)?quán)衡利弊、綜合各項(xiàng)因素之后,再做出決策了。

本文所實(shí)踐的方法,其實(shí)對于其他 Go 語言框架也都是通用的,包括且不限于 Gin、gRPC。只要開發(fā)者在進(jìn)行微服務(wù)開發(fā)的時(shí)候,遵循以下原則,那么微服務(wù)和單體之間的切換就非常方面:

  • 功能和接口在傳遞時(shí),盡量通過 interface 進(jìn)行實(shí)現(xiàn)細(xì)節(jié)的隱藏,這也便于微服務(wù)和單體架構(gòu)的無感切換;
  • 模塊、組件甚至整個(gè)服務(wù)邏輯的初始化,盡可能采用依賴注入,盡可能減少使用 init 進(jìn)行重度的初始化;
  • 每一個(gè) package 的功能盡可能簡單、獨(dú)立、明確,避免一個(gè) package 中耦合了大量復(fù)雜邏輯。

作者丨張敏

來源丨公眾號(hào):騰訊云開發(fā)者(ID:QcloudCommunity)

分享到:
標(biāo)簽:微服
用戶無頭像

網(wǎng)友整理

注冊時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定