抢红包的实现和思考

    之前做了一个抢红包的活动,由于用户量级不大因此实现的版本不难,但开发人员设计系统的目标不该只是满足当前需求。如淘宝、京东,随着用户量级的逐渐增大,技术含量完全不是一个等级。因此打算从这个角度去深入研究学习。
    本文主要介绍已经实现的满足用户量级较小的系统设计,后续会补充优化的方案。

1 需求   

    满足一定条件的用户可以在活动期间每天的固定时间点抢红包,每人最多只能领取一个。
2 内部逻辑图


 3 步骤

    a,用户点击抢红包发出抢红包的请求。
    b,系统收到请求后做一些列的校验,包括时间,用户是否满足条件等。

    c,验证是否已抢过,如抢过则返回。
    d,获取个人的客观锁,锁的粒度是用户,获得锁的用户请求才能继续后面的逻辑,否则返回。解决同一用户并发抢的问题。
    e,抢红包,每一次抢红包都是一次原子操作,解决不同用户间的并发抢的问题,避免出现数据的不一致。红包不足则返回。
    f,抢到红包则更新个人领取记录,注意是在解锁之前更新该记录,若先解锁则会出现同一用户多次抢的问题。
    g,解锁,返回抢红包成功。
4 关键技术点
    a,锁机制的加入,同一用户的并发问题。由于是抢因此用户一般会连续点击发出请求,如不加限制就会出现一个用户抢多个红包的问题,这里加入个人锁,保证同“一个用户的请求的原子性”。具体的锁实现是每次都像ssdb(或redis)中进行incr操作,若incr返回1则表示获得锁,否则进行decr并返回。因为incr和decr都是原子性的,因此即便出现并发访问也是顺序执行,因此只有一个请求可获得所。该锁的实现类似于“乐观锁”,所有请求都能去修改数据,但只有返回的版本号符合的才能更新成功,这里的版本号就是该值是否等于1。而该锁对于同一用户的请求来说是一个“悲观锁”,同一时刻只能处理一个请求。(redis结合lua的实现版本后面再介绍)。 
    b,不同用户并发抢红包的问题。由于在同一时刻会有大量的用户同时发起请求,而系统一般也是分布式的而不是单机序列化请求,因此如何保证数据的一致性避免出现红包抢多了的问题,且需要保证公平性。这里才去的方式是抢红包的动作作为一次原子操作,即可以抢红包的用户像ssdb(redis)中存储的红包数目的key进行decr操作,当返回值是小于0的时候表示红包不足,没有抢成功。由于decr是原子,且不同服务器都指向同一数据库,保证访问的序列化。这里的实现机制类似于“乐观锁”,都可以去修改,但是只有“版本号”大于0的才成功,否则失败。
5 其他方面 
    1,防刷,如加入验证码,同一账号的罗律,nginx层的ip过滤等,减少异常请求带来的压力。
    2,前端控制开放时间,由于活动时间开启前会有很多大量的预热导致的请求,因此不要过早暴露入口。

    3,系统的兼容性,需满足更多秒杀的场景。
6 系统优化
    1,简化业务逻辑,c、d、e三步若采用redis+lua的实现便可合成一部,写在lua脚本里,而redis支持脚本的原子性执行,避免了两次访问数据库带来的性能开销。

    2,随着用户的增大,第一个瓶颈是ssdb或redis,因为现在的实现机制里没有没有缓存或者队列,所有的请求直接打到数据库,这样会给数据库带来很大的压力。因此可加入缓存、队列、或者写入文件,由于抢红包这类型是限量的,因此可对数据库的请求数目加以限制,减轻压力。但缓存的可靠性和数据一致性需要深入探讨一下。
    3,第二个瓶颈是系统框架,用php写的框架之前的测试qps只能上千,更多的请求就开始报502,可采取的解决方案有nginx+lua或者用Go语言实现,提高并发性能。

    4,待续

猜你喜欢

转载自wangjixiang.iteye.com/blog/2244678