springMVC 实现redis分布式锁

1.先配置spring-data-redis

首先是依赖

    <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>1.8.4.RELEASE</version>
        </dependency>

redisconfig 配置类

@Configuration
@PropertySource("classpath:irongbei.properties")
public class RedisConfig extends JCacheConfigurerSupport {
    @Autowired
    private Environment environment;

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        JedisConnectionFactory fac = new JedisConnectionFactory();
        fac.setHostName(environment.getProperty("redis.host"));
        fac.setPort(Integer.parseInt(environment.getProperty("redis.port")));
        fac.setPassword(environment.getProperty("redis.password"));
        fac.setTimeout(Integer.parseInt(environment.getProperty("redis.timeout")));
//        fac.getPoolConfig().setMaxIdle(Integer.parseInt(environment.getProperty("redis.maxIdle")));
//        fac.getPoolConfig().setMaxTotal(Integer.parseInt(environment.getProperty("redis.maxTotal")));
//        fac.getPoolConfig().setMaxWaitMillis(Integer.parseInt(environment.getProperty("redis.maxWaitMillis")));
//        fac.getPoolConfig().setMinEvictableIdleTimeMillis(
//                Integer.parseInt(environment.getProperty("redis.minEvictableIdleTimeMillis")));
//        fac.getPoolConfig()
//                .setNumTestsPerEvictionRun(Integer.parseInt(environment.getProperty("redis.numTestsPerEvictionRun")));
//        fac.getPoolConfig().setTimeBetweenEvictionRunsMillis(
//                Integer.parseInt(environment.getProperty("redis.timeBetweenEvictionRunsMillis")));
//        fac.getPoolConfig().setTestOnBorrow(Boolean.parseBoolean(environment.getProperty("redis.testOnBorrow")));
//        fac.getPoolConfig().setTestWhileIdle(Boolean.parseBoolean(environment.getProperty("redis.testWhileIdle")));
        return fac;
    }

    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, String> redis = new RedisTemplate<>();
        // 设置redis的String/Value的默认序列化方式
        DefaultStrSerializer defaultStrSerializer = new DefaultStrSerializer();
        redis.setKeySerializer(defaultStrSerializer);
        redis.setValueSerializer(defaultStrSerializer);
        redis.setHashKeySerializer(defaultStrSerializer);
        redis.setHashValueSerializer(defaultStrSerializer);
        redis.setConnectionFactory(redisConnectionFactory);
        redis.afterPropertiesSet();
        return redis;
    }
}

 class DefaultStrSerializer implements RedisSerializer<Object> {
    private final Charset charset;

    public DefaultStrSerializer() {
        this(Charset.forName("UTF8"));
    }

    public DefaultStrSerializer(Charset charset) {
        Assert.notNull(charset, "Charset must not be null!");
        this.charset = charset;
    }


    @Override
    public byte[] serialize(Object o) throws SerializationException {
        return o == null ? null : String.valueOf(o).getBytes(charset);
    }

    @Override
    public Object deserialize(byte[] bytes) throws SerializationException {
        return bytes == null ? null : new String(bytes, charset);

    }
}
View Code

redisLock类 分布式锁实现的类

@Component
public class RedisLock {

    private static final Logger log = LoggerFactory.getLogger(RedisLock.class);

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     *
     * @param key
     * @param value 当前时间+超时时间
     * @return
     */
    public boolean lock(String key,String value){

        if(redisTemplate.opsForValue().setIfAbsent(key,value)){
            log.info(" [Redis分布式锁] key:[{}] 获取到锁",key);
            return true;
        }
        //oldvalue  俩个线程都返回A
        String currentValue = redisTemplate.opsForValue().get(key);
        //如果锁过期->这里如果一个key的value时间是小于当前当前时间 那就是过期了,如果大于当前时间才没有过期
        if(StringUtils.isNotEmpty(currentValue) && Long.parseLong(currentValue) <System.currentTimeMillis()){

            //获取上一个锁的时间
            //第一个线程获取上一个oldvalue 然后设置一个新的值进去 第二个线程就获取到是新的值.
            String oldValue = redisTemplate.opsForValue().getAndSet(key,value);
            //3 这一步就只有第一个线程能匹配到了 第二个线程就获取不到了
            if(StringUtils.isNotEmpty(oldValue) && oldValue.equals(currentValue)){
                log.info(" [Redis分布式锁] key:[{}] 获取到锁",key);
                return true;
            }

        }

        return false;

    }

    /**
     * 解锁
     * @param key
     * @param value
     */
    public void unlock(String key ,String value){
        try {
            String currentValue = redisTemplate.opsForValue().get(key);
            if(StringUtils.isNotEmpty(currentValue) && currentValue.equals(value)){
                redisTemplate.opsForValue().getOperations().delete(key);
            }
        } catch (Exception e) {
            log.error(" [redis分布式锁] 解锁异常, {} ",e);
        }


    }
}

测试类:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations = {"/spring-context.xml","/spring-context-jedis.xml","/spring-context-shiro.xml"})
public class RedisLockTest {
    @Resource(name = "threadPoolTaskExecutor")
    private ThreadPoolExecutor taskExecutor;


    @Autowired
    private RedisLock redisLock;
    private long timeout = 5*1000;

    private static  final Logger logger =LoggerFactory.getLogger(RedisLockTest.class);

    @Test
    public void  test (){
        for (int i = 0; i <200 ; i++) {
            taskExecutor.execute(()->{
                String time = String.valueOf(System.currentTimeMillis()+timeout);
                if(!redisLock.lock("testlock",time)){
                    logger.info("哎呦喂..人太多了 在排队中..");
                   return;
                }else {
                    try {
                        Thread.sleep(4000);
                        redisLock.unlock("testlock",time);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            });
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }


    }
}

 补充 

和同事讨论,结果确实如同事所说,解锁的时候有些不严谨,因为是分俩步操作的.可能会在del之前被别人获取到锁然后再del删除掉别人获取的锁.下面是新的解锁方式,目前有些公司redis服务器不支持这样的命令

  为什么使用lua语言在操作Redis 主要是保证操作的原子性.一步操作

  但是使用lua要非常小心,如果脚本错误可能会阻塞整个Redis实例

 private static final Long SUCCESS = 1L;
    /**
     * 释放锁
     * @param key
     * @param value
     * @return
     */
    public  boolean releaseLock(String key, String value) {
        key =prifix+key;
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

        RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);

        try {
            Object result = redisTemplate.execute(redisScript, Collections.singletonList(key), value);
            System.out.println(result);
            if (SUCCESS.equals(result)) {
                log.info("[redis 分布式锁解锁成功] key:[{}] 结果:{}",key,result);
                return true;
            }
            log.info("[redis 分布式锁解锁失败] key:[{}]  结果{}",key,result);
        } catch (Exception e) {
            log.info("[redis 分布式锁解锁失败]  key:[{}] msg:{}",key,e.getMessage());
            e.printStackTrace();
        }

        return false;
    }

 测试代码

    @Test
    public void testPrefix(){
        redisLock.lock("test","10000");
        redisLock.lock("test1","10000");
//        redisLock.lock("test2","10000");
//        redisLock.lock("test3","10000");
        redisLock.releaseLock("test","10000");
        redisLock.releaseLock("test1","10002");
    }
原文地址:https://www.cnblogs.com/bj-xiaodao/p/10734464.html