├── .gitignore ├── LICENSE ├── README.md ├── go.mod └── mist.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 AsyncIns 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![MistLogo](http://can.sfhfpc.com/mistLogo-300px.png) 3 | 4 | # Mist 薄雾算法 5 | 6 | 薄雾算法是不同于 snowflake 的全局唯一 ID 生成算法。相比 snowflake ,薄雾算法具有更高的数值上限和更长的使用期限。 7 | 8 | 现在薄雾算法拥有比雪花算法更高的性能! 9 | 10 | ## 考量了什么业务场景和要求呢? 11 | 12 | 用到全局唯一 ID 的场景不少,这里引用美团 Leaf 的场景介绍: 13 | 14 | > 在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识。如在美团点评的金融、支付、餐饮、酒店、猫眼电影等产品的系统中,数据日渐增长,对数据分库分表后需要有一个唯一 ID 来标识一条数据或消息,数据库的自增 ID 显然不能满足需求;特别一点的如订单、骑手、优惠券也都需要有唯一 ID 做标识。此时一个能够生成全局唯一ID 的系统是非常必要的。 15 | 16 | 引用微信 seqsvr 的场景介绍: 17 | 18 | > 微信在立项之初,就已确立了利用数据版本号实现终端与后台的数据增量同步机制,确保发消息时消息可靠送达对方手机。 19 | 20 | 爬虫数据服务的场景介绍: 21 | 22 | > 数据来源各不相同,且并发极大的情况下难以生成统一的数据编号,同时数据编号又将作为爬虫下游整个链路的溯源依据,在爬虫业务链路中十分重要。 23 | 24 | 25 | 这里参考美团 [Leaf](https://tech.meituan.com/2017/04/21/mt-leaf.html) 的要求: 26 | 27 | 1、全局唯一性:不能出现重复的 ID 号,既然是唯一标识,这是最基本的要求; 28 | 29 | 2、趋势递增:在 MySQL InnoDB 引擎中使用的是聚集索引,由于多数 RDBMS 使用 B-tree 的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能; 30 | 31 | 3、单调递增:保证下一个 ID 一定大于上一个 ID,例如事务版本号、IM 增量消息、排序等特殊需求; 32 | 33 | 4、信息安全:如果 ID 是连续的,恶意用户的爬取工作就非常容易做了,直接按照顺序下载指定 URL 即可;如果是订单号就更危险了,竞对可以直接知道我们一天的单量。所以在一些应用场景下,会需要 ID 无规则、不规则; 34 | 35 | 可以用“全局不重复,不可猜测且呈递增态势”这句话来概括描述要求。 36 | 37 | ## 薄雾算法的设计思路是怎么样的? 38 | 39 | 薄雾算法采用了与 snowflake 相同的位数——64,在考量业务场景和要求后并没有沿用 1-41-10-12 的占位,而是采用了 1-47-8-8 的占位。即: 40 | ``` 41 | * 1 2 48 56 64 42 | * +------+-----------------------------------------------------+----------+----------+ 43 | * retain | increas | salt | salt | 44 | * +------+-----------------------------------------------------+----------+----------+ 45 | * 0 | 0000000000 0000000000 0000000000 0000000000 0000000 | 00000000 | 00000000 | 46 | * +------+-----------------------------------------------------+------------+--------+ 47 | ``` 48 | - 第一段为最高位,占 1 位,保持为 0,使得值永远为正数; 49 | - 第二段放置自增数,占 47 位,自增数在高位能保证结果值呈递增态势,遂低位可以为所欲为; 50 | - 第三段放置随机因子一,占 8 位,上限数值 255,使结果值不可预测; 51 | - 第四段放置随机因子二,占 8 位,上限数值 255,使结果值不可预测; 52 | 53 | 54 | ## 薄雾算法生成的数值是什么样的? 55 | 56 | 薄雾自增数为 1~10 的运行结果类似如下: 57 | 58 | ``` 59 | 171671 60 | 250611 61 | 263582 62 | 355598 63 | 427749 64 | 482010 65 | 581550 66 | 644278 67 | 698636 68 | 762474 69 | ``` 70 | 71 | 根据运行结果可知,薄雾算法能够满足“全局不重复,不可猜测且呈递增态势”的场景要求。 72 | 73 | ## 薄雾算法 mist 和雪花算法 snowflake 有何区别? 74 | 75 | snowflake 是由 Twitter 公司提出的一种全局唯一 ID 生成算法,它具有“递增态势、不依赖数据库、高性能”等特点,自 snowflake 推出以来备受欢迎,算法被应用于大大小小公司的服务中。snowflake 高位为时间戳的二进制,遂完全受到时间戳的影响,倘若时间回拨(当前服务器时间回到之前的某一时刻),那么 snowflake 有极大概率生成与之前同一时刻的重复 ID,这直接影响整个业务。 76 | 77 | snowflake 受时间戳影响,使用上限不超过 70 年。 78 | 79 | 薄雾算法 Mist 由书籍《Python3 反爬虫原理与绕过实战》的作者韦世东综合 [百度 UidGenerator](https://github.com/baidu/uid-generator)、 [美团 Leaf](https://tech.meituan.com/2017/04/21/mt-leaf.html) 和 [微信序列号生成器 seqsvr](https://www.infoq.cn/article/wechat-serial-number-generator-architecture) 中介绍的技术点,同时考虑高性能分布式序列号生成器架构后设计的一款“递增态势、不依赖数据库、高性能且不受时间回拨影响”的全局唯一序列号生成算法。 80 | 81 | ![mistSturct](http://can.sfhfpc.com/7798.png) 82 | 83 | 薄雾算法不受时间戳影响,受到数值大小影响。薄雾算法高位数值上限计算方式为`int64(1<<47 - 1)`,上限数值`140737488355327` 百万亿级,假设每天消耗 10 亿,薄雾算法能使用 385+ 年。 84 | 85 | ## 为什么薄雾算法不受时间回拨影响? 86 | 87 | snowflake 受时间回拨影响的根本原因是高位采用时间戳的二进制值,而薄雾算法的高位是按序递增的数值。结果值的大小由高位决定,遂薄雾算法不受时间回拨影响。 88 | 89 | ## 为什么说薄雾算法的结果值不可预测? 90 | 91 | 考虑到“不可预测”的要求,薄雾算法的中间位是 8 位随机值,且末 8 位是也是随机值,两组随机值大大增加了预测难度,因此称为结果值不可预测。 92 | 93 | 中间位和末位随机值的开闭区间都是 [0, 255],理论上随机值可以出现 `256 * 256` 种组合。 94 | 95 | ## 当程序重启,薄雾算法的值会重复吗? 96 | 97 | snowflake 受时间回拨影响,一旦时间回拨就有极大概率生成重复的 ID。薄雾算法中的高位是按序递增的数值,程序重启会造成按序递增数值回到初始值,但由于中间位和末尾随机值的影响,因此不是必定生成(有大概率生成)重复 ID,但递增态势必定受到影响。 98 | 99 | ## 薄雾算法的值会重复,那我要它干嘛? 100 | 101 | 1、无论是什么样的全局唯一 ID 生成算法,都会有优点和缺点。在实际的应用当中,没有人会将全局唯一 ID 生成算法完全托付给程序,而是会用数据库存储关键值或者所有生成的值。全局唯一 ID 生成算法大多都采用分布式架构或者主备架构提供发号服务,这时候就不用担心它的重复问题; 102 | 103 | 2、生成性能比雪花算法高太多倍; 104 | 105 | 3、代码少且简单,在大型应用中,单功能越简单越好; 106 | 107 | ## 是否提供薄雾算法的工程实践或者架构实践? 108 | 109 | 是的,作者的另一个项目 [Medis](https://github.com/asyncins/medis) 是薄雾算法与 Redis 的结合,实现了“全局不重复”,你再也不用担心程序重启带来的问题。 110 | 111 | ## 薄雾算法的分布式架构,推荐 CP 还是 AP? 112 | 113 | CAP 是分布式架构中最重要的理论,C 指的是一致性、A 指的是可用性、P 指的是分区容错性。CAP 当中,C 和 A 是互相冲突的,且 P 一定存在,遂我们必须在 CP 和 AP 中选择。**实际上这跟具体的业务需求有关**,但是对于全局唯一 ID 发号服务来说,大多数时候可用性比一致性更重要,也就是选择 AP 会多过选择 CP。至于你怎么选,还是得结合具体的业务场景考虑。 114 | 115 | 116 | ### 薄雾算法的性能测试 117 | 118 | 采用 Golnag(1.14) 自带的 Benchmark 进行测试,测试机硬件环境如下: 119 | 120 | ``` 121 | 内存 16 GB 2133 MHz LPDDR3 122 | 处理器 2.3 GHz 双核Intel Core i5 123 | 操作系统 macOS Catalina 124 | 机器 MacBook Pro (13-inch, 2017, Two Thunderbolt 3 ports) 125 | ``` 126 | 127 | 128 | 进行了多轮测试,随机取 3 轮测试结果。以此计算平均值,得 `单次执行时间 346 ns/op`。以下是随机 3 轮测试的结果: 129 | 130 | 131 | ``` 132 | goos: darwin 133 | goarch: amd64 134 | pkg: mist 135 | BenchmarkMain-4 3507442 339 ns/op 136 | PASS 137 | ok mist 1.345s 138 | ``` 139 | 140 | ``` 141 | goos: darwin 142 | goarch: amd64 143 | pkg: mist 144 | BenchmarkMain-4 3488708 338 ns/op 145 | PASS 146 | ok mist 1.382s 147 | ``` 148 | 149 | ``` 150 | goos: darwin 151 | goarch: amd64 152 | pkg: mist 153 | BenchmarkMain-4 3434936 360 ns/op 154 | PASS 155 | ok mist 1.394s 156 | ``` 157 | 158 | 159 | # 更新日志 160 | 161 | ##### 2020-05-31 162 | 163 | - 采用大数真随机方法替代时间戳种子随机方法,性能提升 25 倍; 164 | - 根据工程师 @青南 反馈,原来的中间位随机因子搭配末位时间戳序列的方式预测难度并不高,遂将末位也改为随机因子; 165 | - 由于末位数和随机方法的变化,同步改动与之相关的代码; 166 | - 重新测试性能,并改动 README 文档; 167 | 168 | > 性能实验:采用时间戳种子随机方法时性能约为 8800 ns/op,采用大数真随机方法时性能约为 335 ns/op,采用常数时性能约为 15 ns/op 169 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module mist 2 | 3 | go 1.14 4 | -------------------------------------------------------------------------------- /mist.go: -------------------------------------------------------------------------------- 1 | /* 2 | * 薄雾算法 3 | * 4 | * 1 2 48 56 64 5 | * +------+-----------------------------------------------------+----------+----------+ 6 | * retain | increas | salt | sequence | 7 | * +------+-----------------------------------------------------+----------+----------+ 8 | * 0 | 0000000000 0000000000 0000000000 0000000000 0000000 | 00000000 | 00000000 | 9 | * +------+-----------------------------------------------------+------------+--------+ 10 | * 11 | * 0. 最高位,占 1 位,保持为 0,使得值永远为正数; 12 | * 1. 自增数,占 47 位,自增数在高位能保证结果值呈递增态势,遂低位可以为所欲为; 13 | * 2. 随机因子一,占 8 位,上限数值 255,使结果值不可预测; 14 | * 3. 随机因子二,占 8 位,上限数值 255,使结果值不可预测; 15 | * 16 | * 编号上限为百万亿级,上限值计算为 140737488355327 即 int64(1 << 47 - 1),假设每天取值 10 亿,能使用 385+ 年 17 | */ 18 | 19 | package main 20 | 21 | import ( 22 | "crypto/rand" 23 | "math/big" 24 | "sync" 25 | ) 26 | 27 | const saltBit = uint(8) // 随机因子二进制位数 28 | const saltShift = uint(8) // 随机因子移位数 29 | const increasShift = saltBit + saltShift // 自增数移位数 30 | 31 | type Mist struct { 32 | sync.Mutex // 互斥锁 33 | increas int64 // 自增数 34 | saltA int64 // 随机因子一 35 | saltB int64 // 随机因子二 36 | } 37 | 38 | /* 初始化 Mist 结构体*/ 39 | func NewMist() *Mist { 40 | mist := Mist{increas: 1} 41 | return &mist 42 | } 43 | 44 | /* 生成唯一编号 */ 45 | func (c *Mist) Generate() int64 { 46 | c.Lock() 47 | c.increas++ 48 | // 获取随机因子数值 | 使用真随机函数提高性能 49 | randA, _ := rand.Int(rand.Reader, big.NewInt(255)) 50 | c.saltA = randA.Int64() 51 | randB, _ := rand.Int(rand.Reader, big.NewInt(255)) 52 | c.saltB = randB.Int64() 53 | // 通过位运算实现自动占位 54 | mist := int64((c.increas << increasShift) | (c.saltA << saltShift) | c.saltB) 55 | c.Unlock() 56 | return mist 57 | } 58 | 59 | // func main() { 60 | // // 使用方法 61 | // mist := NewMist() 62 | // for i := 0; i < 10; i++ { 63 | // fmt.Println(mist.Generate()) 64 | // } 65 | // } 66 | --------------------------------------------------------------------------------