新聞中心
重要術(shù)語(yǔ)解釋
- 內(nèi)容 :包括商品、圖文、視頻等,服務(wù)端通過算法推薦,最終下發(fā)給客戶端消費(fèi)的內(nèi)容
- 補(bǔ)齊數(shù)據(jù)源 :對(duì)于內(nèi)容id,提供一個(gè)或多個(gè)維度的關(guān)聯(lián)信息
- 內(nèi)容補(bǔ)齊 :獲取到內(nèi)容id后,請(qǐng)求不同補(bǔ)齊數(shù)據(jù)源,獲取內(nèi)容周邊素材,用于客戶端展示
常見服務(wù)分類
作者在阿里做過和學(xué)習(xí)過不少服務(wù)實(shí)現(xiàn),如下,給阿里服務(wù)體系中常見服務(wù)大致分一個(gè)類,每個(gè)類別有些是應(yīng)用層,有些是中間層,這里不作贅述,這里我們重點(diǎn)討論內(nèi)容型服務(wù)

主要關(guān)注點(diǎn)
比如對(duì)于我們淘系業(yè)務(wù),搭建一套服務(wù),我們需要先想清楚下面的幾個(gè)點(diǎn)
- 內(nèi)容來源:從算法、運(yùn)營(yíng)配置或者其他渠道來,唯一標(biāo)識(shí)內(nèi)容id的key向量是哪些,基于這些id,如何進(jìn)行相關(guān)內(nèi)容補(bǔ)齊
- 內(nèi)容約束:算法推薦比較穩(wěn)定,但是運(yùn)營(yíng)配置的內(nèi)容,可能會(huì)過期,不能滿足客戶端展示需求,我們需要保證內(nèi)容完整性,校驗(yàn)內(nèi)容的一致性
- 內(nèi)容運(yùn)維:內(nèi)容下發(fā)服務(wù)上線后,業(yè)務(wù)會(huì)不斷變化,內(nèi)容篩選條件的增減,客戶端也在不斷迭代,接口設(shè)計(jì)需要考慮靈活性
常見問題及解決方案
面臨問題
- 各種字段需要補(bǔ)齊 :不同字段來源于不同數(shù)據(jù)源,容易出現(xiàn)面條式代碼,需要靈活的架構(gòu)來處理這種復(fù)雜度
- 不同場(chǎng)景對(duì)于下發(fā)字段的校驗(yàn)要求不同 :需要基于標(biāo)注的Validator模塊來處理字段粒度判斷
- 運(yùn)營(yíng)和產(chǎn)品的需求經(jīng)常變化 :對(duì)于tab排序、篩選條件,接口設(shè)計(jì)要考慮擴(kuò)展性,提供運(yùn)營(yíng)能力
解決方案
構(gòu)建一套pipeline,每個(gè)處理節(jié)點(diǎn)關(guān)注一個(gè)問題解決
- Datasource :數(shù)據(jù)來源(算法推薦、數(shù)據(jù)庫(kù)、緩存、運(yùn)營(yíng)配置、置頂數(shù)據(jù))。方法名:fromXXX
- Transfer :類型轉(zhuǎn)換。方法名:toXXX
- Filter :數(shù)據(jù)過濾(黑白名單,字段約束)。方法名:byXXX
- Sorter :數(shù)據(jù)排序。方法名:topXXX、shuffleXXX
- Completer :補(bǔ)數(shù)據(jù)。方法名:addXXX
- Validator :有效性驗(yàn)證,過濾不符合要求的數(shù)據(jù),比如商品某個(gè)字段缺失。方法名:checkXXX
- Factory :基于基本元素,數(shù)據(jù)拼接生產(chǎn)。方法名:createXXX
- Iterator :保存各類遍歷過程
是不是看起來有點(diǎn)類似Java8中stream的API,但這套pipeline具體還是偏內(nèi)容下發(fā)業(yè)務(wù),比Java原生API要更豐富,以淘寶的AR淘業(yè)務(wù)為例,pipeline如下:
代碼實(shí)現(xiàn)效果
下面代碼實(shí)現(xiàn)一套基于運(yùn)營(yíng)配置數(shù)據(jù)源的pipeline
1.首先,自定義Pipeline,沒有使用Lambada,對(duì)java7及以下也適用
public class PipeLine{
private List> functionList = new ArrayList<>();
public PipeLineadd(PipeLineFunction pipeLineFunction) {
functionList.add(pipeLineFunction);
return this;
}
public D execute(D data, C context) throws Exception {
for (PipeLineFunction function : functionList) {
data = (D) function.execute(data, context);
}
return data;
}
}
2.其次管線,對(duì)于處理次序和節(jié)點(diǎn)進(jìn)行指定:包括從配置讀取數(shù)據(jù)--->通過arType過濾--->隨機(jī)打亂數(shù)據(jù)--->置頂主題類數(shù)據(jù)--->翻頁(yè)--->增加sku和item信息--->增加AR模型信息--->完整性校驗(yàn)
public void initSkuResultHotRecommendPipeLine() {
PipeLine skuResultHotRecommendPipeLine = new PipeLine();
skuResultHotRecommendPipeLine
.add((data, context) -> skuResultDataSource.fromConfig(context))
.add((data, context) -> skuResultSorter.shuffle(data))
.add((data, context) -> skuResultSorter.topTheme(data, context))
.add((data, context) -> skuResultSorter.page(data, context))
.add((data, context) -> skuResultCompleter.addSkuInfo(data))
.add((data, context) -> skuResultCompleter.addAREffect(data, context))
.add((data, context) -> skuResultValidator.check(data));
}
3.最后,搭建pipeline,接口收到請(qǐng)求后,通過管線處理,下發(fā)對(duì)應(yīng)內(nèi)容
public ResultVOgetSkuList(SkuQuery skuQuery) {
try {
SkuResultVO skuResultVO = skuResultHotRecommendPipeLine
.execute(new SkuResultVO(), skuQuery);
} catch (Exception e) {
log.error("", e.fillInStackTrace());
return ResultVO.failOf(e.getMessage());
}
return ResultVO.failOf(CameraArCause.No_Valid_Ar_Type
.toMessage(skuQuery.toString()));
}
4.我們?cè)儆懻撘幌聦?duì)于有固定的遍歷邏輯的情況,遍歷方式也可以抽象成一個(gè)iterator,在不同的filter作為參數(shù)傳入下,完成遍歷功能,下圖就是對(duì)商品的一種遍歷,這個(gè)特性用到FunctionalInterface標(biāo)注,需要java8及以上
(1) 定義遍歷器
@FunctionalInterface
public interface FilterFunction{
boolean execute(T t) throws Exception;
}
@FunctionalInterface
public interface IterateFunction{
T execute(T t, FilterFunction filterFunction);
}
private IterateFunctionskuVOFilterIterator = (skuResultVO, filter) -> {
ListskuFeedUnitVOList = skuResultVO.getSkuFeedUnitVOList()
.stream().filter(skuFeedUnitVO -> {
ListfilterSkuVOList = skuFeedUnitVO.getSkuVOList()
.stream().filter(skuVO -> {
try {
return filter.execute(skuVO);
} catch (Exception e) {
log.warn("", e);
return false;
}
}).collect(toList());
if (filterSkuVOList.size() == 0) {
return false;
}
skuFeedUnitVO.setSkuVOList(filterSkuVOList);
return true;
}).collect(toList());
if (skuFeedUnitVOList.size() == 0) {
log.warn(CameraArCause.No_Valid_Sku_Feed_Unit_List
.toMessage(skuResultVO.toString()));
}
skuResultVO.setSkuFeedUnitVOList(skuFeedUnitVOList);
return skuResultVO;
};
(2) 在Filter模塊中使用遍歷器,如果把skuResultVO換成一個(gè)返回SkuResultVO的Supplier,是不是有點(diǎn)柯里化的味道啦?
public SkuResultVO byArType(SkuResultVO skuResultVO) {
return skuResultIterator.getSkuVOFilterIterator()
.execute(skuResultVO, (FilterFunction) skuVO ->
!CameraArSwitch.Black_List_Config.getArType()
.contains(skuVO.getArType()));
}
整體架構(gòu)
- API & View層:為各類客戶端、服務(wù)端提供接口
- Controller層:不同來源的調(diào)用適配,權(quán)限控制
- Manager層:每個(gè)接口pipeline的各個(gè)節(jié)點(diǎn)實(shí)現(xiàn)、二方和三方包封裝
- Middleware層:集團(tuán)常見中間件
- Model層:按照阿里Java規(guī)范的各個(gè)層級(jí)POJO
- Common層:自研公共組件,主要是切面類、native命令執(zhí)行類等
Tips
淘系商品信息補(bǔ)全
對(duì)于補(bǔ)齊數(shù)據(jù)源的選擇,要詳細(xì)了解上游各個(gè)補(bǔ)齊數(shù)據(jù)源的業(yè)務(wù)定位和業(yè)務(wù)邊界,選擇合適的補(bǔ)齊數(shù)據(jù)源。比如淘系商品補(bǔ)全數(shù)據(jù)源常見服務(wù)有以下幾個(gè),要根據(jù)業(yè)務(wù)自身需求
|
數(shù)據(jù)補(bǔ)齊二方服務(wù) |
優(yōu)勢(shì) |
劣勢(shì) |
|
商品中心(IC) |
最底層的數(shù)據(jù)源,有item和sku維度的信息 |
維度不夠多,比如一些商品的運(yùn)營(yíng)信息 |
|
阿拉丁 |
維度比較多,比如商品白底圖 |
只有item維度信息,但是有些數(shù)據(jù)源不穩(wěn)定,比如品牌信息有些商品會(huì)缺失 |
|
搜索的Summary |
跟主搜的信息保持一致,信息比較實(shí)時(shí),比如優(yōu)惠、銷量信息 |
只有item維度信息,維度不夠多 |
篩選能力
內(nèi)容下發(fā)除了做好個(gè)性化,如果是一個(gè)公域產(chǎn)品,對(duì)內(nèi)容的篩選能力決定用戶是否能主動(dòng)找到自己想要的商品,我們需要設(shè)計(jì)一個(gè)易擴(kuò)展的篩選器接口,常見的垂直頻道類產(chǎn)品,一級(jí)和二級(jí)tab頁(yè)就滿足業(yè)務(wù)訴求,但對(duì)應(yīng)比較大的公域,比如搜索,需要支持多維度篩選+多篩選能力,都需要實(shí)現(xiàn)兩個(gè)接口,這時(shí), 我們需要設(shè)計(jì)一個(gè)通用的接口格式,做好兩件事。
- 下發(fā)篩選器
- 上傳用戶選擇的篩選項(xiàng)
- 一級(jí)和二級(jí)tab頁(yè)
只需要下發(fā)一級(jí)和二級(jí)的tab樹狀接口即可,用戶通過先后選擇一級(jí)和二級(jí)tab來過濾內(nèi)容,這里不作過多的討論。
- 多維度篩選器
需要下發(fā)多維度篩選器,如果有一級(jí)Tab,多維度篩選器放在每個(gè)Tab內(nèi)部,例子如下:
1.篩選器下發(fā)接口,格式如下,其中Name結(jié)尾的字段為前端展示,Id結(jié)尾的字段作為篩選上傳的接口字段
{
"tabList": [
{
"tabName": "tab1",
"tabId": "xxx",
"filterList": [
{
"filterName": "xxx",
"filterId": "xxx",
"filterItemList": [
{
"filterItemName": "fitler1",
"filterItemId": "xxx"
},
{
"filterItemName": "fitler2",
"filterItemId": "xxx"
}
]
}
]
},
{
"tabName": "tab2",
"tabId": "xxx",
"filterList": [
{
"filterName": "xxx",
"filterId": "xxx",
"filterItemList": [
{
"filterItemName": "fitler1",
"filterItemId": "xxx"
},
{
"filterItemName": "fitler2",
"filterItemId": "xxx"
}
]
}
]
}
]
}
2.上傳用戶選擇的篩選項(xiàng),接口格式如下,tabId可以作為單獨(dú)的字段傳,filterList是另外一個(gè)字段
{
"tabId": "xxx",
"filterList": [
{
"filterId": "xxx",
"filterItemList": [
{
"filterItemId": "xxx"
},
{
"filterItemId": "xxx"
}
]
},
{
"filterId": "xxx",
"filterItemList": [
{
"filterItemId": "xxx"
},
{
"filterItemId": "xxx"
}
]
}
]
}
總結(jié)和展望
總結(jié)
本文通過大淘寶業(yè)務(wù)的例子,梳理出一套 內(nèi)容服務(wù)下發(fā)內(nèi)容時(shí)面臨的問題和挑戰(zhàn)、設(shè)計(jì)一套內(nèi)容處理鏈路,用模塊化的設(shè)計(jì)來控制系統(tǒng)的復(fù)雜度,并且對(duì)接口上線后,如何靈活運(yùn)維進(jìn)行了討論,重點(diǎn)研究了保證易擴(kuò)展、易運(yùn)維的思路,希望對(duì)各位有所啟發(fā)。
展望
內(nèi)容下發(fā)服務(wù)已經(jīng)有些團(tuán)隊(duì)推出了前后端一體的方案,客戶端不用請(qǐng)求應(yīng)用服務(wù)來拿商品數(shù)據(jù),通過統(tǒng)一的平臺(tái)接口,把客戶端布局和內(nèi)容填充一起請(qǐng)求來返回,這么做的好處是效率比較高,迭代起來快,帶來的不便是客戶端和平臺(tái)耦合比較緊,靈活性變差,比如客戶端有一塊內(nèi)容源不走平臺(tái),就會(huì)比較麻煩
分享文章:如何設(shè)計(jì)一個(gè)易擴(kuò)展、易運(yùn)維的內(nèi)容下發(fā)服務(wù)架構(gòu)?
分享鏈接:http://www.fisionsoft.com.cn/article/dpcisie.html


咨詢
建站咨詢
