猪小花1号

个人签名

281篇博客

搜索凑单页大促显示延迟方案设计

猪小花1号2018-08-30 10:06
作者:李世堤

考拉凑单页为整单类活动凑单页面,从大促的表现来看,承载在考拉全站差不多5%左右的请求量,尤其在大促整单类活动比较多的情况,对于凑单商品的实时性就有更高的要求,要不然用户没有入口做凑单,已考拉目前的凑单页地址如https://www.kaola.com/activity/goods/1197184.html,效果如下

考拉目前的搜索凑单页基于杭研的ndir去构建doc,但是考拉在大促的时候零点那一个时刻,按照3月8大促的量,存在几十万商品更新索引信息,因为所有商品的促销信息都需要更新到ndir里面,随着考拉业务的不断发展,商品的数量级仍然会不断增加,但是对于ndir来说,更新索引是比较耗时,基于lucene全文检索实现,更新一般分为删除和插入操作doc,之前考拉的方案由于商品不多,所以在变更活动信息基于mq消息通知搜索,搜索触发更新索引操作会调用促销接口更新ndir,促销会给出当前的实时促销信息,活动结束的时候也会触发mq消息通知,这样保证活动开始和活动结束这个搜索可以即使的构建活动。


但是随着考拉商品sku数量不断扩大,ndir的更新瓶颈显得尤为明显,尤其在大促时候,瞬间有几十万商品参加活动,这样mq消息就显得特别的庞大,按照之前数据量全部更新一次索引差不多两个小时,但是大促开始那个时刻是下单量最大的,这段时间的凑单页不实时直接影响转化率和用户体验。


所以整个大促的活动整体优化方案 
1.搜索增加一个索引字段专门为凑单页去匹配,这个字段只给凑单页使用
2.促销对于整单类活动的时间,促销提前返回全部未开始的整单类活动,整单类活动在发动发布的时候就刷新进缓存
3.促销的整单类活动的开始时间以会员时间为准,如果处于提前购阶段或者未开始的时间存在提前购增加返回提前购标示,整单类还需要把非会员开始时间,整单类结束时间落库
4.对于中途整单类活动已经刷进ndir,运营变更活动,修改,删除等活动都需要的实时刷新到ndir去 

新增一个索引字段,主要是为了避免正在进行的活动和未开始的活动的区分,在考拉的主搜那边会根据goods_tag去获取正在进行的活动,用户前台可以根据是否有促销搜索商品信息,这块信息要保证实时的一致性,否则会引起很大的客诉,https://www.kaola.com/activity/goods/1197184.html凑单页的url比较固定,这样会被用户穷举,加入用户访问了一个未开始的活动,这样也会有问题。对于这个穷举问题,考拉目前的做法是页面和数据分两个请求,页面请求为同步请求,同步请求的时候会根据活动方案号去促销系统查询改方案是否有效,如果为非法的方案号,直接渲染404错误页面,否则跳转正常的凑单页,然后再根据ajax请求搜索数据

 整个基于mq消息通知去更新索引信息,由于活动的数据时间经常变更,因此需要在活动发布,活动开始,价格审核通过,活动暂停,活动恢复等活动变更的流程中去控制ndir里面的索引数据,目前这块业务整合在促销的消息通知里面,为了避免一次消息量过大,促销发送消息时候做了一次分批处理
@Override
public void sendActivityNotifyMessage(List<Long> goodsIdList) {
if (CollectionUtils.isEmpty(goodsIdList)) {
return;
}
List<Long> notifyGoodsIdList = new ArrayList<>(new HashSet<>(goodsIdList));
int batchSize = promotionConfig.getInteger(sendActivityNotifyBatchSize, 500);
ListUtils.split(notifyGoodsIdList, batchSize, new PageProcess<Long>() {
@Override
public void process(List<Long> pageIdList) {
logger.info("send activity notify Message to rabbitmq goodsId:{}",pageIdList);
Map<String,List<Long>> param = Maps.newHashMap();
param.put("goodsIdList", pageIdList);
rabbitTemplate.convertAndSend(param);
}
});
}

活动变更时候为了减少发送量,只会把变更商品发送给ndir那边,变更数据主要为新增,修改和删除的商品id,促销这边基于定时任务发送变更信息,定时任务每一分钟发送一次,数据表一个商品在同一个时间点存在多个活动,这时候变更记录只会有一条,所以数据库设计以商品id和时间维度,具体表设计如下

CREATE TABLE TB_ACTIVITY_NOTIFY
(
ID VARCHAR2(32) NOT NULL,
GOODS_ID NUMBER(10) NOT NULL,
UPDATE_TIME TIMESTAMP(6) DEFAULT SYSDATE NOT NULL,
SEND_FLAG NUMBER(1) DEFAULT 0 NOT NULL,
CONSTRAINT PK_TB_ACTIVITY_NOTIFY PRIMARY KEY (ID)
);

该表为了保证通知性能,需要定时清除,因为大促的商品数据量很大,如果不做定时清理或者分区,会导致通知表的性能下降,所以增加一个send_flag表示通知标示,如果已经发送的成功的话,则修改为Y,这样定时清除就可以通过标示清除已发送数据


网易云大礼包:https://www.163yun.com/gift

本文来自网易实践者社区,经作者李世堤授权发布。