一、背景vivo评论平台提供评论发布、点赞、举报、自定义评论排序等常用能力,帮助前端业务快速构建评论功能,并提供评论操作能力,避免前端业务重复建设和数据孤岛问题。目前已有vivo短视频、vivo浏览器、负一屏、vivo商城等10+业务接入。这些业务的流量大小和波动幅度各不相同。如何保证每个前端业务的高可用,避免某个业务的流量突然暴增导致其他业务不可用?所有商家的评论数据都由中台存储。他们的数据量不同,DB压力不同。作为中台,各个业务的数据应该如何隔离,才能保证整个中台系统的高可用?本文将与大家分享vivo点评平台的解决方案,主要从流量隔离和数据隔离两部分。2.流量隔离2.1流量分组vivo浏览器业务日活亿级,实时热点新闻全网推送。对于这种用户量大、流量大的重要业务,我们提供单独的集群为其提供服务,避免受到其他业务的影响。影响。vivo点评平台通过Dubbo接口对外提供服务。我们通过Dubbo标签路由对整个服务集群进行逻辑划分。Dubbo调用可以根据请求中携带的标签智能选择标签对应的服务提供者。转移。如下图所示:1)Providerlabeling:目前有两种方式完成实例分组,分别是动态规则标记和静态规则标记,其中动态规则的优先级高于静态规则,当这两种规则同时存在时同时发生冲突时,动态规则占上风。公司内部运维系统非常支持动态标记,只需标记指定ip的机器即可(不是docker容器,机器ip是固定的)。2)前端消费者指定的服务标签:发起请求时设置,如下;前端在中台指定路由标签RpcContext.getContext().setAttachment(Constants.REQUEST_TAG_KEY,"browser");请求标签的范围是每次调用,只需要在评论中调用平台服务前设置标签,前台业务调用其他业务的提供者,不受路由标签的影响。2.2多租户限流大流量业务通过独立集群隔离。但是独立部署集群的成本较高,无法为每个前端业务独立部署一套集群。大多数情况下,多个服务还是需要共享一套集群,那么当共享集群的服务遇到突发流量时,如何处理突发流量呢?没错,限流!但是目前很多限流都是采用一刀切的方式来限制接口的整体QPS。这样,某个前端业务的流量突然增加,就会导致所有的前端业务请求都被限制。这就需要多租户限流的出现(这里的一个租户可以理解为前端业务),支持对同一接口不同租户的流量进行限流处理。特点,使用服务ID码作为热点参数,为每个服务配置不同的流量控制大小。那么什么是热点参数限流呢?首先,什么是热点?热点是经常访问的数据。很多时候我们想统计某个热点数据中访问频率最高的前n条数据,限制其访问。例如:商品ID为参数,统计一段时间内最常购买的商品ID。用户标识是一个参数,针对一段时间内频繁访问的用户标识进行限制。热点参数限流会统计传入参数中的热点参数,根据配置的节流阈值和模式限制包含热点参数的资源调用。热点参数限流可以看作是一种特殊的流量控制,只对包含热点参数的资源调用生效。Sentinel采用LRU策略统计最近访问的热点参数,并结合令牌桶算法进行参数级流控。下图是一个注释场景的例子:使用Sentinel做资源保护主要分为几个步骤:定义资源、定义规则、处理规则生效。1)定义资源:这里可以理解为各个中台的API接口路径。2)定义规则:Sentienl支持QPS流控、自适应限流、热点参数限流、集群限流等多种规则,这里我们使用单机热点参数限流。热点参数限流配置{"resource":"com.vivo.internet.comment.facade.comment.CommentFacade:comment(com.vivo.internet.comment.facade.comment.dto.CommentRequestDto)",//需要限流interface"grade":1,//QPS限流模式"count":3000,//接口默认限流大小为3000"clusterMode":false,//单机模式"paramFieldName":"clientCode",//specifiedhotspot参数名称为业务方代码字段。这里我们优化了sentinel组件,增加了这个配置属性,指定参数对象的属性名作为热点参数key"paramFlowItemList":[//热点参数限流规则{"object":"vivo-community",//当clientCode为该值时,匹配限流规则"count":1000,//当前限流大小为1000"classType":"java.lang.String"},{"object":"vivo-shop",//当clientCode为该值时,匹配限流规则"count":2000,//限流大小为2000"classType":"java.lang.String"}]}3)规则生效处理:Sentinel触发限流规则时会抛出ParamFlowException异常,直接把异常抛给前端业务处理并不优雅。Sentinel为我们提供了统一的异常回调处理入口DubboAdapterGlobalConfig,支持我们将异常转化为业务定义的结果并返回。自定义流量限制返回结果;DubboAdapterGlobalConfig.setProviderFallback((invoker,invocation,ex)->AsyncRpcResult.newDefaultAsyncResult(FacadeResultUtils.returnWithFail(FacadeResultEnum.USER_FLOW_LIMIT),invocation));我们做了哪些额外的优化:1)内部限流控制台暂不支持热点参数限流配置,所以我们新增了一个限流配置控制器,支持通过配置中心动态下发限流配置。整体流程如下:动态下发限流配置;公共类VivoCfgDataSourceConfig实现InitializingBean{privatestaticfinalStringPARAM_FLOW_RULE_PREFIX="sentinel.param.flow.rule";@OverridepublicvoidafterPropertiesSet(){//自定义配置分析对象VivoCfgDataSource>paramFlowRuleVivoDataSource=newVivoCfgDataSource<>(PARAM_FLOW_RULE_PREFIX,sources->sources.stream().map(source->JSON.parseObject(source,ParamFlowRule.class)).collect(Collectors.toList()));//注册配置有效监听器ParamFlowRuleManager.register2Property(paramFlowRuleVivoDataSource.getProperty());//初始化限流配置paramFlowRuleVivoDataSource.init();//监听配置中心VivoConfigManager.addListener(((item,type)->{if(item.getName().startsWith(PARAM_FLOW_RULE_PREFIX)){paramFlowRuleVivoDataSource.updateValue(item,type);}}));}}2)原生sentinel指定限流热点参数的方式有两种:第一种是指定接口方法的第n个参数;二是方法参数继承自ParamFlowArgument实现ParamFlowKey方法,该方法的返回值为热点参数的值。这两种方法都不灵活,第一种方法不支持指定对象属性。;第二种方法需要我们修改代码。如果某个接口参数没有继承ParamFlowArgument,上线后想配置热点参数限流,只能修改代码,发布版本。因此,我们对Sentinel组件的热点参数限流源码做了一些优化,增加了“指定参数对象的某个属性”作为热点参数,并支持对象级嵌套。小的代码改动大大方便了热点参数的配置。修改热点参数校验逻辑;publicstaticbooleanpassCheck(ResourceWrapperresourceWrapper,/*@Valid*/ParamFlowRulerule,/*@Valid*/intcount,Object...args){//忽略部分代码//获取参数值。如果值为空,则通过。对象值=args[paramIdx];如果(值==null){返回真;}//使用paramFlowKey方法的结果赋值if(valueinstanceofParamFlowArgument){value=((ParamFlowArgument)value).paramFlowKey();}else{//根据classFieldName指定的热点参数获取热点参数值if(StringUtil.isNotBlank(rule.getClassFieldName())){//通过反射获取参数对象中的classFieldName属性Valuevalue=getParamFieldValue(value,规则.getClassFieldName());}}//忽略部分代码}3、MongoDB数据隔离为什么需要数据隔离?有两个原因。第一点:中台存储了不同前端业务的数据,数据查询时各个业务的数据不能相互影响,业务A无法查询到业务B的数据。第二点:数据每个业务的级别不同,对db运行的压力也不同。比如流量隔离,我们为浏览器业务单独提供了一套服务集群,那么浏览器业务使用的db也需要单独配置,从而可以完全隔离其他业务的服务压力。vivo评测平台使用MongoDB作为存储介质(对数据库选型和Mongodb应用细节感兴趣的同学可以看我们之前的介绍《MongoDB 在评论中台的实践》)。为了隔离来自不同业务方的数据,点评平台提供了两种数据隔离方案:物理隔离和逻辑隔离。3.1物理隔离不同业务方的数据存储在不同的数据库集群中,这就需要我们的系统支持MongoDB的多种数据源。实现过程如下:1)寻找合适的入口通过分析spring-data-mongodb的执行过程源码,发现在执行所有语句之前,会执行一个getDB()动作,获取数据库连接实例,如下。spring-data-mongodbdb操作源码;private
