环境:Spring Initialize创建的SpringBoot项目
引入redis依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
编写一个秒杀controller:
@RestController
@RequestMapping("/skill")
@Slf4j
public class SecKillController {
@Autowired
private SecKillService secKillService;
/**
* 查询秒杀活动特价商品的信息
* @param productId
* @return
*/
@GetMapping("/query/{productId}")
public String query(@PathVariable String productId)throws Exception
{
return secKillService.querySecKillProductInfo(productId);
}
/**
* 秒杀,没有抢到获得"哎呦喂,xxxxx",抢到了会返回剩余的库存量
* @param productId
* @return
* @throws Exception
*/
@GetMapping("/order/{productId}")
public String skill(@PathVariable String productId)throws Exception
{
log.info("@skill request, productId:" + productId);
secKillService.orderProductMockDiffUser(productId);
return secKillService.querySecKillProductInfo(productId);
}
}
service(接口就不写了):
@Service
public class SecKillServiceImpl implements SecKillService {
private static final int TIMEOUT = 10 * 1000; //超时时间 10s
/**
* 国庆活动,皮蛋粥特价,限量100000份
*/
static Map<String, Integer> products;
static Map<String, Integer> stock;
static Map<String, String> orders;
static {
/**
* 模拟多个表,商品信息表,库存表,秒杀成功订单表
*/
products = new HashMap<>();
stock = new HashMap<>();
orders = new HashMap<>();
products.put("123456", 100000);
stock.put("123456", 100000);
}
private String queryMap(String productId) {
return "国庆活动,皮蛋粥特价,限量份"
+ products.get(productId)
+ " 还剩:" + stock.get(productId) + " 份"
+ " 该商品成功下单用户数目:"
+ orders.size() + " 人";
}
@Override
public String querySecKillProductInfo(String productId) {
return this.queryMap(productId);
}
@Override
public void orderProductMockDiffUser(String productId) {
//1.查询该商品库存,为0则活动结束。
int stockNum = stock.get(productId);
if (stockNum == 0) {
throw new SellException(100, "活动结束");
} else {
//2.下单(模拟不同用户openid不同)
orders.put(KeyUtil.getUniqueKey(), productId);
//3.减库存
stockNum = stockNum - 1;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
stock.put(productId, stockNum);
}
}
}
此时使用ab压测工具进行测试
指令:ab -n 500 -c 100 http://localhost:/sell/skill/order/123456
会发现数据并不可靠
这时候我们在秒杀的service层加入synchronized关键字,保证数据的可靠性
在进行测试,发现数据的确没问题,但是新的问题又出现了,就是就是并发的时候等待时间长了,因为保证了只有一个线程在工作,这样整体的并发处理时间就长了
这时候我们可以使用分布式锁来解决这个问题,用redis的键值来作为锁
先写一个上锁和解锁的方法:
@Component
@Slf4j
public class RedisLock {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 加锁
* @param key
* @param value 当前时间+超时时间
* @return
*/
public boolean lock(String key, String value) {
if(redisTemplate.opsForValue().setIfAbsent(key, value)) {
return true;
}
//currentValue=A 这两个线程的value都是B 其中一个线程拿到锁
String currentValue = redisTemplate.opsForValue().get(key);
//如果锁过期
if (!StringUtils.isEmpty(currentValue)
&& Long.parseLong(currentValue) < System.currentTimeMillis()) {
//获取上一个锁的时间
String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
return true;
}
}
return false;
}
/**
* 解锁
* @param key
* @param value
*/
public void unlock(String key, String value) {
try {
String currentValue = redisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
redisTemplate.opsForValue().getOperations().delete(key);
}
}catch (Exception e) {
log.error("【redis分布式锁】解锁异常, {}", e);
}
}
}
然后在service层的orderProductMockDiffUser方法上应用
@Override
public void orderProductMockDiffUser(String productId) {
//加锁
long time = System.currentTimeMillis() + TIMEOUT;
if(!redisLock.lock(productId,String.valueOf(time))){
throw new SellException(101,"哎哟喂,人也太多了,换个姿势试试");
}
//1.查询该商品库存,为0则活动结束。
int stockNum = stock.get(productId);
if (stockNum == 0) {
throw new SellException(100, "活动结束");
} else {
//2.下单(模拟不同用户openid不同)
orders.put(KeyUtil.getUniqueKey(), productId);
//3.减库存
stockNum = stockNum - 1;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
stock.put(productId, stockNum);
}
//解锁
redisLock.unlock(productId,String.valueOf(time));
}
再进行测试,数据是可靠的,并发处理时间也是非常短,说明分布式锁还是特别管用的