redis实现分布式锁

2022-10-25 15:46:24 浏览数 (2)

redis 分布式锁的实现

思路: 1)、先判断没有,2)、再给里面放值

1、代码第一阶段;

代码语言:javascript复制
        public void hello(){
       
        获取和设置值必须是原子的
          String lock =  getFromRedis("lock");get("lock")
          if(lock == null){
              setRedisKey("lock","1");
              执行业务
              delRedisKey("lock")
              return ;
          }else{
             hello();自旋
          }
        }

问题:加锁的原子性不能保证 解决:使用redis的setnx 2、代码第二阶段 setnx->set if not exist:原子操作。判断带保存。

代码语言:javascript复制
       public void hello(){
           1、获取到锁
           Integer lock = setnx("lock',"111"); 0代表没有保存数据,说明已经有人占了。1代表占可坑成功
           if(lock!=0){
               执行业务逻辑
               释放锁、删除锁
               del("lock")
           }else{
               等待重试
               hello();
           }
       }

问题:如果由于各种问题(未捕获的异常、断电等)导致锁没释放。其他人永远获取不到锁。 解决:加个过期时间。 3、代码第三阶段

代码语言:javascript复制
          public void hello(){
             超时和加锁必须原子
              Integer lock = setnx("lock',"111");
              if(lock!=null){
                  expire("lock",10s);
                  执行业务逻辑
                  释放锁
                  del("lock')
              }else{
                  hello();
              }
         
          }

问题:刚拿到锁,机器炸了,没来得及设置超时。 解决:加锁和加超时也必须是原子的。

4、代码第四阶段:

代码语言:javascript复制
       public void hello(){
           String result = setnxex("lock","111",10s);
           if(result=="ok"){
               加锁成功
               执行业务逻辑
               del("lock")
           }else{
               hello();
           }
       }

问题:如果业务逻辑超时,导致锁自动删除,业务执行完又删除一遍锁。至少多个人都获取到了锁。 解决:每个线程加锁设置不同的值 删锁时对比 保证删的是自己加的锁 5、代码第五阶段。

代码语言:javascript复制
       public void hello(){
          String token = UUID;
          String result = setnxex("lock",token,10s);
          if(result == "ok"){
              执行业务
      
              删锁,保证删除自己的锁
              if(get("lock")==token){
                  del("lock")
              }
          }else{
              hello();
          }
       }

问题:我们获取锁的时候,锁的值正在给我们返回。锁过期。redis删除了锁。但是我们拿到了值,而且对比成功(此时此刻正好有人又获取)。我们还删除了锁。至少两个线程又进入同一个代码。 原因:删锁不是原子 解决: lua脚本进行删除。

代码语言:javascript复制
        String script =
            "if redis.call('get', KEYS[1]) == ARGV[1] then
                    return redis.call('del', KEYS[1])
             else
                    return 0
             end";
      
       jedis.eval(script, Collections.singletonList(key), Collections.singletonList(token));

1、分布式锁的核心(保证原子性)

  1. 加锁。占坑一定要是原子的。(判断如果没有,就给redis中保存值)
  2. 锁要自动超时。
  3. 解锁也要原子。

最终的分布式锁的代码:

代码语言:javascript复制
        @Lock
        public void hello(){
            String token = uuid;
            String lock = redis.setnxex("lock",token,10s);
            if(lock=="ok"){
                执行业务逻辑
                脚本删除锁
            }else{
                hello();自旋。
            }
        }

RedisTemplate和Jedis客户端2选一

代码语言:javascript复制
	@Autowired
    JedisPool jedisPool;
	public void incrDistribute(){
       

        Jedis jedis = jedisPool.getResource();
        try {
            String token = UUID.randomUUID().toString();
            // 三秒过期
            String lock = jedis.set("lock", token, SetParams.setParams().ex(3).nx());
            if (lock!=null&&lock.equalsIgnoreCase("OK")) {
                String num = jedis.get("num");
                Integer i = Integer.parseInt(num);
                i=i 1;
                jedis.set("num", i.toString());

                //删除锁
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                jedis.eval(script, Collections.singletonList("lock"),Collections.singletonList(token));
                logger.info("删除锁成功:{}" Thread.currentThread().getId());
            }else {
                try {
                    Thread.sleep(1000);
                    incrDistribute();
                } catch (InterruptedException e) {
                    logger.error("线程睡眠异常:{}",e);
                }
            }
        } finally {
            jedis.close();
        }



    }
代码语言:javascript复制
	@Autowired
    StringRedisTemplate redisTemplate;
    public void incrDistribute(){
		//1、加锁
        String token = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", token, 3, TimeUnit.SECONDS);
        if (lock) {
            ValueOperations<String, String> stringStringValueOperations = redisTemplate.opsForValue();
            String num = stringStringValueOperations.get("num");
            if (num !=null) {
                Integer i = Integer.parseInt(num);
                i=i 1;
                stringStringValueOperations.set("num", i.toString());
            }

            //删除锁
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            DefaultRedisScript<Long> script1 = new DefaultRedisScript<Long>(script,Long.class);
            redisTemplate.execute(script1, Arrays.asList("lock"),token);
            logger.info("删除锁成功:{}" Thread.currentThread().getId());
        }else {
            incrDistribute();
        }
	}

问题:如果业务执行超时 锁被自动删除也是个问题 其他线程获取到锁进来了也是个问题 解决: 使用守护线程 守护线程做加时操作 保证过期时间够用 本线程业务执行完成后 守护线程加时操作自然结束 redisson的看门狗监听也可以

redis实现分布式锁有各种问题 建议使用Redisson

锁的更多考虑 1)、自旋。 自旋次数。 自旋超时。 2)、锁设置 锁粒度;细;记录级别; 1)、各自服务各自锁 2)、分析好粒度,不要锁住无关数据。一种数据一种锁,一条数据一个锁。 3)、锁类型


使用jedis springboot整合redis

application.properties

代码语言:javascript复制
spring.redis.host=192.168.217.130
spring.redis.jedis.pool.max-active=20
spring.redis.jedis.pool.max-idle=5

pom文件进入jar包

代码语言:javascript复制
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.0.1</version>
        </dependency>

添加配置类:

代码语言:javascript复制
package com.xiepanpan.locks.lockstest.config;


import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * @author: xiepanpan
 * @Date: 2020/2/20
 * @Description: jedis 配置类
 */
@Configuration
public class JedisConfig {

    @Bean
    public JedisPool jedisPoolConfig(RedisProperties properties) {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        RedisProperties.Pool pool = properties.getJedis().getPool();

        //设置配置
        jedisPoolConfig.setMaxIdle(pool.getMaxIdle());
        jedisPoolConfig.setMaxTotal(pool.getMaxActive());

        JedisPool jedisPool = new JedisPool(jedisPoolConfig,properties.getHost(),properties.getPort());
        return jedisPool;
    }
}

0 人点赞