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
相关推荐
suweijie7689 分钟前
Redis 7.0.2 Docker Compose 部署
redis·docker
爱和冰阔落1 小时前
【Linux】手写日志与固定线程池:任务队列、工作线程和安全退出
linux·运维·c++·redis·安卓
海宇大数据1 小时前
分布式网关架构实战:基于海宇数据公安二要素认证即时版构建自动化司机准入网关
人工智能·分布式·架构·自动化
像风一样自由20201 小时前
29.Redis在大模型应用中有哪些用途缓存会话与限流
数据库·人工智能·redis·缓存·大模型·milvus·智能体
weixin_460443565 小时前
考试监控大屏为什么不能直接查数据库?WebSocket、Redis与实时数据聚合架构设计
数据库·redis·在线考试系统·培训考试系统·煤矿培训考试系统·煤矿在线考试系统·宏远培训考试系统
九皇叔叔10 小时前
【第二章】Redis基础入门:Redis是什么、为什么快以及核心特性详解
数据库·redis·缓存
木圭的AI时代指南13 小时前
Spark-X2.5 长输入 Agent 实测:全量读取陷阱与解释器寻找死循环复盘,附可复现任务
大数据·分布式·spark
敲代码的嘎仔21 小时前
用 Redis 合并写 + DelayQueue 延迟检测,把视频播放进度写库频率降到 1/60
java·开发语言·数据库·redis·缓存·音视频·高并发
KANGBboy1 天前
doris+kafka安装部署(单机+集群)二时钟服务器 java_home
服务器·分布式
程序猿乐锅1 天前
【黑马点评 | 第一篇】从 Session 到 Redis+Token 的改造与拦截器实现
数据库·redis·缓存