一种分布式id生成规则

本文探讨了一种针对订单号生成的需求,要求包括唯一性、趋势递增、时间信息、防止冲突和限制长度等。通过分析,排除了自增id、uuid和snowflake等方案,最终提出使用时间戳加4位随机数的方案,并结合redis实现,利用其原子性特性避免race condition,同时确保高可用性。在实现过程中,需注意lua脚本的确定性以防止主从同步问题。

需求

有个生成订单号的需求,对于生成订单号有如下要求

  • 不能被猜出订单量
  • 唯一性
  • 趋势递增
  • 订单号包含时间信息
  • 防止race condition生成重复的id
  • 防止时间回拨生成重复的id
  • 满足每秒可以生成1w个订单号
  • 订单号不能过长

分析

  • 不能被猜出订单量,所以自增id排除掉
  • 要求趋势递增,所以uuid排除掉
  • 订单号不能过长,所以snowflake、Mongdb objectID之类的排除掉

方案

订单号=12位时间信息+4位随机数。时间信息精确到秒,加上4位随机数,支持每秒最多生成1w个订单号。

利用redis来实现方案。
incr命令防止4位随机数重复。初始化的时候就设置一个随机数,而不是从0开始incr。类型TCP协议的ISN(Initial Sequence Number)
time命令获取redis服务器的时间,防止各机器本地时间不一致,导致订单号重复。
lua脚本确保原子性,避免race condition.
set命令指定nx参数,避免race condition.

由于redis用的是AWS的服务,所以高可用性可以保障。

伪代码如下

def generate_order_id():
    conn = get_redis_connection("default")
    # 获取redis的时间,保证时间递增有序, 而不依赖应用服务器本地时间
    # 防止redis服务器时钟出现问题, 比如ntp时间同步,时间变为过去的时间而导致订单号重复
    LUA_SCRIPT = """
        local ts = redis.call('time')[1]
        local order_id_last_timestamp = redis.call('get', 'order_id_last_timestamp')
        if order_id_last_timestamp and ts < order_id_last_timestamp then
            return 0
        end
        redis.replicate_commands()
        redis.call('set', 'order_id_last_timestamp', ts)
        return ts
    """
    ts = int(conn.register_script(LUA_SCRIPT)())
    if ts == 0:
        raise Exception('redis服务器的时间出现问题')
    datetime_s = datetime.datetime.fromtimestamp(ts).strftime('%y%m%d%H%M%S')
    key_name = 'order_id_%s' % datetime_s  # 为了防止incr被用户推测出订单数据量,每秒一个key
    salt = random.randint(0, 99999999) % 10000  # 取模取后面4位随机数;初始化的时候就设置的随机数,而不是每次都从0000开始,防止用户猜测
    if conn.set(key_name, salt, ex=10, nx=True):  # nx表示如果key不存在才set进去, 防止刚好其它订单同时设置
        return '%s%04d' % (datetime_s, salt)  # '%04d'使不足4位的数字前面补0
    salt = conn.incr(key_name) % 10000
    return '%s%04d' % (datetime_s, salt)

因为lua脚本写入非确定的值如time命令,会导致主从同步出问题。比如主库执行一次写入time命令的值,从库也执行一次写入time命令的值,2次time命令的值不一样,这样主从就不一致了。
所以默认情况下,会报Write commands not allowed after non deterministic commands错误.
解决方法是使用redis3.2以上的新特性redis.replicate_commands()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值