spring boot redis 商品秒杀设计
商品秒杀多数发生在高并发的时候,本文利用redis单线程原子性解决并发,减库存;
思路方案:
1. 模拟100个用户下单抢10个商品;
2.使用redis加锁,来实现减去库存;
3.其他用户一直等待,直到解锁后后面用户再加锁减库存,依次操作,直到库存为0;
4.用户等待,设置一下超时时间,防止一直等待下去;
5.判断库存是否小于等于0;
其中超时代码中有不要使用System.currentTimeMillis,高并发下性能很差,解决方案写了一个定时任务,1毫米间隔定时执行,然后去获取,代码SystemTimeUtil.now;
1.自定义SystemTimeUtil类,实现获取当前时间戳
package shop.seckill.example.utils;
import org.apache.commons.lang3.time.DateFormatUtils;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
/**
* @author IT006448
* @date 2022/01/17
*/
public class SystemTimeUtil {
private final int period;
private final AtomicLong now;
private static class InstanceHolder {
private static final SystemTimeUtil INSTANCE = new SystemTimeUtil(1);
}
//定时任务设置1毫秒
private SystemTimeUtil(int period) {
this.period = period;
this.now = new AtomicLong(System.currentTimeMillis());
scheduleClockUpdating();
}
private static SystemTimeUtil instance() {
return InstanceHolder.INSTANCE;
}
private void scheduleClockUpdating() {
//周期执行线程池
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> {
Thread thread = new Thread(runnable, "System Clock");
//守护线程
thread.setDaemon(true);
return thread;
});
//任务,开始时间,间隔时间=周期执行,时间单位
scheduler.scheduleAtFixedRate(() -> now.set(System.currentTimeMillis()), 0, period, TimeUnit.MILLISECONDS);
}
private long currentTimeMillis() {
return now.get();
}
/**
* 用来替换原来的System.currentTimeMillis()
*/
public static long now() {
return instance().currentTimeMillis();
}
public static String nowTime() {
long now = now();
return DateFormatUtils.format(now, "yyyy-MM-dd hh:mm:ss");
}
public static void main(String[] args) throws InterruptedException {
String now = SystemTimeUtil.nowTime();
System.out.println(now);
Thread.sleep(1000);
now = SystemTimeUtil.nowTime();
System.out.println(now);
Thread.sleep(2000);
now = SystemTimeUtil.nowTime();
System.out.println(now);
}
}
2.秒杀核心
创建一个秒杀服务接口
public interface SecKillService {
void buy(String goodsId, String userId);
}
package shop.seckill.example.service.imp;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import scf.frame.starter.cache.redis.JedisUtil;
import shop.seckill.example.service.SecKillService;
import shop.seckill.example.utils.SystemTimeUtil;
import java.util.Date;
import java.util.Map;
/**
* @author IT006448
* @date 2022/01/14
*/
@Service
@Slf4j
public class SecKillServiceImpl implements SecKillService {
@Autowired
private JedisUtil jedisUtil;
public static Map<String, Integer> inventoryMap = Maps.newLinkedHashMap();
{
inventoryMap.put("10001", 10);
}
@Override
public void buy(String goodsId, String userId) {
String key = "SECKILL_USER_".concat(goodsId);
//用户开抢时间
//long startTime = System.currentTimeMillis();
//System.currentTimeMillis并发性能太差,用定时任务去获取,性能更好
long startTime = SystemTimeUtil.now();
int timeout = 10000;
//未抢到的情况下,10秒内继续获取锁
while ((startTime + timeout) >= SystemTimeUtil.now()) {
String strInventory = jedisUtil.getStr(goodsId + "_INVENTORY_NUM");
//商品是否剩余
System.out.println(getDate() + " 库存数量:" + strInventory);
if (strInventory != null && Integer.parseInt(strInventory) <= 0) {
System.out.println("商品没有剩余了,跳出");
break;
}
//获取锁
boolean lock = jedisUtil.setIfAbsent(key, userId);
if (lock) {
//加锁成功
try {
jedisUtil.expire(key, 60);
parallelBuy(goodsId, userId);
return;
} finally {
jedisUtil.del(key);
}
} else {
System.out.println(String.format("用户:%s锁了,等待买", userId));
}
}
}
public void parallelBuy(String goodsId, String userId) {
Integer inventory = inventoryMap.get(goodsId);
try {
//模拟减去库存等操作耗时
Thread.sleep(1000);
} catch (Exception e) {
}
if (inventory <= 0) {
System.out.println("商品已经卖完");
return;
//throw new RuntimeException("商品:".concat(goodsId).concat("已经卖完"));
}
--inventory;
jedisUtil.set(goodsId + "_INVENTORY_NUM", inventory.toString(), 60);
System.out.println(getDate() + " 还剩下:".concat(inventory.toString()));
System.out.println(String.format(getDate() + " 用户:%s买了一件", userId));
inventoryMap.put(goodsId, inventory);
}
public String getDate() {
return JSON.toJSONString(new Date(), SerializerFeature.UseISO8601DateFormat);
}
}
3.模拟创建100个随机用户,并并发执行
package shop.seckill.example.service.imp;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;
import shop.seckill.example.service.SecKillService;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.IntStream;
/**
* @author IT006448
* @date 2022/01/14
*/
@Service
@Slf4j
public class ShopServiceImpl implements ShopService {
@Autowired
private SecKillService secKillService;
@Override
public void buy() {
List<String> user = createUser();
StopWatch stopWatch = new StopWatch();
stopWatch.start();
log.info("用户:{}", JSON.toJSONString(user));
user.parallelStream().forEach(x -> {
secKillService.buy("10001", x);
});
stopWatch.stop();
double totalTimeSeconds = stopWatch.getTotalTimeSeconds();
System.out.println("运行总时间:" + totalTimeSeconds);
System.out.println("库存数量:" + SecKillServiceImpl.inventoryMap.get("10001"));
}
public List<String> createUser() {
List<String> user = new LinkedList<>();
IntStream.range(0, 100).parallel().forEach(x -> {
user.add("用户" + x);
});
return user;
}
}

浙公网安备 33010602011771号