关于粉丝号的一点总结

初期场景

最近产品结了一个需求,要给每个进入频道用户颁发一张粉丝卡,这个粉丝卡上的粉丝号每个频道唯一,每个人在每个频道也只能唯一拥有一张粉丝卡。粉丝号段由管理系统录入,粉丝号不保证连续,如录入1-100,102-105,中间独缺了101,过了105,后面进频道的用户无法拥有粉丝卡了。而且必须小号优先发,不能有粉丝先进是102,后进是100。项目使用mongodb开发

初期方案

两张mongo表设计,第一张是针对规则录入,也就是一张stat表,记录后台录入的粉丝号最大id,最小id和当前的粉丝id,第二张针对用户拥有粉丝号的单条记录。使用mongo的findAndModify原子性操作stat表当前粉丝id,然后再写入到rec表

万恶的改需求

后面,产品为了响应能更加灵活控制用户的粉丝卡,并且从粉丝卡中提出增值服务,要求能针对到某个用户发放某个频道的某个粉丝号。而且在前面规则发放的基础上,增加了预留池功能,也即是现将预留的号码段隔离出来,后续给用户单独发放,如预留号码6,8,88等。这些被预留的号码,在规则发放stat的前提下,如1-100,涵盖了上面三个预留号码,这三个号码也不会被规则发放出去

新需求的初期方案

一开始想到的是资源池的方式,在规则录入的时候,如1-100,那么马上生成1-100条记录存在资源池中。如果进行预留,就将响应的未分配给用户的数据打上预留的标志,顺序发放的时候,跳过有预留标志的记录,获取未分配的最小记录给用户。而对于为有预留标志未发放的记录,就是所谓的频道粉丝卡资源池,供后台手动去指定发放。只要是未发放出去的号码,就可以预留,给已经发放过号码的用户手动发放,会回收原来的号码到预留池中。

初期方案的缺点

这种方案确实是简单明了的解决了问题,但是有一个很大的缺陷,后台录入粉丝卡号码有多少,就要插入多少条数据入库。这样假设有1W个频道,每个频道设置1000个粉丝卡,就马上将表提升到千万级别了

实际落地方案

将表设计成三个,stat规则发放,rec用户的粉丝卡记录,reserve表代表预留号段
这里分两个角色的思维角度来看到问题:

  • 自动发放:首先还是继续使用stat的incr方式增加,返回当前规则发放在不考虑预留情况下的号码,然后拿着这个号码n1和本规则stat中定义的最大粉丝号m到预留池服务中去问,如果是当前号码n1的话,要返回哪个没有被预留的实际号码n2。返回的号码n2如果跟与n1相同,证明没有被预留,直接生成rec。如果m>n2>n1,也即是恰好被预留了,那就要先更新stat的当前号码,然后再生成rec。如果n2>m,抛出错误,证明本规则的剩下号码都被预留了,那本规则作废,进行下一条本频道的规则,重复以上操作,若最后没有适合的,就不给用户发放粉丝卡了
  • 预留池:对于自动发放传来的n1,预留池判断这个数字在不在当前的预留池中,如果不在直接返回n1,如果在了,选出这条涵盖的reserve预留记录,返回预留中最大值+1=n2,加入n2已经大于自动发放规则stat的最大规则号码m,则抛出错误

两重角色分开实现,在其中一方的时候,不用管另一方如何实现,将注意力都放在自己的那一层中,同时也分开测试(利用mock)。我发现这样思考的逻辑更清晰,也更好理解。这样设计有个大的问题,我们都没有考虑线程安全问题,在从规则stat表中拿出n1再去reserve中比对时候,如果其他线程抢险更改了stat中的值,那么数据就错乱了。所以这种方案,必须限制在单线程内!而进频道的操作本身就是单线程消费mq,同一个频道又分给同一个partition,所以只会是单机单线程消费,符合方案,所以采用了设计

猜你喜欢

转载自blog.csdn.net/jerryJavaCoding/article/details/82263633
今日推荐