在【Redis Jedis实战(含Spring Boot)】基础上做如下修改:
1、子pom.xml
添加redisson依赖
org.redisson
redisson
3.10.0
注意:redisson-spring-boot-starter与spring-boot-starter-data-redis是有冲突的,redisson-spring-boot-starter中RedissonAutoConfiguration自动配置Bean: redisTemplate、stringRedisTemplate、redisConnectionFactory,spring-boot-starter-data-redis也自动配置了。
这里选择单独引入redisson。
2、Redisson单独实战
RedisUtil.java
package com.java.ashare.redis.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;
/**
* 一、redis自动配置类:RedisRepositoriesAutoConfiguration,可以查看到能配置的属性以及可以使用的bean
* RedisRepositoriesAutoConfiguration源码可知:
* 提供了RedisTemplate
RedisUtil使用springboot默认提供的stringRedisTemplate(Bean)。
RedisDistributedLock.java
package com.java.ashare.redis.clients.redisson;
import java.util.concurrent.TimeUnit;
import org.redisson.Redisson;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import com.java.ashare.redis.utils.RedisUtil;
public class RedisDistributedLock {
private static RedissonClient redissonClient;
static {
Config config = new Config();
// 看门狗功能,注意tryLock时不能设置key过期时间,否则此功能无效
// 当启用watchdog时,它会设置key过期时间为lockWatchdogTimeout,然后底层子线程定时每隔lockWatchdogTimeout/3重新设置key过期时间为lockWatchdogTimeout
// 要么开启watchdog,这样会影响性能;要么设置key过期时间(watchdog功能无效),但要保证key过期时间大于原子业务操作时间
config.setLockWatchdogTimeout(60 * 1000);
// 单机
config.useSingleServer()
.setAddress("redis://192.168.78.169:6379")
// .setPassword("123456")
.setDatabase(0)
.setTimeout(60 * 1000);
// sentinel哨兵模式-主从
/* config.useSentinelServers()
.addSentinelAddress("redis://192.168.78.169:26379", "redis://192.168.78.169:26380", "redis://192.168.78.169:26381")
.setMasterName("mymaster")
// .setPassword("123456")
.setDatabase(0)
.setTimeout(60 * 1000); */
// cluster集群模式
/*config.useClusterServers().addNodeAddress("redis://192.168.78.169:6379", "redis://192.168.78.169:6380", "redis://192.168.78.169:6381",
"redis://192.168.78.169:6382", "redis://192.168.78.169:6383", "redis://192.168.78.169:6384")
.setPassword("123456")
.setTimeout(60 * 1000);*/
redissonClient = Redisson.create(config);
}
/**
* redisson实现redis分布式锁,代码简单,隐藏复杂的实现逻辑
*/
public static void decrStockRedisson() throws InterruptedException {
/**
* 使用hash结构,key=lockKey, field=uuid+threadId, value=1(可重入锁的重入次数)
* 可以理解为ReentrantLock在分布式环境下的实现
*/
RLock lock = redissonClient.getLock("lockKey");
boolean isLock = false;
try {
System.out.println("线程:" + Thread.currentThread().getName() + ",等待获取锁!");
// 30:等待获取锁的超时时间;60:key过期时间
// isLock = lock.tryLock(30, 60, TimeUnit.SECONDS);
// 30:等待获取锁的超时时间
isLock = lock.tryLock(30, TimeUnit.SECONDS);
if(isLock) {
System.out.println("线程:" + Thread.currentThread().getName() + ",获取锁成功!");
// 线程串行进行原子业务操作
Thread.sleep(600 * 1000);
int stock = Integer.parseInt(RedisUtil.valueOperations().get("stock"));
if(stock > 0) {
int realStock = stock - 1;
RedisUtil.valueOperations().set("stock", realStock + "");
System.out.println("扣减成功,剩余库存:" + realStock);
} else {
System.out.println("扣减失败,库存不足!");
}
} else {
System.out.println("线程:" + Thread.currentThread().getName() + ",获取锁失败!");
}
} finally {
lock.unlock();
}
}
/**
* 解决redisson实现redis分布式锁在主从模式下存在的问题:
* 即master的lockKey没有同步到slave之前就挂掉了,sentine哨兵切换master,此时其他线程就会获得锁,锁失效造成线程不安全。
*
* 核心:RedissonRedLock
* 针对完全独立的N个redis实例 或 N个sentinel集群 或 N个cluster集群(N建议大于3,3/5)
* 如果要获取分布式锁,那么需要向这N个sentinel集群通过EVAL命令执行LUA脚本,至少需要N/2+1个sentinel集群响应成功,才能成功获取到分布式锁
* 这样就可以大概率解决主从模式下的问题,除非超过一半以上的sentinel集群都挂掉,这种概率可以排除不计
* N个sentinel集群之间是完全独立的,每个sentinel集群包含正常的主从节点,而不是一个sentinel集群包含N个节点。
*
* 或者
*
* 使用zookeeper实现分布式锁可以解决主从不实时同步的问题,zookeeper特性:zookeeper所有节点始终保持实时同步节点信息,健壮性更好,但性能不及redis。
*/
public static void decrStockRedissonRedLock() throws InterruptedException {
Config config1 = new Config();
config1.useSingleServer()
.setAddress("redis://192.168.78.169:6379")
.setDatabase(0)
.setTimeout(60);
RedissonClient redissonClient1 = Redisson.create(config1);
Config config2 = new Config();
config2.useSingleServer()
.setAddress("redis://192.168.78.169:6380")
.setDatabase(0)
.setTimeout(60);
RedissonClient redissonClient2 = Redisson.create(config2);
Config config3 = new Config();
config3.useSingleServer()
.setAddress("redis://192.168.78.169:6380")
.setDatabase(0)
.setTimeout(60);
RedissonClient redissonClient3 = Redisson.create(config3);
String lockKey = "lockKey";
RLock lock1 = redissonClient1.getLock(lockKey);
RLock lock2 = redissonClient2.getLock(lockKey);
RLock lock3 = redissonClient3.getLock(lockKey);
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
boolean isLock = false;
try {
isLock = redLock.tryLock(30, 60, TimeUnit.SECONDS);
if(isLock) {
int stock = Integer.parseInt(RedisUtil.valueOperations().get("stock"));
if(stock > 0) {
int realStock = stock - 1;
RedisUtil.valueOperations().set("stock", realStock + "");
System.out.println("扣减成功,剩余库存:" + realStock);
} else {
System.out.println("扣减失败,库存不足!");
}
}
} finally {
redLock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
for(int i = 0; i < 2; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
RedisDistributedLock.decrStockRedisson();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
3、Redisson+SpringBoot实战
RedissonConfig.java
package com.java.ashare.redis.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://192.168.78.169:6379")
//.setPassword("123456")
.setDatabase(0)
.setTimeout(60);
return Redisson.create(config);
}
}
RedisDistributedLockApplication.java
package com.java.ashare.redis.distributedlock;
import java.util.concurrent.TimeUnit;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.java.ashare.redis.utils.RedisUtil;
@Component
public class RedisDistributedLockApplication {
/**
* redisson实现分布式锁,代码简单,隐藏复杂的实现逻辑
* 这里选择单独引入redisson
*
* 与springboot集成简单说明:
* 依赖:
*
* org.redisson
* redisson-spring-boot-starter
* 3.10.0
*
* 与spring-boot-starter-data-redis二选一,redisson-spring-boot-starter中RedissonAutoConfiguration自动配置Bean: redisTemplate、stringRedisTemplate、redisConnectionFactory,与spring-boot-starter-data-redis中的Bean可能存在冲突
*
* 自动配置类:RedissonAutoConfiguration
*
* springboot+redisson application.yml说明:
* spring:
* redis:
* host: 192.168.78.169
* port: 6379
* redisson:
* config: classpath:redisson-single-dev.yml
*
* redisson-single-dev.yml
* singleServerConfig:
* address: redis://47.103.5.190:6379
* password: 123456
* timeout: 3000
* database: 0
* 有哪些属性可以查看属性配置类
*
* redissonClient可以使用redisson-spring-boot-starter默认提供的
*/
@Autowired
private RedissonClient redissonClient;
public void decrStockRedisson() throws InterruptedException {
RLock lock = redissonClient.getLock("lockKey");
boolean isLock = false;
try {
isLock = lock.tryLock(30, 60, TimeUnit.SECONDS);
if(isLock) {
int stock = Integer.parseInt(RedisUtil.valueOperations().get("stock"));
if(stock > 0) {
int realStock = stock - 1;
RedisUtil.valueOperations().set("stock", realStock + "");
System.out.println("扣减成功,剩余库存:" + realStock);
} else {
System.out.println("扣减失败,库存不足!");
}
}
} finally {
lock.unlock();
}
}
}