使用springboot+jedis整合秒杀案例
- 需要引入的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
- 业务代码
package springboot.redis.controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.Jedis;
import java.util.Random;
@RestController
public class JedisDemoController {
@GetMapping("jedisSecKill")
public String secKill() {
StringBuilder phone = new StringBuilder("155");
Random random = new Random();
for (int i = 0; i < 10; i++) {
int nextInt = random.nextInt(10);
phone.append(nextInt);
}
boolean b = doSecKill(phone.toString(), "0101");
return b ? "success" : "fail";
}
public boolean doSecKill(String phone, String productId) {
Jedis jedis = new Jedis("110.40.194.18", 6379);
jedis.auth("redis123");
if (StringUtils.isEmpty(phone)) {
System.out.println("手机号不能为空");
return false;
}
String userKey = "kc:" + productId + ":user";
String kcKey = "kc:" + productId;
String kc = jedis.get(kcKey);
System.out.println("kc = " + kc);
if (StringUtils.isEmpty(kc)) {
System.out.println("秒杀还未开始,请耐心等待!");
return false;
}
int num = Integer.parseInt(kc);
if (num <= 0) {
System.out.println("秒杀已经结束,请下次再来!");
return false;
}
if (jedis.sismember(userKey,phone)){
System.out.println("已经参加过活动,不可重复参加");
return false;
}
jedis.decr(kcKey);
jedis.sadd(userKey, phone);
System.out.println("秒杀成功!");
return true;
}
}
- 使用JMeter进行并发测试
发现会产生负数
4. 通过watch和redis的事务支持来解决这个问题;
package springboot.redis.controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import java.util.Random;
@RestController
public class JedisDemoController {
@GetMapping("jedisSecKill")
public String secKill() {
StringBuilder phone = new StringBuilder("155");
Random random = new Random();
for (int i = 0; i < 10; i++) {
int nextInt = random.nextInt(10);
phone.append(nextInt);
}
boolean b = doSecKill(phone.toString(), "0101");
return b ? "success" : "fail";
}
public boolean doSecKill(String phone, String productId) {
Jedis jedis = new Jedis("110.40.194.18", 6379);
jedis.auth("redis123");
if (StringUtils.isEmpty(phone)) {
System.out.println("手机号不能为空");
return false;
}
String userKey = "kc:" + productId + ":user";
String kcKey = "kc:" + productId;
jedis.watch(kcKey);
String kc = jedis.get(kcKey);
System.out.println("kc = " + kc);
if (StringUtils.isEmpty(kc)) {
System.out.println("秒杀还未开始,请耐心等待!");
return false;
}
int num = Integer.parseInt(kc);
if (num <= 0) {
System.out.println("秒杀已经结束,请下次再来!");
return false;
}
if (jedis.sismember(userKey,phone)){
System.out.println("已经参加过活动,不可重复参加");
return false;
}
Transaction multi = jedis.multi();
multi.decr(kcKey);
multi.sadd(userKey,phone);
List<Object> exec = multi.exec();
if (exec == null || exec.size()==0){
System.out.println("秒杀失败");
return false;
}
// jedis.decr(kcKey);
// jedis.sadd(userKey, phone);
System.out.println("秒杀成功!");
return true;
}
}
但是当我加大并发数量加到2000和秒杀商品的数量加到100时,会发现
5. 使用Lua脚本解决库存遗留问题
package springboot.redis.controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import java.util.Random;
@RestController
public class JedisDemoController {
static String secKillString= "local userid=KEYS[1];\r\n" +
"local prodid=KEYS[2];\r\n" +
"local kckey='kc:'..prodid;\r\n" +
"local userKey='kc:'..prodid..':user';\r\n" +
"local userExists=redis.call('sismember',userKey,userid);\r\n" +
"if tonumber(userExists)==1 then \r\n" +
" return 2;" +
"end\r\n" +
"local num= redis.call(\"get\" ,kckey);\r\n" +
"if tonumber(num)<=0 then \r\n" +
" return 0;\r\n" +
"else \r\n" +
" redis.call(\"decr\",kckey);\r\n" +
" redis.call(\"sadd\",userKey,userid);\r\n" +
"end \r\n" +
"return 1;";
@GetMapping("jedisSecKill")
public String secKill() {
StringBuilder phone = new StringBuilder("155");
Random random = new Random();
for (int i = 0; i < 10; i++) {
int nextInt = random.nextInt(10);
phone.append(nextInt);
}
boolean b = doSecKill(phone.toString(), "0101");
return b ? "success" : "fail";
}
public boolean doSecKill(String phone, String productId) {
Jedis jedis = new Jedis("110.40.194.183", 6379);
jedis.auth("redis123");
if (StringUtils.isEmpty(phone)) {
System.out.println("手机号不能为空");
return false;
}
String scriptLoad = jedis.scriptLoad(secKillString);
Object evalsha = jedis.evalsha(scriptLoad, 2, phone, productId);
String result = String.valueOf(evalsha);
if ("0".equals(result)){
System.out.println("已抢光");
return false;
}else if ("1".equals(result)){
System.out.println("抢购成功");
return false;
} else if ("2".equals(result)) {
System.out.println("一个用户只能抢购一次");
return false;
}else {
System.out.println("抢购异常");
return false;
}
}
}
有时会发生连接超时
6. 使用Redis连接池解决超时问题
package springboot.redis.conf;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class JedisPoolUtil {
private static volatile JedisPool jedisPool = null;
private JedisPoolUtil() {
}
public static JedisPool getJedisPool() {
if (jedisPool == null) {
synchronized (JedisPoolUtil.class) {
if (jedisPool == null) {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(200);
config.setMaxIdle(32);
config.setMaxWaitMillis(1000 * 100);
config.setBlockWhenExhausted(true);
config.setTestOnBorrow(true);
jedisPool = new JedisPool(config, "110.40.194.18", 6379, 6000);
}
}
}
return jedisPool;
}
}