优惠卷秒杀——分布式锁

news2024/12/24 9:08:34

 

在集群的模式下,有多个jvm,每个jvm内部有他自己的锁,导致并行执行存在线程安全问题  

分布式锁:满足分布式系统或集群模式下多线程可见并且互斥的锁

 基于Redis实现分布式锁

 基于redis锁的初级版本

public interface ILock {


    /**
     * 尝试获取锁
     * @param secnodTime 锁特有的超时时间过期会自动释放
     * @return
     */
    public boolean tryLock(long secnodTime);

    /**
     * 释放锁
     */
    public void unlock();

}

 

public class SimpleRedisLock implements ILock{
    private String name;
    private StringRedisTemplate stringRedisTemplate;
    private static final String KEY_PREFIX="lock:";
    public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
        this.name = name;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean tryLock(long secnodTime) {
        //获取当前线程
        long threadId = Thread.currentThread().getId();
        //写入Redis 加入过期时间防止宕机发生在写入时间和过期时间之中
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId + "", secnodTime, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(flag);
    }

    @Override
    public void unlock() {
        stringRedisTemplate.delete(KEY_PREFIX + name);
    }
}

 

@Autowired
private ISeckillVoucherService seckillVoucherService;
@Autowired
private RedisIdWorked redisIdWorked;
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
 * 限时优惠卷的秒杀
 * @param voucherId
 * @return
 */
@Override
public Result seckillVoucher(Long voucherId) {
    //1.查询优惠卷
    SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
    if(voucher==null){
        //该卷不存在
        return Result.fail("秒杀卷不存在");
    }
    //2.判断在秒杀开始前
    //得到秒杀开始时间
    LocalDateTime beginTime = voucher.getBeginTime();
    if (beginTime.isAfter(LocalDateTime.now())){
        //活动尚未开始
        return Result.ok("活动尚未开始");
    }
    //3.判断在秒杀开始后
    LocalDateTime endTime = voucher.getEndTime();
    if(endTime.isBefore(LocalDateTime.now())){
        return Result.ok("活动已经结束");
    }
    //4.判断库存是否充足
    Integer stock = voucher.getStock();
    if(stock<1){
        return Result.fail("库存不足");
    }
    UserDTO user = UserHolder.getUser();
    Long id = user.getId();
    SimpleRedisLock lock = new SimpleRedisLock("order:"+id,stringRedisTemplate);
    //尝试获取锁
    boolean flag = lock.tryLock(1200L);
    if(!flag){
        return Result.fail("一个人只能下一单");
    }
    try {
        //如果这个类本身调用是不具备管理事务的,如果使用Spring管理可以控制事务的一致性
        //获取一个spring的代理对象
        IVoucherOrderService proxy =(IVoucherOrderService) AopContext.currentProxy();
        //利用spring代理对象确保事务的一致性
        return proxy.createVoucherOrder(voucherId);
    } finally {
        //释放锁
        lock.unlock();
    }
}
@Transactional
public Result createVoucherOrder(Long voucherId){
    //5.保证一人一单
    //5.1用户id
    UserDTO user = UserHolder.getUser();
    Long id = user.getId();
    Integer count = query().eq("user_id", id).eq("voucher_id", voucherId).count();
    if(count>0){
        return Result.fail("该用户已经购买");
    }
    //6.扣减库存
    seckillVoucherService.update().setSql("stock=stock-1")
            .eq("voucher_id",voucherId).gt("stock",0)//stock>0
            .update();
    //7.创建订单
    VoucherOrder voucherOrder = new VoucherOrder();
    //7.1订单id
    long order = redisIdWorked.nextId("order");
    voucherOrder.setId(order);
    //7.2用户id
    voucherOrder.setUserId(id);
    //7.3购买代金卷的id
    voucherOrder.setVoucherId(voucherId);
    save(voucherOrder);
    //8.返回订单id
    return Result.ok(order);
}

分布式锁的误删问题

线程1获取了锁但业务发生阻塞导致,锁被超时释放带来的问题

 

 改进的点是 添加线程标识做出相应的判断,判断是不是自己的锁,是就释放

public class SimpleRedisLock implements ILock{
    private String name;
    private StringRedisTemplate stringRedisTemplate;
    private static final String KEY_PREFIX="lock:";
    //使用UUID来区分不同的JVM
    private static final String ID_PREFIX= UUID.randomUUID().toString(true)+"-";
    public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
        this.name = name;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean tryLock(long secnodTime) {
        //获取当前线程
        String threadId =ID_PREFIX +Thread.currentThread().getId();
        //写入Redis 加入过期时间防止宕机发生在写入时间和过期时间之中
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId, secnodTime, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(flag);
    }

    @Override
    public void unlock() {
        //获取当前线程标识
        String threadId =ID_PREFIX +Thread.currentThread().getId();
        //获取redis中的线程标识然后做比较
        String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
        if(id.equals(threadId)){
            stringRedisTemplate.delete(KEY_PREFIX + name);
        }

    }
}

判断锁和释放锁是两个动作,他们两个之间产生了阻塞导致产生问题,为了避免这个问题必须使这两个动作为原子性一起执行不能产生间隔

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/514867.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第十四届蓝桥杯C++--A组题解(更新中)

本来考场上做完想着这回图一乐&#xff0c;打打暴力混个省奖得了&#xff0c;看完民间题解感觉跟自己估计的差不多&#xff0c;应该挺寄的&#xff0c;没想到出分捡了个省一&#xff0c;喜提弱省省一倒数 这篇博客把自己会的题先放上来&#xff0c;其他的题慢慢补&#xff0c;好…

干翻Mybatis源码系列之第八篇:Mybatis提供的缓存方案细节注意

前言 Mybatis缓存设计成了两层的体系&#xff0c;第一层叫做一级缓存&#xff0c;第二层叫做二级缓存&#xff08;全局缓存&#xff09;。我们从这里可以看到Mybatis的缓存方案是有两种处理方式的。 一级缓存&#xff08;默认开启&#xff09; 一级缓存默认开启的&#xff0c…

[天翼杯 2021]esay_eval

[天翼杯 2021]esay_eval <?php class A{ #定义一个名为A的类public $code ""; #定义一个公共属性code function __call($method,$args){ #call魔术方法 当调用一个不存在的成员方法的时候触发eval($this->code); #将code的值以php代码执行 }function __w…

c高级day3作业

#!/bin/bash # 脚本名称:myfirstshell echo hello hostname ls -a .. echo $PATH echo $HOME df -h id -g hostname echo goodbye #!/bin/bash a(ls -l /etc | grep "^-" ) #/etc文件中普通文件 b(ls -l /etc | grep "^d" ) #/etc文件中一级目录文件 …

一篇了解智慧网关

智慧网关是指基于互联网技术的智能网关&#xff0c;能够连接不同的物联网设备和传感器&#xff0c;实现数据采集、信息传递、远程控制、通信管理等功能。作为物联网架构中的核心设备之一&#xff0c;智慧网关在智能家居、智慧城市、智能制造、智能交通、智能农业等领域得到了广…

solidworks三维建模竞赛练习题

solidworks三维建模竞赛练习题&#xff1a;3D01‐ 01 solidworks三维建模竞赛练习题&#xff1a;3D01‐ 02 solidworks三维建模竞赛练习题&#xff1a;3D01‐ 03 solidworks三维建模竞赛练习题&#xff1a;3D01‐ 04 solidworks三维建模竞赛练习题&#xff1a;3D01‐ 05 solidw…

windows docker 创建mysql主从 容器创建mysql主从

1. docker命令新建mysql-master&#xff0c;只是简单测试&#xff0c;没有设置映射&#xff0c;只是验证主从连接&#xff0c;需要可自行添加 docker run --restartalways --name mysql-master --privilegedtrue -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD123456 mysql:8.0 2.…

Linux - 第11节 - 网络入门

目录 1.计算机网络背景 1.1.网络发展 1.2.认识 "协议" 2.网络协议初识 2.1.协议分层 2.2.OSI七层模型 2.3.TCP/IP五层&#xff08;或四层&#xff09;模型 3.网络传输基本流程 3.1.同局域网的两台主机通信 3.2.跨网络的两台主机通信 1.计算机网络背景 1.1…

spring事务报错Transaction rolled back because it has been marked as rollback-only

之前经常报"Transaction rolled back because it has been marked as rollback-only"这个异常 字面意思是"事务回滚了&#xff0c;因为它被标记了必须回滚"&#xff0c;最开始完全不懂事务的嵌套&#xff0c;每次出现这个错误都想知道为什么&#xff0c;但…

套接字类型和协议设置

创建套接字 成功返回文件描述符&#xff0c;失败返回-1 int socket (int __domain, int __type, int __protocol) ;__domain&#xff1a;套接字中使用的协议族信息 一般使用PF_INET&#xff08;IPv4互联网协议族&#xff09;&#xff0c;其它协议族不常使用或尚未普及。另外&a…

算法设计与分析:大整数的加减乘除运算

目录 任务描述 相关知识 大整数的思想 大整数加法 大整数减法 大整数与整数的乘法 大整数乘法 大整数与整数的除法 n的阶乘求解思路 编程要求 测试说明 任务描述 本关任务&#xff1a;掌握大整数的基本思想&#xff0c;并运用大整数的基本运算计算出常规整数n的阶乘…

seaweedfs服务启动参数及翻译(seaweed参数、seaweed命令、weed参数、weed命令)(在单个容器同时启动master、volume、filer服务)

文章目录 weed命令翻译weed server命令翻译 weed filer命令 docker容器运行示例&#xff08;docker run命令&#xff09;以下是一个在单个容器启动master、volume、filer服务的示例 weed server 容器debug 版本&#xff1a;3.47从官方镜像docker容器里扒下来的&#xff0c;只扒…

【多线程】单例模式

目录 饿汉模式 懒汉模式-单线程版 懒汉模式-多线程版 懒汉模式-多线程版(改进) 单例是一种设计模式。 啥是设计模式 ? 设计模式好比象棋中的 " 棋谱 ". 红方当头炮 , 黑方马来跳 . 针对红方的一些走法 , 黑方应招的时候有一些固定的套路. 按照套路来走局势…

5.11 C高级作业

编写一个名为myfirstshell.sh的脚本&#xff0c;它包括以下内容。 1、包含一段注释&#xff0c;列出您的姓名、脚本的名称和编写这个脚本的目的 2、和当前用户说“hello 用户名” 3、显示您的机器名 hostname 4、显示上一级目录中的所有文件的列表 5、显示变量PATH和HOME的…

高性能网络 SIG 月度动态:长期投入得到业界认可,新增一位 virtio reviewer

高性能网络 SIG&#xff08;Special Interest Group&#xff09; &#xff1a;在云计算时代&#xff0c;软硬件高速发展&#xff0c;云原生、微服务等新的应用形态兴起&#xff0c;让更多的数据在进程之间流动&#xff0c;而网络则成为了这些数据流的载体&#xff0c;在整个云时…

Hive概述和安装

hive简介 Hive&#xff1a;由Facebook开源用于解决海量结构化日志的数据统计工具。 Hive是基于Hadoop的一个数据仓库工具&#xff0c;将结构化的数据文件映射为一张表&#xff0c;并提供类SQL(HQL)查询功能。 Hive本质&#xff1a;将HQL转化成MapReduce程序 &#xff08;1&am…

无锁队列实现及使用场景

写在前面 在看无锁队列之前&#xff0c;我们先来看看看队列的操作。队列是一种非常重要的数据结构&#xff0c;其特性是先进先出&#xff08;FIFO&#xff09;&#xff0c;符合流水线业务流程。在进程间通信、网络通信间经常采用队列做缓存&#xff0c;缓解数据处理压力。根据…

device_node转换成platform_device

device_node转换成platform_device 文章目录 device_node转换成platform_device转换规则主要核心函数of_default_bus_match_tablearmarm64of_platform_register_reconfig_notifier Linux内核是如何将device_node转换成platform_deviceof_platform_populate函数处理根节点下的子…

在ubuntu连接Xlight FTP Server

一 在windows上搭建服务器 http://www.xlightftpd.com/download.htm 使用英文版&#xff0c;使防止在ubuntu中登录中文版时&#xff0c;显示乱码 新建用户和用户对应的服务器目录 如下所示&#xff0c;默认只有读权限 全都勾选 勾选完毕后的效果 在目录中放一个文件&#…

10款常用的原型设计工具,包含一键生成原型工具

原型图是产品设计师日常工作的“常客”&#xff0c;原型图软件也扮演着产品设计师的“武器”角色。 许多新产品设计师不知道如何选择原型图软件。本文盘点了10个优秀的原型图软件&#xff0c;让我们来看看。 1.即时设计 即时设计是一款免费的在线 UI 设计工具&#xff0c;无…