知名网站建设定制微服务 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锁失效的原因,在这种情况下,我们就需要使用分布式锁来解决这个问题。

二、什么是锁?

⛄基本原理和实现方式

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

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

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

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

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

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

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

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

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

常见的分布式锁有三种

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

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

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

⚡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();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

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);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

修改业务代码

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;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

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

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

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

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

⛵小结

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

如果这篇【文章】有帮助到你,希望可以给【Bug 终结者】点个赞👍,创作不易,如果有对【后端技术】、【前端领域】感兴趣的小可爱,也欢迎关注❤️❤️❤️ 【Bug 终结者】❤️❤️❤️,我将会给你带来巨大的【收获与惊喜】💝💝💝!

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发