概述
主要是两部分内容:
- 设计一种不易冲突的唯一键生成方案.
- 一次唯一键冲突的解决实录
设计一种不易冲突的唯一键生成方案.
一句话说明
使用纳秒数做36进制转换后得到的数字与字母组合成的值作为唯一键.足以应付中等并发量的服务(QPS约等于120).
var num2char = "0123456789abcdefghijklmnopqrstuvwxyz"
//10进制转16或36进制
func NumToBHex(num ,n int) string {
numStr := ""
for num != 0 {
yu := num % n
numStr = string(num2char[yu]) + numStr
num = num / n
}
return numStr
}
//唯一键生成函数
func getUniqId() (uniqId string){
uniqId = NumToBHex(int(time.Now().UnixNano()), 36)
return uniqId
}
一次唯一键冲突的解决实录
其实问题是发现了原本一个以为可靠的方案竟然出现了唯一键冲突, 定位问题的记录如下.
原生成方案原理
[0,9] 1个数字 加上 [a~z]26个字母, 生成一个36进制, 长度为10的唯一键, 按理说发生冲突的概率已经很小很小.
生成代码如下:
func getUniqId() string {
var uniqId string
for i := 0; i < 10; i++ {
rand1 := rand.Int63n(2)
var res int64
if rand1 == 0 {
res = 48 + rand.Int63n(10)
} else {
res = 97 + rand.Int63n(26)
}
character := fmt.Sprintf("%c", res)
storid += character
}
return uniqId
}
冲突原因
- 计算机中的rand的随机都是伪随机, 只是从一张很大的随机整数表中按顺序输出"随机值", 只要seed一样, 多次执行rand的随机结果就一样.
- 因为服务启动时, 使用了unix时间戳作为rand的seed,当使用k8s启用多个pod实例时, 极易发生冲突.
func init() {
rand.Seed(time.Now().Unix())
}
解决方法
- 使用纳秒数作为seed.
func init() {
rand.Seed(time.Now().UnixNano())
}
- 使用上述纳秒数做36进制转换的方案
附: 哈希冲突的计算方式
生日悖论适用于哈希冲突, 即哈希冲突发生的几率往往比我们直观感受上更高.
一个60人的班级里, 有两个同学生日为同一天的概率已经达到了99%.
可参考: 从生日悖论谈哈希碰撞