百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

微服务 Spring Boot 整合Redis分布式锁 实现优惠卷秒杀

mhr18 2024-12-05 13:39 25 浏览 0 评论

文章目录

  • 一、集群环境下 秒杀 一人一单的并发问题
  • :snowman:基本原理和实现方式:zap:Redis 分布式锁的核心实现思路
  • 三、实战开发 实现 Redis 分布式锁
  • 四、ApiFox 测试 集群模式下是否能够解决并发问题

:partly_sunny:前言

在 微服务 Spring Boot 整合Redis 实现优惠卷秒杀 一人一单 下, 单线程的情况,不会出现并发的问题 ,那么,我们的 秒杀场景都是出现在并发环境下的,多个用户同时去抢购一件商品 ,这时就体现出了 系统 的 抗受 高并发、高可用 性 ,在用户 访问数多的情况下,我们需要去 搭建集群 并配置负载均衡 去均匀的分配服务器的压力 ,以免出现 服务宕机 导致系统不可用,集群下我们的 秒杀一人一单存在问题,下面详细介绍。

一、集群环境下 秒杀 一人一单的并发问题

之前我们在单机情况下通过加 sync 锁就可以达到线程安全 ,但是在集群环境下,就不可以了。

开启集群来测试

将服务启动2份,端口为8002和 8083

如何开始Services 服务列表?

View --> Tool Windows --> Services打开服务列表 或者 快捷键 (Alt + 8)

出现以下Services,点击新建服务

单击 Run Configuration Type 选择Spring Boot 即可 (注意:如果没有Spring Boot 选项,那就手动启动程序,会自动出现 Spring Boot 列表)

即可完成新建服务,实现集群的效果

为什么会出现此问题呢?

由于现在我们 部署了多个tomcat每个tomcat都有一个属于自己的JVM ,那么 假设在服务器A的tomcat内部,有两个线程 ,这两个线程由于 使用的是同一份代码 ,那么 他们的锁对象是同一个 ,是 可以实现互斥 的, 但是 如果现在是服务器B的tomcat内部,又有两个线程,但是他们的锁对象写的虽然和服务器A一样 ,但是 锁对象却不是同一个 ,所以 线程3和线程4可以实现互斥 ,但是 却无法和线程1和线程2实现互斥 ,这就是 集群环境下,syn锁失效的原因 ,在这种情况下,我们就需要 使用分布式锁来解决 这个问题。

二、什么是分布式锁?

:snowman:基本原理和实现方式

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

分布式锁的 核心思想就是让大家都使用同一把锁 ,只要大家使用的是同一把锁,那么我们就能锁住线程,不让线程进行,让程序串行执行,这就是分布式锁的核心思路

分布式锁它应该满足一些什么样的条件呢?

可见性:多个线程都能看到相同的结果

注意:这个地方说的可见性并不是并发编程中指的内存可见性,只是说多个进程之间都能感知到变化的意思

互斥:互斥是分布式锁的最基本的条件,使得程序串行执行

高可用:程序不易崩溃,时时刻刻都保证较高的可用性

高性能:由于加锁本身就让性能降低,所有对于分布式锁本身需要他就较高的加锁性能和释放锁性能

安全性:安全也是程序中必不可少的一环

常见的分布式锁有三种

MySQL: MySQL 本身就带有锁机制,但是由于MySQL性能本身一般,所以采用分布式锁的情况下,其实使用MySQL作为分布式锁比较少见

Redis: Redis作为分布式锁是非常常见的一种使用方式 ,现在 企业级开发中基本都使用Redis或者Zookeeper作为分布式锁 ,利用 setnx 这个方法, 如果插入key成功,则表示获得到了锁,如果有人插入成功,其他人插入失败则表示无法获得到锁 ,利用这套逻辑来实现分布式锁

Zookeeper:Zookeeper也是企业级开发中较好的一个实现分布式锁的方案, 是通过创建临时节点来实现的

:zap:Redis 分布式锁的核心实现思路

实现分布式锁时需要实现的两个基本方法:

  • 获取锁(setnx)
  • 互斥 :确保只能有一个线程获取锁
  • 非阻塞 :尝试一次,成功返回true,失败返回false
  • 释放锁:手动释放超时释放 : 获取锁时添加一个超时时间

核心思路:

我们利用 Redis 的setNx 方法 ,当有多个线程进入时,我们就利用该方法,第一个线程进入时,redis 中就有这个key 了,返回了1,如果结果是1,则表示他抢到了锁,那么他去执行业务,然后再删除锁,退出锁逻辑,没有抢到锁的哥们,等待一定时间后重试即可

三、实战开发 实现 Redis 分布式锁

加锁:新建 Lock 锁接口

ILock 锁接口

package com.chen.utils;

public interface ILock {
 

    /**
     * 尝试获取锁
     * @param timeoutSecond
     * @return
     */
    boolean tryLock(long timeoutSecond);

    /**
     * 释放锁
     */
    void unLock();
}

SimpleRedisLock 锁实现类

package com.chen.utils;

import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.BooleanUtil;
import com.chen.utils.ILock;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import java.util.concurrent.TimeUnit;

/**
* redis 分布式锁实现类,实现获取锁与释放锁
*/
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 timeoutSecond) {
 
        String threadId = ID_PREFIX + Thread.currentThread().getId();
        // 尝试获取锁
        Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId, timeoutSecond, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(success);
    }


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

修改业务代码

public Result seckillVoucher(Long voucherId) {
 
        //1. 查询优惠卷
        SeckillVoucher seckillVoucher = seckillVoucherService.getById(voucherId);
        //2. 判断秒杀是否开始 开始时间大于当前时间表示未开始抢购
        if (seckillVoucher.getBeginTime().isAfter(LocalDateTime.now())) {
 
            return Result.fail("秒杀尚未开始!");
        }
        //3. 判断秒杀是否结束
        if (seckillVoucher.getEndTime().isBefore(LocalDateTime.now())) {
 
            return Result.fail("秒杀已经结束!");
        }
        //4. 判断库存是否充足
        if (seckillVoucher.getStock() < 1) {
 
            return Result.fail("库存不足!");
        }
        // 新增代码
    	Long userId = UserHolder.getUser().getId();
        // 创建锁对象
        SimpleRedisLock lock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);
        // 获取锁对象
        boolean tryLock = lock.tryLock(2000);
        if (!tryLock) {
 
            return Result.fail("不允许重复下单!");
        }
        try {
 
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOrder(voucherId, userId);
        } catch (Exception e) {
 

        } finally {
 
            //释放锁
            lock.unLock();
        }
        return null;
    }

四、ApiFox 测试 集群模式下是否能够解决并发问题

加入请求地址、参数 进行测试

第二个项目同上操作,换一下端口为8083 再次进行测试,返回结果

完成,以上接口,测试正常~

:boat:小结

以上就是【 Bug 终结者 】对 微服务 Spring Boot 整合Redis分布式锁 实现优惠卷秒杀 一人一单 的简单介绍, 在分布式系统下,高并发的场景下,会出现此类库存超卖问题,本篇文章介绍了采用分布式锁来解决,但是依然是有弊端,集群环境下,不同的服务之间删除锁会出现误删问题, 下章节,我们将继续进行优化,持续关注!

相关推荐

SpringBoot 各种分页查询方式详解(全网最全)

一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...

《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略

《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...

LoadRunner(loadrunner录制不到脚本)

一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...

Redis数据类型介绍(redis 数据类型)

介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...

RMAN备份监控及优化总结(rman备份原理)

今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...

备份软件调用rman接口备份报错RMAN-06820 ORA-17629 ORA-17627

一、报错描述:备份归档报错无法连接主库进行归档,监听问题12541RMAN-06820:WARNING:failedtoarchivecurrentlogatprimarydatab...

增量备份修复物理备库gap(增量备份恢复数据库步骤)

适用场景:主备不同步,主库归档日志已删除且无备份.解决方案:主库增量备份修复dg备库中的gap.具体步骤:1、停止同步>alterdatabaserecovermanagedstand...

一分钟看懂,如何白嫖sql工具(白嫖数据库)

如何白嫖sql工具?1分钟看懂。今天分享一个免费的sql工具,毕竟现在比较火的NavicatDbeaverDatagrip都需要付费才能使用完整功能。幸亏今天有了这款SQLynx,它不仅支持国内外...

「开源资讯」数据管理与可视化分析平台,DataGear 1.6.1 发布

前言数据齿轮(DataGear)是一款数据库管理系统,使用Java语言开发,采用浏览器/服务器架构,以数据管理为核心功能,支持多种数据库。它的数据模型并不是原始的数据库表,而是融合了数据库表及表间关系...

您还在手工打造增删改查代码么,该神器带你脱离苦海

作为Java开发程序,日常开发中,都会使用Spring框架,完成日常的功能开发;在相关业务系统中,难免存在各种增删改查的接口需求开发。通常来说,实现增删改查有如下几个方式:纯手工打造,编写各种Cont...

Linux基础知识(linux基础知识点及答案)

系统目录结构/bin:命令和应用程序。/boot:这里存放的是启动Linux时使用的一些核心文件,包括一些连接文件以及镜像文件。/dev:dev是Device(设备)的缩写,该目录...

PL/SQL 杂谈(二)(pl/sql developer使用)

承接(一)部分。我们从结构和功能这两个方面展示PL/SQL的关键要素。可以看看PL/SQL的优雅的代码。写出一个好的代码,就和文科生写出一篇优秀的作文一样,那么赏心悦目。1、与SQL的集成PL/S...

电商ERP系统哪个好用?(电商erp哪个好一点)

电商ERP系统哪个好用?做电商的,谁还没被ERP折腾过?有老板说:“我们早就上了ERP,订单、库存、财务全搞定,系统用得飞起。”也有运营吐槽:“系统是上了,可库存老不准,订单漏单错单天天有,财务对账还...

汽车检测线系统实例,看集中控制与PLC分布控制

PLC可编程控制器,上个世纪70年代初,为取代早期继电器控制线路,开始采取存储指令方式,完成顺序控制而设计的。开始仅有逻辑运算、计时、计数等简单功能。随着微处理的发展,PLC可编程能力日益提高,已经能...

苹果五件套成公司年会奖品主角,几大小技巧教你玩转苹果新品

钱江晚报·小时新闻记者张云山随着春节的临近,各家大公司的年会又将陆续上演。上周,各大游戏公司的年会大奖,苹果五件套又成了标配。在上海的游戏公司中,莉莉丝奖品列表拉得相当长,从特等奖到九等奖还包含了特...

取消回复欢迎 发表评论: