redis集群下分布式锁的实现

1,redis集群的配置文件

redis.cluster.nodes=192.168.1.102:7000,192.168.1.102:7001,192.168.1.102:7002,192.168.1.102:7003,192.168.1.102:7004,192.168.1.102:7005
redis.cluster.testOnReturn=true
redis.cluster.maxIdle=10
redis.cluster.minIdle=2
redis.cluster.maxWaitMills=3000
redis.cluster.testOnBorrow=true
redis.cluster.maxTotal=4
redis.cluster.connectionTimeOut=30000

2,redis集群的连接配置

/**
* 提供集群部署的情况下面的redisTemplate支持
* @param factory
* @return
*/
@Bean("redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<Object, Object>();
        redisTemplate.setConnectionFactory(factory);
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(om);
        redisTemplate.setKeySerializer(serializer);
        redisTemplate.setValueSerializer(serializer);
        redisTemplate.afterPropertiesSet();
        //redisTemplate.setEnableTransactionSupport(true);
        return redisTemplate;
    }

/**
     * jedis 连接池
     * @return
     */
    private JedisPoolConfig jedisPoolConfig() {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(redisConfig.getCluster().getMaxTotal());
        config.setMaxIdle(redisConfig.getCluster().getMaxIdle());
        config.setMaxWaitMillis(redisConfig.getCluster().getMaxWaitMills());
        config.setTestOnBorrow(redisConfig.getCluster().getTestOnBorrow());
        return config;
    }
    
    @Bean(name = "redisCacheManager")
    public CacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        cacheManager.setDefaultExpiration(1800);
        return cacheManager;
    }
    
    /**
     * redisCluster配置
     *
     * @return
     */
    @Bean
    public RedisClusterConfiguration redisProperties() {
        Map<String, Object> source = new HashMap<String, Object>();
        source.put("spring.redis.cluster.nodes", redisConfig.getCluster().getNodes());
        source.put("spring.redis.cluster.timeout", redisConfig.getCluster().getConnectionTimeOut());
        return new RedisClusterConfiguration(new MapPropertySource("RedisProperties", source));
    }
    
    @Bean("jedisConnFactory")
    public JedisConnectionFactory jedisConnectionFactory() {
        //redis cluster
        JedisConnectionFactory factory = new JedisConnectionFactory(redisProperties(),jedisPoolConfig());
        return factory;
    }

3,redis集群下分布式锁的主要实现方法

package com.dream21th.dream21thredis.lock;
import java.util.Random;


import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import lombok.extern.slf4j.Slf4j;


/**
 * Redis distributed lock implementation.
 *
 * @author dream21th
 */
@Slf4j
public class RedisLock {
    private RedisTemplate redisTemplate;


    private static final int DEFAULT_ACQUIRY_RESOLUTION_MILLIS = 100;


    /**
     * Lock key path.
     */
    private String lockKey;


    /**
     * 锁超时时间,防止线程在入锁以后,无限的执行等待
     */
    private int expireMsecs = 60 * 1000;


    /**
     * 锁等待时间,防止线程饥饿
     */
    private int timeoutMsecs = 10 * 1000;


    private volatile boolean locked = false;


    /**
     * Detailed constructor with default acquire timeout 10000 msecs and lock expiration of 60000 msecs.
     *
     * @param lockKey lock key (ex. account:1, ...)
     */
    public RedisLock(RedisTemplate redisTemplate, String lockKey) {
        this.redisTemplate = redisTemplate;
        this.lockKey = lockKey + "_lock";
    }


    /**
     * Detailed constructor with default lock expiration of 60000 msecs.
     *
     */
    public RedisLock(RedisTemplate redisTemplate, String lockKey, int timeoutMsecs) {
        this(redisTemplate, lockKey);
        this.timeoutMsecs = timeoutMsecs;
    }


    /**
     * Detailed constructor.
     *
     */
public RedisLock(RedisTemplate redisTemplate, String lockKey, int timeoutMsecs, int expireMsecs) {
        this(redisTemplate, lockKey, timeoutMsecs);
        this.expireMsecs = expireMsecs;
    }


    /**
     * @return lock key
     */
    public String getLockKey() {
        return lockKey;
    }


    private String get(final String key) {
        Object obj = null;
        try {
            obj = redisTemplate.execute(new RedisCallback<Object>() {
                @Override
                public Object doInRedis(RedisConnection connection) throws DataAccessException {
                    //StringRedisSerializer serializer = new StringRedisSerializer();
                Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
                    byte[] data = connection.get(serializer.serialize(key));
                    connection.close();
                    if (data == null) {
                        return null;
                    }
                    return serializer.deserialize(data);
                }
            });
        } catch (Exception e) {
            log.error("get redis error, key : {}", key);
        }
        return obj != null ? obj.toString() : null;
    }


    private boolean setNX(final String key, final String value) {
        Object obj = null;
        try {
            obj = redisTemplate.execute(new RedisCallback<Object>() {
                @Override
                public Object doInRedis(RedisConnection connection) throws DataAccessException {
                    //StringRedisSerializer serializer = new StringRedisSerializer();
                Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
                    Boolean success = connection.setNX(serializer.serialize(key), serializer.serialize(value));
                    connection.close();
                    return success;
                }
            });
        } catch (Exception e) {
        log.info("{}",e);
            log.error("setNX redis error, key : {}", key);
        }
        return obj != null ? (Boolean) obj : false;
    }


    private String getSet(final String key, final String value) {
        Object obj = null;
        try {
            obj = redisTemplate.execute(new RedisCallback<Object>() {
                @Override
                public Object doInRedis(RedisConnection connection) throws DataAccessException {
                    //StringRedisSerializer serializer = new StringRedisSerializer();
                Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
                    byte[] ret = connection.getSet(serializer.serialize(key), serializer.serialize(value));
                    connection.close();
                    return serializer.deserialize(ret);
                }
            });
        } catch (Exception e) {
            log.error("setNX redis error, key : {}", key);
        }
        return obj != null ? (String) obj : null;
    }


    /**
     * 获得 lock.
     * 实现思路: 主要是使用了redis 的setnx命令,缓存了锁.
     * reids缓存的key是锁的key,所有的共享, value是锁的到期时间(注意:这里把过期时间放在value了,没有时间上设置其超时时间)
     * 执行过程:
     * 1.通过setnx尝试设置某个key的值,成功(当前没有这个锁)则返回,成功获得锁
     * 2.锁已经存在则获取锁的到期时间,和当前时间比较,超时的话,则设置新的值
     *
     * @return true if lock is acquired, false acquire timeouted
     * @throws InterruptedException in case of thread interruption
     */
    public boolean lock() throws InterruptedException {
   
        int timeout = timeoutMsecs;
        while (timeout >= 0) {
            long expires = System.currentTimeMillis() + expireMsecs + 1;
            String expiresStr = String.valueOf(expires); //锁到期时间
            //log.info("=========="+Thread.currentThread().getName()+"========="+timeout);
            if (this.setNX(lockKey, expiresStr)) {
                // lock acquired
                locked = true;
                return true;
            }


            String currentValueStr = this.get(lockKey); //redis里的时间
            if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
                //判断是否为空,不为空的情况下,如果被其他线程设置了值,则第二个条件判断是过不去的
                // lock is expired


                String oldValueStr = this.getSet(lockKey, expiresStr);
                //获取上一个锁到期时间,并设置现在的锁到期时间,
                //只有一个线程才能获取上一个线上的设置时间,因为jedis.getSet是同步的
                if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
                    //防止误删(覆盖,因为key是相同的)了他人的锁——这里达不到效果,这里值会被覆盖,但是因为什么相差了很少的时间,所以可以接受
                    //[分布式的情况下]:如过这个时候,多个线程恰好都到了这里,但是只有一个线程的设置值和当前值相同,他才有权利获取锁
                    // lock acquired
                    locked = true;
                    return true;
                }
            }
            //timeout -= DEFAULT_ACQUIRY_RESOLUTION_MILLIS;


            int sleepTime=new Random().nextInt(10)*DEFAULT_ACQUIRY_RESOLUTION_MILLIS;
            timeout -=sleepTime;
            //log.info("=========="+Thread.currentThread().getName()+"========="+sleepTime);
            /*
                延迟100 毫秒,  这里使用随机时间可能会好一点,可以防止饥饿进程的出现,即,当同时到达多个进程,
                只会有一个进程获得锁,其他的都用同样的频率进行尝试,后面有来了一些进行,也以同样的频率申请锁,这将可能导致前面来的锁得不到满足.
                使用随机的等待时间可以一定程度上保证公平性
             */
            Thread.sleep(sleepTime);


        }
        return false;
    }




    /**
     * Acqurired lock release.
     */
    public void unlock() {
        if (locked) {
            redisTemplate.delete(lockKey);
            locked = false;
        }
    }
    
    
    public boolean isExpired(){
    String currentValueStr = this.get(lockKey); //redis里的时间
    if(currentValueStr==null){
    return true;
    }
    return Long.parseLong(currentValueStr) < System.currentTimeMillis()?true:false;
    }


}

4,测试方法,模仿100的线程操作()

package com.dream21th.dream21thredis.service;
import java.util.stream.IntStream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import com.dream21th.dream21thredis.lock.RedisLock;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class RedisLockService {
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
private static String key = "lock-test";
private static int count=10;
public void test() {
RedisLock lock = new RedisLock(redisTemplate, key, 10000, 20000);
try {
if (lock.lock()) {
log.info(Thread.currentThread().getName()+"------>>>>begin");
Thread.sleep(1000);

if(count>=1){

                                        count--;

log.info("count:{}",count);
}
log.info(Thread.currentThread().getName()+"------>>>>end");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 为了让分布式锁的算法更稳键些,持有锁的客户端在解锁之前应该再检查一次自己的锁是否已经超时,再去做DEL操作,因为可能客户端因为某个耗时的操作而挂起,
// 操作完的时候锁因为超时已经被别人获得,这时就不必解锁了.
if(!lock.isExpired()){
lock.unlock();
}

}
}

public void run(){
IntStream.range(0, 100).forEach(i->{
Thread run=new Thread(new Runnable() {
@Override
public void run() {
test();
}
});
run.start();
});
}

}

5,测试controller

package com.dream21th.dream21thredis.controller;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.dream21th.dream21thredis.service.RedisLockService;


@RestController
@RequestMapping("/redisLock")
public class RedisLockController {


@Autowired
private RedisLockService redisLockService;

@PostMapping("/testLock")
public void testRedisLock(){
redisLockService.run();
}
}

6,测试结果

2018-06-19 14:42:40.706  INFO 7064 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2018-06-19 14:42:40.706  INFO 7064 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2018-06-19 14:42:40.821  INFO 7064 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 115 ms
2018-06-19 14:42:41.072  INFO 7064 --- [      Thread-93] c.d.d.service.RedisLockService           : Thread-93------>>>>begin
2018-06-19 14:42:42.072  INFO 7064 --- [      Thread-93] c.d.d.service.RedisLockService           : count:9
2018-06-19 14:42:42.072  INFO 7064 --- [      Thread-93] c.d.d.service.RedisLockService           : Thread-93------>>>>end
2018-06-19 14:42:42.079  INFO 7064 --- [      Thread-73] c.d.d.service.RedisLockService           : Thread-73------>>>>begin
2018-06-19 14:42:43.080  INFO 7064 --- [      Thread-73] c.d.d.service.RedisLockService           : count:8
2018-06-19 14:42:43.080  INFO 7064 --- [      Thread-73] c.d.d.service.RedisLockService           : Thread-73------>>>>end
2018-06-19 14:42:43.335  INFO 7064 --- [     Thread-100] c.d.d.service.RedisLockService           : Thread-100------>>>>begin
2018-06-19 14:42:44.336  INFO 7064 --- [     Thread-100] c.d.d.service.RedisLockService           : count:7
2018-06-19 14:42:44.336  INFO 7064 --- [     Thread-100] c.d.d.service.RedisLockService           : Thread-100------>>>>end
2018-06-19 14:42:44.341  INFO 7064 --- [      Thread-21] c.d.d.service.RedisLockService           : Thread-21------>>>>begin
2018-06-19 14:42:45.341  INFO 7064 --- [      Thread-21] c.d.d.service.RedisLockService           : count:6
2018-06-19 14:42:45.341  INFO 7064 --- [      Thread-21] c.d.d.service.RedisLockService           : Thread-21------>>>>end
2018-06-19 14:42:45.346  INFO 7064 --- [      Thread-78] c.d.d.service.RedisLockService           : Thread-78------>>>>begin
2018-06-19 14:42:46.346  INFO 7064 --- [      Thread-78] c.d.d.service.RedisLockService           : count:5
2018-06-19 14:42:46.346  INFO 7064 --- [      Thread-78] c.d.d.service.RedisLockService           : Thread-78------>>>>end
2018-06-19 14:42:46.353  INFO 7064 --- [      Thread-16] c.d.d.service.RedisLockService           : Thread-16------>>>>begin
2018-06-19 14:42:47.353  INFO 7064 --- [      Thread-16] c.d.d.service.RedisLockService           : count:4
2018-06-19 14:42:47.353  INFO 7064 --- [      Thread-16] c.d.d.service.RedisLockService           : Thread-16------>>>>end
2018-06-19 14:42:47.361  INFO 7064 --- [      Thread-47] c.d.d.service.RedisLockService           : Thread-47------>>>>begin
2018-06-19 14:42:48.361  INFO 7064 --- [      Thread-47] c.d.d.service.RedisLockService           : count:3
2018-06-19 14:42:48.361  INFO 7064 --- [      Thread-47] c.d.d.service.RedisLockService           : Thread-47------>>>>end
2018-06-19 14:42:48.448  INFO 7064 --- [      Thread-31] c.d.d.service.RedisLockService           : Thread-31------>>>>begin
2018-06-19 14:42:49.448  INFO 7064 --- [      Thread-31] c.d.d.service.RedisLockService           : count:2
2018-06-19 14:42:49.448  INFO 7064 --- [      Thread-31] c.d.d.service.RedisLockService           : Thread-31------>>>>end
2018-06-19 14:42:49.458  INFO 7064 --- [      Thread-61] c.d.d.service.RedisLockService           : Thread-61------>>>>begin
2018-06-19 14:42:50.459  INFO 7064 --- [      Thread-61] c.d.d.service.RedisLockService           : count:1
2018-06-19 14:42:50.460  INFO 7064 --- [      Thread-61] c.d.d.service.RedisLockService           : Thread-61------>>>>end
2018-06-19 14:42:50.472  INFO 7064 --- [      Thread-94] c.d.d.service.RedisLockService           : Thread-94------>>>>begin
2018-06-19 14:42:51.473  INFO 7064 --- [      Thread-94] c.d.d.service.RedisLockService           : count:0
2018-06-19 14:42:51.473  INFO 7064 --- [      Thread-94] c.d.d.service.RedisLockService           : Thread-94------>>>>end
2018-06-19 14:42:51.501  INFO 7064 --- [      Thread-12] c.d.d.service.RedisLockService           : Thread-12------>>>>begin
2018-06-19 14:42:52.501  INFO 7064 --- [      Thread-12] c.d.d.service.RedisLockService           : Thread-12------>>>>end
2018-06-19 14:42:40.706  INFO 7064 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2018-06-19 14:42:40.706  INFO 7064 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2018-06-19 14:42:40.821  INFO 7064 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 115 ms
2018-06-19 14:42:41.072  INFO 7064 --- [      Thread-93] c.d.d.service.RedisLockService           : Thread-93------>>>>begin
2018-06-19 14:42:42.072  INFO 7064 --- [      Thread-93] c.d.d.service.RedisLockService           : count:9
2018-06-19 14:42:42.072  INFO 7064 --- [      Thread-93] c.d.d.service.RedisLockService           : Thread-93------>>>>end
2018-06-19 14:42:42.079  INFO 7064 --- [      Thread-73] c.d.d.service.RedisLockService           : Thread-73------>>>>begin
2018-06-19 14:42:43.080  INFO 7064 --- [      Thread-73] c.d.d.service.RedisLockService           : count:8
2018-06-19 14:42:43.080  INFO 7064 --- [      Thread-73] c.d.d.service.RedisLockService           : Thread-73------>>>>end
2018-06-19 14:42:43.335  INFO 7064 --- [     Thread-100] c.d.d.service.RedisLockService           : Thread-100------>>>>begin
2018-06-19 14:42:44.336  INFO 7064 --- [     Thread-100] c.d.d.service.RedisLockService           : count:7
2018-06-19 14:42:44.336  INFO 7064 --- [     Thread-100] c.d.d.service.RedisLockService           : Thread-100------>>>>end
2018-06-19 14:42:44.341  INFO 7064 --- [      Thread-21] c.d.d.service.RedisLockService           : Thread-21------>>>>begin
2018-06-19 14:42:45.341  INFO 7064 --- [      Thread-21] c.d.d.service.RedisLockService           : count:6
2018-06-19 14:42:45.341  INFO 7064 --- [      Thread-21] c.d.d.service.RedisLockService           : Thread-21------>>>>end
2018-06-19 14:42:45.346  INFO 7064 --- [      Thread-78] c.d.d.service.RedisLockService           : Thread-78------>>>>begin
2018-06-19 14:42:46.346  INFO 7064 --- [      Thread-78] c.d.d.service.RedisLockService           : count:5
2018-06-19 14:42:46.346  INFO 7064 --- [      Thread-78] c.d.d.service.RedisLockService           : Thread-78------>>>>end
2018-06-19 14:42:46.353  INFO 7064 --- [      Thread-16] c.d.d.service.RedisLockService           : Thread-16------>>>>begin
2018-06-19 14:42:47.353  INFO 7064 --- [      Thread-16] c.d.d.service.RedisLockService           : count:4
2018-06-19 14:42:47.353  INFO 7064 --- [      Thread-16] c.d.d.service.RedisLockService           : Thread-16------>>>>end
2018-06-19 14:42:47.361  INFO 7064 --- [      Thread-47] c.d.d.service.RedisLockService           : Thread-47------>>>>begin
2018-06-19 14:42:48.361  INFO 7064 --- [      Thread-47] c.d.d.service.RedisLockService           : count:3
2018-06-19 14:42:48.361  INFO 7064 --- [      Thread-47] c.d.d.service.RedisLockService           : Thread-47------>>>>end
2018-06-19 14:42:48.448  INFO 7064 --- [      Thread-31] c.d.d.service.RedisLockService           : Thread-31------>>>>begin
2018-06-19 14:42:49.448  INFO 7064 --- [      Thread-31] c.d.d.service.RedisLockService           : count:2
2018-06-19 14:42:49.448  INFO 7064 --- [      Thread-31] c.d.d.service.RedisLockService           : Thread-31------>>>>end
2018-06-19 14:42:49.458  INFO 7064 --- [      Thread-61] c.d.d.service.RedisLockService           : Thread-61------>>>>begin
2018-06-19 14:42:50.459  INFO 7064 --- [      Thread-61] c.d.d.service.RedisLockService           : count:1
2018-06-19 14:42:50.460  INFO 7064 --- [      Thread-61] c.d.d.service.RedisLockService           : Thread-61------>>>>end
2018-06-19 14:42:50.472  INFO 7064 --- [      Thread-94] c.d.d.service.RedisLockService           : Thread-94------>>>>begin
2018-06-19 14:42:51.473  INFO 7064 --- [      Thread-94] c.d.d.service.RedisLockService           : count:0
2018-06-19 14:42:51.473  INFO 7064 --- [      Thread-94] c.d.d.service.RedisLockService           : Thread-94------>>>>end
2018-06-19 14:42:51.501  INFO 7064 --- [      Thread-12] c.d.d.service.RedisLockService           : Thread-12------>>>>begin
2018-06-19 14:42:52.501  INFO 7064 --- [      Thread-12] c.d.d.service.RedisLockService           : Thread-12------>>>>end
7,码云地址:https://gitee.com/dream21th/dream21th-redis
  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dream21st

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值