redis(11)事务秒杀案例

x33g5p2x  于9个月前 转载在 Redis  
字(2.3k)|赞(0)|评价(0)|浏览(449)

秒杀案例描述

现在有1个秒杀的功能,1个原来价值5000元的手机现在搞活动,降价到1块钱,做秒杀活动。库存就10个,假设有10000人抢购。

目前逻辑是:抢到了商品库存就减1,然后把用户id加入到秒杀成功者清单中

Redis 事务(模拟秒杀并发)

  1. 模拟我们使用工具 ab 模拟测试:
  2. CentOS6 默认安装
  3. CentOS7 需要手动安装: yum install httpd-tools

执行以下命令

  1. ab -n 2000 -c 200 -k -p ~/postfile -T application/x-www-form-urlencoded

postfile是1个我们自定义的文件,内容如下

  1. prodid=0101& # 以 & 号结尾,存放当前目录

超卖问题

秒杀的结果如下图所示:

明明10个名额抢完了,还提示秒杀成功,且redis中库存显示不为0

使用redis乐观锁解决超卖问题

利用乐观锁淘汰用户,解决超卖问题。

具体代码如下:

  1. public class SecKill_redis {
  2. public static void main(String[] args) {
  3. Jedis jedis =new Jedis("192.168.44.168",6379);
  4. System.out.println(jedis.ping());
  5. jedis.close();
  6. }
  7. //秒杀过程
  8. public static boolean doSecKill(String uid,String prodid) throws IOException {
  9. //1 uid和prodid非空判断
  10. if(uid == null || prodid == null) {
  11. return false;
  12. }
  13. //2 连接redis
  14. //Jedis jedis = new Jedis("192.168.44.168",6379);
  15. //通过连接池得到jedis对象
  16. JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
  17. Jedis jedis = jedisPoolInstance.getResource();
  18. //3 拼接key
  19. // 3.1 库存key
  20. String kcKey = "sk:"+prodid+":qt";
  21. // 3.2 秒杀成功用户key
  22. String userKey = "sk:"+prodid+":user";
  23. //监视库存
  24. jedis.watch(kcKey);
  25. //4 获取库存,如果库存null,秒杀还没有开始
  26. String kc = jedis.get(kcKey);
  27. if(kc == null) {
  28. System.out.println("秒杀还没有开始,请等待");
  29. jedis.close();
  30. return false;
  31. }
  32. // 5 判断用户是否重复秒杀操作
  33. if(jedis.sismember(userKey, uid)) {
  34. System.out.println("已经秒杀成功了,不能重复秒杀");
  35. jedis.close();
  36. return false;
  37. }
  38. //6 判断如果商品数量,库存数量小于1,秒杀结束
  39. if(Integer.parseInt(kc)<=0) {
  40. System.out.println("秒杀已经结束了");
  41. jedis.close();
  42. return false;
  43. }
  44. //7 秒杀过程
  45. //使用事务
  46. Transaction multi = jedis.multi();
  47. //组队操作
  48. multi.decr(kcKey);
  49. multi.sadd(userKey,uid);
  50. //执行
  51. List<Object> results = multi.exec();
  52. if(results == null || results.size()==0) {
  53. System.out.println("秒杀失败了....");
  54. jedis.close();
  55. return false;
  56. }
  57. //7.1 库存-1
  58. //jedis.decr(kcKey);
  59. //7.2 把秒杀成功用户添加清单里面
  60. //jedis.sadd(userKey,uid);
  61. System.out.println("秒杀成功了..");
  62. jedis.close();
  63. return true;
  64. }
  65. }

测试结果

 

库存遗留问题

当我们加大并发量再去测试时,可能会出现连接超时的问题

  1. 当连接数超过了redis的最大连接数,可能就会导致部分请求在等待,直到超时,这个我们可以写1个连接池解决问题。节省每次连接 redis 服务带来的消耗,把连接好的实例反复利用。通过参数管理连接的行为
  2. 当并发量过大,还有可能存在库存遗留问题

已经秒光,可是还有库存,原因:乐观锁导致很多请求都失败。先点的没秒到,后点的可能秒到了。
 

LUA脚本解决库存遗留问题

解决库存遗留问题,我们首先想到使用悲观锁,但在redis中不能直接使用悲观锁,所以使用LUA脚本

LUA 脚本在 Redis 中的优势

  • 将复杂的或者多步的 redis 操作,写为一个脚本,一次提交给 redis 执行,减少反复连接 redis 的次数,提升性能。
  • LUA 脚本是类似 redis 事务,有一定的原子性,不会被其他命令插队,可以完成一些 redis 事务性的操作。
  • 但是注意 redis 的 lua 脚本功能,只有在 Redis 2.6 以上的版本才可以使用。
  • 利用 lua 脚本淘汰用户,解决超卖问题,redis 2.6 版本以后,通过 lua 脚本解决争抢问题,实际上是 redis 利用其单线程的特性,用任务队列的方式解决多任务并发问题。

相关文章

最新文章

更多