基于redis单节点实现分布式锁

来源:互联网 发布:域名授权系统源码php 编辑:程序博客网 时间:2024/06/05 05:44

分布式锁常见的手段有:基于redis 实现和基于zookeeper实现,小编这里简单的介绍一下采用单节点的redis来实现分布式锁。

基于redis 单节点实现分布式锁
这里需要声明一下,redis单节点实现的锁存在的弊端有,节点的机器不允许宕机,应该能够想的通。如果各位程序员采用的是集群模式下的redis, 那么用此分布式锁,是会发生 多个客户端都会拥有锁,如果你的业务不强制限制这点,可以继续使用。

楼主环境:
三个节点的sentinel集群 , redis一主一从,java 使用的是 jedisSentinelPool 作为redis 的数据库连接池。

基础铺垫
因为公司业务的发展时间较短,并发量以及缓存的数据量不是很大,一核一G的虚拟机单节点redis 的qps 读写大约在1-3万,公司系统是四核四G,一主一从完全够用,考虑使用sentinel 来实现99.99%高可用。因为jedisSentinelPool就是通过哨兵来获取主节点的HostAndPost , 所以该模式下 我们能够保证 java中获取的连接一直是:master的连接,即使master宕机了,slave升级到master,我们的分布式锁还是可继续使用,我们的业务不强制要求,锁只能有一个线程持有。

准备:
lua 脚本 unlock.lua 放到你项目的资源目录下,一般为:src/main/resource . 脚本内容如下:

if redis.call("get",KEYS[1]) == ARGV[1] then    return redis.call("del",KEYS[1])else    return 0end

本地线程 和 redis 固定key :

private static final String LOCK_NODE ="LOCK";private static ThreadLocal<String> local = new ThreadLocal<>();

获取锁的核心代码:

   /**     * agui 获取分布式锁     * 弊端:只能是redis 单节点好用  所以要保证 该节点不能挂掉     * @return     */    public static boolean getLock() {        Jedis jedis = null;        try {            jedis =  jedisSentinelPool.getResource();            String value = UUID.randomUUID().toString();            String ret = jedis.set(LOCK_NODE, value, "NX", "PX", 10000);            if(!StringUtils.isEmpty(ret) && ret.equals("OK")){                local.set(value);                return true;            }        } catch (Exception e) {            logger.error("Cache获取锁失败:" + e);            return false;        } finally {            releaseResource(jedis);        }        return false;    }

锁的释放:
用 spring 提供的org.springframework.util.FileCopyUtils 来读取 unlock.lua 中的脚本代码,用jedis.evals()去执行。

    /**     * agui     *      * 释放锁     *      */    public static void releaseLock() {        String script =null;        Jedis jedis = null ;        try {            script = FileCopyUtils.copyToString(new            FileReader(ResourceUtils.getFile("classpath:unlock.lua")));            jedis =  jedisSentinelPool.getResource();             List<String> keys = new ArrayList<String>();            keys.add(LOCK_NODE);            List<String> args = new ArrayList<String>();            args.add(local.get());            jedis.eval(script, keys, args);        } catch (IOException e) {            logger.error("读取lua 脚本失败 :" + e.getMessage());        } catch (Exception e) {            logger.error("Cache释放锁失败:" + e);        } finally {            releaseResource(jedis);        }        System.out.println(script);    }
    /**     * 释放redis资源     *      * @param jedis     */    private static void releaseResource(Jedis jedis) {        if (jedis != null) {            jedis.close();//          jedisSentinelPool.destroy(); //          jedisSentinelPool.returnResource(jedis);        }    }

jedisSentinelPool 配置 信息 :

因为楼主用的此连接池,所以通过getResource()该方法来获取jedis对象,不一定非得跟楼主一致都用哨兵连接池,能够获取到jedis 对象就好。

<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">        <property name="minEvictableIdleTimeMillis" value="60000" />        <property name="timeBetweenEvictionRunsMillis" value="30000" />        <property name="numTestsPerEvictionRun" value="-1" />        <property name="maxTotal" value="100" />        <property name="maxIdle" value="50" />        <property name="minIdle" value="10" />        <property name="maxWaitMillis" value="1500"/>    </bean>    <bean id="jedisSentinelPool" class="redis.clients.jedis.JedisSentinelPool" destroy-method="destroy">        <constructor-arg name="masterName">            <value>master1</value>        </constructor-arg>        <constructor-arg name="sentinels">            <set value-type="java.lang.String">                <value>${redis.cluster.host1}</value>                <value>${redis.cluster.host2}</value>            </set>        </constructor-arg>        <constructor-arg name="password">            <value>${redis.auth.password}</value>        </constructor-arg>        <constructor-arg name="poolConfig" ref="jedisPoolConfig" />    </bean>    <bean id="redisUtils" class="com.bigpay.common.core.cache.redis.RedisUtils">        <property name="jedisSentinelPool" ref="jedisSentinelPool" />    </bean>

说明
细心的程序员会发现,我们getLock()返回的是 boolean 类型的值,这点说明我们的锁为乐观锁,它并不会去阻塞等待锁的释放,另外因为:采用的是 此方法jedis.set(LOCK_NODE, value, “NX”, “PX”, 10000); 所以不存在当获得锁的线程A出现延迟,A锁因为过期时间到了而被释放,此时又有线程B获取到了锁,A因为走完逻辑代码之后要进行释放锁,此时的锁是线程B的,如果不用 NX 参数去限制,那么会存在A把B的锁给释放掉了, 希望此文章对你们有所帮助。