高并发之Redis秒杀的本质

Redis秒杀的本质

Wirte by 021. Leave a message if i messed up ! : )

基于Redis的分布式锁- SkrNet

秒杀场景与特点

场景

  • 秒杀活动,到时间点后,用户会对商品进行购买

特点

  • 秒杀场景下,瞬时的并发会比较高
  • 商品的数量是有限的,不能超买超卖
  • 每个用户最多只能抢购一件商品

整体架构设计

截屏2021-11-26 下午8.14.40

核心思想

  • 幂等性

    • 任意时间同一用户IP(默认一个用户一个客户端)无论操作多少次最多只能抢到资源一次.
  • 原子性

    • 同一时间只有一个线程扣查询库存并且扣库存成功,不能被打断.
  • 一致性

    • 在扣除库存成功后,能通知到其他集群节点,防止数据脏读.
  • 顺序性

    • 有序的更新数据库库存,温柔的像个绅士.

Redis 的 Lua脚本

脚本的原子性(官方介绍)

  • Redis uses the same Lua interpreter to run all the commands. Also Redis guarantees that a script is executed in an atomic way: no other script or Redis command will be executed while a script is being executed. This semantic is similar to the one of MULTI / EXEC. From the point of view of all the other clients the effects of a script are either still not visible or already completed.

    However this also means that executing slow scripts is not a good idea. It is not hard to create fast scripts, as the script overhead is very low, but if you are going to use slow scripts you should be aware that while the script is running no other client can execute commands.

  • Redis 使用单个 Lua 解释器去运行所有脚本,并且, Redis 也保证脚本会以原子性(atomic)的方式执行: 当某个脚本正在运行的时候,不会有其他脚本或 Redis 命令被执行。 这和使用 MULTI / EXEC 包围的事务很类似。 在其他别的客户端看来,脚本的效果(effect)要么是不可见的(not visible),要么就是已完成的(already completed)。 另一方面,这也意味着,执行一个运行缓慢的脚本并不是一个好主意。写一个跑得很快很顺溜的脚本并不难, 因为脚本的运行开销(overhead)非常少,但是当你不得不使用一些跑得比较慢的脚本时,请小心, 因为当这些蜗牛脚本在慢吞吞地运行的时候,其他客户端会因为服务器正忙而无法执行命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
-- KEYS [goods]
-- ARGV [uid]
-- return -1-库存不足 0-重复购买 1-成功

local goods = KEYS[1]
local activity = ARGV[1]
local uid = ARGV[2]
local goodsuids = goods .. ':' .. activity .. ':uids'

local goodsstock = goods .. ':' .. activity .. ':stock'
local stock = redis.call('GET', goodsstock)

if not stock or tonumber(stock) <= 0 then
return -1
end


local isin = redis.call('SISMEMBER', goodsuids, uid)

if isin > 0 then
return 0
end



redis.call('DECR', goodsstock)
redis.call('SADD', goodsuids, uid)
return 1




#从参数中获取goods编号,activity编号,uid。
1.首先判断库存
#使用GET命令,查询“goods:activity:stock”获取库存,判断是否还有库存,库存小于等于0,返回-1
2.判断是否已经购买过
#使用SISMEMBER命令,判断“goods:activity:uids”set中,是否有uid,有表示参加过秒杀,返回0
3.减库存
#使用DECR命令,对“goods:activity:stock”减一,扣减库存.
4.更新参与秒杀记录
#使用SADD命令添加“goods:activity:uids”用户uid,返回1