在内容为王的时代,任何一个ToCApp都会有内容分发服务,包括产品、图文、视频等,这些页面的业务变化非常快。如何设计一个易于扩展、稳定、低延迟的内容分发接口,需要开发者不断思考和探索。作者总结了内容交付服务中遇到的常见问题和挑战,设计了一套灵活的架构来支持不断变化的业务,着重梳理了内容服务交付内容时需要注意的点,并拆分了内容分发服务的各个环节。通过分层架构,最终达到易扩展、易运维的业务效果。重要术语解释内容:包括商品、图片、视频等,由服务端通过算法推荐,最终发送给客户端进行消费内容完成数据源:针对内容id,提供一个或多个维度的关联信息内容completion:获取contentid后,请求不同的数据源补全,获取content周边素材,用于客户端展示常用的服务分类。笔者在阿里做过和学习过很多服务实现。分为一类,每一类有的是应用层,有的是中间层。我不会在这里详细介绍。在这里,我们重点关注基于内容的服务的主要关注点。比如我们淘宝业务,要搭建一套服务,我们需要想清楚以下几个内容来源:从算法、运营配置或者其他渠道,唯一标识内容id的关键向量有哪些?根据这些id,如何完成相关内容。可能会过期,无法满足客户端的显示要求。我们需要保证内容的完整性,验证内容的一致性。在不断的迭代中,界面设计需要考虑常见问题和解决方案的灵活性。各种字段需要完成:不同的字段来自不同的数据源,容易出现面条代码,需要灵活的架构来应对如此复杂的场景。对下发字段的校验需求不同:需要基于注解的Validator模块处理字段粒度判断。运营和产品需求变化频繁:针对tab排序和过滤条件,接口设计要考虑可扩展性,提供运营能力构建解决方案的管道,每个处理节点专注于解决一个问题Datasource:数据源(算法推荐,数据库,缓存、操作配置、顶级数据)。方法名:fromXXXTransfer:类型转换。方法名:toXXXFilter:数据过滤(黑白名单,字段约束)。方法名:byXXXSorter:数据排序。方法名:topXXX、shuffleXXXCompleter:补充数据。方法名:addXXXValidator:有效性验证,过滤掉不符合要求的数据,比如缺失某个商品的某个字段。方法名:checkXXXFactory:基于基本元素,数据拼接生产。方法名:createXXXIterator:保存各种遍历过程。是不是有点像Java8中stream的API,但是这个pipeline是针对内容分发业务的,比Java原生的API更加丰富。以淘宝的AR淘宝业务为例,流水线如下:代码实现效果下面的代码实现了一组基于运营配置数据源的流水线1。一、不使用Lambada自定义Pipeline,java7及以下同样适用。publicclassPipeLine{privateList>functionList=newArrayList<>();publicPipeLineadd(PipeLineFunctionpipeLineFunction){functionList.add(pipeLineFunction);归还这个;}publicDexecute(Ddata,Ccontext)throwsException{for(PipeLineFunctionfunction:functionList){data=(D)function.execute(data,context);}返回数据;}}2.其次,pipeline指定处理顺序和节点:包括slave配置读取数据--->按arType过滤--->随机打乱数据--->top主题数据--->翻转--->添加sku和项目信息--->添加AR模型信息----->完整性检查publicvoidinitSkuResultHotRecommendPipeLine(){PipeLineskuResultHotRecommendPipeLine=newPipeLine();skuResultHot推荐endPipeLine.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,接口接收到请求后,通过管线处理,下发对应内容publicResultVOgetSkuList(SkuQueryskuQuery){尝试{SkuResultVOskuResultVO=skuResultHotRecommendPipeLine.execute(newSkuResultVO(),skuQuery);}catch(Exceptione){log.error("",e.fillInStackTrace());返回结果VO.failOf(e.getMessage());}返回结果VO.failOf(CameraArCause.No_Valid_Ar_Type.toMessage(skuQuery.toString()));}4、再讨论一下,对于固定遍历逻辑的情况,也可以将遍历方法抽象成一个迭代器,将不同的过滤器作为参数传入,完成遍历功能。图是积的一种遍历。此功能标有FunctionalInterface,需要java8及以上(1)定义遍历器@FunctionalInterfacepublicinterfaceFilterFunction{booleanexecute(Tt)throwsException;}@FunctionalInterfacepublicinterfaceIterateFunction{Texecute(Tt,FilterFunctionfilterFunction);}privateIterateFunctionskuVOFilterIterator=(skuResultVO,filter)->{ListskuFeedUnitVOList=skuResultVO.getSkuFeedUnitVOList().stream().filter(skuFeedUnitVO->{列表filterSkuVOList=skuFeedUnitVO.getSkuVOList().stream().filter(skuVO->{try{returnfilter.execute(skuVO);}catch(Exceptione){log.warn("",e);returnfalse;}}).collect(toList());如果(filterSkuVOList.size()==0){返回false;}skuFeedUnitVO.setSkuVOList(filterSkuVOList);返回真;}).collect(toList());如果(skuFeedUnitVOList.size()==0){日志。警告(CameraArCause.No_Valid_Sku_Feed_Unit_List.toMessage(skuResultVO.toString()));}skuResultVO.setSkuFeedUnitVOList(skuFeedUnitVOList);返回skuResultVO;};(2)如果在Filter模块中使用遍历器,如果将skuResultVO替换成一个返回SkuResultVO的Supplier,是不是有点咖喱味?publicSkuResultVObyArType(SkuResultVOskuResultVO){returnskuResultIterator.getSkuVOFilterIterator().execute(skuResultVO,(FilterFunction)skuVO->!CameraArSwitch.Black_List_Config.getArType().contains(skuVO.getArType()));API&View层:为各种客户端和服务端提供接口Controller层:不同来源的调用适配,权限控制Manager层:各接口管道各节点的实现,二方三方封装封装Middleware层:集团公共中间件模型层:按照阿里Java规范在每一层的POJOCommon层:自研公共组件,主要是切面类、原生命令执行类等。Tips淘货信息补全对于补充数据源的选择,需要详细了解各个上游补充的业务定位和业务边界数据源,选择合适的补充数据源,比如下面淘宝商品补充数据源的常用服务,要根据业务自身的需求进行补充。商品中心(IC)底部的数据源具有item和sku维度。信息维度不够。例如,一些产品的运营信息有更多的维度。比如商品的白底图只有item维度信息,但是有些数据源不稳定。例如,品牌信息和一些产品会缺少摘要和主要搜索信息。保持一致,信息相对实时。比如打折、促销信息只有商品维度信息,维度不够。筛选能力除了个性化,如果是公域产品,内容筛选能力决定了用户能否主动找到自己想要的产品,我们需要设计一个易于扩展的筛选接口。对于常见的垂直渠道产品,一级二级tab页可以满足业务需求,但是对应比较大的公共领域,比如搜索,需要支持多维度筛选+多筛选能力需要实现两个接口.这时候就需要设计一个通用的接口格式来做好两件事。发送过滤器上传用户选择的过滤器项。一级二级标签页只需要传递一级二级标签树接口即可。用户可以通过选择一级和二级标签来过滤内容,这里不再赘述。太多的讨论。多维过滤器需要传递多维过滤器。如果有一级选项卡,则多维过滤器放在每个选项卡内。示例如下:1.过滤下发接口,格式如下,其中Name末尾的字段为前端展示,Id末尾的字段作为过滤的接口字段,上传{“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。上传用户选择的过滤项。界面格式如下,tabId可单独使用单独字段pass,filterList是另外一个字段:"xxx"}]},{"filterItemId":"xxx","filterItemList":[{"filterItemId":"xxx"},{"filterItemId":"xxx"}]}]}总结与展望总结,本文通过大淘宝业务的例子,梳理了一套内容服务在交付内容时面临的问题和挑战,设计了一套内容处理环节,并采用模块化设计来控制系统的复杂度,之后接口上线,探讨如何灵活运维,着重保证易扩展易运维的思想。我希望能启发你。展望内容分发服务,部分团队推出了前后端一体化方案,客户端无需请求应用服务。获取产品数据,通过统一的平台接口,请求客户端布局和内容填充一起返回。这样做的好处是效率更高,迭代速度快。不便之处在于客户端和平台是紧耦合和灵活的。性变得更糟。比如客户端有个内容源不上平台,就比较麻烦