使用redis zset实现抽奖,奖池商品按时间随机分布

摘要:
ok,需求完美解决,锁的问题直接上代码,锁就是保证zset的排序操作与移除操作是原子操作,否则便会出现超发,使用了redis的setNx做分布式锁。

话不多说,直接上需求描述:

最近需要上一期活动,这个活动是以转盘抽奖为形式的抽奖活动,要求每个用户用积分进行抽奖,且中奖率为100%即不可出现不中任何奖品的情况,之后,又加了一个要求,即不能实行纯随机的抽取,如果如此会产生一个极端情况,如果开始的时候活动极其火爆由于随机的不可控性头一天用户便将所有优质奖品全部抽走,那么后来的用户将只会抽到保底奖品。

那么奖品就需要按时间分布在从活动开始到结束的时间段,其次需要做的是,在某些特殊的时间段,我们希望多投放一些奖品给用户抽到。

需求分析:

那么开奖策略可以为为每个奖品设置开奖时间,只有在开奖后来抽奖才能抽到该奖品,否则视为未中奖发保底奖品,我们只需要拿当前时间与最接近奖品开奖时间对比即可。

由上需求,那么就需要一个容器来存放这些奖品,对这个容器的要求:

1. 它可以以时间轴为维度取出奖品;

2. 它可以以时间轴为维度放入奖品;

3.它可以以时间轴为维度将奖品排序;

同时,后台应该有地方配置每个小时应投放的奖品数量,同时为保证配置数据能及时生效,应当是每小时前去向奖品池投放下一个小时的奖品;

如下图所示,每个奖品都有对应开奖时间,奖品1只有10000毫秒之后的请求才可以抽到,且只有奖品1抽走之后才可以抽奖品2;

抽奖步骤:

使用redis zset实现抽奖,奖池商品按时间随机分布第1张

性能安全考虑:

显然,抽奖是容易引发并发问题的场景,高并发情况往往会带来两个问题

1. 超发问题,例如将10个奖品发给了11个人,用锁可解决;

2.数据库等基础组件负载过高导致宕机,以数据库为例,如果每个用户每抽走一个奖品都去连接数据库更新库存,数据库很有可能承受不住(数据库能承受的qps远不如redis);

方案:

使用redis的zset数据结构,这里简单说明下zset,它是一个基于跳表实现的有序集合,尤其适合排序场景比较多的场景,是一个典型的用空间换取时间的数据结构。这里我们用开奖时间戳作为score,保证其按照时间排序,存入的时候可以直接将奖品ID与时间戳存入其中即可。

同时设置定时任务,每个小时去拿下一个小时的所需的奖品,随机将其散列在下一个小时的各个时间上,并在此时就将各个奖品库存扣除。

ok,需求完美解决,锁的问题直接上代码,锁就是保证zset的排序操作与移除操作是原子操作,否则便会出现超发,使用了redis的setNx做分布式锁。

/**
     * 抽奖
     *
     * @paramturnTableNum 转盘编号
     * @return奖品ID
     */
    public long getLotteryResult(long userId, intturnTableNum,
        Map<Long, ActivityTurntableGoodsConfig>goodsConfigMap) {
        Set<String> prizeSet = null;
        String prizeResStr = null;
        try{
            if(RedisUtils
                .lock(RedisKey.TURNTABLE_PRIZE_QUEUE_LOCK, String.valueOf(turnTableNum))) {
Set prizeSet =RedisClusterAccessor
            .zrangeByScore(RedisKey.TURNTABLE_PRIZE_QUEUE, String.valueOf(turnTableNum),
                0, System.currentTimeMillis(), 0,1);
                if (null !=prizeResStr) {
                    //在奖池中移除奖品
                    log.debug("{} remove prize {} {}", XGameContextHolder.get(), turnTableNum,
                        prizeResStr);
                    RedisClusterAccessor
                        .zrem(RedisKey.TURNTABLE_PRIZE_QUEUE, String.valueOf(turnTableNum),
                            prizeResStr);
                }
            }
        } catch(Exception e) {
            throwe;
        } finally{
            RedisUtils.unlock(RedisKey.TURNTABLE_PRIZE_QUEUE_LOCK, String.valueOf(turnTableNum));
        }
        if (null ==prizeResStr) {
            return -1;
        }
        return CommonUtil.safeParseLong(prizeResStr.split("_")[0]);
    }                                                                                        

免责声明:文章转载自《使用redis zset实现抽奖,奖池商品按时间随机分布》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇SQL Server 2000 安装错误,解决方法 Process Exit Code: (1060) 指定的服务未安装jfreeChart柱状图各属性详细设置下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

C# 通过ServiceStack 操作Redis——Hash类型的使用及示例

接着上一篇,下面转到hash类型的代码使用 Hash:结构 key-key-value,通过索引快速定位到指定元素的,可直接修改某个字段 /// <summary> /// Hash:类似dictionary,通过索引快速定位到指定元素的,耗时均等,跟string的区别在于不用反序列化,直接修改某个字段 /// str...

7--docker-compose详解

目录 一、Docker Compose简介 二、docker compose 安装 与 卸载 1.下载 2.授权 3.检测版本 4.卸载 三、docker compose使用 1.相关概念 2.场景 3.docker-compose模板 4.通过docker-compose运⾏⼀组容器 5. docker-compose 模板⽂件...

Tair rdb(redis存储引擎)实现介绍

淘宝那岩曾经在淘宝核心系统团队博客上介绍过Tair ldb的实现,本文将尝试着介绍rdb(redis存储引擎)的实现。 Tair是淘宝开源的分布式KV缓存系统,内部将功能模块化,抽离出底层存储细节,可以接入不同的存储引擎。redis是一个开源的、高效的key-value存储,提供了strings、hashs、lists、sets、sorted sets等多...

Redis常见配置redis.conf

redis的配置文件。相信学过SSH或SSM的读者都知道,配置文件的使用在当下开发已十分普遍,希望大家要熟悉习惯这 种开发方式,废话不多说,来开始我们今天的内容吧。 首先得找到 redis 的配置文件 redis.conf(就在你redis的安装目录下): (注意:在修改配置文件前请先备份一份,以防万一)...

java使用Redis(六个类型)

下载插件:https://mvnrepository.com/artifact/redis.clients/jedis/3.0.0 maven项目依赖: <dependency> <groupId>redis.clients</groupId> <artifact...

Redis弱密码修改

Redis链接密码修改,Redis命令行进入,如图7006端口为master其他的7000-7005为slave,按以下步骤修改master节点的requirepass ,和修改所有slave节点的masterauth和requirepass。  注意: 1.一定要config rewrite不然重启后从配置文件读取密码。 2.slave对应的master...