Spring Boot使用Jedis实现分布式锁

港控/mmm° 2022-11-22 00:08 313阅读 0赞

Spring Boot使用Jedis实现分布式锁

在单机应用中通过使用synchronized关键字、JUCLock来实现线程安全是没问题的,但在分布式环境中就有可能出现问题,因为这些同步机制、锁是不能跨机器的,所以这里介绍的分布式锁就很有必要。

首先创建两个Spring Boot项目。

pom.xml(两个项目都一样):

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  3. <modelVersion>4.0.0</modelVersion>
  4. <parent>
  5. <groupId>org.springframework.boot</groupId>
  6. <artifactId>spring-boot-starter-parent</artifactId>
  7. <version>2.3.5.RELEASE</version>
  8. <relativePath/> <!-- lookup parent from repository -->
  9. </parent>
  10. <groupId>com.kaven</groupId>
  11. <artifactId>distributelock</artifactId>
  12. <version>0.0.1-SNAPSHOT</version>
  13. <name>distributelock</name>
  14. <description>Demo project for Spring Boot</description>
  15. <properties>
  16. <java.version>1.8</java.version>
  17. </properties>
  18. <dependencies>
  19. <dependency>
  20. <groupId>org.springframework.boot</groupId>
  21. <artifactId>spring-boot-starter</artifactId>
  22. </dependency>
  23. <dependency>
  24. <groupId>org.springframework.boot</groupId>
  25. <artifactId>spring-boot-starter-test</artifactId>
  26. <scope>test</scope>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.springframework.boot</groupId>
  30. <artifactId>spring-boot-starter-web</artifactId>
  31. </dependency>
  32. <dependency>
  33. <groupId>redis.clients</groupId>
  34. <artifactId>jedis</artifactId>
  35. <version>3.3.0</version>
  36. <type>jar</type>
  37. <scope>compile</scope>
  38. </dependency>
  39. </dependencies>
  40. <build>
  41. <plugins>
  42. <plugin>
  43. <groupId>org.springframework.boot</groupId>
  44. <artifactId>spring-boot-maven-plugin</artifactId>
  45. </plugin>
  46. </plugins>
  47. </build>
  48. </project>

Jedis组件(两个项目都一样):

  1. package com.kaven.distributelock.component;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.stereotype.Component;
  4. import redis.clients.jedis.Jedis;
  5. @Component
  6. public class JedisComponent {
  7. @Bean
  8. public Jedis jedis(){
  9. return new Jedis("localhost",6379);
  10. }
  11. }

项目一接口:

  1. package com.kaven.distributelock.controller;
  2. import com.kaven.distributelock.lock.JedisLock;
  3. import org.springframework.web.bind.annotation.PutMapping;
  4. import org.springframework.web.bind.annotation.RestController;
  5. import redis.clients.jedis.Jedis;
  6. import javax.annotation.Resource;
  7. import java.util.UUID;
  8. import java.util.concurrent.atomic.AtomicInteger;
  9. @RestController
  10. public class NumberController {
  11. private AtomicInteger count = new AtomicInteger(0);
  12. @Resource
  13. private Jedis jedis;
  14. @PutMapping("/number")
  15. public synchronized void subNumber() throws InterruptedException {
  16. Integer number = Integer.parseInt(jedis.get("number"));
  17. Thread.sleep(400);
  18. number-=1;
  19. jedis.set("number" , number.toString());
  20. count.addAndGet(1);
  21. System.out.println(count.get());
  22. }
  23. }

项目二接口:

  1. package com.kaven.distributelock.controller;
  2. import org.springframework.web.bind.annotation.PutMapping;
  3. import org.springframework.web.bind.annotation.RestController;
  4. import redis.clients.jedis.Jedis;
  5. import javax.annotation.Resource;
  6. import java.util.concurrent.atomic.AtomicInteger;
  7. import java.util.concurrent.locks.Lock;
  8. @RestController
  9. public class NumberController {
  10. private AtomicInteger count = new AtomicInteger(0);
  11. @Resource
  12. private Jedis jedis;
  13. @PutMapping("/number")
  14. public synchronized void subNumber() throws InterruptedException {
  15. Integer number = Integer.parseInt(jedis.get("number"));
  16. Thread.sleep(1000);
  17. number-=1;
  18. jedis.set("number" , number.toString());
  19. count.addAndGet(1);
  20. System.out.println(count.get());
  21. }
  22. }

启动类(两个项目都一样):

  1. package com.kaven.distributelock;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class DistributeLockApplication {
  6. public static void main(String[] args) {
  7. SpringApplication.run(DistributeLockApplication.class, args);
  8. }
  9. }

项目一运行在8080端口,项目二运行在8081端口。

测试:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
从上面几张图可以得知,项目一接口会将number100次,而项目二接口会将number50次,并且都执行成功了,理想情况下redis里面的number应该为0,但实际情况却为86,如下图所示,所以synchronized关键字在分布式环境下是不能保障数据同步的(只能单机应用场景的锁,是不能保障分布式环境下数据同步)。

在这里插入图片描述
接下来使用Jedis实现分布式锁(两个项目都一样):

  1. package com.kaven.distributelock.lock;
  2. import redis.clients.jedis.Jedis;
  3. import redis.clients.jedis.params.SetParams;
  4. import java.util.Collections;
  5. public class JedisLock {
  6. private static final String LOCK_SUCCESS = "OK";
  7. private static final Long RELEASE_SUCCESS = 1L;
  8. /** * 尝试获取分布式锁 * @param jedis Redis客户端 * @param lockKey 锁 * @param requestId 请求标识 * @param millisecondsToExpire 超期时间 * @return 是否获取成功 */
  9. public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, long millisecondsToExpire) {
  10. String result = jedis.set(lockKey,requestId, SetParams.setParams().nx().px(millisecondsToExpire));
  11. if (LOCK_SUCCESS.equals(result)) {
  12. return true;
  13. }
  14. return false;
  15. }
  16. /** * 释放分布式锁 * @param jedis Redis客户端 * @param lockKey 锁 * @param requestId 请求标识 * @return 是否释放成功 */
  17. public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
  18. String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
  19. Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
  20. if (RELEASE_SUCCESS.equals(result)) {
  21. return true;
  22. }
  23. return false;
  24. }
  25. }

项目一接口:

  1. package com.kaven.distributelock.controller;
  2. import com.kaven.distributelock.lock.JedisLock;
  3. import org.springframework.web.bind.annotation.PutMapping;
  4. import org.springframework.web.bind.annotation.RestController;
  5. import redis.clients.jedis.Jedis;
  6. import javax.annotation.Resource;
  7. import java.util.UUID;
  8. import java.util.concurrent.atomic.AtomicInteger;
  9. @RestController
  10. public class NumberController {
  11. private AtomicInteger count = new AtomicInteger(0);
  12. @Resource
  13. private Jedis jedis;
  14. @PutMapping("/number")
  15. public void subNumber() throws InterruptedException {
  16. String requestId = UUID.randomUUID().toString();
  17. String LOCKKEY = "LOCKKEY";
  18. if(JedisLock.tryGetDistributedLock(jedis , LOCKKEY , requestId , 3000)){
  19. Integer number = Integer.parseInt(jedis.get("number"));
  20. Thread.sleep(400);
  21. number-=1;
  22. jedis.set("number" , number.toString());
  23. count.addAndGet(1);
  24. System.out.println(count.get());
  25. JedisLock.releaseDistributedLock(jedis , LOCKKEY , requestId);
  26. }
  27. }
  28. }

项目二接口:

  1. package com.kaven.distributelock.controller;
  2. import com.kaven.distributelock.lock.JedisLock;
  3. import org.springframework.web.bind.annotation.PutMapping;
  4. import org.springframework.web.bind.annotation.RestController;
  5. import redis.clients.jedis.Jedis;
  6. import javax.annotation.Resource;
  7. import java.util.UUID;
  8. import java.util.concurrent.atomic.AtomicInteger;
  9. @RestController
  10. public class NumberController {
  11. private AtomicInteger count = new AtomicInteger(0);
  12. @Resource
  13. private Jedis jedis;
  14. @PutMapping("/number")
  15. public void subNumber() throws InterruptedException {
  16. String requestId = UUID.randomUUID().toString();
  17. String LOCKKEY = "LOCKKEY";
  18. if(JedisLock.tryGetDistributedLock(jedis , LOCKKEY , requestId , 3000)){
  19. Integer number = Integer.parseInt(jedis.get("number"));
  20. Thread.sleep(1000);
  21. number-=1;
  22. jedis.set("number" , number.toString());
  23. count.addAndGet(1);
  24. System.out.println(count.get());
  25. JedisLock.releaseDistributedLock(jedis , LOCKKEY , requestId);
  26. }
  27. }
  28. }

redis里面的number变量设置成3000(字符串),因为没有获取到锁的话,代码就直接跑完了,所以这里让测试次数多一些,项目一接口请求2000次,项目二接口请求1000次(因为获取到锁只是少部分)。
在这里插入图片描述
在这里插入图片描述
同时点击测试。
在这里插入图片描述
在这里插入图片描述

结果如下:

项目一接口获取到锁34次。
在这里插入图片描述
项目二接口获取到锁218次。
在这里插入图片描述

在这里插入图片描述

结果很显然是正确的(34+218=3000-2748)。

Spring Boot使用Jedis实现分布式锁就介绍到这里。

写博客是博主记录自己的学习过程,如果有错误,请指正,谢谢!

发表评论

表情:
评论列表 (有 0 条评论,313人围观)

还没有评论,来说两句吧...

相关阅读

    相关 spring boot redis分布式

    随着现在分布式架构越来越盛行,在很多场景下需要使用到分布式锁。分布式锁的实现有很多种,比如基于[数据库][Link 1]、zookeeper等,本文主要介绍使用Redis做分布