Redis分布式锁的实现

  • 1.锁
  • Java锁
  • 悲观锁与乐观锁
  • synchronized使用
  • 2.分布式锁
  • 介绍
  • 运用(单体架构vs分布式架构)


1.锁

Java锁

  • 乐观锁 悲观锁
  • 读锁(共享锁) 写锁(排它锁)
  • 自旋锁 非自旋锁
  • 无锁 偏向锁 轻量级锁 重量级锁
  • 分布式锁
  • 区间锁
  • 重入锁 非重入锁
  • 公平锁 非公平锁

悲观锁与乐观锁

悲观锁:
悲观锁顾名思义来解析就是很悲观,认为自己在使用数据的时候一定会有其他的线程来修改数据。所以它会在这之前就先加锁来确保数据不被其他线程修改

实现方式:
(1)内置synchronized
(2)Lock

实用场景:写操作比较多 可以先加锁拉来确保数据的正确性

乐观锁:
乐观锁与悲观锁相反,认为自己在使用数据的时候不会有其他的线程来修改数据。所以不会添加锁,只是在更新数据的同时去判断一下是否之前有其他线程更新过

实现方式:
(1)CAS算法

实用场景:读操作比较多的时候 不加锁的特点是能够让读操作性能得到一定提升

synchronized使用

使用方式:

  • 同步实例方法,锁是当前实例对象
  • 同步类方法,锁是当前类对象
  • 同步代码块,锁是括号里的对象

实现方式:
synchronized是jvm的内置锁,通过内部对象Monitor(监听器锁)实现。

示例

package com.example.demo.test;

public class Lock {

    private final static Object object = new Object();

    public static void lock(){
        synchronized (object){
            System.out.println("我是第一个同步块");

            System.out.println("我是第二个同步块");
        }
    }

    public static void main(String[] args) {
        lock();
    }
}

2.分布式锁

介绍

分布式锁的场景在现物联网平台是一个极其多的一个技术,比如商品秒杀、优惠券抢购、偶尔出的一些小活动等等。只要是一个分布式的架构中,基本上都会接触到分布式锁的运用

运用(单体架构vs分布式架构)

首先 我们先来看一段简单的代码 解决问题先发现问题

package com.example.demo.test;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "test")
public class DistributedLock {

    @GetMapping(value = "lock")
    public Object lockProduct(){
        //这里假设我是存库 我这个商品有50个库存
        int stock = 50;
        if (stock>=1){
            int num = stock - 1;
            System.out.println("商品售卖成功!"+num);
            return "true";
        }else{
            System.out.println("商品售卖失败!");
            return "false";
        }
    }
}

上面的代码会出现当并发高时 比如我两个或者多个都同时执行了stock-1的这一行代码 那么都是获取的50库存数,假如我库存到最后只剩下一个库存时 这时候就会出现超卖的情况 这时候可以使用加锁来解决 也就是上面介绍的synchronized方法来同步代码块

public Object lockProduct(){
        synchronized (this){
            int stock = 50;
            if (stock>=1){
                int num = stock - 1;
                System.out.println("商品售卖成功!"+num);
                return "true";
            }else{
                System.out.println("商品售卖失败!");
                return "false";
            }
        }
    }

在单机的环境下上面这个是没问题 但是在分布式环境下并发高还是会出现超卖的情况,解决方式当然是使用分布式锁 也就是下面要说的 这里我们使用redis作为解决方案,具体实如下

package com.example.demo.test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping(value = "test")
public class DistributedLock {

    @Autowired
    private StringRedisTemplate template;

    @GetMapping(value = "lock")
    public Object lockProduct(){
        //模拟一个商品
        Product product = new Product("10001","一双鞋子",499.00);
        //设置一个UUID来作为每一个线程的唯一标识
        String uuid = UUID.randomUUID().toString();
        try {
            //将商品sku存储redis
            Boolean flag = template.opsForValue().setIfAbsent(product.getSku(),
                    uuid,
                    20,
                    TimeUnit.SECONDS);

            if (!flag){
                return "false";
            }
            int shoes_num = Integer.valueOf(template.opsForValue().get("shoes"));
            if (shoes_num>0){
                int stock = shoes_num - 1;
                template.opsForValue().set("shoes", String.valueOf(stock));
                System.out.println("商品售卖成功!"+stock);
            }else{
                System.out.println("商品售卖失败!");
                return "false;";
            }
        }finally {
            if (uuid.equals(template.opsForValue().get(product.getSku()))){
                template.delete(product.getSku());
            }
        }
        return "true";
    }
}

但是在上面还是会出现一个问题,接口请求会有一个时间。我在上面设置redis的失效时间为20s,假如我的接口请求时间超过30秒那么就会出现我的key找不到了 来解决这个问题的办法很简单 我们可以用redisson框架 使用方式也很简单 直接引入redisson依赖

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

写一个配置类配置一下redisson

package com.example.demo.config;

import org.redisson.Redisson;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class RedissonConfiguration {

    @Bean
    public Redisson redisson(){
        Config config = new Config();
        //下面为单机模式 其他模式可以亲自尝试
        config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0);
        return (Redisson) Redisson.create();
    }
}

接下来就简单了 看下面代码

package com.example.demo.test;

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping(value = "test")
public class DistributedLock {

    @Autowired
    private StringRedisTemplate template;

    @Autowired
    private Redisson redisson;

    @GetMapping(value = "lock")
    public Object lockProduct(){
        //模拟一个商品
        Product product = new Product("10001","一双鞋子",499.00);
        RLock redissonLock = redisson.getLock(product.getSku());
        try {
            redissonLock.lock();;
            int shoes_num = Integer.valueOf(template.opsForValue().get("shoes"));
            if (shoes_num>0){
                int stock = shoes_num - 1;
                template.opsForValue().set("shoes", String.valueOf(stock));
                System.out.println("商品售卖成功!"+stock);
            }else{
                System.out.println("商品售卖失败!");
                return "false;";
            }
        }finally {
            redissonLock.unlock();
        }
        return "true";
    }
}

只需要引入Redisson就可以直接一次解决锁的配置与释放,对开发解决了成本与开发时遇到的问题

好了,这期说到这。很久没写blog了,之前因一直项目原因时间紧凑。接下来会在写blog的这条路上矜持不谢 感谢观看

尝试 或许可以成功 但一定不会遗憾!