秒杀考核未解决的问题

534 阅读2分钟

生成订单号

  • 目前 : time.Now().UnixNano()
    • time.Now().UnixNano() + 随机数 + redis递增值
    • 雪花算法 (未实践)

消费端

  • 目前 : 请求接口模拟实现
    • RabbitMQ + nohup
    • RabbitMQ + pm2
      • 消息幂等性校验
      • ACK 保证
      • 持久化

锁问题

释放锁

逻辑未执行完,锁过期释放,然后这个客户端之后又尝试删除这个其实已经被其他客户端拿到的锁

  • 用DEL指令有可能造成一个客户端删除了其他客户端的锁,通过校验这个值保证每个客户端都用一个随机字符串’签名’了,这样每个锁就只能被获得锁的客户端删除了。
  • 既然释放锁时既需要校验这个值又需要删除锁,那么就需要保证原子性,redis支持原子地执行一个lua脚本,所以我们通过lua脚本实现原子操作
    if redis.call("get",KEYS[1]) == ARGV[1] then
         return redis.call("del",KEYS[1])     
    else
         return 0
    end 
    

业务逻辑长

加锁和释放锁之间的逻辑执行得太长,以至于超出了锁的超时限制,第二个线程就提前重新持有了这把锁,导致临界区代码不能得到严格的串行执行。

  • 调大锁的超时时间,之后若再出现超时带来的并发问题,人工介入修正数据
    • 不是一个完美的方案,因为但业务逻辑执行时间是不可控的
    • 且如果锁超时时间设置过长,当持有锁的客户端宕机,释放锁就得依靠redis的超时时间,这将导致业务在一个超时时间周期内不可用。
  • 锁快要超时了,客户端可以给redis服务实例发送一个Lua脚本让redis服务端延长锁的时间,只要这个锁的key还存在而且值还等于客户端设置的那个值
    • 启动另外一个线程去检查的问题,这个key是否超时,在某个时间还没释放
    if redis.call("get",KEYS[1]) == ARGV[1] then
        redis.call("set",KEYS[1],ex=3000)   
    

哨兵切换带来的延迟问题

主节点挂掉时,从节点会取而代之。原先第一个客户端在主节点中申请成功了一把锁,但是这把锁还没有来得及同步到从节点,主节点突然挂掉了。然后从节点变成了主节点,这个新的节点内部没有这个锁,所以当另一个客户端过来请求加锁时,立即就批准了。这样就会导致系统中同样一把锁被两个客户端同时持有,不安全性由此产生。

  • 可以容忍,人工解决

压测结果

  • 一万人一秒内抢购一、十、一百件商品,SUCCESS,本机压测结果如下