小工具      在线工具  汉语词典  dos游戏  css  js  c++  java

SpringBoot基于redisson 实现分布式锁

Java 额外说明

收录于:52天前

分布式锁主流实现方案及选择

a:基于Redis的分布式锁。使用并发量很大、性能要求很高而可靠性问题可以通过其他方案弥补的场景
b:基于ZooKeeper的分布式锁。适用于高可靠(高可用),而并发量不是太高的场景

在实际生产中,尤其是在分布式环境中,由于我们的逻辑实际上只处理一份业务数据,所以接口并发时难免会出现并发问题,使得业务数据不正确。这时候就需要一个类似锁的方法。保证数据幂等性的事物,比如闪购业务。实现分布式锁的方式有很多种,比如zookeeper、redis、数据库等,使用原生的redis方式实现还是比较复杂的。基于这个场景,我们使用redisson来实现分布式锁。

以redis为例,这里选用功能比较丰富强大的redis客户端redisson来完成,https://github.com/redisson/redisson

1.启动redis服务器

cd /Users/sunww/Documents/soft/Java/redis-2.8.17

redis服务器

2.Spring Boot集成redisson框架

这里的spring Boot版本是2.2.5

<dependency>
  <groupId>org.redisson</groupId>
  <artifactId>redisson</artifactId>
  <version>3.6.5</version>
</dependency>

在application.properties中添加

#Redis settings
redisson.address=redis://127.0.0.1:6379

3.主要订单逻辑&redisson锁实现源码

创建redisson配置管理类等,如下:

package com.robinboot.service.redisson;

import org.springframework.beans.factory.annotation.Value;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * redisson配置
 */
@Configuration
public class RedissonConfig {

    @Value("${redisson.address}")
    private String addressUrl; // redisson.address=redis://127.0.0.1:6379

    @Bean
    public RedissonClient getRedisson() throws Exception{
        RedissonClient redisson = null;
        Config config = new Config();
        config.useSingleServer()
                .setAddress(addressUrl);
        redisson = Redisson.create(config);

        System.out.println(redisson.getConfig().toJSON().toString());
        return redisson;
    }
}
package com.robinboot.service.redisson;

import org.redisson.api.RLock;

import java.util.concurrent.TimeUnit;

/**
 * @Auther: TF12778
 * @Date: 2020/7/29 16:23
 * @Description:
 */
public interface RedissonLocker {

    RLock lock(String lockKey);

    RLock lock(String lockKey, long timeout);

    RLock lock(String lockKey, TimeUnit unit, long timeout);

    boolean tryLock(String lockKey, TimeUnit unit, long waitTime, long leaseTime);

    void unlock(String lockKey);

    void unlock(RLock lock);
}
package com.robinboot.service.redisson;

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * @auther: TF12778
 * @date: 2020/7/29 16:23
 * @description:
 */
@Component
public class RedissonLockerImpl implements RedissonLocker {

    @Autowired
    private RedissonClient redissonClient; // RedissonClient已经由配置类生成,这里自动装配即可

    // lock(), 拿不到lock就不罢休,不然线程就一直block
    @Override
    public RLock lock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock();
        return lock;
    }

    // leaseTime为加锁时间,单位为秒
    @Override
    public RLock lock(String lockKey, long leaseTime) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(leaseTime, TimeUnit.SECONDS);
        return null;
    }

    // timeout为加锁时间,时间单位由unit确定
    @Override
    public RLock lock(String lockKey, TimeUnit unit, long timeout) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(timeout, unit);
        return lock;
    }

    @Override
    public boolean tryLock(String lockKey, TimeUnit unit, long waitTime, long leaseTime) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            return lock.tryLock(waitTime, leaseTime, unit);
        } catch (InterruptedException e) {
            return false;
        }
    }

    @Override
    public void unlock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.unlock();
    }

    @Override
    public void unlock(RLock lock) {
        lock.unlock();
    }
}

这里主要使用redisson创建分布式锁来下单,如下:

package com.robinboot.service.facade.impl;

import com.robinboot.facade.RedissionFacadeService;
import com.robinboot.result.Result;
import com.robinboot.service.domain.Stock;
import com.robinboot.service.domain.StockOrder;
import com.robinboot.service.redisson.RedissonLocker;
import com.robinboot.service.service.StockOrderService;
import com.robinboot.service.service.StockService;
import com.robinboot.service.utils.CuratorFrameworkUtils;
import com.robinboot.utils.ServiceException;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.concurrent.TimeUnit;

/**
 * @auther: TF12778
 * @date: 2020/7/29 11:09
 * @description:
 */
@Service("redissionFacadeService")
public class RedissionFacadeServiceImpl implements RedissionFacadeService {

    private static final String LOCK_KEY = "lockswww";

    @Autowired
    StockService stockService;

    @Autowired
    StockOrderService stockOrderService;

    @Autowired
    private RedissonLocker redissonLocker;

    /**
     * 下单步骤:校验库存,扣库存,创建订单,支付
     *
     */
//    @Transactional  此处不需要加事物,否则订单数量超表
    @Override
    public Result<String> saveOrder(int sid) {
        try {
               redissonLocker.lock(LOCK_KEY);
                /**
                 * 1.查库存
                 */
                Stock stock = new Stock();
                stock.setId(sid);
                Stock stockResult = stockService.selectDetail(stock);
                if (stockResult == null || stockResult.getCount() <= 0 || stockResult.getSale() == stockResult.getCount()) {
                    throw new ServiceException("库存不足", "500");
                }

                /**
                 * 2.根据查询出来的库存,更新已卖库存数量
                 */
                int count = stockService.updateStock(stockResult);
                if (count == 0){
                    throw new ServiceException("库存为0", "500");
                }

                /**
                 * 3.创建订单
                 */
                StockOrder order = new StockOrder();
                order.setSid(stockResult.getId());
                order.setName(stockResult.getName());
                int id = stockOrderService.saveStockOrder(order);
                if (id > 0) {
                    return  new Result<String>("success", "下单成功", "0", null, "200" );
                }
                return  new Result<String>("error", "下单失败", "0", null, "500" );

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            redissonLocker.unlock(LOCK_KEY); // 释放锁
        }

        return  new Result<String>("error", "下单失败", "0", null, "500" );
    }
}
package com.robinbootweb.dmo.controller;

import com.robinboot.facade.CuratorFacadeService;
import com.robinboot.facade.RedissionFacadeService;
import com.robinboot.result.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

/**
 * @auther: TF12778
 * @date: 2020/7/29 11:07
 * @description:
 */
@RestController
@RequestMapping("/redission")
public class RedissionController {

    @Autowired
    RedissionFacadeService redissionFacadeService;

    /**
     * http://localhost:8090/robinBootApi/redission/saveOrder
     * @param
     * @return
     */
    @ResponseBody
    @RequestMapping(value = "/saveOrder", method = RequestMethod.GET)
    public Result<String> saveOrder() {

        Result<String> result =  redissionFacadeService.saveOrder(1);
        return result;
    }
}

4.启动jmeter测试工具

通过命令jmeter启动成功

5.初始化数据库信息

CREATE TABLE `stock` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL DEFAULT '' COMMENT '名称',
  `count` int(11) NOT NULL COMMENT '库存',
  `sale` int(11) NOT NULL COMMENT '已售',
  `version` int(11) NOT NULL COMMENT '乐观锁,版本号',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
 
CREATE TABLE `stock_order` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `sid` int(11) NOT NULL COMMENT '库存ID',
  `name` varchar(30) NOT NULL DEFAULT '' COMMENT '商品名称',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4868 DEFAULT CHARSET=utf8

6.高并发,发起测试

这里我们模拟200个用户抢iPhone的操作,如下:

以下是200个请求的返回结果

1、当库存充足时,可以看到下单成功,如下界面

2、当库存不足时,可以看到下单失败,如下界面

查看数据库信息,可以看到我们的100部iPhone已经全部售罄(sale=100)

查看订单表,可以看到iPhone下单了100个,并且没有出现超卖的情况,说明锁定成功。

. . .

相关推荐

额外说明

>> 和 >>>> 之间的区别

1、>>:右移运算符,num >> 1,相当于num除以2。 按二进制形式把所有的数字向右移动对应位数,低位移出并舍弃,高位的空位补符号位,即正数补零,负数补1,符号位不变。 如:-1在32位二进制中表示为:11111111 11111111 11111

额外说明

[Java |基础知识]计算机中数据的存储规则

文章目录 前言: 1.计算机中的数据 2.二进制的介绍 二进制的运算规则 常见的进制 3.字符的存储 4.汉字的存储 5.图片的存储 6.音频的存储 总结: 前言: 本篇文章只是为了科普 计算机中数据的存储规则 1.计算机中的数据 计算机的数据大致分为三

额外说明

Java提高——05-shiro-授权的简单判断

QQ 1274510382 Wechat JNZ_aming 商业联盟 QQ群538250800 技术搞事 QQ群599020441 解决方案 QQ群152889761 加入我们 QQ群649347320 共享学习 QQ群674240731 纪年科技am

额外说明

C语言实现:猴子吃桃问题

C语言实现:猴子吃桃问题 文章目录 C语言实现:猴子吃桃问题 1. 问题 2. 解决方案 3. 实现代码 4. 执行结果 5. 解决方法说明——穷举法 1. 问题 猴子吃桃问题:有一只猴子第一天摘下若干个桃子,当即吃掉了一半,又多吃了一个;第二天又将剩下

额外说明

JavaWeb+在线图书商城+EL(表达式语言)+ JSTL(JSP标准标签库)(超详细)

-作者简介:练习时长两年半的Java up主 -个人主页:老茶icon - ps:点赞-是免费的,却可以让写博客的作者开兴好久好久- -系列专栏:Java全栈,计算机系列(火速更新中) - 格言:种一棵树最好的时间是十年前,其次是现在 -动动小手,点个关

额外说明

Scala案例:词频统计

一、提出任务 统计文本文件里单词出现次数。 二、完成任务 1、创建Scala项目 - ScalaWordCount       创建net.hw.wc包: 2、在项目根目录添加文本文件test.txt 3、在net.hw.wc包里创建scala源程序Wo

额外说明

MySql之慢Sql定位分析

MySql之慢Sql定位分析 定位低效率执行SQL 可以通过以下两种方式定位执行效率较低的SQL语句。 慢查询日志:通过慢查询日志定位那些执行效率较低的SQL语句,用- log-slow-queries[= file name]选项启动时, mysqld

额外说明

SpringCloud整合spring security+ oauth2+Redis实现认证授权

文章目录 设置通用父工程依赖 构建eureka注册中心 构建认证授权服务 配置文件设置 Security配置类 授权服务配置类 登录实现 测试验证 设置通用父工程依赖 在微服务构建中,我们一般用一个父工程来通知管理依赖的各种版本号信息。父工程pom文件如

额外说明

深入sql server中的事务

深入sql server中的事务    一.         概述... 1 二.         并发访问的不利影响... 1 1.       脏读(dirty read)... 1 2.       不可重复读(nonrepeatable read

额外说明

【VMware】win 10:VMware 15 虚拟机安装 win 7 系统

目录 一、准备虚拟机 二、win7 ghost ISO镜像文件下载 三、VMware新建虚拟机 四、VMware安装win7 五、解决vmware虚拟机屏幕没有适应窗口全屏问题 一、准备虚拟机 自己先安装好 VMware 15 虚拟机 二、win7 gh

ads via 小工具