Redis 秒杀功能 超卖问题 一人一单问题 分布式锁 精彩!精彩!
项目地址 Gitee
https://gitee.com/yangjunbo-jetli/hmdp-redis.git
一、全局唯一ID



java
package com.hmdp.utils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
@Component
public class RedisWorker {
//开始时间戳
private static final long BEGIN_TIMESTAMP = 1767225600L;
//序列号的位数
private static final int COUNT_BITS = 32;
private StringRedisTemplate stringRedisTemplate;
//利用构造函数注入 stringRedisTemplate
public RedisWorker(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
public long nextId(String keyPrefix){
//1、生成时间戳,当前时间距离 1970/01/01 的秒数
LocalDateTime now = LocalDateTime.now();
long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
long timestamp = nowSecond - BEGIN_TIMESTAMP;
//2、生成序列号
//2.1、获取当前日期精确到天
String data = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
//2.2、获取自增序列号
Long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + data);
//3、拼接并返回完成唯一 Id
return timestamp << COUNT_BITS | count;
}
public static void main(String[] args){
LocalDateTime time = LocalDateTime.of(2026, 1, 1, 0, 0, 0);
long epochSecond = time.toEpochSecond(ZoneOffset.UTC);
System.out.println(epochSecond);
}
}

java
package com.hmdp;
import com.hmdp.service.impl.ShopServiceImpl;
import com.hmdp.utils.RedisWorker;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@SpringBootTest
class HmDianPingApplicationTests {
@Autowired
private ShopServiceImpl shopService;
@Autowired
private RedisWorker redisWorker;
private ExecutorService es = Executors.newFixedThreadPool(500);
/**
* 测试 300 个线程每个线程创建 100 个 Id,Id 是否重复。
* @throws InterruptedException
*/
@Test
void testIdWorker() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(300);
Runnable task = () -> {
for (int i = 0; i < 100; i++) {
long id = redisWorker.nextId("order");
System.out.println(id);
}
countDownLatch.countDown();
};
long begin = System.currentTimeMillis();
for (int i = 0; i < 300; i++) {
es.submit(task);
}
countDownLatch.await();
long end = System.currentTimeMillis();
System.out.println("time = " + (end - begin));
}
@Test
void testSaveShop() throws InterruptedException {
shopService.saveShop2Redis(1L,1L);
}
}


二、优惠券秒杀功能



三、秒杀超卖问题













四、单节点秒杀一人一单问题






五、集群秒杀一人一单问题



六、Redis 分布式锁误删除问题




七、Redis 分布式锁原子性问题





八、完整的分布式锁代码
java
package com.hmdp.utils;
import cn.hutool.core.lang.UUID;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
public class SimpleRedisLock implements ILock{
private String name;
private StringRedisTemplate stringRedisTemplate;
public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
this.name = name;
this.stringRedisTemplate = stringRedisTemplate;
}
private static final String KEY_PREFIX = "lock:";
private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";
private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
static {
UNLOCK_SCRIPT = new DefaultRedisScript<>();
UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
UNLOCK_SCRIPT.setResultType(Long.class);
}
@Override
public boolean tryLock(long timeoutSec) {
//获取线程标识
//UUID + 线程 ID 可以确认哪个服务的哪个线程抢到了锁。
String threadId = ID_PREFIX + Thread.currentThread().getId();
//获取锁
Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);//防止发生异常
}
@Override
public void unlock() {
/*//获取线程标识
String threadId = ID_PREFIX + Thread.currentThread().getId();
//获取当前锁的线程标识
String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
//判断是否一致
if(threadId.equals(id)){
//一致,释放锁。
stringRedisTemplate.delete(KEY_PREFIX + name);
}*/
//使用 lua 脚本,保证判断和删除操作的原子性。
stringRedisTemplate.execute(
UNLOCK_SCRIPT,
Collections.singletonList(KEY_PREFIX + name),
ID_PREFIX + Thread.currentThread().getId()
);
}
}
lua
-- 比较线程标识与锁中的标识是否一致
if(redis.call('get',KEYS[1]) == ARGV[1]) then
-- 一致释放锁
return redis.call('del',KEYS[1])
end
return 0