0%

改进的SnowFlake,多机唯一ID生成器

改进的SnowFlake,多机唯一ID生成器

  • 所谓的snowFlake ID生成器, 其实就是把int64分解分解为几个段,通过时间,机器ID,自增序列来生成唯一ID
  • 好用又没有什么实现难度
  • 可以灵活的按照自己的需要调整位的分配

这里用c++实现了下, 同时完善了下,添加了如下特性

  • 设计每秒4095k个ID\
  • 对于过高的请求频率(超出了每ms请求上限,就陷入自旋等待)
  • 对于时间发生倒流,报错并陷入自旋等待
  • 如果出错时想要报错返回而不是自旋等待, 吧continue改为return就行

测试方案

  • 每次生成1亿个ID,使用set保存,不应该有重复
  • 连续跑200次,不应该有错误

linux版实现(win自行修改getMs函数)

  • head only, 直接使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/************************************************
* @desc 改进的snowflake算法, 设计每ms生成4095个id, 过高将自旋等待
* 依赖机器时间(ms), 如果时间被回调, 那么将陷入自旋等待, 并不断的告警
* 通过的测试用例: 单次生成1亿个id并插入set, 不应有错误, 连续跑100次, 不应有错误
* 测试用例在我的练手项目中, 后续想办法搬过来
*
* !!!多线程不安全, 如需多线程使用, 自行加锁!!!
************************************************/

#pragma once

#include <sys/time.h>

#include <chrono>
#include <ctime>
#include <mutex>


class SnowFlake
{
private:
int64_t m_epoch{};
int64_t m_lastUpdateTime{};
int64_t m_curr{};
int m_id{};
int m_sequence{};

uint64_t GetNowMs()
{
struct timeval tv
{
};

gettimeofday(&tv, NULL);

return tv.tv_sec * 1000 + tv.tv_usec / 1000;
}

public:
std::string DumpInfo()
{
std::stringstream oss{};
oss << "m_epoch:\t" << m_epoch << "\n";
oss << "m_lastUpdateTime:\t" << m_lastUpdateTime << "\n";
oss << "m_curr:\t" << m_curr << "\n";
oss << "m_id:\t" << m_id << "\n";
oss << "m_sequence:\t" << m_sequence << "\n";
return oss.str();
}
/**
* 系统起始时间 ms
* @param epoch
*/
void SetEpoch(uint64_t epoch)
{
this->m_epoch = epoch;
}
/**
* 序列的ID, 可以用来区分多个机器 或者 单个机器内区分不同的类型以提高效率
* @param id
*/
void SetID(int id)
{
this->m_id = id;
}

virtual int64_t Generate()
{
int64_t value{};
int64_t timeGap{};
// 防止时间回调 每ms不够时自旋等待的 snowflake
while (true)
{
m_curr = GetNowMs();
timeGap = m_curr - m_lastUpdateTime;
// 防止时间被回调
// 没人回调生产环境的时间吧? 如果真有人手贱改了生产环境的时间, 就只能自旋等了
if (timeGap < 0)
{
//ERROR("time go back!!! is any one change it ??? " << timeGap)
continue;
}
else if (timeGap == 0)
{
if (m_sequence >= 0xFFF)
{
continue;
}
else
{
// 唯一出口, 会造成seq 0 不会出现 可接受
++m_sequence;
break;
}
}
else
{
m_sequence = 0;
m_lastUpdateTime = m_curr;
continue;
}
}

// timeGap 始终为0 但是还是加一下好
value |= (m_lastUpdateTime - m_epoch + timeGap) << 22; // 41 位表示时间(自epoch 起 ms), 可用69.7年
value |= m_id & 0x3FF << 12; // 10 位(1024个) 表示机器ID或者类型ID, 用来表示机器ID时, 可以做到多机唯一
value |= m_sequence & 0xFFF; // 12 位(4095个) 表示序列, 没毫秒4095个可用ID
value &= 0x7FFFFFFFFFFFFFFF; // 最高位不应该有数字强制抹去最高位
return value;
}
};

class ThreadSafeSnowFlake : public SnowFlake
{
private:
std::mutex m_lock{};

public:
/**
* 多线程安全的获取唯一ID
* @return
*/
int64_t Generate() override
{
std::lock_guard<std::mutex> lockGard(m_lock);
return SnowFlake::Generate();
}
};

测试逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
int main(int argc, char **argv) {
int iRet = 0;
// iRet = beforeRun();
// if (iRet) {
// std::cerr << "init fail with " << iRet << std::endl;
// }
// testing::InitGoogleTest(&argc, argv);
// iRet = RUN_ALL_TESTS();
SnowFlakeChange snowFlakeChange{};
snowFlakeChange.SetID(10);
// unix time : +8 2020-04-18 14:50:44
// snowFlakeChange.SetEpoch(1587192644152);
snowFlakeChange.SetEpoch(getNowMs());

// gen
TimeGap gap{};
std::set<long> uidSet{};
{
// LOG_DEBUG("start correct test")
for (int i = 0; i < 100000000; ++i) {
auto uid = snowFlakeChange.Generate();
if (!uidSet.insert(uid).second) {
LOG_ERROR("uid fail ")
LOG_ERROR("\n" << snowFlakeChange.DumpInfo())
iRet = -1;
}
}
}
LOG_DEBUG("time use(us) : " << gap.gap())
return iRet;
}