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
相关推荐
難釋懷8 小时前
Nginx外置缓存-nginx + redis
redis·nginx·缓存
ACP广源盛139246256738 小时前
GSV6155@ACP# Type-C 视频信号转换芯片:技术架构、工程痛点适配与落地场景分析
大数据·c语言·开发语言·人工智能·分布式·嵌入式硬件·架构
Valora1 天前
简单学会redis
redis
踏着七彩祥云的小丑1 天前
忘记Redis是否安装过时查看
数据库·redis·缓存
一嘴一个橘子1 天前
redis 通用命令详解
redis
☆凡尘清心☆1 天前
CentOS Stream 9 Redis 7.2.7 源码编译一键安装脚本
linux·运维·redis·centos
麻瓜code1 天前
【Redis 】数据类型、持久化与过期删除
数据库·redis·缓存
笨蛋不要掉眼泪1 天前
RabbitMQ消息队列:发送者的可靠性
java·分布式·rabbitmq
AAA@峥1 天前
Ceph 分布式文件系统 CephFS 实战指南
运维·分布式·ceph
cyadyx1 天前
RabbitMQ 使用说明文档
分布式·rabbitmq