关于Redis分布式锁学习思考

分布式架构

image

windows配置nginx

配置nginx.conf

由于在本地模拟测试,所以我们做相同ip,不同端口的负载均衡。

worker_processes  1;

events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  65;

	upstream sunpy {
		server 127.0.0.1:8090 weight=1;
		server 127.0.0.1:8091 weight=1;
	}
	
    server {
        listen       8888;
        server_name  localhost;

        location / {
			proxy_pass http://sunpy;
        }
    }
}

启动nginx

nginx -c conf/nginx.conf

测试

image

不加分布式锁会出现的情况

public void testRedisLock(String shopCode) {
	int amount = Integer.parseInt(stringRedisTemplate.opsForValue().get(shopCode));

	if (amount <= 0) {
		log.info("+++++++++++++++++++++++++++> 已经没有库存!");
	} else {
		int result = amount - 1;
		stringRedisTemplate.opsForValue().set(shopCode, String.valueOf(amount - 1));
		log.info("**************************> 当前库存为 = " + result);
	}
}

压测结果:

image

问题:没有使用分布式锁,库存问题出现了超卖,出现了脏数据。

利用redis的setnx实现分布式锁

导包:

<!--redis-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!--spring2.x集成redis所需common-pool2-->
<dependency>
	<groupId>org.apache.commons</groupId>
	<artifactId>commons-pool2</artifactId>
</dependency>

<dependency>
	<groupId>com.fasterxml.jackson.core</groupId>
	<artifactId>jackson-databind</artifactId>
	<version>${jackson-databind.version}</version>
</dependency>

程序:

@Slf4j
@Service
public class RedisService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    public void testRedisLock(String shopCode) {
        String redisLock = "SPY-LOCK";

        Boolean absent = stringRedisTemplate.opsForValue().setIfAbsent(redisLock, "程序加锁测试", 10, TimeUnit.SECONDS);

        if (!absent) {
            log.info("===========================> 加锁操作失败!");
            return;
        }

        try {
            int amount = Integer.parseInt(stringRedisTemplate.opsForValue().get(shopCode));

            if (amount <= 0) {
                log.info("+++++++++++++++++++++++++++> 已经没有库存!");
            } else {
                int result = amount - 1;
                stringRedisTemplate.opsForValue().set(shopCode, String.valueOf(result));
                log.info("**************************> 当前库存为 = " + result);
            }
        } finally {
            stringRedisTemplate.delete(redisLock);
        }
    }
}

压测结果:

image

image

Redis分布式锁会出现哪些问题

  • 锁被其他人释放
  • 死锁
  • 锁提前过期
  • 单点Redis出现故障

出现解其他人锁的问题

问题描述:
当前线程删除了其他线程的锁,主要还是在于锁获取的token令牌是相同的。
解决方法:
每次设置锁的时候,保持不一样,只有锁令牌相同允许通过,才可以删除锁。

public void testRedisLock(String shopCode) {
	String redisLock = "SPY-LOCK";
	String token = UUID.randomUUID().toString();
	Boolean absent = stringRedisTemplate.opsForValue().setIfAbsent(redisLock, token, 10, TimeUnit.SECONDS);

	if (!absent) {
		return;
	}

	try {
		int amount = Integer.parseInt(stringRedisTemplate.opsForValue().get(shopCode));

		if (amount <= 0) {
		} else {
			int result = amount - 1;
			stringRedisTemplate.opsForValue().set(shopCode, String.valueOf(result));
			log.info("**************************> 当前库存为 = " + result);
		}
	} finally {
		String value = stringRedisTemplate.opsForValue().get(redisLock);

		if (token.equals(value)) {
			stringRedisTemplate.delete(redisLock);
		}
	}
}

死锁问题

问题描述:
当前线程在锁内,如果没有自己主动释放锁,出现异常等其他情况中断程序,没有运行释放锁的程序,会造成其他线程无法获取锁,而自己一直独占这种死锁情况。
解决方法:
提供一个redis锁过期时间,但是也会有问题,就是锁如果设置时间过短,程序未运行完就释放锁了,主要原因在于程序运行时间无法准确估计。

Boolean absent = stringRedisTemplate.opsForValue().setIfAbsent(redisLock, "程序加锁测试", 10, TimeUnit.SECONDS);

Redisson框架

为什么使用Redisson框架

  • Redisson框架本身提供了锁续期方案,优化了死锁问题;
  • Redisson提供了重入锁机制;
  • Redisson还提供了读写锁机制;
  • Redisson提供了计数器和信号量(类似jdk中CountDownLatch和Semphore);
  • 针对redis单点问题,Redisson提供了对Redis哨兵模式、集群模式支持的分布式锁;

springboot整合Redisson

导包:

<!--使用redisson作为分布式锁-->
<dependency>
	<groupId>org.redisson</groupId>
	<artifactId>redisson</artifactId>
	<version>3.17.6</version>
</dependency>

获取RedissonClient的bean:

@Configuration
public class InitRedisConfig {

    @Value("${spring.redis.host}")
    private String redisHost;
    @Value("${spring.redis.port}")
    private Integer redisPort;
    @Value("${spring.redis.database}")
    private Integer redisDatabase;

    /**
     * 所有对Redisson的使用都是通过RedissonClient对象
     * @return
     */
    @Bean(destroyMethod = "shutdown")
    public RedissonClient redissonClient(){
        // 创建配置 指定redis地址及节点信息
        Config config = new Config();
        String redisAddr = "redis://" + redisHost + ":" + redisPort;
        config.useSingleServer()
                .setAddress(redisAddr)
                .setPassword(null)
                .setDatabase(redisDatabase);

        // 根据config创建出RedissonClient实例
        return Redisson.create(config);
    }
}

分布式锁减库存:

public void testRedission(String shopCode) {
	String redisLock = "SPY-LOCK";
	RLock rLock = redissonClient.getLock(redisLock);

	try {
		rLock.lock(10, TimeUnit.SECONDS);
		int amount = Integer.parseInt(stringRedisTemplate.opsForValue().get(shopCode));

		if (amount <= 0) {
			log.info("**************************> 没有库存了");
		} else {
			int result = amount - 1;
			stringRedisTemplate.opsForValue().set(shopCode, String.valueOf(result));
			log.info("**************************> 当前库存为 = " + result);
		}
	} finally {
		rLock.unlock();
	}
}

结果:
image

image

redisson的非阻塞锁

利用redission中的tryLock方法,实现非阻塞锁。

public void testRedission(String shopCode) {
	String redisLock = "SPY-LOCK";
	RLock rLock = redissonClient.getLock(redisLock);

	try {
		boolean flag = rLock.tryLock(10, TimeUnit.SECONDS);

		if (!flag) {
			log.info("++++++++++++++++++++++++++> 没有获取到锁");
			return;
		}

		int amount = Integer.parseInt(stringRedisTemplate.opsForValue().get(shopCode));

		if (amount <= 0) {
			log.info("**************************> 没有库存了");
		} else {
			int result = amount - 1;
			stringRedisTemplate.opsForValue().set(shopCode, String.valueOf(result));
			log.info("**************************> 当前库存为 = " + result);
		}
	} catch (InterruptedException e) {
		e.printStackTrace();
	} finally {
		rLock.unlock();
	}
}

redisson自动续期

实现:

boolean flag = rLock.tryLock(10, 30, TimeUnit.SECONDS);

10:waitTime,等待锁的时间,在这个指定时间内无法获取锁返回失败。
30:leaseTime,锁持有时间,到达指定时间了,锁未持有了,那么锁会释放。
TimeUnit.SECONDS:时间单位

参考

怎样实现redis分布式锁? - Kaito的回答 - 知乎
https://www.zhihu.com/question/300767410/answer/1931519430

https://blog.csdn.net/cz_00001/article/details/127867215

posted @ 2023-08-19 18:59  sunpeiyu  阅读(25)  评论(0编辑  收藏  举报