前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >redis实现分布式锁

redis实现分布式锁

作者头像
周杰伦本人
发布2022-10-25 15:46:24
3930
发布2022-10-25 15:46:24
举报
文章被收录于专栏:同步文章

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;
    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-02-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档