1. ID生成器
在工作中,我们经常需要用到ID生成器。比如当当前系统与别的系统有一些数据需要同步时,为了实现幂等性,往往会为每一次同步请求设定一个全局统一的ID。可见ID生成器在许多项目中都有至关重要的多用。
ID生成器有许多的实现方式。
1. 比如用著名的UUID,这种方法可以简单的获取到全局唯一的ID。但是因为获得的ID是字符串,在一些需要纯数据的场合,处理起来效率没有这么高。
2.使用数据库的自增ID,但是需要数据落库之后才能拿到。
本篇文章分享一种简单的ID生成器,可以支持多台实例使用。另外也支持自定定义每日或者每月重置。
2.设计思路
为了提高ID生成器的访问速度,可以将当前的ID值存在内存中。而为了防止多个实例出现相同的ID,我们需要分配不同的ID段到不同的实例上,这样保证每个实例用的ID数字段都是不同的,也就不会出现重复的情况。同时将最新分配的数据段记录到数据库中,实现持久化。并且使用唯一键和乐观锁防止并发。分配ID段的步长可以自定义,如果设定为1,则每次分配的ID段都是1,能保证生成的ID是连续的,但是也导致每次生成ID都要去更新数据库。
总结上面的设计思路后,假设当前业务编号(type)为1,ID生成器的分配ID段的步长为100,每日自动重置为0,当前日期为20200815,可以得到:
1. 调用ID生成器生成函数时,首先到内存中寻找有没有已经分配的ID段,如果没有,则到数据库中搜索是否有type=1 and time=20200815的记录,如果没有,创建一条记录(idvalue=100, type = 1,time=20200815),并且把0-100的数据段存到内存中使用。如果存在则更新idvalue=idvalue+步长。(使用乐观锁防止并发)
2.如果内存中存在已经分配的ID,则取ID,并且更新内存中ID的最新值。如果ID值超出了分配的ID段。则到数据库中搜出type=1 and time=20200815 的Idvalue,并且把(idvalue - idvalue+步长)放入内存中,并且更新数据库idvalue=idvalue+步长。
3.如果需要月结,年节等,只需要控制time字段就能实现。
3.代码
其中部分逻辑等待读者自己去实现。(临时写的,如果发现问题可以交流)
class id_creator(objects):
id_value_dict = {}
step = 100 # step可以写成一个外部的字典,这样可以简单的更新步长
def create_id(time, idtype):
key = _create_key(time, idtype) # 生成key的逻辑自己实现即可
segment = id_value_dict.get(key)
# 这里segment是一个类
if segment is None or segment.value > sement.max_v:
segment = self._create_new_segment(time, idtype)
id_value = segment.value
segment.value = segment.value + 1
return id_value
def _create_new_segment(time, type):
#从数据库中获取记录,自行实现
database_record = get_data_from_sql(time, idtype)
# 这里使用乐观锁,一直循环到更新或者创建成功
while True:
# 如果没有数据库记录,则直接创建记录
if database_recode is None:
try:
_create_database_record(time, idtype, step)
max_value = step
except:
continue
# 如果有,更新记录
else:
max_value = database_recode.id_value + step
try:
_update_database_record(time, idtype, max_value)
except:
continue
if max_value > 0:
break
# 创建新的segment
segment = Segment(value=max_value - step + 1, max_v=max_value)
key = _create_key(time, idtype)
id_value_dict[key] = segment
return segment