面试准备 -- redis实现分布式锁
昨天,我们介绍了 zookeeper 实现分布式锁的原理和具体实现。今天,我们来学习使用 Redis 来做分布式锁。下面我们来简单实现一个分布式锁:public static void main(String[] args) {Jedis jedis = new Jedis("localhost");//设置锁Long result = ...
昨天,我们介绍了 zookeeper 实现分布式锁的原理和具体实现。今天,我们来学习使用 Redis 来做分布式锁。
下面我们来简单实现一个分布式锁:
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost");
//设置锁
Long result = jedis.setnx("key", "value");
try {
//加锁成功,设置锁过期时间
if (result==1) {
jedis.expire("key", 60 * 5);
//模拟业务(为了简单演示先这样做)
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//删除锁(释放锁的意思)
jedis.del("key");
}
}
setnx 这个方法是在插入前判断这个键是否已经存在,存在返回 0,不存在返回 1 。但是上述方法存在几个弊端。
弊端一
正常情况下,我们设置了这个键,然后会对这个键设置过期时间,这就算是成功拿到了锁了。
假设我们setnx成功了,但是突然间我们程序挂掉了,那锁会一直存在,就算其他实例还活着的情况下,永远也拿不到锁了。
弊端二
上述代码中,我们持有锁的时间是 5 分钟,5分钟后锁会自动过期。假设我们的线程 A 执行业务的时间超过了 5 分钟,这时我们的锁失效了。
线程 B 拿到了锁,在执行过程中。恰好线程 A 执行完,然后释放锁,结果把线程 B 的锁给释放掉了。接下来程序都乱套。
线程 A 拿到锁并设置了锁过期时间。
结果,由于某种情况,执行时间超过了锁设置的过期时间,锁被自动释放了。
这时,线程 B 过来了,拿到锁并设置过期时间。
又是那么巧,线程 B 在执行过程中,线程 A 执行完了。就要开始释放锁了。
等线程 B 执行完,发现自己的锁没了,这不哭晕在厕所吗?
关于上述情况,也是有解决方案的,比如使用 lua 脚本来保证其原子性。恰好 Redisson 框架底层也是使用 lua 脚本来保证原子性,也就不需要我们自己写脚本啦,下面我们就看看 Redisson 框架。
Redisson 简介
redisson 是 redis 官方推荐的 java 语言实现分布式锁的项目。该项目在基于 netty 框架的基础上提供了一系列分布式特性的工具类。下面我们来使用 Redisson 实现分布式锁。
首先我们需要构建配置文件
#这里只配置地址
redis.address= redis://172.16.81.147:6379
#有兴趣的可以去查看 BaseConfig 中具体配置详情
配置类
/**
* @author Gentle
* @date 2019/06/01 : 10:38
*/
@Data
@Component
@ConfigurationProperties(prefix = "redis")
public class RedisConfigInfoProperties {
private String address;
}
/**
* @author Gentle
* @date 2019/05/30 : 23:37
*/
@Component
@Configurable
public class RedissonConfig {
@Autowired
private RedisConfigInfoProperties redisConfigInfoProperties;
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress(redisConfigInfoProperties.getAddress())
.setConnectionMinimumIdleSize(0)
.setConnectionPoolSize(3);
return Redisson.create(config);
}
}
注解
/**
* @author Gentle
* @date 2019/05/31 : 23:10
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisLockAnnotation {
/**
* 时间
* @return
*/
long time() default 20;
/**
* 时间类型 时 分 秒
* @return
*/
TimeUnit timeunit() default TimeUnit.SECONDS;
/**
* 加锁的 key
* @return
*/
String key() default "test";
}
AOP 拦截器
/**
* @author Gentle
* @date 2019/05/31 : 23:25
*/
@Aspect
@Order(5)
@Component
public class RedisLockAop {
@Autowired
private RedissonClient redissonClient;
@Around("@annotation(redisLockAnnotation)")
public void redisLockHandler(ProceedingJoinPoint joinPoint, RedisLockAnnotation redisLockAnnotation){
//拿到锁对象
RLock lock = redissonClient.getLock(redisLockAnnotation.key());
boolean acquire =false;
try {
//尝试加锁
acquire = lock.tryLock(redisLockAnnotation.time(),redisLockAnnotation.timeunit());
//成功就执行方法
if (acquire){
joinPoint.proceed();
}
} catch (Throwable throwable) {
throwable.printStackTrace();
}finally {
//拿到锁才释放
if (acquire){
lock.unlock();
}
}
}
}
服务接口及实现
/**
* @author Gentle
* @date 2019/05/31 : 23:39
*/
public interface TestService {
/**
* 计数服务
*/
void countNumber();
}
/**
* @author Gentle
* @date 2019/05/31 : 23:40
*/
@Service
public class TestServiceImpl implements TestService {
int a =0;
@Override
//redis 分布式锁注解
@RedisLockAnnotation
public void countNumber() {
System.out.println(a++);
}
}
测试类
import com.gentle.service.TestService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisLockApplicationTests {
@Autowired
TestService testService;
@Test
public void contextLoads() throws InterruptedException {
//100个线程
CountDownLatch countDownLatch = new CountDownLatch(100);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 100; i++) {
executorService.submit(() -> {
testService.countNumber();
countDownLatch.countDown();
});
}
countDownLatch.await();
}
}
测试结果
代码已推送至 Gitee
Gitee
https://gitee.com/reway_wen/springboot-learn/tree/master/redis-lock-demo
GitHub 待更新
总结
这次,我们主要学习了 Redis 分布式锁的示例,并针对该示例出现的几个问题进行了剖析。接下来我们
学习了使用 Redisson 框架来实现我们的分布式锁。
有兴趣的同学可以关注公众号,一起学习!
更多推荐
所有评论(0)