秒杀功能
秒杀场景现在已经非常常见了,各种电商平台都有秒杀的产品,接下来我们模拟一个秒杀的项目,最终能够确保高并发下的线程安全。界面比较简单,但是功能基本实现。
界面
点击“秒杀点我”按钮后台就会输出秒杀结果。
第一版
使用Redis缓存数据库,使用一个key-value存储秒杀商品数量,使用set集合存储秒杀成功的用户。我们以商品0101为示例,设置商品的初始数量为200件。不考虑并发问题,实现功能。
html、jsp、servlet文件不重要省略。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
package com.redis.secondskill; import java.util.List; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.Transaction; public class SS0 { public static boolean doSecKill(String uid,String prodid) { JedisPool jedisPool = JedisPollTool.getInstance(); Jedis jedis = jedisPool.getResource(); String productCountStr = "sec:" +prodid+ ":count" ; String productUserStr = "sec:" +prodid+ ":user" ; String productCount = jedis.get(productCountStr); if ( null == productCount) { System.out.println( "秒杀还没有开始" ); JedisPollTool.distroy(jedisPool, jedis); return false ; } if (jedis.sismember(productUserStr, uid)) { System.out.println(uid + "用户已经秒杀成功" ); JedisPollTool.distroy(jedisPool, jedis); return false ; } int prodCount = Integer.parseInt(productCount); if (prodCount <= 0 ) { System.out.println( "秒杀结束" ); JedisPollTool.distroy(jedisPool, jedis); return false ; } jedis.decr(productCountStr); jedis.sadd(productUserStr, uid); JedisPollTool.distroy(jedisPool, jedis); System.out.println(uid + "秒杀成功" ); return true ; } } |
使用linux httpd-tools工具进行并发测试。
ab -n 1000 -c 200 -p /test/file.txt -T "application/x-www-form-urlencoded" 192.168.0.101:8080/redis-demo/ss
结果
从结果大致来看,没有什么问题,来查看一个后台Redis的数据
秒杀的结果里面居然有负数,证明卖超了。
第二版
使用Redis的事务,保证没有超卖的情况发生。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
package com.redis.secondskill; import java.util.List; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.Transaction; public class SS1 { public static boolean doSecKill(String uid,String prodid) { JedisPool jedisPool = JedisPollTool.getInstance(); Jedis jedis = jedisPool.getResource(); String productCountStr = "sec:" +prodid+ ":count" ; String productUserStr = "sec:" +prodid+ ":user" ; jedis.watch(productCountStr); //开始监视 String productCount = jedis.get(productCountStr); if ( null == productCount) { System.out.println( "秒杀还没有开始" ); JedisPollTool.distroy(jedisPool, jedis); return false ; } if (jedis.sismember(productUserStr, uid)) { System.out.println(uid + "用户已经秒杀成功" ); JedisPollTool.distroy(jedisPool, jedis); return false ; } int prodCount = Integer.parseInt(productCount); if (prodCount <= 0 ) { System.out.println( "秒杀结束" ); JedisPollTool.distroy(jedisPool, jedis); return false ; } Transaction transaction = jedis.multi(); transaction.decr(productCountStr); transaction.sadd(productUserStr, uid); List<Object> exec = transaction.exec(); if (exec == null || exec.size() == 0 ) { System.out.println( "秒杀失败,稍后重试" ); JedisPollTool.distroy(jedisPool, jedis); return false ; } JedisPollTool.distroy(jedisPool, jedis); System.out.println(uid + "秒杀成功" ); return true ; } } |
结果
由于使用了watch和事务,每次的并发线程访问中只有一个线程能够提交成功,可以保证不出现超卖的现象,但是对于一些用户来说是极其不公平的。
第三版
使用Lua脚本来实现,因为Redis是单线程的,又是C语言编写的,可以使用Lua调用Redis的命令,Lua会具有排他性,所以能够保证安全。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
package com.redis.secondskill; import java.util.HashSet; import java.util.Set; import redis.clients.jedis.HostAndPort; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; public class SS2 { static String luaScript = "local userid=KEYS[1];\r\n" + "local prodid=KEYS[2];\r\n" + "local qtkey='sec:'..prodid..\":count\";\r\n" + "local usersKey='sec:'..prodid..\":user\";\r\n" + "local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" + "if tonumber(userExists)==1 then \r\n" + " return 2;\r\n" + "end\r\n" + "local num = redis.call(\"get\" ,qtkey);\r\n" + "if tonumber(num)<=0 then \r\n" + " return 0;\r\n" + "else \r\n" + " redis.call(\"decr\",qtkey);\r\n" + " redis.call(\"sadd\",usersKey,userid);\r\n" + "end\r\n" + "return 1" ; public static boolean doSecKill(String uid,String prodid) { JedisPool jedisPool = JedisPollTool.getInstance(); Jedis jedis = jedisPool.getResource(); String sha1 = jedis.scriptLoad(luaScript); Object result= jedis.evalsha(sha1, 2 , uid,prodid); String reString=String.valueOf(result); if ( "0" .equals( reString ) ) { System.err.println( "已抢空!!" ); } else if ( "1" .equals( reString ) ) { System.out.println(uid + "抢购成功!!!!" ); } else if ( "2" .equals( reString ) ) { System.err.println( "该用户已抢过!!" ); } else { System.err.println( "抢购异常!!" ); } JedisPollTool.distroy(jedisPool, jedis); return true ; } } |
结果
这才是我们最希望看到的结果!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/cong____cong/article/details/105566983