项目里会遇到很多共通的场景但没有一个规范的解决方案。
下面就以100万店铺举例子,比如要做状态修改,状态修改之前需要对店铺有效性、是否状态允许修改等校验,然后再更新。假设查询校验每个店铺需要20ms,更新数据需要100ms。为了保证接口不超时,最多只允许3s处理请求。
1、店铺自己对单个店铺修改状态:此场景由于只操作了一个店铺的数据,可全部使用同步接口,更容易保证数据一致性,服务的处理结果及时,接口处理成功就是成功,失败就是失败。
2、连锁店操作修改1000家店铺:此场景难度就升级了,因为无论对1000家店铺的查询校验还是更新都会超时,这时候可采用查询多线程执行,更新mq的形式达到要求。具体做法就是使用一个线程池去分批执行对店铺的查询校验,如果有某个或者某些店铺不符合要求,可按需求是选择过滤还是直接打回请求,这里请求如果接口并发量很低可采用公共线程池,否则并发高还是需要采用接口内的线程池,防止由于任务等待执行也有可能超时,在通过所有校验后需要批量更新数据,因为此时由于量大也会超时,这时不建议使用多线程执行,因为可能会由于发布等操作导致更新丢失,采用mq分发请求,异步完成更新操作,通过mq去保证数据一致性、完整性。
3、运营10万+的店铺数据更新:这时候同步查询校验已经不现实了,因为千级的采用多线程去查询校验还凑合能完成,到了10万+的时候多线程也满足不了,这时候需要完全采用mq异步去操作,先跟产品约定好默认的处理逻辑,至于处理结果可能存储到db供后期查询。
4、全量店铺数据定时更新:这个很明显的定时任务特性,可采用定时任务慢慢更新数据,但是如果对时间有要求可将任务拆分,分布在不同机器调用,甚至可以采用mq分发到整个集群去处理。
自营销活动:此场景出现在单店店铺上活动,店铺数据量为1,商品数据量可以限定一定范围(1-100),可以通过同步调用返回校验结果,并且同步调用批量绑定菜品接口写入数据。因为活动关系和库存有可能因为不在一个库或者其他原因导致底层不能保证事务,保证数据一致性,这时候可采用同步调用之余try catch异常,用异步发mq的形式补偿数据,要求上活动接口幂等。
连锁店和BD直上活动:此场景时单次请求同步处理已经不现实了,大批量的操作妥妥的会导致超时,这时候引入线程池,通过线程池提交任务,完成对所有店铺的校验,包括活动冲突,商品是否符合活动要求等,等所有校验都通过了这时候可以通过mq分发去完成上活动的操作,这里使用mq不用多线程一方面是考虑数据更新在发布丢失,一方面更加把处理分布到整个集群里,而且由于上活动会依赖外部接口,而外部接口的可用性也会出现异常,通过mq的ack机制可保证数据最终一致性。
招商活动:此场景所有同步都用不了了,到了10万+的地步,性能再好的接口也无法满足量级的爆发,这时候在设计系统的时候,可以将所有需要处理的数据拆分成10或者100个一组的mq消息,为什么要拆呢?因为后面的写入数据时速度太快了DB压力大,写入速度慢了消息消费不了又会堵MQ。处理结果可以通过异步写入数据库等保存下来,因为是异步处理,只需要定好处理逻辑即可,比如如果店铺不存在怎么办:丢掉,店铺是新零售店铺怎么办:丢掉。因为招商活动也有特殊的地方,因为量大,所以需要看一下活动的效果,这个做成T+1的话运营可能无法接受,所以将所有招商推送的数据和报名数据同步到了es,通过es更高效的查询统计完成对活动数据的实时统计,同时也避免了对DB大表的频繁大量数据的count。
菜品的解锁、锁定:这个场景原先是在业务代码里通过同步调用的方式去完成对菜品的解锁和锁定,非常繁琐,而且很容易出问题,只要向菜品那边更新状态失败,也不会重试,大量不同上活动、作废、审核等流程都需要接入锁定和解锁的方法,相当麻烦,如果活动到期了菜品没被解锁还很容易导致被投诉,这个问题的投诉量原先是居高不下的。重新设计后采用活动关系表DRC消息和审核逻辑驱动,通过活动关系表的状态变更判断菜品是否需要解锁、锁定。审核数据也可以这么做,后期会优化。再通过将锁定和解锁转化成一个个task,通过状态判定执行的结果成功与否,之所以再转化task,主要是为了不停的重试和防止短时间内重试太多次。