一、秒杀场景介绍
1.1 什么是秒杀?
秒杀就是在同一时间段内对同一商品进行抢购。对于程序系统来讲就是多个进程同时访问同一个共享资源。
1.2 秒杀场景需要解决的问题
高并发:秒杀场景和其他场景不同,大量用户会在同一时间进行访问。
读多写少:访问请求数量远远大于商品数量,只有少部分用户能够秒杀成功。
数据正确性:秒杀流程比较简单,一般就是下订单减库存。但是,在此过程中一定要保证数据的正确性,防止超卖的现象。
防作弊:秒杀场景中,要做到防止通过插件等手段获得不良利益。
扩展性:对于超出预期的并发量,要能够动态扩展满足业务需求。
1.3 秒杀架构的设计理念
访问拦截:在前端限制大部分流量并且要求同一IP只能选购一件商品,只允许少部分流量进入服务后端。
分流:对于秒杀系统瞬时会有大量用户涌入,把瞬间的大量请求通过负载均衡均匀分布在各节点。
异步处理:将请求的接受和处理异步化,可以极大的提升系统并发。
可拓展:当秒杀系统连接用户大于预计值时,要能够动态扩展以应对大量用户、高并发的需求。
内存缓存:秒杀系统最大的瓶颈在于数据库读写性能,如果能够把部分数据或业务逻辑转移到内存缓存,效率会有极大地提升。可以在MySQL数据库上层使用Redis进行缓存,提高整体性能。
二、秒杀场景后端的实现
Redis是一个分布式缓存系统,支持多种数据结构,我们可以利用Redis轻松实现一个强大的秒杀系统。
2.1 为什么使用Redis
Redis是单线程的,可以很好地解决并发问题,如果使用普通的代码逻辑实现秒杀会出现并发问题导致多人秒杀成功货物超发的情况。
Redis可以实现分布式锁,对于不同商品可以很好的支持并发。
2.2 Redis分布式锁的实现
Redis通过分布式锁控制同步访问共享资源。它的基本原理是:通过一个状态值来表示锁,对锁的占用和释放通过状态值来标识。
Redis为单线程,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。Redis的SETNX命令可以方便的实现分布式锁。
SETNX key value
如返回1,则该客户端获得锁,把key的value设置为时间戳表示该键已被锁定,该客户端最后可以通过DEL来释放该锁。
如返回0,表明该锁已被其他客户端取得,这时我们可以先返回或进行重试等对方完成或等待锁超时。
如果一个持有锁的客户端失败或崩溃了不能释放锁,该怎么解决?
新客户端发送SETNX key value
想要获得锁,由于源客户端还持有锁,所以Redis返回给新客户端一个0,然后新客户端发送GET key
命令检查锁是否超时了,如果没超时,则等待或重试。反之,如果已超时,则发送DEL key
释放锁,SETNX key value
获得锁。如果在新客户端SETNX key value
之前,有个客户端更快一步执行了上面的操作,那么新客户端会返回0,继续等待或重试。
2.3 Redis缓存与数据库的同步
Redis对于每个用户的秒杀,在获取锁之后使用 RPUSH key value插入秒杀请求,当插入的秒杀请求数达到上限时,停止所有后续插入。然后我们可以在台启动多个工作线程,使用 LPOP key 读取秒杀成功者的用户id,然后再操作数据库做最终的下订单减库存操作。
三、总结
本文仅仅对秒杀场景做了一些基本的介绍,其中包括秒杀场景的基本概念、特点、问题以及Redis部分的实现原理。其余部分感兴趣的朋友可自行学习。