java高并发抽奖设计 求个位大神指导!!!!!

半吊子攻城狮 2016-08-31 12:34:38
场景业务:
1、每人每天最多抽2次
2、免费抽奖次数用完之后 每抽一次扣除N个积分,积分不足进行提示
3、中奖后发放优惠券到账户,或者发送商品到礼品盒(可延迟发送)
4、如果发现(优惠券、礼品)库存不足返回未中奖
5、抽奖记录要存放在数据库中
问题:
1、如何防止高并发情况下用户出现3次或者N次免费抽奖机会,因为每抽一次奖品都会在数据库记录一条抽奖数据 包括是否中奖 奖品是什么,如果用户并发访问会出现 上次抽奖记录未插入 下次抽奖已经开始 此时查询抽奖次数仍未免费抽奖,这就是问题所在(积分扣除也是一样的问题)
2、中奖后处理策略
3、发放礼品如何保证最后一个礼品最后一个礼品(商品)不会出现多送的情况

个位大神都给些解决方案 越详细越好 或者之前有做过的解决方案一起来探讨一下
...全文
12100 24 打赏 收藏 转发到动态 举报
写回复
用AI写文章
24 条回复
切换为时间正序
请发表友善的回复…
发表回复
你好毒你好毒 2018-04-12
  • 打赏
  • 举报
回复 4
用到的技术有redis的watch和事物,消息队列。 下面是简单逻辑: 用户积分表 user_id, socre, version 奖品表 id, name, type, quantity(数量), probabillty(库存), description, version 逻辑: 1、每次服务启动(前提是如下所说消息队列处理完毕),将积分表和奖品表的数据同步到redis 存储结构为hash表 如:user_score_table_key --> {user_id : 表POJO} 接下来2-7的操作全部在缓存执行 2、免费2次则判断当日是否有抽过奖,如果当日没有抽过奖并且抽过次数小于2,则用免费次数 如果考虑单用户并发抽奖,则锁住免费次数的key进行修改,如果修改成功则可以进行抽奖 3、如果免费次数用完,则通过用户ID查询用户剩余积分,如果满足抽奖所需积分条件 如果考虑单用户并发抽奖,则锁住用户积分,如果修改成功则可以进行抽奖 4、通过2和3判断是否能抽奖 5、计算抽奖逻辑 根据概率生成n个球(每个球代表奖项)并随机打乱,根据数据库对应的奖项以及概率来生成,可以缓存到程序中只生成一次提高效率。 然后随机产生一个球对应的索引,如果索引所在的球是奖品则执行第7步,反之执行第6步 6、如果未中奖则提示未中奖 如果要记录未中奖的流水也可以生产消息让consumer去执行添加 7、如果已中奖 判断中奖项目是否有库存 如果有库存锁住库存 进行库存修改 提交修改redis缓存,成功则真正中奖 8、根据抽奖使用的免费次数或者积分抽奖以及是否中奖提交消息队列 消息队列格式如:{order_no:流水号(唯一),user_id:1,score:-1(5),awards_id:-1(1),create_time} 9、消息队列consumer拿到消息做如下处理: 提交到专门处理消息的服务,如果服务返回error,则随机等待1-3秒再进行数据提交,重试3次,3次还失败则持久化到对应的数据库表并发邮件提醒相关人员人工处理 10、消费服务做如下处理: 在一个事物中操作: 如果使用积分为-1,则使用的是免费次数(不扣积分),反之扣除数据库对应的积分 如果中奖ID为-1,则未中奖,反之减去奖品剩余库存 上面两个两个修改都要根据查询出来的version进行修改比对,如果提交返回1,则修改成功并生成对应的流水,包括使用积分的 记录,抽奖的记录,以及用户中奖记录,返回给consumer对应的状态如果“ok”,如果上述提交失败则返回"error"
LBL121520 2018-03-13
  • 打赏
  • 举报
回复
至于奖品超发的问题,我的想法是就使用动态线程控制吧,像批处理一样,也是有个单批次数量上线的,奖品池数量大于单次处理数据量上限,就无所谓了。小于的时候就控制线程个数把批次数据量动态设定为奖品剩余量,这样就不会超发了吧。批次数据量就相当于用户数,即奖品不足的情况下用奖品剩余数量来控制参加抽奖的人数。
LBL121520 2018-03-13
  • 打赏
  • 举报
回复
可以用表用户锁,即数据库端将正在抽奖的用户上锁,必须上一次抽奖完成后解锁下一次抽奖才能开始,可以避免多设备,多平台同时操作的可能。 至于用户之间的并发就开线程吧
  • 打赏
  • 举报
回复
不允许同一用户并发抽奖 try{ 锁 = 获取分布式锁(date+userId) InterProcessMutex mutex = ZkManager.getInstance().getLock("/lock/userSyncControlMap"+date+userId); if(锁!=null){ if(reids抽奖机会<2){ 抽奖 redis.incr(抽奖机会date+userId) } }else{ 您当前正在抽奖中.....,请稍后再进行抽奖 } }catch(Exception e){ }finally{ 释放锁 }
  • 打赏
  • 举报
回复
分布式集群环境: 1.限制抽奖次数问题 限制同一用户某天抽奖次数为N 想法如下:1,本地缓存ehcache,缓存同一用户当天抽奖次数 key:date+userId,drawNum 2,如果本地缓存未查询到用户抽奖次数大于N,查询分布式缓存redis中用户的抽奖次数,如果抽奖次数仍未大于N,查询数据库 3,一切正常,执行抽奖逻辑,执行原子操作incr结果到redis,记录抽奖次数 注:同一用户进行抽奖操作时,需要通过分布式锁组件(如:zookeeper进行控制)保证同一时间同一用户不会进行并发抽奖(同一用户抽奖串行操作) 2,奖品超额发放问题 当并发很大时候,服务器校验奖品数量的判断,很容易被突破过去,造成奖品超发 如:奖品数量10,有100个用户同时,进行抽奖请求,第100个用户请求时,服务器判断,奖品还有剩余(因为高并发,数据库还未来得及进行奖品递降操作),允许进行抽奖,这就造成,奖品超发 想法如下,抽完奖以后,奖品递减操作 update 奖品表 set 奖品数量 = 奖品数量 - 1 where 奖品数量>=1,如果update失败则告诉用户奖品已送完,或者告诉用户未中奖,并进行相关数据记录
陪妳去流浪丶 2017-04-25
  • 打赏
  • 举报
回复
引用 8 楼 zhuweisyyc 的回复:
1.免费抽奖是用户纬度的,可用数据库乐观锁处理,示例:table: userId date time ; 每次更新的时候time 做乐观锁 update table where userId=? and time =? 如果更新不到,说明次数不一致了,本次抽奖就是重复的 2.中奖后应该没啥大问题,都可以异步到队列处理。可用单独存放一个中奖发放表(入表的代表都是有资格发放的,并且数量是够的) 3.奖品的超发问题其实是个全局锁的问题,如果效率有要求就放全局缓存(memcache等),还有可以预分配机制,分配到各机器,不过需要更新机制比较复杂。 放内存的都是不靠谱的,因为系统可能是分布式的。
说的靠谱
tianfang 2017-03-22
  • 打赏
  • 举报
回复
异步,排队 用户的操作界面是异步的,提交后,查询才能看到。可以是js做的自动查询 所有用户的抽奖请求都放进一个队列中,可以是JMS的,也可以LMAX disruptor的(强烈推荐)。所有请求都是一个处理程序来处理;用户的当日抽奖信息可以存储在内存数据库/redis等快速存储。两个表,昨天和今天,昨天用异步同步到硬盘数据库。 处理程序大概的流程: 1. 查询奖品。没奖品,返回在当日抽奖信息中纪录抽奖失败 2 扣除积分 3 抽奖 4 中奖与否的记录,奖品减少 不要认为这种排队的方法效率低,因为不加锁,所有效率非常高。LMAX disruptor是证券行业订单处理,每秒接近千万次 http://tech.meituan.com/disruptor.html Disruptor是英国外汇交易公司LMAX开发的一个高性能队列,研发的初衷是解决内存队列的延迟问题(在性能测试中发现竟然与I/O操作处于同样的数量级)。基于Disruptor开发的系统单线程能支撑每秒600万订单,2010年在QCon演讲后,获得了业界关注。 http://www.infoq.com/cn/articles/High-Performance-Java-Inter-Thread-Communications 文档索引 http://coolshell.cn/articles/9169.html
Ivan_333 2017-03-21
  • 打赏
  • 举报
回复
分布式锁
编程点滴 2017-03-08
  • 打赏
  • 举报
回复
编程点滴 2017-03-08
  • 打赏
  • 举报
回复
推荐两个链接 http://www.infoq.com/cn/articles/how-to-design-a-small-and-beautiful-spike-system http://blog.csdn.net/qq_16681169/article/details/53750704
javasishen 2017-03-02
  • 打赏
  • 举报
回复
http://download.csdn.net/detail/javasishen/9766909 高并发技术,原创,很实用。
guo98 2017-02-07
  • 打赏
  • 举报
回复
1.如果用户数量不大,则服务启动缓存所有用户已经参加过抽奖记录,缓存使用线程安全map , key (用户ID) -- value ({参与次数, 积分}) 2.如果用户数据量大,则用户点击参与抽奖的时候进行数据库校验,将校验对象临时缓存(可以按照时间间隔或缓存数量进行临时缓存清理,临时缓存属于线程安全map,key (用户ID) -- value({参与次数, 积分})) 3.如果用户满足进行抽奖线程,首先更新线程安全map,通过用户ID更新value.参与次数++,value.积分 = value.积分 - N, 这样就解决了并发带来的问题。 4.抽奖线程执行完毕,通过用户ID,更新数据库参加抽奖记录和积分。 至于其他问题不是大问题,自己实现就好了。 当然以上实现是基于单个服务。
qq_15153891 2016-12-15
  • 打赏
  • 举报
回复
做过类似的 用户机会 用户机会update成功之后再去抽奖 抽中奖品之后带锁去更新奖池记录,如更新失败返回未抽中 不知道这样是否满足你的需求呢
IT-JAVA小刘 2016-12-05
  • 打赏
  • 举报
回复
根据你得业务需求和以上用户回复,我感觉里边有一些需要确定的问题 1,免费次数是这个用户没天拥有的免费次数还是当前供所有用户使用的免费次数,比如当天可以有100次免费次数,所有人都可以用这样。 2,如果是上述这种情况的话@zhuweisyyc 说的使用乐观锁方式可以解决这个表的并发问题 3,对于优惠卷发放的问题我认为也可以放到队列中,按先到先得的方式插入,直到优惠价方法结束,到最后没有分到卷的用户提示没有卷即可。 4,针对每天只有两次抽奖记录这个问题,我想问下一个用户是不是可以同时登陆多个,这种情况确实会出现使用多次,但是如果是一个用户只能登陆一个的话,不存在这个问题。现在我假设会出现登陆多个的情况分析下,这种同样可以采用乐观锁的方式解决,针对次数表进行乐观判断即可解决!!
wrong1111 2016-10-10
  • 打赏
  • 举报
回复
引用 8 楼 zhuweisyyc 的回复:
1.免费抽奖是用户纬度的,可用数据库乐观锁处理,示例:table: userId date time ; 每次更新的时候time 做乐观锁 update table where userId=? and time =? 如果更新不到,说明次数不一致了,本次抽奖就是重复的 2.中奖后应该没啥大问题,都可以异步到队列处理。可用单独存放一个中奖发放表(入表的代表都是有资格发放的,并且数量是够的) 3.奖品的超发问题其实是个全局锁的问题,如果效率有要求就放全局缓存(memcache等),还有可以预分配机制,分配到各机器,不过需要更新机制比较复杂。 放内存的都是不靠谱的,因为系统可能是分布式的。
、 顶!
zhuweisyyc 2016-10-09
  • 打赏
  • 举报
回复
1.免费抽奖是用户纬度的,可用数据库乐观锁处理,示例:table: userId date time ; 每次更新的时候time 做乐观锁 update table where userId=? and time =? 如果更新不到,说明次数不一致了,本次抽奖就是重复的 2.中奖后应该没啥大问题,都可以异步到队列处理。可用单独存放一个中奖发放表(入表的代表都是有资格发放的,并且数量是够的) 3.奖品的超发问题其实是个全局锁的问题,如果效率有要求就放全局缓存(memcache等),还有可以预分配机制,分配到各机器,不过需要更新机制比较复杂。 放内存的都是不靠谱的,因为系统可能是分布式的。
  • 打赏
  • 举报
回复
1、首先有个误区,每人2次,不是通过数据库存取来实现,而是内存。 2、每人2次,要放内存,但不能用concurrenthasmap,因为是每天+每人2次。 3、每人每天2次,其实就是一个AtomicLongMap(读写原子性),实现缓存特性:ScheduledExcutor实现每天凌晨0点归0。 下班了,因为之前实现过类似,暂时写这点。
funnyone 2016-09-21
  • 打赏
  • 举报
回复
引用 4 楼 sp1234 的回复:
[quote=引用 2 楼 u010587476 的回复:] [quote=引用 1 楼 Sun1956 的回复:] 1、用户免费抽奖进入队列,再从队列中一个一个取出来,进行后续逻辑判断处理! 2、中奖后插入记录,绑定用户与奖品的数据关系,用户进入查询页面,刷新出所拥有奖品或者优惠卷 3、还是可以用队列来实现,把所有奖品放进队列,慢慢取,或者同步,并发时,保证只有一个线程进入发放。 暂时就想这么多,具体实现还会有很多细节要注意!
能说的详细一些么 特别是问题1[/quote] 就是取消并发,让几十万人慢慢排队等待唯一一个人(机器过程实例)来处理。[/quote] 这种方法不可行,这样把并发改为单线程操作,用户需要等多久才会响应。
  • 打赏
  • 举报
回复
如果不分析清楚本质问题,那么结果就是纠结技术上的“加锁、排队、事务”,最后你的问题被带到沟里去了。
  • 打赏
  • 举报
回复
引用 2 楼 u010587476 的回复:
[quote=引用 1 楼 Sun1956 的回复:] 1、用户免费抽奖进入队列,再从队列中一个一个取出来,进行后续逻辑判断处理! 2、中奖后插入记录,绑定用户与奖品的数据关系,用户进入查询页面,刷新出所拥有奖品或者优惠卷 3、还是可以用队列来实现,把所有奖品放进队列,慢慢取,或者同步,并发时,保证只有一个线程进入发放。 暂时就想这么多,具体实现还会有很多细节要注意!
能说的详细一些么 特别是问题1[/quote] 就是取消并发,让几十万人慢慢排队等待唯一一个人(机器过程实例)来处理。
加载更多回复(3)

25,985

社区成员

发帖
与我相关
我的任务
社区描述
高性能WEB开发
社区管理员
  • 高性能WEB开发社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧