redis分布式锁的实现

前言:

Redis是什么:
Redis是现在最受欢迎的NoSQL数据库之一,Redis是一个使用ANSI C编写的开源、包含多种数据结构、支持网络、基于内存、可选持久性的键值对存储数据库,其具备如下特性:

基于内存运行,性能高效
支持分布式,理论上可以无限扩展
key-value存储系统
开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API
相比于其他数据库类型,Redis具备的特点是:

C/S通讯模型
单进程单线程模型
丰富的数据类型
操作具有原子性
持久化
高并发读写
支持lua脚本

分布式锁常见的场景:
互联网秒杀(淘宝双11、京东的618等)
抢优惠券

一、实现分布式锁的几个特性:

1. 互斥性  (即任意时刻,只有一个客户端持有锁)
2. 不发生死锁(即使一个客户端在持有锁期间崩溃而没有释放锁的情况下,也能保证后续其他客户端加锁)
3. 具有容错性(只有大部分redis节点正常运行,客户端就可以加锁和解锁)
4. 解铃还需系铃人(也就是加锁和解锁必须是同一个客户端,客户端不能把别人的锁给解了)
5. 锁不能自己失效(锁正常执行程序时,锁不能因为某些原因失效)

二、redis执行分布式锁的命令

文档地址:

http://redisdoc.com/

setnx (SET if Not eXists)
语法:

SETNX key value

将 key 的值设为 value ,当且仅当 key 不存在。

若给定的 key 已经存在,则 SETNX 不做任何动作。

返回值:

设置成功,返回 1

设置失败,返回 0

get

语法:

GET key

返回值:

当 key 不存在时,返回 null ,否则,返回 key 的值。

如果 key 不是字符串类型,那么返回一个错误

一个简单的springboot应用程序:

相关依赖:

	<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.16.RELEASE</version>
        <relativePath/>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <fastjson.version>1.2.29</fastjson.version>
        <druid.version>1.0.26</druid.version>
        <mysql.version>5.1.46</mysql.version>
        <swagger.version>2.7.0</swagger.version>
        <lombok.version>1.18.2</lombok.version>
        <maven.build.timestamp.format>yyyy-MM-dd</maven.build.timestamp.format>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!---->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.13.4</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.2</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

redis分布式锁的实现

application.yml配置

server:
  port: 8060

spring:
  redis:
    database: 0
    host: 127.0.0.1
    port: 6379
    password:
    timeout: 20000
    jedis:
      pool:
        max-active: 50 //是最大激活连接数,这里取值为20,表示同时最多有50个reids连接
        max-wait: 60000。//最大等待毫秒数, 单位为 ms, 超过时间会出错误信息
        max-idle: 5。 //最大等待连接中的数量,设 0 为没有限制
        min-idle: 0 //最小等待连接中的数量,设 0 为没有限制

1.1设置redis预售库存:

redis分布式锁的实现

1.2扣减库存的示例代码1:

 /**
  * stringRedisTemplate redis模版
  */
  @Autowired
  private RedisTemplate redisTemplate;
    
 /**
  * 库存扣减
  * @return
 */
 @RequestMapping("/operation_stock")
 public String operStock() {
     String stock = (String) redisTemplate.opsForValue().get("stock");// jedis.get("stock")
     if (StringUtil.isNotBlank(stock) && Integer.parseInt(stock) > 0) {
         int realStock = Integer.parseInt(stock) - 1;
         redisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key, val)
         log.info(String.format("扣减成功:[%s]", realStock));
     } else {
         log.info("扣减失败,库存不足");
     }
     return "success";
 }

1.2扣减库存的示例代码2:


  /**
   * 操作库存
   * @return 返回值
   */
 @RequestMapping("/operation/stock")
 public String operationStock() {
     synchronized (this) {
         String stock = (String) redisTemplate.opsForValue().get("stock");// jedis.get("stock")
         if (StringUtil.isNotBlank(stock) && Integer.parseInt(stock) > 0) {
             int realStock = Integer.parseInt(stock) - 1;
             redisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key, val)
             log.info(String.format("扣减成功:[%s]", realStock));
         } else {
             log.info("扣减失败,库存不足");
         }
         return "success";
     }
 }

集群下的操作:

redis分布式锁的实现
使用Jemeter压测接口:
redis分布式锁的实现redis分布式锁的实现

启动一个8060的应用:
redis分布式锁的实现

启动一个8070的应用:redis分布式锁的实现

@RequestMapping("/operation_stock")
    public String operStock() {
        String lockKey = "product_001";
        String clentId = UUID.randomUUID().toString();
        try {
//        Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, "testProduct");
//        redisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);

        redisTemplate.opsForValue().set(lockKey, clentId, 10, TimeUnit.SECONDS);

        String stock = (String) redisTemplate.opsForValue().get("stock");// jedis.get("stock")
        if (StringUtil.isNotBlank(stock) && Integer.parseInt(stock) > 0) {
            int realStock = Integer.parseInt(stock) - 1;
            redisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key, val)
            log.info(String.format("扣减成功:[%s]", realStock));
        } else {
            log.info("扣减失败,库存不足");
        }

        } finally {
            // 防止锁永久失效的问题
            if (clentId.equals(redisTemplate.opsForValue().get(lockKey))) {
                redisTemplate.delete(lockKey);
            }
        }

        return "success";
    }

采用Redisson实现:

 @Bean
    public Redisson redisson () {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
        return (Redisson) Redisson.create(config);
    }
    /**
     * rediss分布式锁机制
     * @return
     */
    @RequestMapping("/operation/redission/stock")
    public String operationRedissionStock() {
        String lockKey = "lockKey";
        RLock redissonLock = redisson.getLock(lockKey);
        try {
            redissonLock.lock(30, TimeUnit.SECONDS);
            String stock = (String) redisTemplate.opsForValue().get("stock");// jedis.get("stock")
            if (StringUtil.isNotBlank(stock) && Integer.parseInt(stock) > 0) {
                int realStock = Integer.parseInt(stock) - 1;
                redisTemplate.opsForValue().set("stock", realStock + "");
                log.info(String.format("扣减成功:[%s]", realStock));
            } else {
                log.info("扣减失败,库存不足");
            }
        } finally {
            redissonLock.unlock();
        }

        return "success";
    }

Redission实现原理:
redis分布式锁的实现

redission官网地址:

https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95
上一篇:C++ 学习日志 --17


下一篇:odoo 在先进先出规则中采购退货,库存成本处理,库存成本按照采购价减扣