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");
    }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM