偶然查库检查用户邀请码使用情况的时候,发现居然有重复的邀请码。。。
引
2018/05/25,这是个很正常的一天,除了京东白条出账催债这个很恼人的事情,大体也就没什么大事缠身的样子。
同事在检查用户邀请码使用情况的时候,突然叫道我,说了句很严重很致命的话:“怎么邀请码有重复的?”
咩话?!点解?!我记得邀请码设计是讨论之后敲定的方案,怎么会有重复的邀请码出现???
问题来了,当然还得确认确认是个什么毛病才行。。。
述
在本文进行下去之前,让我来说明一下当时的设计是个什么样的方案。
首先在邀请码的需求上,我们采用的是6
位字符串,邀请码生成组合中有4
位是字母,另外2
位是数字。而在字母组合上,我们去除了邀请码中可能输入错误的字母O
和L
,并将剩余的 24 个字母打乱组合成四个基础字母组。
随后遍历四组基础字母和数字,生成无重复的邀请码生成组合。
文字说明比较生硬,但经过上述处理后,产生的邀请码生成组合没有重复,可以看下面给出的范例:(注意观察,结合后面的内容思考这里埋的坑)
|
|
这只是第一步,因为全部组合的可能性只有 1118 个,完全不可能当作邀请码来使用(根本不够嘛),所以我们更激进的,直接用每个组合进行遍历生成一批邀请码。
验
明眼人应该还没看到这里就能想到上面的设计方案存在什么问题了,我先走个过程,来验证一下代码中是否存在问题。
在判断重复的时候,我们采用 Redis Set 的特性来排除重复。Redis Set 这一数据结构内部不允许出现重复,并且是无序的,所以能满足下面两点需求:
- 添加到 Set 内的数据不会有重复
- 随机取出邀请码并移出 Set
在实现的时候,我将上面产生的邀请码生成组合落库,通过状态字段过滤已用组合,实际产生的邀请码存入 Redis Set 中,每当 Set 的集合个数少于 10000 条,就生成新的邀请码丢入 Set。(埋坑)
在取邀请码的时候,用到 spop 取出,表示这个邀请码已用过,不再可用。(埋了个大坑!)
那么看到这里,我就来说一下上面的实现存在了什么问题:
首先,组合中有相互存在的字母,你有h
我也有h
,你有5
我也有5
,并没有保证生成组合两两间的完全去重。
其次,生成的条件只有一个,只要满足 6 位字符就是个合理的邀请码,这样一来就有可能出现多组都是hahhhh
,或者555e55
之类的邀请码。
研
上面提到的三个埋坑点,其实很大的原因是Redis Set内部元素一定唯一
这一特性,使得我在开发过程中过度依赖了 Redis 特性,忽视了邀请码 pop 后的场景会产生的问题,以及没有注意到邀请码生成组合本身存在的缺陷。
埋坑点 1 虽然不是影响最严重的因素,但由于设置的阈值过低,很有可能已经使用过的邀请码早就被 pop 出 Set 了,没有元素重复判断的参照。
埋坑点 2 则是随机取出邀请码,这里用到了 spop,spop 做的事情是,返回一个随机的元素,并且将这个元素从 Set 中移除。由于元素移除后,Set 中不存在这个元素,从而会导致相同的元素能够成功进入 Set 中。这样的情景其实只是保证了 Redis Set 内的唯一性,但没有保证整个邀请码系统的邀请码唯一性。
然而上面的埋坑点都不是根源,要知道这些邀请码都是由数据库中的邀请码生成组合产生的,追根揭底还是要检查邀请码组合的构成。
最终我发现,如果邀请码生成组合内如果两两间存在相同的字符,在现有的生成方法中必定会出现像上面提到的hahhhh
的情况。
|
|
再来看邀请码生成组合的生成方法
|
|
这样的生成方式存在的缺陷已经很明显了,通过遍历数组的方式,仅在最里层遍历做去重,并不能做到有一个组合是hatc12
,其他组合不能有这个组合中的任何字母的情况。