Redisson简介
Redis 是最流行的 NoSQL 数据库解决方案之一,而 Java 是世界上最流行(注意,没有说“最好”)的编程语言之一。虽然两者看起来很自然地在一起“工作”,但是要知道,Redis 其实并没有对 Java 提供原生支持。
相反,作为 Java 开发人员,我们若想在程序中集成 Redis,必须使用 Redis 的第三方库。而 Redisson 就是用于在 Java 程序中操作 Redis 的库,它使得我们可以在程序中轻松地使用 Redis。Redisson 在 java.util 中常用接口的基础上,为我们提供了一系列具有分布式特性的工具类。
Redisson底层采用的是Netty 框架。支持Redis 2.8以上版本,支持Java1.6+以上版本。
Redisson实现分布式锁步骤
引入依赖
创建spring boot web 项目。引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.6.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
application.yml
server:
port: 8090
spring:
redis:
host: 127.0.0.1
port: 6379
password:
Redisson 配置类
springBoot 启动类中加入redis配置 :
package com.service.redis.servicespringbootredisdemo;
import org.redisson.Redisson;
import org.redisson.config.Config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class ServiceSpringbootRedisDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceSpringbootRedisDemoApplication.class, args);
}
@Bean
public Redisson redisson(){
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
// config.useCustomServers().;
return (Redisson) Redisson.create(config);
}
}
也可以使用redis的配置类:
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
@Configuration
public class RedissonConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private String port;
//@Value("${spring.redis.password}")
//private String password;
/**
* RedissonClient,单机模式
* @return
* @throws IOException
*/
@Bean(destroyMethod = "shutdown")
public RedissonClient redisson() throws IOException {
Config config = new Config();
//config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password);
config.useSingleServer().setAddress("redis://" + host + ":" + port);
return Redisson.create(config);
}
}
Redisson分布式锁实现
这个是重要的部分,而且业务代码也不多。
redisson 实现还是很简单的
package com.service.redis.servicespringbootredisdemo.test;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@RestController
public class TestController {
private static final Logger logger = LoggerFactory.getLogger(TestController.class);
@Autowired
private Redisson redisson;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("deductStock1")
public String deductStock1() {
String lockKey = "lockKey";
RLock redissonLock = redisson.getLock(lockKey);
try {
//加锁,实现锁续命功能
redissonLock.lock();
//尝试加锁,最大等待时间300毫秒,上锁30毫秒自动解锁
//if (redissonLock.tryLock(300,30,TimeUnit.MILLISECONDS)){
//你自己的业务啊,随便写!
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + "");
System.out.println("扣减库存成功,剩余:" + realStock + "");
} else {
System.out.println("扣减失败");
}
//}
} catch (InterruptedException e) {
System.out.println("异常!!!");
throw new RuntimeException(e);
} finally {
//解锁
redissonLock.unlock();
}
return "end";
}
}
tryLock一般用于特定满足需求的场合,但不建议作为一般需求的分布式锁,一般分布式锁建议用void lock(long leaseTime, TimeUnit unit)。因为从性能上考虑,在高并发情况下后者效率是前者的好几倍。
tryLock(long waitTime, long leaseTime, TimeUnit unit)
在源码中出现leaseTime时间判断的有2个分支,实际上就是加锁时是否设置过期时间,未设置过期时间(-1)时则会有watchDog的锁续约,注册了加锁事件的续约任务。
经过多次演变。全部贴上。
package com.service.redis.servicespringbootredisdemo.test;
import org.redisson.Redisson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@RestController
public class TestController {
private static final Logger logger = LoggerFactory.getLogger(TestController.class);
@Autowired
private Redisson redisson;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("deductStock")
public String deductStock() {
String lockKey = "lockKey";
String uuid = UUID.randomUUID().toString();
try {
//下面这行代码相当于jedis.setnx(key,value);
// Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "test");
//加超时时间(如果在这里挂掉还不是完蛋)
// stringRedisTemplate.expire(lockKey,10, TimeUnit.SECONDS);
//上面两行结合成下一行 ,不管是30还是多少,有的线程执行多,有的少.缺少一个自动续期。
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, uuid, 30, TimeUnit.SECONDS);
if (!result) {
System.out.println("取锁失败,请稍后重试。");
//这里也可以处理自己的业务。返回错误码什么的、
return "error";
}
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + "");
System.out.println("扣减库存成功,剩余:" + realStock + "");
} else {
System.out.println("扣减失败");
}
} finally {
//用完以后肯定是要删掉的
if (uuid.equals(stringRedisTemplate.opsForValue().get(lockKey)))
//保证每个线程只删自己的。
stringRedisTemplate.delete(lockKey);
}
return "end";
}
}