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
相关推荐
新鲜势力呀2 小时前
PHP 消息队列实战:从同步接口阻塞到 RabbitMQ + Redis队列 + 异步任务架构完整优化方案
redis·rabbitmq·php
for_ever_love__3 小时前
Redis 持久化讲透:RDB、AOF 与混合持久化怎么选
java·数据库·redis·持久化·aof·区别·rdb
拾贰_C4 小时前
【English | conversation 】call | 抖音AI短句情景:打电话---come over
数据库·redis·缓存
ShineWinsu8 小时前
对于Redis:缓存的解析
java·redis·缓存·缓存穿透·缓存击穿·缓存雪崩·缓存预热
害人终害己8 小时前
redis修改密码的地方在哪里
数据库·redis·缓存
我不会起名字3228 小时前
Redis 入门(一):Redis 是什么,为什么后端都离不开它
linux·redis·docker·安装·cli
for_ever_love__9 小时前
缓存穿透、击穿、雪崩:三个经典问题与完整解决方案
java·数据库·redis·缓存·哈希算法·布隆过滤器·雪崩
老陈说编程10 小时前
1. 鸿蒙 (HarmonyOS) 2012 至 2026 年的发展历程
分布式·华为·个人开发·harmonyos·鸿蒙·鸿蒙系统·程序员创富
hweiyu0018 小时前
Redis命令:HSTRLEN
redis·缓存
字节渡客19 小时前
Redis键明明过期了,业务还在读到旧数据
数据库·redis·spring