基于redis实现分布式锁包括redisson的使用

1、导入pom.xml

       
        
            org.springframework.boot
            spring-boot-starter-data-redis
        

        
        
            org.redisson
            redisson-spring-boot-starter
            3.15.6
        

2、redis和redisson配置

  #redis配置
  redis:
    host: 127.0.0.1
    port: 6379
    database: 0
    # redis连接超时时间
    timeout: 10s
package com.lezu.springboot.configuration.redis;

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


@Configuration
public class RedisConfig {


//    @Bean
//    public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
//        RedisTemplate template = new RedisTemplate();
//        template.setConnectionFactory(factory);
//        // key采用String的序列化方式
//        template.setKeySerializer(new StringRedisSerializer());
//        // hash的key也采用String的序列化方式
//        template.setHashKeySerializer(new StringRedisSerializer());
//        // value序列化方式采用jackson
//        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
//        // hash的value序列化方式采用jackson
//        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
//        template.afterPropertiesSet();
//        return template;
//    }


    @Bean
    public Redisson redisson() {
        //单机模式
        Config config = new Config();
        config.useSingleServer()
                .setAddress("redis://127.0.0.1:6379")
                .setDatabase(0);
        return (Redisson) Redisson.create(config);
    }


// 主从
//        Config config = new Config();
//        config.useMasterSlaveServers()
//                .setMasterAddress("127.0.0.1:6379")
//                .addSlaveAddress("127.0.0.1:6389", "127.0.0.1:6332", "127.0.0.1:6419")
//                .addSlaveAddress("127.0.0.1:6399");
//        RedissonClient redisson = Redisson.create(config);

//哨兵
//        Config config = new Config();
//        config.useSentinelServers()
//                .setMasterName("mymaster")
//                .addSentinelAddress("127.0.0.1:26389", "127.0.0.1:26379")
//                .addSentinelAddress("127.0.0.1:26319");
//        RedissonClient redisson = Redisson.create(config);


//集群
//        Config config = new Config();
//        config.useClusterServers()
//                .setScanInterval(2000) // cluster state scan interval in milliseconds
//                .addNodeAddress("127.0.0.1:7000", "127.0.0.1:7001")
//                .addNodeAddress("127.0.0.1:7002");
//        RedissonClient redisson = Redisson.create(config);


}

3、redis分布式锁工具类

package com.lezu.springboot.utils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class RedisLock {

    @Autowired
    private StringRedisTemplate redisTemplate;

    private ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 10,
            TimeUnit.SECONDS, new LinkedBlockingDeque<>(100));

    /**
     * 加锁
     *
     * @param key
     * @param userId
     * @param timeOut
     * @return
     */
    public boolean lock(String key, String userId, long timeOut) {
        boolean success = redisTemplate.opsForValue().setIfAbsent(key, userId, timeOut, TimeUnit.SECONDS);
        if (!success) {
            log.error("key:{},userId:{}  get lock failer ", key, userId);
            return false;
        }
        try {
            renewExpiration(key, userId, timeOut);
            TimeUnit.SECONDS.sleep(2);
            return true;
        } catch (Exception e) {
            log.error("lock occur error:{}", e);
            return false;
        } finally {
            releaseLock(key, userId);
        }
    }

    /**
     * 续命操作
     *
     * @param key
     * @param timeOut
     */
    private void renewExpiration(String key, String userId, long timeOut) {
        threadPool.execute(() -> {
            Long currentTimeMills = System.currentTimeMillis();
            System.out.println("开始:" + LocalDateTime.now() + " ,timeOut:" + timeOut);
            while (true) {
                if (redisTemplate.hasKey(key) && userId.equals(redisTemplate.opsForValue().get(key))) {
                    Long cost = System.currentTimeMillis() - currentTimeMills;
                    //过期时间还剩余一半进行续命
                    if (cost > timeOut * 1000 / 2) {
                        currentTimeMills = System.currentTimeMillis();
                        redisTemplate.expire(key, timeOut, TimeUnit.SECONDS);
                        System.out.println("进行中:" + LocalDateTime.now() + " ,timeOut:" + timeOut);
                    }
                } else {
                    break;
                }
            }
        });
    }

    /**
     * 释放锁
     *
     * @param key
     * @param userId
     */
    public void releaseLock(String key, String userId) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        DefaultRedisScript redisScript = new DefaultRedisScript(script, Long.class);
        Long result2 = redisTemplate.execute(redisScript, Arrays.asList(key), userId);
        System.out.println("result2=" + result2);
    }

}

4、实际使用操作

package com.lezu.springboot.controller;

import com.lezu.springboot.configuration.ServerConfig;
import com.lezu.springboot.utils.RedisLockUtil;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


@RestController
public class RedisController {

    @Autowired
    private RedisLockUtil releaseLock;

    @Autowired
    private Redisson redisson;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private ServerConfig serverConfig;


    /**
     * 使用ReentrantLock锁解决单体应用的并发问题
     */
    private Lock lock = new ReentrantLock();


    /**
     * 单机业务
     *
     * @return
     */
    @RequestMapping("/deductStock1")
    public String deductStock1() throws InterruptedException {
        /**
         * 单机下单操作
         * 存在的问题:
         * 并发量过大会存在超卖问题
         */
        //商品数量
        int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
        if (stock > 0) {
            int resultStock = stock - 1;
            redisTemplate.opsForValue().set("stock", resultStock + "");
            System.out.println("扣减成功,剩余库存:" + resultStock + "---当前IP和端口号地址:" + serverConfig.getUrl());
        } else {
            System.out.println("扣减失败,库存不足!");
        }

        return "success";
    }

    /**
     * 使用synchronized来进行加锁操作(解决并发量过大发生超卖或重复卖的问题)
     * 存在的问题:
     * 分布式多台机器应用下会存在超卖
     */
    @RequestMapping("/deductStock2")
    public String deductStock2() throws InterruptedException {
        synchronized (this) {
            int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int resultStock = stock - 1;
                redisTemplate.opsForValue().set("stock", resultStock + "");
                System.out.println("扣减成功,剩余库存:" + resultStock + "---当前IP和端口号地址:" + serverConfig.getUrl());
            } else {
                System.out.println("扣减失败,库存不足!");
            }
        }
        return "success";
    }

    /**
     * 使用ReentrantLock加锁
     * 存在的问题:
     * 分布式多台机器应用下会存在超卖
     */
    @RequestMapping("/deductStock3")
    public String deductStock3() throws InterruptedException {
        lock.lock();
        try {
            int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int resultStock = stock - 1;
                redisTemplate.opsForValue().set("stock", resultStock + "");
                System.out.println("扣减成功,剩余库存:" + resultStock + "---当前IP和端口号地址:" + serverConfig.getUrl());
            } else {
                System.out.println("扣减失败,库存不足!");
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "error";
        } finally {
            lock.unlock();
        }
        return "success";
    }


    /**
     * 分布式锁示例
     * 存在的问题
     *
     * @return
     */
    @RequestMapping("/deductStock4")
    public String deductStock4() {
        String lockKey = "product_001"; //商品ID(前端传入)
        try {
            /**
             * 存在的问题
             * 1、突然宕机.... finally释放不了锁
             * 2、设置了超时时间 代码报错 设置超时时间的代码没有走
             */
//            Boolean result = redisTemplate.opsForValue().setIfAbsent("product_001", "userId");
//            //业务代码....
//
//            //代码突然报错导致设置超时时间这段代码没走
//            redisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);

            /**
             * 存在的问题
             * 1、加锁的这个key最多存活10秒钟,如果程序执行了10秒中该线程还想持有这把锁就会存在问题(不管怎么设置超时时间都不合理)
             */
            Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, "lock",10, TimeUnit.SECONDS);

            //加锁失败直接返回error
            if (!result) {
                return "系统繁忙,请稍后再试!";
            }
            //执行业务代码...
            int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int resultStock = stock - 1;
                redisTemplate.opsForValue().set("stock", resultStock + "");
                System.out.println("扣减成功,剩余库存:" + resultStock + "---当前IP和端口号地址:" + serverConfig.getUrl());
            } else {
                System.out.println("扣减失败,库存不足!");
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "error";
        } finally {
            //删除key释放锁
            redisTemplate.delete(lockKey);
        }
        return "success";
    }


    /**
     * 加锁并且设置超时时间
     * 自己写一个续命线程来防止超时时间的不合理操作
     * 存在的问题:
     * 并发量过大会存在加锁失败和超卖的问题
     *
     * @return
     */
    @RequestMapping("/deductStock5")
    public String deductStock5() {
        String lockKey = "product_001"; //商品ID(前端传入)
        try {
            //加锁
            boolean lock = releaseLock.lock(lockKey, "userId", 10);
            if (!lock) {
                return "系统繁忙,请稍后再试!";
            }
            //执行业务代码...
            int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int resultStock = stock - 1;
                redisTemplate.opsForValue().set("stock", resultStock + "");
                System.out.println("扣减成功,剩余库存:" + resultStock + "---当前IP和端口号地址:" + serverConfig.getUrl());
            } else {
                System.out.println("扣减失败,库存不足!");
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "error";
        } finally {
            //释放锁
            releaseLock.releaseLock(lockKey, "userId");
        }
        return "success";
    }


    /**
     * redisson分布式锁
     * 最终方案:
     * 使用redisson来实现分布式锁可以解决设置超时时间不合理问题
     * 内置看门狗机制(不设置默认30秒,设置之后就不在触发看门狗机制)
     * 监听策略:设置的超时时间/3,每10秒钟去看一下该线程是否还想持有这把锁,如果还想持有这把锁那么会再次把超时时间再次设置为30秒
     *
     * @return
     */
    @RequestMapping("/deductStock6")
    public String deductStock6() {
        String lockKey = "product_001"; //商品ID(前端传入)
        RLock redissonLock = redisson.getLock(lockKey);
        try {
            //加锁(启动看门狗机制)
            redissonLock.lock();
            //redissonLock.lock(30, TimeUnit.SECONDS);
            //执行业务代码...
            int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int resultStock = stock - 1;
                redisTemplate.opsForValue().set("stock", resultStock + "");
                System.out.println("扣减成功,剩余库存:" + resultStock + "---当前IP和端口号地址:" + serverConfig.getUrl());
            } else {
                System.out.println("扣减失败,库存不足!");
            }

        } catch (Exception e) {
            e.printStackTrace();
            return "error";
        } finally {
            //释放锁
            redissonLock.unlock();
        }
        return "success";
    }


}

你可能感兴趣的:(redis,redis,分布式锁)