java-基于redis实现分布式锁

概述
    目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。”所以,很多系统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。

    在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。

比如一个操作要修改用户的状态,修改状态需要先读出用户的状态,在内存里进行修改,改完了再存回去。如果这样的操作同时进行了,就会出现并发问题,因为读取和保存状态这两个操作不是原子的。(Wiki 解释:所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch 线程切换。)

选用Redis实现分布式锁原因

  • Redis有很高的性能
  • Redis命令对此支持较好,实现起来比较方便

JAVA代码实现:

1.配置redis:

package cn.sunll.lemolang.conf;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import cn.sunll.lemolang.redis.lock.Lock;
import cn.sunll.lemolang.redis.lock.RedisLock;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * 分布式锁配置
 * @Title: LockConfig.java
 * @Package: cn.sunll.lemolang.conf
 * @Description: 
 * @author: sunll
 * @date: 2018年3月26日 下午7:53:03
 * @version: V1.0
 */
@Configuration
public class LockConfig {
    @Value("${spring.redis.lock.maxTotal}")
    private int    maxTotal;
    @Value("${spring.redis.lock.maxIdle}")
    private int    maxIdle;
    @Value("${spring.redis.lock.minIdle}")
    private int    minIdle;

    @Value("${spring.redis.host}")
    private String hostName;
    @Value("${spring.redis.port}")
    private int    port;
    @Value("${spring.redis.pass}")
    private String password;
    @Value("${spring.redis.lock.index}")
    private int    index;

    @Bean
    public Lock redisLock() {
        Lock redisLock = new RedisLock(jedisPool(), index);
        return redisLock;
    }

    @Bean
    public JedisPool jedisPool() {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(maxTotal);
        poolConfig.setMaxIdle(maxIdle);
        poolConfig.setMinIdle(minIdle);
        poolConfig.setTestOnBorrow(false);
        return new JedisPool(poolConfig, hostName, port, 2000, password);
    }
}

2.设计分布式锁接口类:

package cn.sunll.lemolang.redis.lock;

/**
 * @Title: Lock.java
 * @Package: cn.sunll.lemolang.lock
 * @Description: 
 * @author: sunll
 * @date: 2018年3月26日 下午7:44:04
 * @version: V1.0
 */
public interface Lock {
    /**
     * <p>尝试获取锁,无论是否获得都立即返回
     * <p>建议优先使用该方法
     * @param lockKey
     * @param requestId
     * @param expireMillisecond 锁超时自动释放时间
     * @return
     */
    boolean tryLock(String lockKey, String requestId, int expireMillisecond);

    /**
     * <p>尝试获取锁,指定时间内未获得则返回
     * 
     * <p>NOTE:除非必要否则尽量使用不带timeoutMillisecond参数的tryLock,
     * 如果要使用本方法,一定要考虑timeoutMillisecond参数值大小,
     * 因为在timeoutMillisecond超时时间内redis连接是不释放的,
     * 如果该值过大,并发量大的时候可能导致redis连接耗尽
     * @param lockKey
     * @param requestId
     * @param expireMillisecond 锁超时自动释放时间
     * @param timeoutMillisecond 获取锁超时时间
     * @return
     */
    boolean tryLock(String lockKey, String requestId, int expireMillisecond,
                    int timeoutMillisecond);

    /**
     * 解锁
     * 
     * @param lockKey 锁标识
     * @param requestId 
     * @return
     */
    boolean unLock(String lockKey, String requestId);
}

3.实现LOCK类:

package cn.sunll.lemolang.redis.lock;

import java.util.Collections;
import java.util.Random;
import java.util.concurrent.TimeUnit;

import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import cn.sunll.lemolang.log.InvoiceLogger;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

/**
 * @Title: RedisLock.java
 * @Package: cn.sunll.lemolang.lock
 * @Description: 
 * @author: sunll
 * @date: 2018年3月26日 下午7:44:27
 * @version: V1.0
 */
public class RedisLock implements Lock {
    private static final Logger logger                   = Logger
        .getInstance(RedisLock.class);

    private static final String        LOCK_SUCCESS             = "OK";
    private static final String        SET_IF_NOT_EXIST         = "NX";
    private static final String        SET_WITH_EXPIRE_TIME     = "PX";
    private static final Long          RELEASE_SUCCESS          = 1L;
    private static final String        LOCK_KEY_PREFIX          = "RL_";

    /**
     * 锁默认超时时间3秒
     */
    private static final int           DEFAULT_LOCK_EXPIRE_TIME = 1000 * 3;
    /**
     * 默认获取锁超时时间5秒
     */
    private static final int           DEFAULT_TRY_LOCK_TIMEOUT = 1000 * 5;
    /**
     * redis连接池
     */
    private JedisPool jedisPool;
    /**
     * 默认选择redis的1作为分布式锁的库
     */
    private Integer                    dbIndex                  = 1;

    /**
     * @param jedisPool
     */
    public RedisLock(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }

    /**
     * @param dbIndex
     * @param jedisPool
     */
    public RedisLock(JedisPool jedisPool, Integer dbIndex) {
        this.dbIndex = dbIndex;
        this.jedisPool = jedisPool;
    }

    @Override
    public boolean tryLock(String lockKey, String requestId, int expireMillisecond) {
        Jedis jedis = null;
        try {
            jedis = getConnection();
            return tryLock(jedis, getLockKey(lockKey), requestId, expireMillisecond);
        } catch (Exception e) {
            logger.error("try lock fail:{}", e);
        } finally {
            returnConnection(jedis);
        }
        return false;
    }

    @Override
    public boolean tryLock(String lockKey, String requestId, int expireMillisecond,
                           int timeoutMillisecond) {
        if (timeoutMillisecond < 0) {
            timeoutMillisecond = DEFAULT_TRY_LOCK_TIMEOUT;
        }
        lockKey = getLockKey(lockKey);
        boolean result = false;
        Jedis jedis = null;
        try {
            Long timeout = System.currentTimeMillis() + timeoutMillisecond;
            jedis = getConnection();
            result = tryLock(jedis, lockKey, requestId, expireMillisecond);
            if (!result) {
                while (timeout > System.currentTimeMillis()) {
                    result = tryLock(lockKey, requestId, expireMillisecond);
                    logger.info("try lock again result:{}", result);
                    if (result) {
                        break;
                    }
                    try {
                        TimeUnit.MILLISECONDS.sleep(new Random().nextInt(200));
                    } catch (InterruptedException e) {
                        logger.error("try lock error:{}", e);
                        Thread.currentThread().interrupt();
                    }
                }
            }
        } catch (Exception e) {
            logger.error("try lock fail : {}", e);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
        return result;
    }

    /**
     * 上锁
     *
     * @param jedis
     * @param lockKey
     * @param requestId
     * @param expireMillisecond 锁超时自动释放时间
     * @return
     */
    private boolean tryLock(Jedis jedis, String lockKey, String requestId, int expireMillisecond) {
        if (expireMillisecond < 0) {
            expireMillisecond = DEFAULT_LOCK_EXPIRE_TIME;
        }
        logger.info("try lock key:{},requestId:{}", lockKey, requestId);
        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME,
            expireMillisecond);
        logger.info("try lock result:{}", result);
        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

    @Override
    public boolean unLock(String lockKey, String requestId) {
        lockKey = getLockKey(lockKey);
        logger.info("unlock lockKey:{}", lockKey);
        Jedis jedis = null;
        try {
            jedis = getConnection();
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            Object result = jedis.eval(script, Collections.singletonList(lockKey),
                Collections.singletonList(requestId));
            logger.info("unlock lockKey:{},result:{}", lockKey, result);
            if (RELEASE_SUCCESS.equals(result)) {
                return true;
            }
        } catch (Exception e) {
            logger.error("unlock fail:{}", e);
        } finally {
            returnConnection(jedis);
        }
        return false;
    }

    /**
     * 获取连接
     *
     * @return
     */
    private Jedis getConnection() {
        Jedis jedis = jedisPool.getResource();
        if (dbIndex > 0) {
            jedis.select(dbIndex);
        }
        return jedis;
    }

    /**
     * 归还连接
     *
     * @param jedis
     */
    private void returnConnection(Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }

    /**
     * 获取锁key
     * 
     * @param key
     * @return
     */
    private String getLockKey(String key) {
        Assert.notNull(key, "redis lock key must is not null");
        if (key.startsWith(LOCK_KEY_PREFIX)) {
            return key;
        }
        return LOCK_KEY_PREFIX + key;
    }
}

4.使用redis分布式锁:

        boolean isGetLock = false;
        String lockValue = UUID.randomUUID().toString().substring(1, 8);
        try {
            isGetLock = redisLock.tryLock"lockKey", lockValue,3000);
            if (isGetLock) {
               //业务逻辑处理
            }
        }catch (Exception e) {
            logger.error("加锁异常:{}", e);
        } finally {
            if (isGetLock) {
                redisLock.unLock("lockKey", lockValue);
            }
        }

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值