2 |
3 |
4 |
5 | 并发测试
6 |
7 |
8 |
9 |
29 |
30 | 并发测试
31 |
32 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 | 4.0.0
6 |
7 | DistributeLock
8 | DistributeLock
9 | pom
10 | 1.0-SNAPSHOT
11 |
12 | aspectDemo
13 | redisDistributeLock
14 |
15 |
16 | DistributeLock
17 |
18 |
19 |
20 |
21 |
22 | org.springframework.boot
23 | spring-boot-dependencies
24 | 2.0.1.RELEASE
25 | pom
26 | import
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | org.apache.maven.plugins
35 | maven-compiler-plugin
36 |
37 | 1.8
38 | 1.8
39 |
40 |
41 |
42 |
43 | org.springframework.boot
44 | spring-boot-maven-plugin
45 | 2.0.1.RELEASE
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/redisDistributeLock/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | DistributeLock
7 | DistributeLock
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | redisDistributeLock
13 |
14 |
15 |
16 | org.springframework.boot
17 | spring-boot-starter-data-redis
18 |
19 |
20 |
21 |
22 | org.springframework.boot
23 | spring-boot-starter-web
24 |
25 |
26 | redis.clients
27 | jedis
28 |
29 |
30 |
31 |
32 |
33 |
34 | org.apache.maven.plugins
35 | maven-compiler-plugin
36 |
37 | 1.8
38 | 1.8
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/redisDistributeLock/src/main/java/com/xiongyx/ApplicationMain.java:
--------------------------------------------------------------------------------
1 | package com.xiongyx;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | /**
7 | * @author xiongyx
8 | * on 2019/5/18.
9 | */
10 | @SpringBootApplication
11 | public class ApplicationMain {
12 |
13 | public static void main(String[] args) {
14 | SpringApplication.run(ApplicationMain.class,args);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/redisDistributeLock/src/main/java/com/xiongyx/TestServiceA.java:
--------------------------------------------------------------------------------
1 | package com.xiongyx;
2 |
3 | import com.xiongyx.lock.impl.RedisDistributeLock;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.stereotype.Service;
6 | import org.springframework.transaction.annotation.Transactional;
7 |
8 | import javax.annotation.PostConstruct;
9 | import javax.annotation.Resource;
10 | import java.util.concurrent.Executors;
11 | import java.util.concurrent.TimeUnit;
12 | import java.util.concurrent.atomic.AtomicInteger;
13 |
14 | /**
15 | *
16 | * Description:
17 | *
18 | *
19 | * @author zhangjw
20 | * @version 1.0
21 | */
22 | @Service
23 | public class TestServiceA {
24 | private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(TestServiceA.class);
25 |
26 | @Resource
27 | private RedisDistributeLock redisDistributeLock;
28 |
29 |
30 | private AtomicInteger count = new AtomicInteger();
31 |
32 |
33 | @Transactional
34 | public void exeTask(Integer i) {
35 | try {
36 | String s = redisDistributeLock.lockAndRetry(i.toString(), 2000, 1);
37 | TimeUnit.SECONDS.sleep(8);
38 | count.incrementAndGet();
39 | redisDistributeLock.unLock(i.toString(),s);
40 | } catch (InterruptedException e) {
41 | e.printStackTrace();
42 | }
43 | }
44 |
45 | @PostConstruct
46 | public void monitor() {
47 | // Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate((Runnable) () -> {
48 | //
49 | // int qps = count.getAndSet(0);
50 | // LOGGER.info("qps = {}", qps);
51 | //
52 | // }, 0, 1, TimeUnit.SECONDS);
53 |
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/redisDistributeLock/src/main/java/com/xiongyx/config/JedisConfig.java:
--------------------------------------------------------------------------------
1 | package com.xiongyx.config;
2 |
3 | import org.springframework.beans.factory.annotation.Value;
4 | import org.springframework.boot.context.properties.ConfigurationProperties;
5 | import org.springframework.stereotype.Component;
6 |
7 | /**
8 | * @author xiongyx
9 | * on 2019/5/18.
10 | */
11 | @Component
12 | @ConfigurationProperties
13 | public class JedisConfig {
14 | @Value("${spring.redis.host}")
15 | public String host;
16 | @Value("${spring.redis.port}")
17 | public int port;
18 | @Value("${spring.redis.database}")
19 | public int database;
20 | @Value("${spring.redis.jedis.pool.max-idle}")
21 | public int maxIdle;
22 | @Value("${spring.redis.jedis.pool.min-idle}")
23 | public int minIdle;
24 | @Value("${spring.redis.jedis.pool.max-active}")
25 | public int maxActive;
26 | @Value("${spring.redis.jedis.pool.max-wait}")
27 | public String maxWait;
28 | @Value("${spring.redis.timeout}")
29 | public String timeout;
30 |
31 | public String getHost() {
32 | return host;
33 | }
34 |
35 | public void setHost(String host) {
36 | this.host = host;
37 | }
38 |
39 | public int getPort() {
40 | return port;
41 | }
42 |
43 | public void setPort(int port) {
44 | this.port = port;
45 | }
46 |
47 | public int getDatabase() {
48 | return database;
49 | }
50 |
51 | public void setDatabase(int database) {
52 | this.database = database;
53 | }
54 |
55 | public int getMaxIdle() {
56 | return maxIdle;
57 | }
58 |
59 | public void setMaxIdle(int maxIdle) {
60 | this.maxIdle = maxIdle;
61 | }
62 |
63 | public int getMinIdle() {
64 | return minIdle;
65 | }
66 |
67 | public void setMinIdle(int minIdle) {
68 | this.minIdle = minIdle;
69 | }
70 |
71 | public int getMaxActive() {
72 | return maxActive;
73 | }
74 |
75 | public void setMaxActive(int maxActive) {
76 | this.maxActive = maxActive;
77 | }
78 |
79 | public String getMaxWait() {
80 | return maxWait;
81 | }
82 |
83 | public void setMaxWait(String maxWait) {
84 | this.maxWait = maxWait;
85 | }
86 |
87 | public String getTimeout() {
88 | return timeout;
89 | }
90 |
91 | public void setTimeout(String timeout) {
92 | this.timeout = timeout;
93 | }
94 | }
--------------------------------------------------------------------------------
/redisDistributeLock/src/main/java/com/xiongyx/config/RedisConfig.java:
--------------------------------------------------------------------------------
1 | package com.xiongyx.config;
2 |
3 | import com.fasterxml.jackson.annotation.JsonAutoDetect;
4 | import com.fasterxml.jackson.annotation.PropertyAccessor;
5 | import com.fasterxml.jackson.databind.ObjectMapper;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.cache.annotation.CachingConfigurerSupport;
8 | import org.springframework.context.annotation.Bean;
9 | import org.springframework.context.annotation.Configuration;
10 | import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
11 | import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
12 | import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
13 | import org.springframework.data.redis.core.RedisTemplate;
14 | import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
15 | import org.springframework.data.redis.serializer.StringRedisSerializer;
16 | import redis.clients.jedis.JedisPoolConfig;
17 |
18 | import java.time.Duration;
19 |
20 | /**
21 | * @author xiongyx
22 | * redis 相关配置
23 | */
24 | @Configuration
25 | public class RedisConfig extends CachingConfigurerSupport {
26 |
27 | @Autowired
28 | private JedisConfig jedisConfig;
29 |
30 | @Bean
31 | public JedisConnectionFactory jedisConnectionFactory (){
32 | JedisPoolConfig poolConfig = new JedisPoolConfig();
33 | poolConfig.setMaxTotal(jedisConfig.getMaxActive());
34 | poolConfig.setMaxIdle(jedisConfig.getMaxIdle());
35 | int maxWait = Integer.parseInt(jedisConfig.getMaxWait().substring(0,jedisConfig.maxWait.length()-2));
36 | poolConfig.setMaxWaitMillis(maxWait);
37 | poolConfig.setMinIdle(jedisConfig.getMaxIdle());
38 | poolConfig.setTestOnBorrow(true);
39 | poolConfig.setTestOnReturn(false);
40 | poolConfig.setTestWhileIdle(true);
41 |
42 | int readTimeout = Integer.parseInt(jedisConfig.getTimeout().substring(0,jedisConfig.maxWait.length()-2));
43 | // 使用jedis连接池
44 | JedisClientConfiguration jedisClientConfiguration = JedisClientConfiguration.builder()
45 | .usePooling().poolConfig(poolConfig)
46 | .and().readTimeout(Duration.ofMillis(readTimeout)).build();
47 |
48 | RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
49 | redisStandaloneConfiguration.setDatabase(jedisConfig.getDatabase());
50 | redisStandaloneConfiguration.setPort(jedisConfig.getPort());
51 | redisStandaloneConfiguration.setHostName(jedisConfig.getHost());
52 |
53 | return new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration);
54 | }
55 |
56 | /**
57 | * retemplate相关配置
58 | */
59 | @Bean
60 | public RedisTemplate redisTemplate(JedisConnectionFactory factory) {
61 | RedisTemplate template = new RedisTemplate<>();
62 |
63 | // 配置连接工厂
64 | template.setConnectionFactory(factory);
65 |
66 | //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
67 | Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer<>(Object.class);
68 |
69 | ObjectMapper om = new ObjectMapper();
70 | // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
71 | om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
72 | // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
73 | om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
74 | jacksonSeial.setObjectMapper(om);
75 |
76 | // 值采用json序列化
77 | template.setValueSerializer(jacksonSeial);
78 | //使用StringRedisSerializer来序列化和反序列化redis的key值
79 | template.setKeySerializer(new StringRedisSerializer());
80 |
81 | // 设置hash key 和value序列化模式
82 | template.setHashKeySerializer(new StringRedisSerializer());
83 | template.setHashValueSerializer(jacksonSeial);
84 | template.afterPropertiesSet();
85 |
86 | return template;
87 | }
88 | }
--------------------------------------------------------------------------------
/redisDistributeLock/src/main/java/com/xiongyx/constants/RedisConstants.java:
--------------------------------------------------------------------------------
1 | package com.xiongyx.constants;
2 |
3 | /**
4 | * @Desciption:
5 | * @author: Chai jin qiu
6 | * @date: 2019/5/22
7 | */
8 | public class RedisConstants {
9 |
10 | /**
11 | * key的分隔符
12 | */
13 | public static final String KEY_SEPARATOR = ":";
14 | /**
15 | * key的最大字节数
16 | */
17 | public static final int FINALLY_KEY_LIMIT = 256;
18 | }
19 |
--------------------------------------------------------------------------------
/redisDistributeLock/src/main/java/com/xiongyx/controller/TestController.java:
--------------------------------------------------------------------------------
1 | package com.xiongyx.controller;
2 |
3 | import com.xiongyx.TestServiceA;
4 | import com.xiongyx.lock.impl.RedisDistributeLock;
5 | import com.xiongyx.redis.RedisClient;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.web.bind.annotation.RequestMapping;
8 | import org.springframework.web.bind.annotation.RequestParam;
9 | import org.springframework.web.bind.annotation.RestController;
10 |
11 | import javax.annotation.Resource;
12 | import java.util.ArrayList;
13 | import java.util.List;
14 | import java.util.concurrent.ExecutionException;
15 | import java.util.concurrent.ExecutorService;
16 | import java.util.concurrent.Executors;
17 | import java.util.concurrent.Future;
18 |
19 | /**
20 | * @author xiongyx
21 | * on 2019/5/18.
22 | */
23 |
24 | @RestController
25 | public class TestController {
26 |
27 | private static final String TEST_REDIS_LOCK_KEY = "lock_key";
28 |
29 | private static final int EXPIRE_TIME = 100;
30 |
31 | @Resource
32 | private TestServiceA testServiceA;
33 | @Autowired
34 | private RedisDistributeLock redisDistributeLock;
35 |
36 | @Autowired
37 | private RedisClient redisClient;
38 |
39 |
40 | @RequestMapping("/testRedis")
41 | public String testRedis(@RequestParam("id") String id) {
42 | String oldValue = (String)redisClient.get("user_id");
43 |
44 | redisClient.set("user_id",id);
45 |
46 | String newValue = (String)redisClient.get("user_id");
47 | return newValue;
48 | }
49 |
50 | @RequestMapping("/test")
51 | public String test() throws ExecutionException, InterruptedException {
52 | int threadNum = 5;
53 |
54 | ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
55 |
56 | List futureList = new ArrayList<>();
57 | for(int i=0; i{
60 | System.out.println("线程尝试获得锁 i=" + currentThreadNum);
61 | String requestID = redisDistributeLock.lockAndRetry(TEST_REDIS_LOCK_KEY,EXPIRE_TIME);
62 | System.out.println("获得锁,开始执行任务 requestID=" + requestID + "i=" + currentThreadNum);
63 |
64 | // if(currentThreadNum == 1){
65 | // System.out.println("模拟 宕机事件 不释放锁,直接返回 currentThreadNum=" + currentThreadNum);
66 | // return;
67 | // }
68 |
69 | try {
70 | // 休眠完毕
71 | Thread.sleep(3000);
72 | } catch (InterruptedException e) {
73 | e.printStackTrace();
74 | }
75 | System.out.println("任务执行完毕" + "i=" + currentThreadNum);
76 | redisDistributeLock.unLock(TEST_REDIS_LOCK_KEY,requestID);
77 | System.out.println("释放锁完毕");
78 |
79 | redisDistributeLock.lockAndRetry(TEST_REDIS_LOCK_KEY,requestID,EXPIRE_TIME);
80 | System.out.println("重入获得锁,开始执行任务 requestID=" + requestID + "i=" + currentThreadNum);
81 | redisDistributeLock.unLock(TEST_REDIS_LOCK_KEY,requestID);
82 | System.out.println("释放重入锁完毕");
83 | });
84 |
85 | futureList.add(future);
86 | }
87 |
88 | for(Future future : futureList){
89 | future.get();
90 | }
91 |
92 | return "ok";
93 | }
94 |
95 |
96 | @RequestMapping("/testLock")
97 | public String testLock(Integer threadNum) throws ExecutionException, InterruptedException {
98 | ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
99 |
100 | List futureList = new ArrayList<>();
101 | for (int i = 0; i < threadNum; i++) {
102 | int currentThreadNum = i;
103 | executorService.execute(() -> {
104 | testServiceA.exeTask(currentThreadNum);
105 | });
106 | }
107 | return "ok";
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/redisDistributeLock/src/main/java/com/xiongyx/exception/RedisLockFailException.java:
--------------------------------------------------------------------------------
1 | package com.xiongyx.exception;
2 |
3 | /**
4 | * @author xiongyx
5 | * @date 2019/5/27
6 | *
7 | * redis分布式锁 加锁失败异常
8 | */
9 | public class RedisLockFailException extends RuntimeException {
10 |
11 | public RedisLockFailException(String message) {
12 | super(message);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/redisDistributeLock/src/main/java/com/xiongyx/lock/api/DistributeLock.java:
--------------------------------------------------------------------------------
1 | package com.xiongyx.lock.api;
2 |
3 | /**
4 | * 分布式锁 api接口
5 | */
6 | public interface DistributeLock {
7 |
8 | /**
9 | * 尝试加锁
10 | * @param lockKey 锁的key
11 | * @return 加锁成功 返回uuid
12 | * 加锁失败 返回null
13 | * */
14 | String lock(String lockKey);
15 |
16 | /**
17 | * 尝试加锁 (requestID相等 可重入)
18 | * @param lockKey 锁的key
19 | * @param expireTime 过期时间 单位:秒
20 | * @return 加锁成功 返回uuid
21 | * 加锁失败 返回null
22 | * */
23 | String lock(String lockKey, int expireTime);
24 |
25 | /**
26 | * 尝试加锁 (requestID相等 可重入)
27 | * @param lockKey 锁的key
28 | * @param requestID 用户ID
29 | * @return 加锁成功 返回uuid
30 | * 加锁失败 返回null
31 | * */
32 | String lock(String lockKey, String requestID);
33 |
34 | /**
35 | * 尝试加锁 (requestID相等 可重入)
36 | * @param lockKey 锁的key
37 | * @param requestID 用户ID
38 | * @param expireTime 过期时间 单位:秒
39 | * @return 加锁成功 返回uuid
40 | * 加锁失败 返回null
41 | * */
42 | String lock(String lockKey, String requestID, int expireTime);
43 |
44 | /**
45 | * 尝试加锁,失败自动重试 会阻塞当前线程
46 | * @param lockKey 锁的key
47 | * @return 加锁成功 返回uuid
48 | * 加锁失败 返回null
49 | * */
50 | String lockAndRetry(String lockKey);
51 |
52 | /**
53 | * 尝试加锁,失败自动重试 会阻塞当前线程 (requestID相等 可重入)
54 | * @param lockKey 锁的key
55 | * @param requestID 用户ID
56 | * @return 加锁成功 返回uuid
57 | * 加锁失败 返回null
58 | * */
59 | String lockAndRetry(String lockKey, String requestID);
60 |
61 | /**
62 | * 尝试加锁 (requestID相等 可重入)
63 | * @param lockKey 锁的key
64 | * @param expireTime 过期时间 单位:秒
65 | * @return 加锁成功 返回uuid
66 | * 加锁失败 返回null
67 | * */
68 | String lockAndRetry(String lockKey, int expireTime);
69 |
70 | /**
71 | * 尝试加锁 (requestID相等 可重入)
72 | * @param lockKey 锁的key
73 | * @param expireTime 过期时间 单位:秒
74 | * @param retryCount 重试次数
75 | * @return 加锁成功 返回uuid
76 | * 加锁失败 返回null
77 | * */
78 | String lockAndRetry(String lockKey, int expireTime, int retryCount);
79 |
80 | /**
81 | * 尝试加锁 (requestID相等 可重入)
82 | * @param lockKey 锁的key
83 | * @param requestID 用户ID
84 | * @param expireTime 过期时间 单位:秒
85 | * @return 加锁成功 返回uuid
86 | * 加锁失败 返回null
87 | * */
88 | String lockAndRetry(String lockKey, String requestID, int expireTime);
89 |
90 | /**
91 | * 尝试加锁 (requestID相等 可重入)
92 | * @param lockKey 锁的key
93 | * @param expireTime 过期时间 单位:秒
94 | * @param requestID 用户ID
95 | * @param retryCount 重试次数
96 | * @return 加锁成功 返回uuid
97 | * 加锁失败 返回null
98 | * */
99 | String lockAndRetry(String lockKey, String requestID, int expireTime, int retryCount);
100 |
101 | /**
102 | * 释放锁
103 | * @param lockKey 锁的key
104 | * @param requestID 用户ID
105 | * @return true 释放自己所持有的锁 成功
106 | * false 释放自己所持有的锁 失败
107 | * */
108 | boolean unLock(String lockKey, String requestID);
109 | }
110 |
--------------------------------------------------------------------------------
/redisDistributeLock/src/main/java/com/xiongyx/lock/impl/RedisDistributeLock.java:
--------------------------------------------------------------------------------
1 | package com.xiongyx.lock.impl;
2 |
3 | import com.xiongyx.lock.api.DistributeLock;
4 | import com.xiongyx.lock.script.LuaScript;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 | import org.springframework.stereotype.Component;
8 | import com.xiongyx.redis.RedisClient;
9 |
10 | import javax.annotation.Resource;
11 | import java.io.IOException;
12 | import java.util.*;
13 |
14 | /**
15 | * redis 分布式锁的简单实现
16 | * @author xiongyx
17 | */
18 | @Component("distributeLock")
19 | public final class RedisDistributeLock implements DistributeLock {
20 |
21 | /**
22 | * 无限重试
23 | * */
24 | public static final int UN_LIMIT_RETRY_COUNT = -1;
25 |
26 | private RedisDistributeLock() {
27 | try {
28 | LuaScript.initLockScript();
29 | LuaScript.initUnLockScript();
30 | } catch (IOException e) {
31 | throw new RuntimeException("LuaScript init error!",e);
32 | }
33 | }
34 |
35 | /**
36 | * 持有锁 成功标识
37 | * */
38 | private static final Long ADD_LOCK_SUCCESS = 1L;
39 | /**
40 | * 释放锁 失败标识
41 | * */
42 | private static final Long RELEASE_LOCK_SUCCESS = 1L;
43 |
44 | /**
45 | * 默认过期时间 单位:秒
46 | * */
47 | private static final int DEFAULT_EXPIRE_TIME_SECOND = 300;
48 | /**
49 | * 默认加锁重试时间 单位:毫秒
50 | * */
51 | private static final int DEFAULT_RETRY_FIXED_TIME = 100;
52 | /**
53 | * 默认的加锁浮动时间区间 单位:毫秒
54 | * */
55 | private static final int DEFAULT_RETRY_TIME_RANGE = 10;
56 | /**
57 | * 默认的加锁重试次数
58 | * */
59 | private static final int DEFAULT_RETRY_COUNT = 30;
60 |
61 | @Resource
62 | private RedisClient redisClient;
63 |
64 | //===========================================api=======================================
65 |
66 | @Override
67 | public String lock(String lockKey) {
68 | String uuid = UUID.randomUUID().toString();
69 |
70 | return lock(lockKey,uuid);
71 | }
72 |
73 | @Override
74 | public String lock(String lockKey, int expireTime) {
75 | String uuid = UUID.randomUUID().toString();
76 |
77 | return lock(lockKey,uuid,expireTime);
78 | }
79 |
80 | @Override
81 | public String lock(String lockKey, String requestID) {
82 | return lock(lockKey,requestID,DEFAULT_EXPIRE_TIME_SECOND);
83 | }
84 |
85 | @Override
86 | public String lock(String lockKey, String requestID, int expireTime) {
87 | List keyList = Collections.singletonList(lockKey);
88 |
89 | List argsList = Arrays.asList(
90 | requestID,
91 | expireTime + ""
92 | );
93 | Long result = (Long)redisClient.eval(LuaScript.LOCK_SCRIPT, keyList, argsList);
94 |
95 | if(result.equals(ADD_LOCK_SUCCESS)){
96 | return requestID;
97 | }else{
98 | return null;
99 | }
100 | }
101 |
102 | @Override
103 | public String lockAndRetry(String lockKey) {
104 | String uuid = UUID.randomUUID().toString();
105 |
106 | return lockAndRetry(lockKey,uuid);
107 | }
108 |
109 | @Override
110 | public String lockAndRetry(String lockKey, String requestID) {
111 | return lockAndRetry(lockKey,requestID,DEFAULT_EXPIRE_TIME_SECOND);
112 | }
113 |
114 | @Override
115 | public String lockAndRetry(String lockKey, int expireTime) {
116 | String uuid = UUID.randomUUID().toString();
117 |
118 | return lockAndRetry(lockKey,uuid,expireTime);
119 | }
120 |
121 | @Override
122 | public String lockAndRetry(String lockKey, int expireTime, int retryCount) {
123 | String uuid = UUID.randomUUID().toString();
124 |
125 | return lockAndRetry(lockKey,uuid,expireTime,retryCount);
126 | }
127 |
128 | @Override
129 | public String lockAndRetry(String lockKey, String requestID, int expireTime) {
130 | return lockAndRetry(lockKey,requestID,expireTime,DEFAULT_RETRY_COUNT);
131 | }
132 |
133 | @Override
134 | public String lockAndRetry(String lockKey, String requestID, int expireTime, int retryCount) {
135 | if(retryCount <= 0){
136 | // retryCount小于等于0 无限循环,一直尝试加锁
137 | while(true){
138 | String result = lock(lockKey,requestID,expireTime);
139 | if(result != null){
140 | return result;
141 | }
142 |
143 | // 休眠一会
144 | sleepSomeTime();
145 | }
146 | }else{
147 | // retryCount大于0 尝试指定次数后,退出
148 | for(int i=0; i keyList = Collections.singletonList(lockKey);
165 |
166 | List argsList = Collections.singletonList(requestID);
167 |
168 | Object result = redisClient.eval(LuaScript.UN_LOCK_SCRIPT, keyList, argsList);
169 |
170 | // 释放锁成功
171 | return RELEASE_LOCK_SUCCESS.equals(result);
172 | }
173 |
174 | //==============================================私有方法========================================
175 |
176 | /**
177 | * 获得最终的获得锁的重试时间
178 | * */
179 | private int getFinallyGetLockRetryTime(){
180 | Random ra = new Random();
181 |
182 | // 最终重试时间 = 固定时间 + 浮动时间
183 | return DEFAULT_RETRY_FIXED_TIME + ra.nextInt(DEFAULT_RETRY_TIME_RANGE);
184 | }
185 |
186 | /**
187 | * 当前线程 休眠一段时间
188 | * */
189 | private void sleepSomeTime(){
190 | // 重试时间 单位:毫秒
191 | int retryTime = getFinallyGetLockRetryTime();
192 | try {
193 | Thread.sleep(retryTime);
194 | } catch (InterruptedException e) {
195 | throw new RuntimeException("redis锁重试时,出现异常",e);
196 | }
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/redisDistributeLock/src/main/java/com/xiongyx/lock/script/LuaScript.java:
--------------------------------------------------------------------------------
1 | package com.xiongyx.lock.script;
2 |
3 | import org.springframework.util.StringUtils;
4 |
5 | import java.io.*;
6 | import java.util.Objects;
7 |
8 | /**
9 | * @Author xiongyx
10 | * on 2019/4/9.
11 | */
12 | public class LuaScript {
13 |
14 | /**
15 | * 加锁脚本 lock.lua
16 | * 1. 判断key是否存在
17 | * 2. 如果存在,判断requestID是否相等
18 | * 相等,则删除掉key重新创建新的key值,重置过期时间
19 | * 不相等,说明已经被抢占,加锁失败,返回null
20 | * 3. 如果不存在,说明恰好已经过期,重新生成key
21 | */
22 | public static String LOCK_SCRIPT;
23 |
24 | /**
25 | * 解锁脚本 unlock.lua
26 | */
27 | public static String UN_LOCK_SCRIPT;
28 |
29 | public static void initLockScript() throws IOException {
30 | if (StringUtils.isEmpty(LOCK_SCRIPT)) {
31 | InputStream inputStream = Objects.requireNonNull(
32 | LuaScript.class.getClassLoader().getResourceAsStream("lock.lua"));
33 | LOCK_SCRIPT = readFile(inputStream);
34 | }
35 | }
36 |
37 | public static void initUnLockScript() throws IOException {
38 | if (StringUtils.isEmpty(UN_LOCK_SCRIPT)) {
39 | InputStream inputStream = Objects.requireNonNull(
40 | LuaScript.class.getClassLoader().getResourceAsStream("unlock.lua"));
41 | UN_LOCK_SCRIPT = readFile(inputStream);
42 | }
43 | }
44 |
45 | private static String readFile(InputStream inputStream) throws IOException {
46 | try (
47 | BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))
48 | ) {
49 | String line;
50 | StringBuilder stringBuilder = new StringBuilder();
51 | while ((line = br.readLine()) != null) {
52 | stringBuilder.append(line)
53 | .append(System.lineSeparator());
54 | }
55 |
56 | return stringBuilder.toString();
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/redisDistributeLock/src/main/java/com/xiongyx/redis/RedisClient.java:
--------------------------------------------------------------------------------
1 | package com.xiongyx.redis;
2 |
3 | import java.util.List;
4 | import java.util.Map;
5 | import java.util.concurrent.TimeUnit;
6 |
7 | /**
8 | * @author xiongyx
9 | * @date 2019/5/21
10 | */
11 | public interface RedisClient {
12 |
13 | /**
14 | * 执行脚本
15 | * */
16 | Object eval(String script, List keys, List args);
17 |
18 | /**
19 | * get
20 | * */
21 | Object get(String key);
22 |
23 | /**
24 | * set
25 | * */
26 | void set(String key, Object value);
27 |
28 | /**
29 | * set
30 | * */
31 | void set(String key, Object value, long expireTime, TimeUnit timeUnit);
32 |
33 | /**
34 | * setNX
35 | * */
36 | Boolean setNX(String key, Object value);
37 |
38 | /**
39 | * 设置过期时间
40 | * */
41 | Boolean expire(String key, long time, TimeUnit type);
42 |
43 | /**
44 | * 移除过期时间
45 | * */
46 | Boolean persist(String key);
47 |
48 | /**
49 | * 增加
50 | * */
51 | Long increment(String key, long number);
52 |
53 | /**
54 | * 增加
55 | * */
56 | Double increment(String key, double number);
57 |
58 | /**
59 | * 删除
60 | * */
61 | Boolean delete(String key);
62 |
63 | // ==========================hash========================
64 |
65 | void hset(String key, String hashKey, Object value);
66 |
67 | void hsetAll(String key, Map map);
68 |
69 | Boolean hsetNX(String key, String hashKey, Object value);
70 |
71 | Object hget(String key, String hashKey);
72 |
73 | Map hgetAll(String key);
74 | }
75 |
--------------------------------------------------------------------------------
/redisDistributeLock/src/main/java/com/xiongyx/redis/RedisClientImpl.java:
--------------------------------------------------------------------------------
1 | package com.xiongyx.redis;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.data.redis.core.RedisCallback;
5 | import org.springframework.data.redis.core.RedisTemplate;
6 | import org.springframework.data.redis.core.script.DefaultRedisScript;
7 | import org.springframework.stereotype.Component;
8 | import redis.clients.jedis.Jedis;
9 | import redis.clients.jedis.JedisCluster;
10 |
11 | import java.util.List;
12 | import java.util.Map;
13 | import java.util.concurrent.TimeUnit;
14 |
15 | /**
16 | * @author xiongyx
17 | * @date 2019/4/3
18 | */
19 | @Component("redisClient")
20 | public class RedisClientImpl implements RedisClient{
21 |
22 | @Autowired
23 | private RedisTemplate redisTemplate;
24 |
25 | @Override
26 | public Object eval(String script, List keys, List args) {
27 | DefaultRedisScript redisScript = new DefaultRedisScript<>();
28 | redisScript.setScriptText(script);
29 | redisScript.setResultType(Integer.class);
30 |
31 | Object result = redisTemplate.execute((RedisCallback