背景
在业务中由于想生成唯一的 uuid,找了很多雪花算法的库,最后看到 sonyflake 的 star 很高,评价也不错就用了这个库。
但是在本地测试的时候完全没有问题,一部署到线上就出现莫名其妙的 panic,最后定位到是 sonyflake 库的问题。
原因
sonyflake 这个库使用的是 IP 最低的 16Bit 作为机器号,但是在获取这个 IP 的时候会有一个校验,判断 IP 是否是一个私有的 IP。
func isPrivateIPv4(ip net.IP) bool {
return ip != nil &&
(ip[0] == 10 || ip[0] == 172 && (ip[1] >= 16 && ip[1] < 32) || ip[0] == 192 && ip[1] == 168)
}
可以看出,仅当 IP 是 10,172 开头,或者是 192.168 开头,才会返回 true。
func privateIPv4(interfaceAddrs types.InterfaceAddrs) (net.IP, error) {
as, err := interfaceAddrs()
if err != nil {
return nil, err
}
for _, a := range as {
ipnet, ok := a.(*net.IPNet)
if !ok || ipnet.IP.IsLoopback() {
continue
}
ip := ipnet.IP.To4()
if isPrivateIPv4(ip) {
return ip, nil
}
}
return nil, ErrNoPrivateAddress
}
func isPrivateIPv4(ip net.IP) bool {
return ip != nil &&
(ip[0] == 10 || ip[0] == 172 && (ip[1] >= 16 && ip[1] < 32) || ip[0] == 192 && ip[1] == 168)
}
可以看到,在第 7 行的 for 循环中,如果 IP 都不符合上面的规范就不会返回,最终在第 18 行会返回一个 ErrNoPrivateAddress 的报错。其实报错出去也还好,至少能在初始化的时候知道有问题了去处理,但是初始化函数选择吞掉了这个报错
func NewSonyflake(st Settings) *Sonyflake {
sf, _ := New(st)
return sf
}
这就导致,即使有错误也不会打印任何信息,返回的 sf 是一个 nil,当你在使用 sf 的时候就会报 panic 的错误。
解决方案
好在库提供了自己设置机器号的途径,那就是在调用 NewSonyflake 的时候手动传进去一个机器号,这样他就不会再自己使用 IP 判断,而是直接用你传进去的机器号。
sonyflake.NewSonyflake(sonyflake.Settings{
MachineID: func() (uint16, error) {
return 0,nil
},
})
当然我们也可以对他通过 IP 获取机器号的算法进行一些改造,去除一点小小的限制
func ipv4() (net.IP, error) {
as, err := net.InterfaceAddrs()
if err != nil {
return nil, err
}
for _, a := range as {
ipnet, ok := a.(*net.IPNet)
if !ok || ipnet.IP.IsLoopback() {
continue
}
v4 := ipnet.IP.To4()
if v4 == nil {
continue
}
return v4, nil
}
return nil, fmt.Errorf("no available ipv4")
}
这样就可以愉快的使用了。