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

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

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

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

需求分析:

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

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

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

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

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

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

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

  抽奖步骤:

性能安全考虑:

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

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

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

方案:

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

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

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

  

/**
     * 抽奖
     *
     * @param turnTableNum 转盘编号
     * @return 奖品ID
     */
    public long getLotteryResult(long userId, int turnTableNum,
        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) {
            throw e;
        } finally {
            RedisUtils.unlock(RedisKey.TURNTABLE_PRIZE_QUEUE_LOCK, String.valueOf(turnTableNum));
        }
        if (null == prizeResStr) {
            return -1;
        }
        return CommonUtil.safeParseLong(prizeResStr.split("_")[0]);
    }                                                                                        

原文地址:https://www.cnblogs.com/tz346125264/p/10129053.html

时间: 2024-10-09 22:07:59

使用redis zset实现抽奖,奖池商品按时间随机分布的相关文章

Redis学习之代理 ip 池设计方法详解

代理 ip 因为配置简单而且廉价,经常用来作为反反爬虫的手段,但是稳定性一直是其诟病.筛选出优质的代理 ip 并不简单,即使付费购买的代理 ip 源,卖家也不敢保证 100% 可用:另外代理 ip 的生命周期也无法预知,可能上一秒能用,下一秒就扑街了.基于这些原因,会给使用代理 ip 的爬虫程序带来很多不稳定的因素.要排除代理 ip 的影响,通常的做法是建一个代理 ip 池,每次请求前来池子取一个 ip,用完之后归还,保证池子里的 ip 都是可用的.本文接下来就探讨一下,如何使用 Redis 数

使用redis所维护的代理池抓取微信文章

搜狗搜索可以直接搜索微信文章,本次就是利用搜狗搜搜出微信文章,获得详细的文章url来得到文章的信息.并把我们感兴趣的内容存入到mongodb中. 因为搜狗搜索微信文章的反爬虫比较强,经常封IP,所以要在封了IP之后切换IP,这里用到github上的一个开源类,当运行这个类时,就会动态的在redis中维护一个ip池,并通过flask映射到网页中,可以通过访问 localhost:5000/get/ 来获取IP 这是搜狗微信搜索的页面, 构造搜索url .搜索时会传递的参数,通过firefox浏览器

网站点击率统计 Redis Zset NetMq

最近我们在开发一个项目,其中一项需求就是统计网站稿件点击率,为点击时效性前端框架我们采用的是NodeJs+Redis 这种方式 ,数据组织通过Zset 以及关键词进行拆分,每篇稿件对应的是一条Zset 记录,通过Score 增加来统计每篇稿件点击率,后期在通过Net Mq 同步到关系型数据库中,Redis是Keyvalue 型数据库要与关系行数据库对应上,因次必须是对象优先设计出来,这样才能跟好理清逻辑,以及方便数据交互

将mysql表数据批量导入redis zset结构中

工作中有这样一个需求,要将用户的魅力值数据做排行,生成榜单展示前40名,每隔5分钟刷新一次榜单.这样的需求用redis的zset是很方便实现的.但是数据存在mysql的表中,有400多万条,怎么将其快速的放入redis中呢? 一般我们想到是通过程序把数据从mysql中查出来,然后存入redis,但是这样不仅耗时,而且不能保证写入redis的数据的准确性,这中间存在一个部署的时差.通过google老师一查原来redis提供了批量导入数据的功能,原帖地址: http://baijian.github

redis的连接方法|连接池|操作

1.先看下redis的连接 import redis # 连接服务端 r = redis.Redis(host="127.0.0.1",port=6379) #获取所有的key值 print(r.keys()) #获取某个键的value print(r.get("name")) # 设置一个键值对 r.set("name","ddd",ex=3) 2.在看下redis的连接池,创建一个连接池,并且连接该连接池 connect_

使用redis+flask维护动态代理池

在进行网络爬虫时,会经常有封ip的现象.可以使用代理池来进行代理ip的处理. 代理池的要求:多站抓取,异步检测.定时筛选,持续更新.提供接口,易于提取. 代理池架构:获取器,过滤器,代理队列,定时检测. 使用https://github.com/Germey/ProxyPool/tree/master/proxypool代码进行分析. run.py里面的代码 from proxypool.api import app from proxypool.schedule import Schedule

Java与redis交互、Jedis连接池JedisPool

Java与redis交互比较常用的是Jedis. 先导入jar包: commons-pool2-2.3.jar jedis-2.7.0.jar 基本使用: public class RedisTest1 { public static void main(String[] args) { Jedis jedis = new Jedis("localhost",6379); jedis.set("username","chichung"); jed

【活动】学分兑换奖池全面更新啦!看看有没有你心仪的~

哭唧唧的学院菌,昨日看了一眼学分兑换!what?喜欢的奖品怎么没了??本着为人(xun)民(si)服(wang)务(fa)的原则,特意向头儿申请了经费!从今开始! 学分兑换的奖品池将做一次大更新,键盘.鼠标.书籍这里通通都有~~~ 手里攒了那么多学分,这次终于派上用处了吧~此处是不是应该有掌声(傲娇脸.jpg) 另外有的小伙伴会问:学分兑换在哪? 学分兑换为51CTO学院APP专享活动,位于底部菜单[我的]的页面 学分兑换中有的奖品为什么消失了? 1.学分兑换里面,库存为0的奖品将消失在列表里哦

Redis zset数据类型

zadd():添加元素 zcard :返回元素个数 原文地址:https://www.cnblogs.com/mm163/p/10503124.html