Java使用Redis實現(xiàn)秒殺功能
秒殺功能
秒殺場景現(xiàn)在已經(jīng)非常常見了,各種電商平臺都有秒殺的產(chǎn)品,接下來我們模擬一個秒殺的項目,最終能夠確保高并發(fā)下的線程安全。界面比較簡單,但是功能基本實現(xiàn)。
界面

點擊“秒殺點我”按鈕后臺就會輸出秒殺結(jié)果。

第一版
使用Redis緩存數(shù)據(jù)庫,使用一個key-value存儲秒殺商品數(shù)量,使用set集合存儲秒殺成功的用戶。我們以商品0101為示例,設(shè)置商品的初始數(shù)量為200件。不考慮并發(fā)問題,實現(xiàn)功能。
html、jsp、servlet文件不重要省略。
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 + "用戶已經(jīng)秒殺成功");
JedisPollTool.distroy(jedisPool, jedis);
return false;
}
int prodCount = Integer.parseInt(productCount);
if(prodCount <= 0) {
System.out.println("秒殺結(jié)束");
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工具進行并發(fā)測試。
ab -n 1000 -c 200 -p /test/file.txt -T "application/x-www-form-urlencoded" 192.168.0.101:8080/redis-demo/ss
結(jié)果

從結(jié)果大致來看,沒有什么問題,來查看一個后臺Redis的數(shù)據(jù)
秒殺的結(jié)果里面居然有負數(shù),證明賣超了。

第二版
使用Redis的事務(wù),保證沒有超賣的情況發(fā)生。
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); //開始監(jiān)視
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 + "用戶已經(jīng)秒殺成功");
JedisPollTool.distroy(jedisPool, jedis);
return false;
}
int prodCount = Integer.parseInt(productCount);
if(prodCount <= 0) {
System.out.println("秒殺結(jié)束");
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;
}
}
結(jié)果

由于使用了watch和事務(wù),每次的并發(fā)線程訪問中只有一個線程能夠提交成功,可以保證不出現(xiàn)超賣的現(xiàn)象,但是對于一些用戶來說是極其不公平的。
第三版
使用Lua腳本來實現(xiàn),因為Redis是單線程的,又是C語言編寫的,可以使用Lua調(diào)用Redis的命令,Lua會具有排他性,所以能夠保證安全。
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;
}
}
結(jié)果

這才是我們最希望看到的結(jié)果!
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
- Java中RedisUtils工具類的使用
- Java使用RedisTemplate如何根據(jù)前綴獲取key列表
- Redis五種數(shù)據(jù)結(jié)構(gòu)在JAVA中如何封裝使用
- 使用Java實現(xiàn)Redis限流的方法
- IDEA版使用Java操作Redis數(shù)據(jù)庫的方法
- JAVA中 redisTemplate 和 jedis的配合使用操作
- Java簡單使用redis-zset實現(xiàn)排行榜
- Java使用RedisTemplate模糊刪除key操作
- 在Java中使用redisTemplate操作緩存的方法示例
- Java與SpringBoot對redis的使用方式
相關(guān)文章
詳解java代碼中init method和destroy method的三種使用方式
這篇文章主要介紹了詳解java代碼中init method和destroy method的三種使用方式,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-03-03
springboot項目docker分層構(gòu)建的配置方式
在使用dockerfile構(gòu)建springboot項目時,速度較慢,用時比較長,為了加快構(gòu)建docker鏡像的速度,采用分層構(gòu)建的方式,這篇文章主要介紹了springboot項目docker分層構(gòu)建,需要的朋友可以參考下2024-03-03
淺析java中String類型中“==”與“equal”的區(qū)別
這篇文章主要介紹了淺析java中String類型中“==”與“equal”的區(qū)別,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08
java微信企業(yè)號開發(fā)之開發(fā)模式的開啟
這篇文章主要為大家詳細介紹了java微信企業(yè)號開發(fā)之開發(fā)模式的開啟方法,感興趣的小伙伴們可以參考一下2016-06-06
Java Swing JProgressBar進度條的實現(xiàn)示例
這篇文章主要介紹了Java Swing JProgressBar進度條的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-12-12

