Skip to content

Commit e8dfd9c

Browse files
committed
feat: 新增雪花算法ID实现
1 parent d3b6461 commit e8dfd9c

File tree

2 files changed

+172
-1
lines changed

2 files changed

+172
-1
lines changed

backend/common/model.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
from datetime import datetime
44
from typing import Annotated
55

6-
from sqlalchemy import DateTime
6+
from sqlalchemy import BigInteger, DateTime
77
from sqlalchemy.ext.asyncio import AsyncAttrs
88
from sqlalchemy.orm import DeclarativeBase, Mapped, MappedAsDataclass, declared_attr, mapped_column
99

10+
from backend.utils.snowflake import snowflake
1011
from backend.utils.timezone import timezone
1112

1213
# 通用 Mapped 类型主键, 需手动添加,参考以下使用方式
@@ -17,6 +18,15 @@
1718
]
1819

1920

21+
# 雪花算法ID,使用方法同 id_key
22+
snowflake_id_key = Annotated[
23+
int,
24+
mapped_column(
25+
BigInteger, primary_key=True, index=True, default=snowflake.generate, sort_order=-999, comment='雪花算法主键 ID'
26+
),
27+
]
28+
29+
2030
# Mixin: 一种面向对象编程概念, 使结构变得更加清晰, `Wiki <https://en.wikipedia.org/wiki/Mixin/>`__
2131
class UserMixin(MappedAsDataclass):
2232
"""用户 Mixin 数据类"""

backend/utils/snowflake.py

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
"""
4+
Twitter雪花算法(Snowflake)的Python实现
5+
用于生成分布式唯一ID
6+
7+
ID结构(64位):
8+
- 1位符号位,固定为0
9+
- 41位时间戳(毫秒级)
10+
- 5位数据中心ID
11+
- 5位机器ID
12+
- 12位序列号
13+
14+
https://github.com/twitter-archive/snowflake/blob/snowflake-2010/src/main/scala/com/twitter/service/snowflake/IdWorker.scala
15+
"""
16+
17+
import time
18+
19+
from dataclasses import dataclass
20+
21+
22+
class SnowflakeException(Exception):
23+
"""雪花算法基础异常类"""
24+
25+
pass
26+
27+
28+
class InvalidSystemClock(SnowflakeException):
29+
"""时钟回拨异常"""
30+
31+
pass
32+
33+
34+
class InvalidWorkerId(SnowflakeException):
35+
"""无效的工作节点ID异常"""
36+
37+
pass
38+
39+
40+
class InvalidDatacenterId(SnowflakeException):
41+
"""无效的数据中心ID异常"""
42+
43+
pass
44+
45+
46+
@dataclass
47+
class SnowflakeConfig:
48+
"""雪花算法配置类"""
49+
50+
# 64位ID的划分
51+
WORKER_ID_BITS: int = 5
52+
DATACENTER_ID_BITS: int = 5
53+
SEQUENCE_BITS: int = 12
54+
55+
# 最大取值计算
56+
MAX_WORKER_ID: int = -1 ^ (-1 << WORKER_ID_BITS) # 2**5-1 0b11111
57+
MAX_DATACENTER_ID: int = -1 ^ (-1 << DATACENTER_ID_BITS)
58+
59+
# 移位偏移计算
60+
WORKER_ID_SHIFT: int = SEQUENCE_BITS
61+
DATACENTER_ID_SHIFT: int = SEQUENCE_BITS + WORKER_ID_BITS
62+
TIMESTAMP_LEFT_SHIFT: int = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS
63+
64+
# 序号循环掩码
65+
SEQUENCE_MASK: int = -1 ^ (-1 << SEQUENCE_BITS)
66+
67+
# 元年时间戳, 2010-01-01 00:00:00
68+
EPOCH: int = 1262275200000
69+
70+
# 默认配置
71+
DEFAULT_DATACENTER_ID: int = 1
72+
DEFAULT_WORKER_ID: int = 0
73+
DEFAULT_SEQUENCE: int = 0
74+
75+
76+
class Snowflake:
77+
"""雪花算法ID生成器"""
78+
79+
def __init__(
80+
self,
81+
datacenter_id: int = SnowflakeConfig.DEFAULT_DATACENTER_ID,
82+
worker_id: int = SnowflakeConfig.DEFAULT_WORKER_ID,
83+
sequence: int = SnowflakeConfig.DEFAULT_SEQUENCE,
84+
):
85+
"""
86+
初始化ID生成器
87+
88+
:param datacenter_id: 数据中心ID (0-31)
89+
:param worker_id: 工作节点ID (0-31)
90+
:param sequence: 起始序列号
91+
"""
92+
if not 0 <= worker_id <= SnowflakeConfig.MAX_WORKER_ID:
93+
raise InvalidWorkerId(f'工作节点ID必须在0-{SnowflakeConfig.MAX_WORKER_ID}之间')
94+
95+
if not 0 <= datacenter_id <= SnowflakeConfig.MAX_DATACENTER_ID:
96+
raise InvalidDatacenterId(f'数据中心ID必须在0-{SnowflakeConfig.MAX_DATACENTER_ID}之间')
97+
98+
self.worker_id = worker_id
99+
self.datacenter_id = datacenter_id
100+
self.sequence = sequence
101+
self.last_timestamp = -1
102+
103+
def __call__(self) -> int:
104+
"""使实例可调用,用于default_factory"""
105+
return self.generate()
106+
107+
@staticmethod
108+
def _gen_timestamp() -> int:
109+
"""
110+
生成当前时间戳,单位:毫秒
111+
112+
:return: 当前时间戳
113+
"""
114+
return int(time.time() * 1000)
115+
116+
def _til_next_millis(self, last_timestamp: int) -> int:
117+
"""
118+
等待到下一个毫秒
119+
120+
:param last_timestamp: 上次生成ID的时间戳
121+
:return: 下一个毫秒的时间戳
122+
"""
123+
timestamp = self._gen_timestamp()
124+
while timestamp <= last_timestamp:
125+
timestamp = self._gen_timestamp()
126+
return timestamp
127+
128+
def generate(self) -> int:
129+
"""
130+
获取新的唯一ID,单位:毫秒
131+
132+
:return: 新的唯一ID
133+
"""
134+
timestamp = self._gen_timestamp()
135+
136+
if timestamp < self.last_timestamp:
137+
raise InvalidSystemClock(f'系统时钟回拨,拒绝生成ID直到 {self.last_timestamp}')
138+
139+
if timestamp == self.last_timestamp:
140+
self.sequence = (self.sequence + 1) & SnowflakeConfig.SEQUENCE_MASK
141+
if self.sequence == 0:
142+
timestamp = self._til_next_millis(self.last_timestamp)
143+
else:
144+
self.sequence = 0
145+
146+
self.last_timestamp = timestamp
147+
148+
new_id = (
149+
((timestamp - SnowflakeConfig.EPOCH) << SnowflakeConfig.TIMESTAMP_LEFT_SHIFT)
150+
| (self.datacenter_id << SnowflakeConfig.DATACENTER_ID_SHIFT)
151+
| (self.worker_id << SnowflakeConfig.WORKER_ID_SHIFT)
152+
| self.sequence
153+
)
154+
return new_id
155+
156+
157+
snowflake = Snowflake()
158+
159+
if __name__ == '__main__':
160+
for _ in range(10):
161+
print(snowflake.generate())

0 commit comments

Comments
 (0)