参考:https://github.com/14251104246/redis-demo
需要安装redis服务器。
pom.xml需要添加:
<!-- zhou add start-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!-- zhou add end-->
通过JMeter模拟并发:
首先初始化为5个:http://127.0.0.1:8090/api/spike/initSku
之后jmeter秒杀
http://127.0.0.1:8090/api/spike/reduceSku 此为减少库存,即为卖出一件商品。
出现超过5件的商品:http://127.0.0.1:8090/api/spike/successNum
因reduceSku中库存数量sku
的读和写操作不在同一个原子操作上。
http://127.0.0.1:8090/api/spike/reduceSku3 正常没有问题。
通过加锁方式解决超卖问题
如下改造reduceSku()
方法,作为一个新接口http://127.0.0.1:8090/api/spike/reduceSku4
reduceSku2:http://127.0.0.1:8090/api/spike/reduceSku
spring的redisTemplate
执行事务
- 注意: 若要使用spring的
redisTemplate
执行事务,需要在开启事务后执行一个redis的查询操作(但不能使用查询到的值)。原因有两点:- spring对redis事务的
exec()
方法返回结果做了处理(把返回值的OK
结果删掉)。- 导致在事务中只有
set
等更新操作时,事务执行失败与成功返回的结果一样
- 导致在事务中只有
- 事务过程中查询redis的值只会在事务执行成功后才放回。而在事务执行过程中只会返回
null
- spring对redis事务的
- 接口
http://127.0.0.1:8090/api/spike/reduceSku3
是使用spring的redisTemplate
执行事务的例子。代码如下
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.*;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 用于测试redis秒杀
*/
@RestController
@RequestMapping("/api/spike")
@Slf4j
public class SpikeController {
@Resource(name = "stringRedisTemplate")
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedissonClient redissonClient;
private AtomicInteger successNum = new AtomicInteger(0);
@RequestMapping(value = "/initSku", method = RequestMethod.GET)
public String initSku() {
stringRedisTemplate.opsForValue().set("product_sku", "5");
successNum.set(0);
return "初始化库存成功";
}
/**
* 会出现超卖情况的减少库存方式
*
* @return
*/
@RequestMapping(value = "/reduceSku", method = RequestMethod.GET)
public String reduceSku() {
Integer sku = Integer.parseInt(stringRedisTemplate.opsForValue().get("product_sku"));
sku = sku - 1;
if (sku < 0) {
return "库存不足";
}
stringRedisTemplate.opsForValue().set("product_sku", sku.toString());
return "减少库存成功,共减少" + successNum.incrementAndGet();
}
/**
* 加入事务的减少库存方式
*
* @return
*/
@RequestMapping(value = "/reduceSku2", method = RequestMethod.GET)
public String reduceSku2() {
stringRedisTemplate.setEnableTransactionSupport(true);
List<Object> results = stringRedisTemplate.execute(new SessionCallback<List<Object>>() {
@Override
public List<Object> execute(RedisOperations operations) throws DataAccessException {
operations.watch("product_sku");
String product_sku = (String) operations.opsForValue().get("product_sku");
operations.multi();
operations.opsForValue().get("product_sku");//必要的空查询
Integer sku = Integer.parseInt(product_sku);
sku = sku - 1;
if (sku < 0) {
return null;
}
operations.opsForValue().set("product_sku", sku.toString());
return operations.exec();
// operations.unwatch(); //执行exec()后自动unwatch()
}
});
if (results != null && results.size() > 0) {
return "减少库存成功,共减少" + successNum.incrementAndGet();
}
return "库存不足";
// return result.toString();
}
/**
* 直接用jredis加入事务的减少库存方式
*
* @return
*/
@RequestMapping(value = "/reduceSku3", method = RequestMethod.GET)
public String reduceSku3() {
Jedis jedis = new Jedis("127.0.0.1", 6379);
List<Object> result ;
Transaction transaction = null;
try {
jedis.watch("product_sku");
int sku = Integer.parseInt(jedis.get("product_sku"));
if (sku > 0) {
transaction = jedis.multi();
transaction.set("product_sku", String.valueOf(sku - 1));
// int exp = 1/0;
result = transaction.exec();
if (result == null || result.isEmpty()) {
System.out.println("Transaction error...");// 可能是watch-key被外部修改,或者是数据操作被驳回
// transaction.discard(); //watch-key被外部修改时,discard操作会被自动触发
return "Transaction error...";
}
} else {
return "库存不足";
}
return "减少库存成功,共减少" + successNum.incrementAndGet();
} catch (Exception e) {
log.error(e.getMessage());
transaction.discard();
return "fail";
}
}
@RequestMapping(value = "/reduceSku4", method = RequestMethod.GET)
public String reduceSku4() {
RLock rLock = redissonClient.getLock("product_sku");
try {
rLock.lock();
Integer sku = Integer.parseInt(stringRedisTemplate.opsForValue().get("product_sku"));
sku = sku - 1;
if (sku < 0) {
return "库存不足";
}
stringRedisTemplate.opsForValue().set("product_sku", sku.toString());
return "减少库存成功,共减少" + successNum.incrementAndGet();
} finally {
rLock.unlock();
}
}
@RequestMapping(value = "/successNum", method = RequestMethod.GET)
public String successNum() {
return "顾客成功抢到的商品数量:" + successNum.get();
}
}