在项目分库之后, 数据遍布在不同服务器上的数据库,因此各个表主键自增性和唯一性已经没办法保证,所以需要有一个简单快速的分布式主键生成策略。
通过snowFlake算法生成唯一id:
class SnowFlakeId:
worker_id = 0
datacenter_id = 0
sequence = 0
twepoch = 1288834974657
worker_id_bits = 5
datacenter_id_bits = 5
max_worker_id = -1 ^ (-1 << worker_id_bits)
max_datacenter_id = -1 ^ (-1 << datacenter_id_bits)
sequence_bits = 12
worker_id_shift = sequence_bits
datacenter_id_shift = sequence_bits + worker_id_bits
timestamp_left_shift = sequence_bits + worker_id_bits + datacenter_id_bits
sequence_mask = -1 ^ (-1 << sequence_bits)
last_timestamp = -1
logger = Log().get_logger()
__lock = Lock()
def __init__(self, work_id, datacenter_id):
if self.worker_id > self.max_worker_id or self.worker_id < 0:
raise BusinessException(f'worker Id can not be greater than {self.max_worker_id} or less than 0')
if self.datacenter_id > self.max_datacenter_id or self.datacenter_id < 0:
raise BusinessException(f'datacenter Id can not be greater than {self.max_datacenter_id} or less than 0')
self.work_id = work_id
self.datacenter_id = datacenter_id
self.logger.info(f'worker starting. timestamp left shift {self.timestamp_left_shift},'
f' datacenter id bits {self.datacenter_id_bits},'
f' worker id bits {self.worker_id_bits},'
f' sequence bits {self.sequence_bits}, worker_id {self.work_id}')
def next_id(self):
with self.__lock:
timestamp = self._get_current_timestamp()
if timestamp < self.last_timestamp:
raise BusinessException(f'Clock moved backwards. Refusing to generate id for'
f' {self.last_timestamp - timestamp} milliseconds')
if self.last_timestamp == timestamp:
self.sequence = (self.sequence + 1) & self.sequence_mask
if self.sequence == 0:
timestamp = self._til_next_millis(self.last_timestamp)
else:
self.sequence = 0
self.last_timestamp = timestamp
return ((timestamp - self.twepoch) << self.timestamp_left_shift) \
| (self.datacenter_id << self.datacenter_id_shift) \
| (self.worker_id << self.worker_id_shift) \
| self.sequence
def _til_next_millis(self, last_timestamp):
timestamp = self._get_current_timestamp()
while timestamp <= last_timestamp:
timestamp = self._get_current_timestamp()
return timestamp
@staticmethod
def _get_current_timestamp():
return int(datetime.datetime.now().timestamp() * 1000)
这段代码其实是根据java版本雪花算法代码翻译过来的,根据设置的work_id和datacenter_id的不同,生成19位增长性id,由于自己实现了id生成算法,所以无需引入第三方库
使用方式:
if __name__ == '__main__':
IdWorker = SnowFlakeId(0, 1)
for i in range(10000):
print(IdWorker.next_id())
time.sleep(0.1)
运行结果:
第二种:使用redis生成唯一主键
from datetime import datetime
import redis
class RedisUtil:
__redis_conn_pool = redis.ConnectionPool(host='localhost',
port=16379,
db=0,
password='',
max_connections=100)
@classmethod
def increment_and_get(cls, name: str):
return redis.Redis(connection_pool=cls.__redis_conn_pool).incrby(name)
@classmethod
def expired(cls, name, time):
return redis.Redis(connection_pool=cls.__redis_conn_pool).expire(name, time)
@classmethod
def ttl(cls, name):
return redis.Redis(connection_pool=cls.__redis_conn_pool).ttl(name)
class RedisId:
@classmethod
def getId(cls) -> int:
id = RedisUtil.increment_and_get('id_generation')
if RedisUtil.ttl('id_generation') == -1:
RedisUtil.expired('id_generation', 20)
return int(f'{cls._get_time_str()}{id:0>6d}')
@staticmethod
def _get_time_str():
return datetime.now().strftime('%Y%m%d%H%M%S')
这里由于使用了redis,需要引入第三方redis包
使用方式:
if __name__ == '__main__':
for i in range(15):
print(RedisId.getId())
time.sleep(1.75)
这里加入了一个逻辑,一定时间后(代码是20秒)会重置自增值,防止自增量无限上涨