前言
抽奖的功能在互联网的项目中随处可见,通过这些给用户惊喜的功能,来达到引流的效果。但是这个功能说难也难,说简单也简单。因为一般互联网的工程都是集群部署,抽奖的时候还要考虑线程间奖品争夺的问题,通过redis可以很轻松实现抽奖功能,我阐述下我的思路,请大家参考。
一、抽奖功能要点
1.1、奖品多抽问题
抽奖功能其实和商城是一样的,前面会有各种验证筛选,比如:黑名单过滤,地区用户过滤等等。
最后剩下可以拿到奖品的几个用户,这几个用户都要在此通过数据库的数据来做最后一步的验证,避免出现出现奖品发多的问题。
实现思路:
在该用户认定为可以拿奖的时候,这个奖品的库存肯定会有库存减1
的操作。这个时候可以通过goods_num(库存字段)>1
。如下:
update t_goods set goods_num=goods_num-1 where goods_id=#{id} and goods_num>1
如此,update会返回修改行数,如果修改行数大于1时,可以认为库存成功,然后即可插入获奖记录信息。注意:要保证减库存和插入获奖记录是在一个事物中。
1.2、概率计算问题
如果要自己计算概率逻辑,java代码通常会接触Random
类进行随机值的计算。思路如下:
随机一个0-100的数值,然后按照各个奖品的概率计算区间。0-20是一等奖;20-30是二等奖;50-100是三等奖。 按照这样的逻辑来处理,代码实现逻辑注意严谨,这个时候的数值非常重要。而且奖品池的大小也在越来越小。有时是概率抽奖,有时候是奖品池的逻辑,不过都可以按照这种思路处理。
如上思路单机时或者简单的逻辑下只能自己实现,如果集群模式下概率的逻辑放到每个服务中可能会有问题。
例如:
如果两个人都抽到了1等奖的最后一个奖品,这个时候肯定会有一个线程异常,如果是奖品池的逻辑的话,就需要再次计算这个线程应该获得哪个奖品。这种情况对于有顺序要求的场景是无法接受的。
二、redis实现抽奖
Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。set
集合中有两个指令非常适合在抽奖的场景使用。
SPOP key [count]
移除并返回集合中的一个随机元素。SRANDMEMBER key [count]
返回集合中一个或多个随机数。
如此,将所有的奖品通过SADD
添加到SET
集合中,然后通过随机命令获取对应的奖品即可。而且,抽奖一般是有时效性,正好可以配合redis的key的失效时间使用。使得抽奖功能很完美的解决。
但是,好的东西肯定也会有其弊端,那就是redis的数据一致性。将奖品的信息放到了两个存储介质中储存,就有可能出现数据不一致的问题,一般数据会以关系型数据库为准。redis的要按照场景,在必要的步骤进行数据同步的判断,避免数据不一致导致的灾难。