Redis分布式锁的应用

在微服务架构中,分布式并发问题常常出现。本文作者分享了利用Redis实现分布式锁的经验和感悟,包括引入Redis依赖、配置Redis、设计RedisLockHelper以及在实际场景中的应用。通过这种方式,解决了并发控制的问题,并邀请读者交流讨论。

       针对现在的大多数项目都是基于微服务的开发,在docker上部署多实例时,就会遇到分布式并发问题。解决分布式并发问题的方案有很多,有基于数据库啊、缓存啊、ZooKeeper啊等等,我就不举例其中的优劣了,很多博客上都有很详细的说明。我在公司也做过几种方案的分布式锁,

这里我重点说下,我在利用Redis做分布式锁的一些心得和体会。

你看!!

 

 

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(华丽分割线)~~~~~~~~~~~~~~~~~~~~~~~~~~~

1.引入redis jar包

Gradle项目:

compile ('org.springframework.boot:spring-boot-starter-data-redis')

Maven项目:


<dependency>  
    <groupId>org.springframework.data</groupId>  
    <artifactId>spring-data-redis</artifactId>  
    <version>1.7.2.RELEASE</version>  
</dependency>  
<dependency>  
    <groupId>redis.clients</groupId>  
    <artifactId>jedis</artifactId>  
    <version>2.8.1</version>  
</dependency>  

2.配置Redis

很多人都喜欢在把Redis的配置写在yml文件里面,这里我不否定这种做法。但是,为了能够更充分的利用Redis针对不同场景的客户端。我这里把Redis配置做成配置类并集群不同客户端配置。

@Configuration
public class JedisPoolConfiguration {
    //yml中读取基本配置

    @Value("${spring.redis.host}")
    private String redisHost;

    @Value("${spring.redis.password}")
    private String redisPassword;


    @Bean JedisPool  jedisPool(){

        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(256);//最大连接数
        poolConfig.setMaxWaitMillis(10000);//获取连接10s内拿不到异常

        return new JedisPool(poolConfig,
                redisHost,
                Protocol.DEFAULT_PORT,
                Protocol.DEFAULT_TIMEOUT,
                redisPassword,
                0);


    }

 
    @Bean JedisPool couponsPool(){
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(256);
        poolConfig.setMaxWaitMillis(10000);//获取连接10s内拿不到异常

        return new JedisPool(poolConfig,
                redisHost,
                Protocol.DEFAULT_PORT,
                Protocol.DEFAULT_TIMEOUT,
                redisPassword,
                2);

    }


  ...//可以添加特殊配置需求的Redis客户端

}

3.RedisLockHelper(主要是获取锁和解锁

@Component
public class RedisLockHelper {
    @Autowired
    private JedisPool jedisPool;

    private static final String LOCK_SUCCESS = "OK";        //加锁成功的标识
    private static final Long RELEASE_SUCCESS = 1L;         //解锁成功标识
    private static final String SET_IF_NOT_EXIST = "NX";    //key不存在则set
    private static final String SET_WITH_EXPIRE_TIME = "PX";//超时时间

    private static final Long tryIntervalMillis  = 1000   //轮询间隔毫秒数
    private static final int maxTryCount  =          //最大轮询次数



    /**
     * 尝试获取分布式锁
     * @param Key 锁
     * @param expireTime 超时时间
     * @return
     */
    public String doTryLock( String Key, int expireTime) {

        String requestId = UUID.randomUUID().toString();//请求标识(标识,验证解锁)

        Jedis jedis = null;
        try{
            jedis=jedisPool.getResource();
            String result = jedis.set(Key, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);//详情请看redis官方API,这里不多介绍

            if (LOCK_SUCCESS.equals(result)) {
                logger.info("Lock Success");
                return requestId;
            }
        }catch (Exception e){
            logger.info("Lock Error",e);
        }finally {//使用完后释放资源
            if (Objects.nonNull(jedis)) jedis.close();
        }

        return null;

    }


    /**
     * 轮询尝试获取锁
     * @param lockKey 唯一锁
     * @param expireTime 超期时间
     * @return 成功返回true,超过轮询次数或异常返回false
     */
    public String tryLock(String lockKey, int expireTime){
        int tryCount = 0;

        while (true){

            if (++tryCount >= maxTryCount) {
                // 获取锁超时
                return null;
            }
            try {
                String result = doTryLock(lockKey, expireTime);
                if (StringUtils.isNotEmpty(result)) {
                    return result;
                }

                Thread.sleep(tryIntervalMillis);//尝试加锁失败,休息
            } catch (Exception e) {
                logger.error("tryLock interrupted", e);
                return null;
            }

        }
    }

    /**
     * 释放分布式锁
     * @param lockKey 锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public void releaseLock(String lockKey, String requestId) {
        Jedis jedis = null;

        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; //Lua脚本 详情:http://redisdoc.com/string/set.html#set
        try{
            jedis = jedisPool.getResource();

            Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

            if (RELEASE_SUCCESS.equals(result)) {
                logger.info("Unlock success and  unlock id :{}",requestId);
            }

        }catch (Exception e){
            logger.error("releaseLock error", e);
        }finally {//使用完后释放资源
            if (Objects.nonNull(jedis)) jedis.close();
        }

    }
}    
    

4.Redis分布式锁的场景应用

//创建一个有粒度的key,根据你的具体业务确定
String lockKey = “xxx_xxx”;
String lockValue = redisLockHelper.tryLock(lockKey,"100000"));//第二个参数是过期时间
if (StringUtils.isBlank(lockValue)) {
    logger.error(" redisLock failed. Please reset the response time or the number of lock attempts");
    throw new BadRequestException("系统太忙,本次操作失败");
// 一般来说,不会走到这一步;如果真的有这种情况,并且在合理设置锁尝试次数和等待响应时间之后仍然处理不过来,可能需要考虑优化程序响应时间或者用消息队列排队执行了
}
try {
      //具体要锁的业务:建议粒度越小越好
}catch (Exception e){
      //异常处理
}finally { 
 //解锁
 redisLockHelper.releaseLock(lockKey, lockValue);
}

整个Redis分布式锁的使用流程大致就是这样,具体的一些详细API介绍后续会补充。欢迎大佬提出宝贵意见(欢迎加714635093一起讨论)~~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值