Redis 秒杀功能 超卖问题 一人一单问题 分布式锁 精彩!精彩!

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
相关推荐
笨鸟先飞的橘猫3 小时前
MMO游戏中的“跨服团队副本”匹配与状态同步系统
分布式·学习·游戏·lua·skynet
Emily呀6 小时前
【无标题】
redis
愈努力俞幸运6 小时前
function calling与mcp
android·数据库·redis
IronMurphy7 小时前
Redis拷打第一讲
数据库·redis·缓存
楠枬8 小时前
Redis 事务
数据库·redis·缓存
轻刀快马8 小时前
穿透 MQ 专栏 (五):【终局之战】MySQL 和 MQ 的世纪联姻:扒开“分布式事务”的遮羞布
数据库·分布式·消息队列
摇滚侠9 小时前
Redis 查询接口加缓存 缓存雪崩 缓存穿透 缓存击穿 精彩!精彩!
redis·缓存
Mr. zhihao10 小时前
[特殊字符] 从 Redis 缓存穿透到布隆过滤器,再到布谷鸟过滤器:一次穿透防护的进化之旅
数据库·redis·缓存
@小匠10 小时前
Redis 7 持久化机制
数据库·redis·缓存