? ? ? ? 在最近的工作中遇到了一个问题。就是关于商品主数据和关联商品的数据一致性问题。比方说现在有一个商品主数据spuA。同时现在有一个促销套包的需求,套包里面会添加商品。如果这次这个套包里添加的商品是spuA的话,那么在前端展示的促销套包里面的商品的状态应该怎么取呢(商品名称/价格/上下架等等信息)?因为这是个普遍性问题,所有需要保证一致性的、添加商品数据的需求都会存在这个问题。所以我想单独提出来,写出我的思考和我最终的解决方案。
1 主动查询
????????第一种方案是促销套包里面不存商品的信息,最多存一个spuId,每次前端过来的请求都会拿着spuId主动去调用商品的接口,返回最新的商品信息。
????????这种方案虽然不会有数据一致性的问题,但是每次都会去调用商品的接口来获取数据,小数据量下还好,如果遇到大数据量下效率不是很高。
2 定时任务
????????第二种方案是促销套包这里会存商品的信息,冗余一份数据。然后通过定时的方式来刷新套包里面的商品数据。
????????这种方案虽然不用前端过来的每次请求都去查商品接口了,但是有时延性的问题。十分钟一刷,那么数据得等到十分钟后才能被修改。另外如果当前这个商品很少发生改动,比如说几个月内都不会发生数据变化,那么也会十分钟一刷,浪费系统资源。还有,刷新的频率设置成多少也是个问题,需要在时延性和系统资源浪费上做取舍。
3 数据监听
????????第三种方案就是类似于canal的那种,监听商品表数据变化,如果变化了,就发送一条mq的广播消息。这样下游的促销套包就可以感知到数据变化,以此来进行数据更新。
????????本来我以为这种方案挺好,但跟同事一交流的话,发现这种方案也有它的问题。比如说当前这个监听发送的服务有多个集群节点。那么一个商品spuA,一开始是将价格从100改为200,然后又将价格从200改为300。这是两次操作。可能第一个节点监听到从100改为200的操作,而第二个节点监听到从200改为300的操作。而第一个节点组装完数据所花费的时间是要大于第二个节点的。这样造成的结果就是价格从200改为300的操作会先发送出去,而价格从100改为200的操作会后发送。这样的话促销套包那里最后这个商品记录的价格会是200,而不是数据库记录的300,从而出现bug:
第一个节点10:00监听到商品数据从100改为200 | 第一个节点组装发送所需的数据花了10分钟,10:10完成 | 第一个节点10:11发送mq消息 | 促销套包10:12接收到消息,将商品的价格从300改为200 | 第二个节点10:05监听到商品数据从200改为300 | 第二个节点组装发送所需的数据花了3分钟,10:08完成 | 第二个节点10:09发送mq消息 | 促销套包10:10接收到消息,将商品的价格从100改为300 |
????????我知道RocketMQ里有分区顺序消息的功能,但是它也只是能保证数据的发送和消费是有顺序的,但如果我在这之前的业务操作上的组装数据不能保证有顺序、有先后性的话,那还是会有问题。
????????当然这种情况下是可以加上分布式锁的,但是这样会使性能有所损失。如果商品数据一段时间内的改动很频繁的话,这样会很影响发送消息的性能。
4 数据监听(优化)
????????第四种方案,基于第三种方案出现的问题,做出以下改动:不再组装数据,只记录spuId或skuId,同时记录当前是增/删/改操作(第三种方案也会记录增/删/改),下游的促销套包拿到商品id后主动再查询一次商品接口来获得最新的数据。
????????这种方案是我目前正在使用的方案,兼顾了数据查询的性能和资源调度上的取舍。其实对于第三种方案还有一个问题:RocketMQ的消息体大小是有上限的(其他的MQ应该也会有类似的限制),如果商品数据量很大的话,这点也会是个问题。而第四种方案,消息体里面只会放商品id和增/删/改操作,最大程序上可以避免这个问题。同时下游服务不是完全依赖于mq发送过来的数据,也就没有第三种方案出现的问题。因为发mq消息肯定是在数据发生变动之后才发送的,所以等到下游服务接收到消息时,再去查询商品数据肯定是最新的,只要能保证每一次数据的变化都能成功发送mq消息即可。
5 总结
????????最后想说的一点是:具体采用什么方案还是要看数据量的大小和业务的需求。如果前期数据量不大的情况下还是要学着简单处理,后续再调整。不要一上来就整大而全的解决方案。盲目引入多个组件会使程序复杂度增高,难于维护。另外,如果业务是需要保证强一致性场景的话,那么也就不能使用canal了。就得采用别的方案。
原创不易,未得准许,请勿转载,翻版必究
|