架构详解——淘系圈品进化史

图2.8 横向分页处理

在处理商品增加消息时,需要循环64张表中求min和max直到找到该start和end在哪张表中,然后在该表中根据start和end取出符合的商品,核心代码逻辑如下所示。

public List getCampaignItemRelationList(int start, int end,

Function<Integer, Long> getMaxId,

Function<Integer, Long> getMinId,

Function<CampaignItemRelationQuery, List> queryItems) {

List relationList = Lists.newArrayList();

for (int i = 0; i < 64; i++) {

//min以及max的值均走缓存,不会对db产生压力

long minId = getMinId.apply(i);

long maxId = getMaxId.apply(i);

long tableTotal = maxId - minId + 1;

if (minId <= 0 || maxId <= 0) {

continue;

}

//起始减本表内总量,如果大于0,则一定是从下一张表开始的,直接跳出循环,降低start以及end继续

if (start - tableTotal > 0) {

start -= tableTotal;

end -= tableTotal;

continue;

}

// 进入到这里,说明一定已经有一部分落在这里了,那么继续遍历取值

// 先判定是否是最后一张表,如果是,则去除需要的 ,然后返回,如果不是最后一张表,那么需要取出本张表中所需的数据,然后进行下次迭代

// 判定为最后一张表的条件是 表的起始点+pageSize < maxId,即(minId+start)+(end-start) <= maxId,简化为 minId + end <= maxId

if (minId + end <= maxId) {

//如果minId + end 还小于本表的最大值,那么说明min以及max均落入了表内,那么只取本表的数据即可

relationList.addAll(queryItems.apply(getQuery(start + minId, end + minId, i)));

break;

} else {

//走入这里,说明数据进行了跨表

//首先取出本表符合条件的全部数据,然后将起始值设置为0,然后降低

relationList.addAll(queryItems.apply(getQuery(start + minId, maxId, i)));

//新的结束值应该为pageSize-当前表中取得的数量总量,即(end-start)-(tableTotal-start),简化后得到end-tableTotal

end = (int) (end - tableTotal);

start = 0;

}

}

return relationList;

}

这种处理方式存在几个缺点:

    1. 对于数据集中的表来说是一种不错的方法,但对于数据稀疏型表来说就非常低效,如果数据分布很稀疏,count很大,分批处理后任务数量非常大,最后获得的商品ID也就几百个,比如,新零售圈品方式由于框架限制,也采用了一样的分页处理方式,一次全量圈品商品增加消息量可达20w,实际可能只获得了几百个商品。
  1. 每个消息处理都需要循环查询很多张表直到start、end所在的那张表,通过max和min判断start和end是否出自该表,频繁取max、min也会给DB造成压力,为了避免对DB的压力,又需要利用缓存max、min。

针对于第一个缺点:数据稀疏型的数据源消息数量过大,可以在不改动框架的同时进行改善,只需换个角度计算总数count,如下图2.9所示,count取的是所有表中的最大值和最小值的差,这样即使是稀疏型数据源,count值也不会很大,然后任务处理的时候根据start和end循环从所有表中取出对应的商品ID。而且这种方式也会稍微减少取max和min的次数。如果密集型数据源采用这种分页处理方式,将会导致单页数据量过大问题。

图2.9 纵向分页处理方式

针对于第二个缺点:频繁取max和min问题,上面的处理方式是用全局的眼光计算count,然后分页处理,因此无法直接定位start和end应该取自哪张表,其实,可以针对于每个表单独分页处理,消息中不仅包含start、end,还包含分表的index信息。但是这种方式依然存在对稀疏型数据源划分任务数过多的问题,而且现在圈品分批框架也不支持这种方式。


  动作模块



动作模块的作用是处理圈品metaq消息,动作模块与消息类型是一一对应的,动作模块分为:规则变化、商品增加、商品删除。

规则变化动作

规则变化动作模块处理规则变化类型的消息,该动作主要处理流程是,调用分批处理模块进行分批,然后将每批包含的信息通过metaq发送出去,也就是产出商品增加和商品删除消息。

商品增加动作

商品增加动作模块处理商品增加类型的消息,动作处理流程图如下图2.10所示。

图2.10 商品增加动作处理流程图

商品删除动作

商品删除动作模块处理商品删除类型的消息,动作处理流程图与图2.10类似,只是最后业务处理模块调用商品删除处理的方法。


  业务处理模块



业务处理模块框架类图如图2.11所示,每一种业务都需要实现TargetHandler,其中handle方法处理圈品增加,rollback方法处理圈品删除。目前已经接入的几个大业务分别是:品类券、免息券、会员卡等。

图2.11 业务处理类图


  阶段总结



这一阶段,圈品从无到有,诞生于品类券,又脱胎于品类券,在业务方面,支撑了品类券、免息券、会员卡等业务,在性能方面,能处理百万级甚至千万级商品。系统是在不断发展中完善,这一阶段的圈品存在以下不足点。

处理商品变更消息性能问题

2.2.4中讲解了处理商品变更的必要性以及存在的问题,当有效的圈品池越来越多时,处理商品变更消息QPS越来越高,系统性能越来越差,而且很多规则需要调用HSF或者查询缓存之类的耗时操作,因此这些规则无法支持处理商品变更消息。这一阶段,承载圈品系统集群CPU一直都在50%以上,即便集群拥有600多台机器。

复杂顶级规则处理问题

面对复杂顶级规则,圈品没有很好的办法处理,然而在业务快速变化情况下,圈品需要有能力应对复杂规则,即使目前没有出现太复杂的顶级规则,圈品在处理卖家列表圈品方式也存在局限性。

系统稳定性和可控性问题

  1. **稳定性问题:**通过2.4节可以了解到,在进行大量圈品的时候,只要触发圈品变更,规则变化消息立马会裂变出更多的圈品消息,圈品metaq消息堆积量可达到百万,由于下游系统限流导致大量异常,系统负载又高,消息处理又耗时长,metaq消息处理存在雪崩风险,有时一条消息重复处理上万次。

  2. **可控性问题:**由于触发圈品变更时,会立马裂变出更多的消息,消息大量堆积时,圈品不能选择性处理、不能停止处理消息、不能选择性忽略消息等等,这就意味着系统发生问题的时候,没有抓手进行控制,只有眼巴巴的看着。举个实例,两条消息重复执行几万次,一个是删除该商品,一个是增加该商品,不停的给商品打标去标,商品产生大量商品变更,导致搜索引擎同步延迟,当时就只能眼巴巴看着。再举个例子,由于某一种圈品规则代码有bug会导致fullGc,然后该规则相关的圈品池产生了大量消息,由于无法选择性处理消息,导致整个圈品系统瘫痪。

分批处理缺陷问题

在2.4.2章节中讲到了第一阶段分页处理的缺陷,不同数据源的分页处理不能一概而论,框架应该给予更多的灵活性。

数据一致性问题

在进行大量圈品时,系统或下游系统异常无法避免,所以数据有可能存在不一致的情况。对于业务来说,该增加的商品没有增加,可能还能接受,如果该删除的商品没有删除,那么就很可能资损了。

===

===

第二阶段




  概述



第二阶段,针对于第一阶段的问题进行优化,新架构图如图3.1所示,其中黄色部分是新增部分。圈品总体可以划分为六大块,分别是数据源模块、动作模块、规则模块、业务处理模块、调度模块和设置端。调度模块是新增部分中最重要的,首先新增了任务的模型,如图3.2所示,任务会先保存到DB中,scheduleX秒级定时触发调度逻辑,最后通过metaq分发任务进行分布式处理。图中红色线条表示全量圈品的流程,图中橘黄色表示增量圈品的流程。下面将分别详细介绍新增部分。

图3.1 第二阶段圈品架构图

图3.2 任务模型


  商品变更消息优化



商品变更消息处理流程图如图3.3所示。第一步,建立圈品池与商品的泛化关系,第二步,通过Blink根据商品与圈品池的泛化关系过滤商品变更消息,剩下少量的商品变更消息,第三步,根据圈品池与商品的泛化关系判断哪些圈品池需要处理该商品变更消息。



过滤后的商品变更消息日常平均qps在200左右,而且只有与该商品相关的圈品池才需要处理该商品变更消息,因此,具体到某些圈品池上来看,其处理商品变更消息的qps在100以内,同时系统性能消耗也大大降低,集群机器从巅峰时期700多台降低到现在300多台(由于集群还承载其他业务,实际圈品需要的机器数量可以压缩到100台以内)。


图3.3 商品变更消息流程图

商品变更消息过滤的关键点在于如何建立圈品池与商品的泛化关系,这里的思想是根据具体规则尽量大范围的圈定可能的商品。

比如卖家圈品方式,当小二填写卖家列表后,这个圈品池与哪些卖家有关系就已经确定了,除此之外的卖家肯定不会跟这个圈品池发生关系,因此可以将卖家与圈品池的关系存入tair,供Blink过滤商品变更消息使用。卖家与圈品池的关系是比较通用的思路,其他圈品方式也可以转化成这种关系,比如商品列表圈品方式,当小二填入商品ID后,这些商品属于哪些卖家就确定了,除此之外的卖家的商品不会与该圈品池发生关系。

当然,卖家与圈品池的关系也有不适用的时候,比如大促活动圈品池方式,一次大促活动可能有几十万的卖家参与,而且卖家会不断的报名参加大促,因此很难获取圈品池与卖家的关系。针对大促活动圈品方式,可以建立tmc_tag与圈品池之间的关系,大促商品都有统一的tmc_tag,因此可以通过将tmc_tag与圈品池的关系存在diamonds供Blink过滤商品变更消息使用。总之,其他圈品方式根据具体规则找到圈品池与商品的泛化关系,可以通过商品上的信息和泛化关系判断商品与商品池是否存在关系。


  复杂顶级规则处理


维度定义

第一阶段中已经解释了顶级规则是能够做为数据源的规则,为了更好支持复杂数据源规则,引入了维度的概念,然后通过降维将复杂规则变成简单规则,最后的目的是从数据源中获取所包含的商品ID。

**定义1:**单个商品ID为零维,即没有维度

**定义2:**能够直接获取多个商品ID的规则为一维,例如商品列表规则,单个卖家规则

**定义3:**二维规则由多个一维规则组成,例如多个卖家规则

**定义4:**三维规则由多个二维规则组成,更高维规则由多个比它低一维的规则组成

从上面定义可以看出,卖家列表规则既有可能是一维规则,也有可能是二维规则,当规则只包含一个卖家时为一维规则,当规则包含多个卖家时为二维规则。为了更好理解,拿上面的复杂规则来讲解,一个卖家有很多商品,一个品牌团有很多商家报名,如果现在运营设置圈多个品牌团下面所有商品,下图3.5所示是该规则降维的过程。

图3.5 规则降维过程

规则变化动作调整

在第一阶段,规则变化动作处理流程就是调用分批处理模块进行分批,然后将每批包含的信息通过metaq发送出去,也就是产出商品增加和商品删除消息。现在,规则变化动作处理流程调整为如下图3.6所示,首先需要判断规则是否为一维规则,只有一维规则才能直接通过分批处理,否则就要进行降维,产生的降维任务由规则降维动作进行处理。

图3.6 新规则变化动作处理流程图

增加规则降维动作

规则降维动作处理规则降维类型的任务,动作处理流程图如下图3.7所示。RuleHandler中自定义的降级方法指定了下一维度的规则,因此一次降维任务只能将规则降低一个维度。

降维只针对做为数据源的顶级规则,因此,首先递归获取顶级规则,接着调用自定义降维方法处理顶级规则后得到更低一维度的顶级规则集合,然后使用降维后的顶级规则替换规则树中的顶级规则得到新的规则树集合,最后,将新规则树生成规则变化任务,由规则变化动作判断是否继续降维。

图3.7 规则降维动作处理流程图


  新增调度模块



任务调度


新增任务模型如图3.2所示,任务相当于第一阶段中的圈品消息,第二阶段中任务是需要先落库,然后由调度器来进行调度的。

调度器是任务扭转的动力,所有类型的任务都会插入DB中由调度器统一调度。任务表中已完成的任务会隔一段时间清理,即使是这样,任务表也有可能存在几百万任务,而且圈品的速度很大程度由调度器决定,因此对调度器的性能要求是很高的,不仅如此,调度器应该具备更多的灵活性。

调度器处理流程图如图3.8所示,通过scheduleX秒级定时触发调度逻辑,然后通过metaq分发任务ID,其实分发任务ID也可以通过scheduleX来完成,最初的实现也就是通过scheduleX来进行任务ID的分发,最后还是改成了通过metaq来分发任务,因为scheduleX分发大量任务时存在不可接受的延迟。

讲回到图3.8,任务调度的基础是知道未完成任务的分布,为了避免统计未完成任务分布时产生慢sql,这里做了一个很重要的动作,即下文第一步。

**第一步,**首先获取未完成任务所属的圈品池ID的分布,由于这里只根据状态统计圈品池ID,状态和圈品池都有索引,利用了覆盖索引,因此性能很高;

**第二步,**随机选择十个圈品池保证任务调度分配均衡,同时减少任务统计的耗时;

**第三步,**统计这十个圈品池的未完成任务数量的分布;

**第四步,**根据第三步的数量统计以及系统配置,分配每个圈品池参与调度的任务数量;

**第五步,**根据任务分配数量获取任务ID;

**第六步,**通过metaq将任务ID分批发送到不同的机器进行处理;

**第七步,**接收metaq消息;

**第八步,**将任务ID提交异步处理,这里为了提升处理速度,维护了一个线程池,任务ID只需要提交到阻塞队列中;

**第九步,**任务ID提交异步处理后,立马更新任务状态为处理中,避免任务再次被调度,处理中的任务不属于未完成的任务。

图3.8 任务调度流程图

对比第一阶段中圈品消息模式,第二阶段任务首先保存到DB,然后由调度器进行调度,调度器能够提供更多的灵活性,可以获取以下优点:

  1. 任务优先级可根据圈品池进行调整,部分圈品池出现问题不会影响整体;

  2. 任务调度速度可调整、可暂停,可以根据任务类型分配处理速度;

  3. 任务调度可监控、可精确统计;

  4. 圈品过程可查询、可追踪;


任务统计


一个完善的平台少不了系统可视化,任务处理进度是可视化中重要的部分。任务数量统计就少不了group by和count,任务表最大的时候可能存在上百万的数据,同步方式进行统计肯定是不行的,因此采用如下图3.9所示异步方式。利用覆盖索引方式得到圈品池ID的分布,每个圈品池的任务不会很大,因此每个圈品池分开统计将不会产生慢sql。

图3.9 任务统计思路


分批处理新思路


在第一阶段中提到了分批处理的缺陷问题,这里将讨论如何解决整个问题,新的分批处理方式还在开发当中,设计思路按照该章节所讲。

★ 框架设计

圈品拥有各种各样数据源,每种数据源的特性都有不同,所以无法用一种通用的分批方式处理所有数据源。因此圈品分批处理的框架应该更加通用,让每种数据源都能自定义自己的分批处理方式。

在框架方面的调整如下图3.11所示,新增基础分批对象Pageable,考虑到和老框架到兼容,Pageable包含老框架的使用的分批参数start、end、pageSize,自定义分批对象TablePageable或其他都继承自Pageable,RuleHandler增加自定义分批方式getPageabelList,老框架的分批方式可以写在AbstractRuleHandler的getPageableList中,需要自定义分批方式的RuleHandler覆盖getPageabelList便可。

图3.11 分批处理新框架类图

★ 分页处理思路

在第一阶段中,分页处理为了避免limit大翻页问题,采用了通过主键id进行分页的方式。在这里先讨论下为什么limit会存在大翻页问题,以及优化方案。

如下sql所示,当N值很大时,这个sql的查询效率会很差,并发查询时甚至会拖垮数据库,因为执行这个sql时需要先回表查询N+M行,然后根据limit返回M行,前面查询的N行最后被丢弃(具体讨论可参考limit为什么会慢)。一般遇到这种情况,业务上都是不允许大翻页,应该根据条件过滤,但是圈品要分批取出所有数据,所以圈品就绕不开这个问题。

SELECT * FROM table WHERE campaing_id = 1024 LIMIT N,M

在这里总结了两种解决思路,圈品为了获取所有有效的商品,因此无需考虑数据整体的分页,可以将分表独立分页处理。

id与limit组合优化

前面分析了使用limit大翻页最大的问题是查询前N(即offset)条数据所耗费的时间,在理想的情况下,id是连续自增,可以在where条件中使用id来代替offset,sql即如下所示。

SELECT * FROM table WHERE campaing_id = 1024 and id > N LIMIT M

优化思路中所说的理想情况,几乎没有场景能够达到要求,但是这也不影响该思路的应用,根据上一次翻页结果id使用limit查询下一批,如果id不连续,limit将可以跳过很多不连续id,减少查询次数。

结合圈品实际情况使用该思路,首先通过min和max得到数据在表中分布的最小值和最大值,针对稀疏型数据分批间隔可以很大(为了解决任务数量过多问题,比如间隔是2W,即end-start=2w),start和end分别是每批数据对应的开始id和结束id,然后根据id做为where条件使用limit取下一页数据,接着根据下一页最大id做为where条件使用limit取后面的数据,一直循环下去,直到id>end,对于稀疏型数据,也许循环1-2次就完成了。流程图如下图3.12所示。

图3.12 圈品分页处理优化思路

覆盖索引优化

当sql查询是完全命中索引,即返回参数和查询条件都有索引时,利用覆盖索引方式查询性能很高。先通过limit查询出对应的主键id,然后再根据主键id查询对应的数据,由于无需从磁盘中取数据,所以limit方式比之前性能要高,sql如下所示。

SELECT * FROM table AS t1

INNER JOIN (

SELECT id FROM table WHERE campaing_id = 1024 LIMIT N,M

) AS t2 ON t1.id = t2.id

“覆盖索引优化“到底能优化到什么程度呢,对此进行了一个测试,表item_pool_detail_0733包含10953646条数据,通过item_pool_id = 1129181 and status = -1条件筛选后剩下3865934条数据,item_pool_id和status建立了联合索引。

测试1

我们看下offset较小的时候,sql和执行计划如下所示,执行平均耗时83ms,可以看到在offset较小的时候,sql性能是可以的。

SQL:

SELECT * FROM item_pool_detail_0733 WHERE item_pool_id = 1129181 and status = -1 LIMIT 3860000,100

执行计划:













idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1SIMPLEitem_pool_detail_0733
refidx_pool_status,idx_itempoolididx_pool_status12const,const5950397100.00


测试2

当offset较大的时候,sql如下所示,执行计划和上面是一样的,执行平均耗时6371ms,这个时候sql性能就很差了。

SQL:

SELECT * FROM item_pool_detail_0733 WHERE item_pool_id = 1129181 and status = -1 LIMIT 3860000,100

测试3

最后

小编在这里分享些我自己平时的学习资料,由于篇幅限制,pdf文档的详解资料太全面,细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!

程序员代码面试指南 IT名企算法与数据结构题目最优解

这是” 本程序员面试宝典!书中对IT名企代码面试各类题目的最优解进行了总结,并提供了相关代码实现。针对当前程序员面试缺乏权威题目汇总这一-痛点, 本书选取将近200道真实出现过的经典代码面试题,帮助广“大程序员的面试准备做到万无一失。 “刷”完本书后,你就是“题王”!

image.png

《TCP-IP协议组(第4版)》

本书是介绍TCP/IP协议族的经典图书的最新版本。本书自第1版出版以来,就广受读者欢迎。

本书最新版进行」护元,以体境计算机网络技不的最新发展,全书古有七大部分共30草和7个附录:第一部分介绍一些基本概念和基础底层技术:第二部分介绍网络层协议:第三部分介绍运输层协议;第四部分介绍应用层协议:第五部分介绍下一代协议,即IPv6协议:第六部分介绍网络安全问题:第七部分给出了7个附录。

image.png

Java开发手册(嵩山版)

这个不用多说了,阿里的开发手册,每次更新我都会看,这是8月初最新更新的**(嵩山版)**

image.png

MySQL 8从入门到精通

本书主要内容包括MySQL的安装与配置、数据库的创建、数据表的创建、数据类型和运算符、MySQL 函数、查询数据、数据表的操作(插入、更新与删除数据)、索引、存储过程和函数、视图、触发器、用户管理、数据备份与还原、MySQL 日志、性能优化、MySQL Repl ication、MySQL Workbench、 MySQL Utilities、 MySQL Proxy、PHP操作MySQL数据库和PDO数据库抽象类库等。最后通过3个综合案例的数据库设计,进步讲述 MySQL在实际工作中的应用。

image.png

Spring5高级编程(第5版)

本书涵盖Spring 5的所有内容,如果想要充分利用这一领先的企业级 Java应用程序开发框架的强大功能,本书是最全面的Spring参考和实用指南。

本书第5版涵盖核心的Spring及其与其他领先的Java技术(比如Hibemate JPA 2.Tls、Thymeleaf和WebSocket)的集成。本书的重点是介绍如何使用Java配置类、lambda 表达式、Spring Boot以及反应式编程。同时,将与企业级应用程序开发人员分享一些见解和实际经验,包括远程处理、事务、Web 和表示层,等等。

image.png

JAVA核心知识点+1000道 互联网Java工程师面试题

image.png

image.png

企业IT架构转型之道 阿里巴巴中台战略思想与架构实战

本书讲述了阿里巴巴的技术发展史,同时也是-部互联网技 术架构的实践与发展史。

image.png
存中…(img-roYD29wV-1720115345612)]

MySQL 8从入门到精通

本书主要内容包括MySQL的安装与配置、数据库的创建、数据表的创建、数据类型和运算符、MySQL 函数、查询数据、数据表的操作(插入、更新与删除数据)、索引、存储过程和函数、视图、触发器、用户管理、数据备份与还原、MySQL 日志、性能优化、MySQL Repl ication、MySQL Workbench、 MySQL Utilities、 MySQL Proxy、PHP操作MySQL数据库和PDO数据库抽象类库等。最后通过3个综合案例的数据库设计,进步讲述 MySQL在实际工作中的应用。

[外链图片转存中…(img-X34QIrhw-1720115345612)]

Spring5高级编程(第5版)

本书涵盖Spring 5的所有内容,如果想要充分利用这一领先的企业级 Java应用程序开发框架的强大功能,本书是最全面的Spring参考和实用指南。

本书第5版涵盖核心的Spring及其与其他领先的Java技术(比如Hibemate JPA 2.Tls、Thymeleaf和WebSocket)的集成。本书的重点是介绍如何使用Java配置类、lambda 表达式、Spring Boot以及反应式编程。同时,将与企业级应用程序开发人员分享一些见解和实际经验,包括远程处理、事务、Web 和表示层,等等。

[外链图片转存中…(img-2mfCqX6S-1720115345613)]

JAVA核心知识点+1000道 互联网Java工程师面试题

[外链图片转存中…(img-RnbSdHN4-1720115345613)]

[外链图片转存中…(img-kkFaHXQ8-1720115345614)]

企业IT架构转型之道 阿里巴巴中台战略思想与架构实战

本书讲述了阿里巴巴的技术发展史,同时也是-部互联网技 术架构的实践与发展史。

[外链图片转存中…(img-3fB3cbxb-1720115345614)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值