springboot项目:Redis分布式锁的使用(模拟秒杀系统)

时间:2023-03-08 15:16:41
springboot项目:Redis分布式锁的使用(模拟秒杀系统)

springboot项目:Redis分布式锁的使用(模拟秒杀系统)

模拟秒杀系统:

第一步:编写Service

package com.payease.service;

/**
* liuxiaoming
* 2017-12-14
*/
public interface SecKillService {
/**
* 查询秒杀活动特价商品的信息
*
* @param productId
* @return
*/
String querySecKillProductInfo(String productId); /**
* 模拟不同用户秒杀同一商品的请求
*
* @param productId
* @return
*/
void orderProductMockDiffUser(String productId);
}

第二步:编写Redis加锁解锁工具类

package com.payease.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils; /**
* liuxiaoming
* 2017-12-14
*/
@Component
@Slf4j
public class RedisLock { @Autowired
private StringRedisTemplate stringRedisTemplate; /**
* 加锁
* @param key
* @param value 当前时间+超时时间
* @return
*/
public boolean lock(String key, String value) {
if (stringRedisTemplate.opsForValue().setIfAbsent(key, value)) {
return true;
}
//currentValue=A 这两个线程的value都是B 其中一个线程拿到锁
String currentValue = stringRedisTemplate.opsForValue().get(key);
//如果锁过期
if (!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()) {
//获取上一个锁的时间
String oldValue = stringRedisTemplate.opsForValue().getAndSet(key, value);
if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
return true;
}
}
return false;
} /**
* 解锁
* @param key
* @param value
* @return
*/
public void unlock(String key, String value) {
String currentVaule = stringRedisTemplate.opsForValue().get(key);
try {
if (!StringUtils.isEmpty(currentVaule) && currentVaule.equals(value)) {
stringRedisTemplate.opsForValue().getOperations().delete(key);
}
} catch (Exception e) {
log.error("【redis分布式锁】解锁异常,{}" , e);
} }
}

第三步:编写Service实现类

package com.payease.service.impl;

import com.payease.exception.SellException;
import com.payease.service.RedisLock;
import com.payease.service.SecKillService;
import com.payease.utils.KeyUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import java.util.HashMap;
import java.util.Map; /**
* liuxiaoming
* 1027-12-14
*/
@Service
public class SecKillServiceImpl implements SecKillService { @Autowired
private RedisLock redisLock; private static final int TIMOUT = 10 * 1000; //超时时间 10秒 /**
* 中秋活动 秒杀月饼 限量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("abc123456", 10000);
stock.put("abc123456", 10000);
} private String queryMap(String productId) {
return "中秋活动,月饼特价,限量份"
+ products.get(productId)
+ " 还剩:" + stock.get(productId) + " 份"
+ " 该商品成功下单用户数目:"
+ orders.size() + " 人";
} @Override
public String querySecKillProductInfo(String productId) {
return queryMap(productId);
} /**
* 描述逻辑
*
* @param productId
*/
@Override
public void orderProductMockDiffUser(String productId) { //加锁
long time = System.currentTimeMillis() + TIMOUT;
if (!redisLock.lock(productId, String.valueOf(time))) {
throw new SellException(110, "没抢到,换个姿势再试一遍呀");
}
//1. 查询该商品库存,为0则活动结束。
int stockNum = stock.get(productId);
if (stockNum == 0) {
//库存不足
throw new SellException(100, "活动已经结束,请留意下次活动");
} else { orders.put(KeyUtil.getUniqueKey(), productId);
stockNum = stockNum - 1;
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
stock.put(productId, stockNum);
} //解锁
redisLock.unlock(productId, String.valueOf(time));
} /*@Override
public synchronized void orderProductMockDiffUser(String productId) {
int stockNum = stock.get(productId);
if (stockNum == 0) {
//库存不足
throw new SellException(100, "活动已经结束,请留意下次活动");
} else { orders.put(KeyUtil.genUniqueKey(), productId);
stockNum = stockNum - 1;
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
stock.put(productId, stockNum);
} }*/
}

第四步:编写controller

package com.payease.controller;

import com.payease.service.SecKillService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; @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);
}
}

第五步:启动项目 查看浏览器 进行压测

1.查看秒杀情况 http://127.0.0.1:8080/sell/skill/query/abc123456

2. 在Mac终端输入命令: ab -n 500 -c 100 http://127.0.0.1:8080/sell/skill/order/abc123456
进行压测

springboot项目:Redis分布式锁的使用(模拟秒杀系统)

3.查看浏览器 http://127.0.0.1:8080/sell/skill/query/abc123456

springboot项目:Redis分布式锁的使用(模拟秒杀系统)

注:

压测模拟并发:

springboot项目:Redis分布式锁的使用(模拟秒杀系统)