一起聊聊redis(5)——c#的lua脚本应用实例之高并发抢口罩

本节内容

展示Redis数据库在高并发下实现抢口罩的例子,文章分3个实现的模式

  1. 不加锁
  2. 乐观锁
  3. Lua脚本

前言

写于2020.2.8日疫情严重之日,抢口罩成为每晚8点档黄金时间必备节目,在报道上看到太多不知真假的求助病重不能得到及时医治的,有感于世事无常,珍爱身边人.


不加锁

在这个例子中程序运行是有bug的。当多人同时抢口罩的时候,可能会出现抢到的人数比口罩的库存多的情况。先看下面实现的主要代码

一起聊聊redis(5)——c#的lua脚本应用实例之高并发抢口罩

抢口罩的逻辑,这里用到了哈希类型,使用这个类型主要是用它的自减方法。

一起聊聊redis(5)——c#的lua脚本应用实例之高并发抢口罩

初始化口罩库存为3,Parallel实现并发抢口罩

一起聊聊redis(5)——c#的lua脚本应用实例之高并发抢口罩

运行结果

如上图所示,会出现库存超卖的情况,原因是这样的。由于程序在判断库存和库存减1两个操作没有实现原子性,虽然Redis是单线程运行的。但在某个客户端的判断库存和库存减1这两个操作之间,会可能出现其他的客户端的库存减1的操作插在其中,造成以上的结果。

乐观锁

Redis中Watch命令是一个乐观锁,它可以在执行Exec之前,监视任意数量的数据库键,当其中至少一个发生改变后,它会拒绝执行事务,如下图所示。

一起聊聊redis(5)——c#的lua脚本应用实例之高并发抢口罩

在c#的StackExchange.Redis中没有Watch命令,但它有事务的实现。

一起聊聊redis(5)——c#的lua脚本应用实例之高并发抢口罩

事务的条件用AddContidion方法添加,类似于Watch某个键值

运行结果,由于添加了锁,所以不再存在超卖的情况。

Lua脚本

Lua脚本会在一个Redis服务端的伪客户端执行代码。天生自带原子属性,意味着lua的代码都原子执行。另外使用Lua的脚本,可以代码复用,以后其他语言编写的客户端也可以使用,这好处相当于存储过程和数据库的类比。

详细的代码和结果见下图:

一起聊聊redis(5)——c#的lua脚本应用实例之高并发抢口罩

这里为了演示方便,通常都是上传脚本到服务端,让服务端返回SHA1码,下次执行直接传SHA1码

c#生成Sha1

在实际过程中,通常不会传一大段lua脚本到服务端,就等于你不会传一个存储过程的逻辑到数据库一个道理,根据上一节的内容,通常上传完lua脚本到服务端会返回一个sha1的字符串,下次可以通过传该字符串到服务端,服务端进行比对后找回对应的脚本执行。下面的代码展示怎么在c#中生成sha1


一起聊聊redis(5)——c#的lua脚本应用实例之高并发抢口罩

总结:

用Lua脚本是最好的个人觉得,因为它可以封装好这一部分的逻辑,甚至这些逻辑改动了都不需重新编译客户端,只是修改脚本就好,当然脚本就不能在程序中硬编码。


分享到:


相關文章: