深入解析Redis中的分布式鎖

本篇文章給大家主要帶大家了解一下redis中分布式鎖的實(shí)現(xiàn)和代碼解析,希望對大家有所幫助!

深入解析Redis中的分布式鎖

redis 分布式鎖

大家項(xiàng)目中都會(huì)使用到分布式鎖把,通常用來做數(shù)據(jù)的有序操作場景,比如一筆訂單退款(如果可以退多次的情況)。或者用戶多端下單。【相關(guān)推薦:Redis視頻教程

Maven 依賴

我主要是基于 Spring-Boot 2.1.2 + Jedis 進(jìn)行實(shí)現(xiàn)

<?xml  version="1.0" encoding="UTF-8"?><project> ????<modelversion>4.0.0</modelversion>  ????<parent> ????????<groupid>org.springframework.boot</groupid> ????????<artifactid>spring-boot-starter-parent</artifactid> ????????<version>2.1.2.RELEASE</version> ????</parent>  ????<groupid>cn.edu.cqvie</groupid> ????<artifactid>redis-lock</artifactid> ????<version>1.0-SNAPSHOT</version>  ????<properties> ????????<project.build.sourceencoding>UTF-8</project.build.sourceencoding> ????????<java.version>1.8</java.version> ????????<redis.version>2.9.0</redis.version> ????????<spring-test.version>5.0.7</spring-test.version> ????</properties>  ????<dependencies> ????????<dependency> ????????????<groupid>org.springframework.boot</groupid> ????????????<artifactid>spring-boot-autoconfigure</artifactid> ????????</dependency> ????????<dependency> ????????????<groupid>org.springframework.data</groupid> ????????????<artifactid>spring-data-redis</artifactid> ????????</dependency> ????????<dependency> ????????????<groupid>redis.clients</groupid> ????????????<artifactid>jedis</artifactid> ????????????<version>${redis.version}</version> ????????</dependency>  ????????<dependency> ????????????<groupid>org.springframework.boot</groupid> ????????????<artifactid>spring-boot-starter-logging</artifactid> ????????</dependency> ????????<dependency> ????????????<groupid>org.slf4j</groupid> ????????????<artifactid>log4j-over-slf4j</artifactid> ????????</dependency>  ????????<dependency> ????????????<groupid>org.springframework.boot</groupid> ????????????<artifactid>spring-boot-starter-test</artifactid> ????????????<scope>test</scope> ????????</dependency> ????</dependencies>  ????<build> ????????<plugins> ????????????<plugin> ????????????????<groupid>org.springframework.boot</groupid> ????????????????<artifactid>spring-boot-maven-plugin</artifactid> ????????????</plugin> ????????</plugins> ????</build></project>

配置文件

application.properties 配置文件內(nèi)容如下:

spring.redis.host=127.0.0.1 spring.redis.port=6379 spring.redis.password= spring.redis.timeout=30000 spring.redis.jedis.pool.max-active=8 spring.redis.jedis.pool.min-idle=2 spring.redis.jedis.pool.max-idle=4   logging.level.root=INFO

接口定義

接口定義,對于鎖我們核心其實(shí)就連個(gè)方法 lock 和 unlock.

public?interface?RedisLock?{  ????long?TIMEOUT_MILLIS?=?30000;  ????int?RETRY_MILLIS?=?30000;  ????long?SLEEP_MILLIS?=?10;  ????boolean?tryLock(String?key);  ????boolean?lock(String?key);  ????boolean?lock(String?key,?long?expire);  ????boolean?lock(String?key,?long?expire,?long?retryTimes);  ????boolean?unlock(String?key); }

分布式鎖實(shí)現(xiàn)

我的實(shí)現(xiàn)方式是通過 setnx 方式實(shí)現(xiàn)了,如果存在 tryLock 邏輯的話,會(huì)通過 自旋 的方式重試

//?AbstractRedisLock.java?抽象類 public?abstract?class?AbstractRedisLock?implements?RedisLock?{  ????@Override ????public?boolean?lock(String?key)?{ ????????return?lock(key,?TIMEOUT_MILLIS); ????}  ????@Override ????public?boolean?lock(String?key,?long?expire)?{ ????????return?lock(key,?TIMEOUT_MILLIS,?RETRY_MILLIS); ????} }  //?具體實(shí)現(xiàn) @Component public?class?RedisLockImpl?extends?AbstractRedisLock?{  ????private?Logger?logger?=?LoggerFactory.getLogger(getClass());  ????@Autowired ????private?RedisTemplate<string>?redisTemplate; ????private?ThreadLocal<string>?threadLocal?=?new?ThreadLocal<string>(); ????private?static?final?String?UNLOCK_LUA;  ????static?{ ????????StringBuilder?sb?=?new?StringBuilder(); ????????sb.append("if?redis.call("get",KEYS[1])?==?ARGV[1]?"); ????????sb.append("then?"); ????????sb.append("????return?redis.call("del",KEYS[1])?"); ????????sb.append("else?"); ????????sb.append("????return?0?"); ????????sb.append("end?"); ????????UNLOCK_LUA?=?sb.toString();  ????}  ????@Override ????public?boolean?tryLock(String?key)?{ ????????return?tryLock(key,?TIMEOUT_MILLIS); ????}  ????public?boolean?tryLock(String?key,?long?expire)?{ ????????try?{ ????????????return?!StringUtils.isEmpty(redisTemplate.execute((RedisCallback<string>)?connection?-&gt;?{ ????????????????JedisCommands?commands?=?(JedisCommands)?connection.getNativeConnection(); ????????????????String?uuid?=?UUID.randomUUID().toString(); ????????????????threadLocal.set(uuid); ????????????????return?commands.set(key,?uuid,?"NX",?"PX",?expire); ????????????})); ????????}?catch?(Throwable?e)?{ ????????????logger.error("set?redis?occurred?an?exception",?e); ????????} ????????return?false; ????}  ????@Override ????public?boolean?lock(String?key,?long?expire,?long?retryTimes)?{ ????????boolean?result?=?tryLock(key,?expire);  ????????while?(!result?&amp;&amp;?retryTimes--?&gt;?0)?{ ????????????try?{ ????????????????logger.debug("lock?failed,?retrying...{}",?retryTimes); ????????????????Thread.sleep(SLEEP_MILLIS); ????????????}?catch?(InterruptedException?e)?{ ????????????????return?false; ????????????} ????????????result?=?tryLock(key,?expire); ????????} ????????return?result; ????}  ????@Override ????public?boolean?unlock(String?key)?{ ????????try?{ ????????????List<string>?keys?=?Collections.singletonList(key); ????????????List<string>?args?=?Collections.singletonList(threadLocal.get()); ????????????Long?result?=?redisTemplate.execute((RedisCallback<long>)?connection?-&gt;?{ ????????????????Object?nativeConnection?=?connection.getNativeConnection();  ????????????????if?(nativeConnection?instanceof?JedisCluster)?{ ????????????????????return?(Long)?((JedisCluster)?nativeConnection).eval(UNLOCK_LUA,?keys,?args); ????????????????} ????????????????if?(nativeConnection?instanceof?Jedis)?{ ????????????????????return?(Long)?((Jedis)?nativeConnection).eval(UNLOCK_LUA,?keys,?args); ????????????????} ????????????????return?0L; ????????????}); ????????????return?result?!=?null?&amp;&amp;?result?&gt;?0; ????????}?catch?(Throwable?e)?{ ????????????logger.error("unlock?occurred?an?exception",?e); ????????} ????????return?false; ????} }</long></string></string></string></string></string></string>

測試代碼

最后再來看看如何使用吧. (下面是一個(gè)模擬秒殺的場景)

@RunWith(SpringRunner.class) @SpringBootTest public?class?RedisLockImplTest?{  ????private?Logger?logger?=?LoggerFactory.getLogger(getClass()); ????@Autowired ????private?RedisLock?redisLock; ????@Autowired ????private?StringRedisTemplate?redisTemplate; ????private?ExecutorService?executors?=?Executors.newScheduledThreadPool(8);  ????@Test ????public?void?lock()?{ ????????//?初始化庫存 ????????redisTemplate.opsForValue().set("goods-seckill",?"10"); ????????List<future>?futureList?=?new?ArrayList();  ????????for?(int?i?=?0;?i??{ ????????????try?{ ????????????????action.get(); ????????????}?catch?(InterruptedException?|?ExecutionException?e)?{ ????????????????e.printStackTrace(); ????????????} ????????});  ????}  ????public?int?seckill()?{ ????????String?key?=?"goods"; ????????try?{ ????????????redisLock.lock(key); ????????????int?num?=?Integer.valueOf(Objects.requireNonNull(redisTemplate.opsForValue().get("goods-seckill"))); ????????????if?(num?&gt;?0)?{ ????????????????redisTemplate.opsForValue().set("goods-seckill",?String.valueOf(--num)); ????????????????logger.info("秒殺成功,剩余庫存:{}",?num); ????????????}?else?{ ????????????????logger.error("秒殺失敗,剩余庫存:{}",?num); ????????????} ????????????return?num; ????????}?catch?(Throwable?e)?{ ????????????logger.error("seckill?exception",?e); ????????}?finally?{ ????????????redisLock.unlock(key); ????????} ????????return?0; ????} }</future>

總結(jié)

本文是 Redis 鎖的一種簡單的實(shí)現(xiàn)方式,基于 jedis 實(shí)現(xiàn)了鎖的重試操作。 但是缺點(diǎn)還是有的,不支持鎖的自動(dòng)續(xù)期,鎖的重入,以及公平性(目前通過自旋的方式實(shí)現(xiàn),相當(dāng)于是非公平的方式)。

更多編程相關(guān)知識,請?jiān)L問:Redis視頻教程!!

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點(diǎn)贊10 分享