一,spring boot 使用redis cluster 实现分布式缓存
1)maven依赖jedis客户端
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.4.3</version>
</dependency>
2)properties配置文件配置
#redis cluster config
#RedisCluster集群节点及端口信息
share.redis.jedis.cluster.nodes=127.0.0.1:6380,127.0.0.1:6381,127.0.0.1:6382,127.0.0.1:6383,127.0.0.1:6384,127.0.0.1:6385
#Redis密码
share.redis.jedis.cluster.password=
#在群集中执行命令时要遵循的最大重定向数目
share.redis.jedis.cluster.maxredirects=5
#Redis连接池在给定时间可以分配的最大连接数。使用负值无限制
share.redis.jedis.cluster.maxactive=1000
#以毫秒为单位的连接超时时间
share.redis.jedis.cluster.timeout=2000
#池中“空闲”连接的最大数量。使用负值表示无限数量的空闲连接
share.redis.jedis.cluster.maxidle=8
#目标为保持在池中的最小空闲连接数。这个设置只有在设置max-idle的情况下才有效果
share.redis.jedis.cluster.minidle=5
#连接分配在池被耗尽时抛出异常之前应该阻塞的最长时间量(以毫秒为单位)。使用负值可以无限期地阻止
share.redis.jedis.cluster.maxwait=1000
#redis cluster只使用db0
share.redis.jedis.cluster.index=0
#从jedis连接池获取连接时,校验并返回可用的连接
share.redis.jedis.cluster.testborrow=true
share.redis.jedis.cluster.maxtotal=2000
3)程序集成jedis客服端实现缓存操作
①redis cluster配置自动装配
@Component
@ConfigurationProperties(prefix = "share.redis.jedis.cluster")
public class JedisConfig {
private String nodes;
private String password;
private String maxredirects;
private String maxactive;
private String timeout;
private String maxidle;
private String minidle;
private String maxwait;
private String index;
private String testborrow;
private String maxtotal;
private String testreturn;
public String getNodes() {
return nodes;
}
public void setNodes(String nodes) {
this.nodes = nodes;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getMaxredirects() {
return maxredirects;
}
public void setMaxredirects(String maxredirects) {
this.maxredirects = maxredirects;
}
public String getMaxactive() {
return maxactive;
}
public void setMaxactive(String maxactive) {
this.maxactive = maxactive;
}
public String getTimeout() {
return timeout;
}
public void setTimeout(String timeout) {
this.timeout = timeout;
}
public String getMaxidle() {
return maxidle;
}
public void setMaxidle(String maxidle) {
this.maxidle = maxidle;
}
public String getMinidle() {
return minidle;
}
public void setMinidle(String minidle) {
this.minidle = minidle;
}
public String getMaxwait() {
return maxwait;
}
public void setMaxwait(String maxwait) {
this.maxwait = maxwait;
}
public String getIndex() {
return index;
}
public void setIndex(String index) {
this.index = index;
}
public String getTestborrow() {
return testborrow;
}
public void setTestborrow(String testborrow) {
this.testborrow = testborrow;
}
public String getMaxtotal() {
return maxtotal;
}
public void setMaxtotal(String maxtotal) {
this.maxtotal = maxtotal;
}
public String getTestreturn() {
return testreturn;
}
public void setTestreturn(String testreturn) {
this.testreturn = testreturn;
}
}
②创建JedisCluster用以操做redis集群
@Component
public class RedisShardedPool {
@Autowired
private JedisConfig jedisConfig;
private ShardedJedisPool pool;
public JedisCluster initPool(){
System.out.println("-------------------------------------------------------------------------------------------------");
JedisPoolConfig config=new JedisPoolConfig();
config.setMaxIdle(Integer.parseInt(jedisConfig.getMaxidle()));
config.setMaxTotal(Integer.parseInt(jedisConfig.getMaxtotal()));
config.setMinIdle(Integer.parseInt(jedisConfig.getMinidle()));
config.setTestOnBorrow(Boolean.parseBoolean(jedisConfig.getTestborrow()));
config.setTestOnReturn(Boolean.parseBoolean(jedisConfig.getTestreturn()));
//whether block when resource is exhausted: false will throw a exception,true will block until timeout, default is true
config.setBlockWhenExhausted(true);
String[] clusters = jedisConfig.getNodes().split(",");//形成node结点的数组
// List<JedisShardInfo> jedisShardInfos=new ArrayList<>();
Set<HostAndPort> nodes = new HashSet<>();
for(String cluster : clusters){
String host = cluster.split(":")[0];//获取ip
int port = Integer.parseInt(cluster.split(":")[1]);//获取端口
nodes.add(new HostAndPort(host,port));
// JedisShardInfo jedisShardInfo1=new JedisShardInfo(host,port,1000*2);
// jedisShardInfos.add(jedisShardInfo1);
}
return new JedisCluster(nodes,2000,1000,1,new GenericObjectPoolConfig());
// pool = new ShardedJedisPool(config,jedisShardInfos, Hashing.MURMUR_HASH, Sharded.DEFAULT_KEY_TAG_PATTERN);
}
}
③创建redis cluster集群操作工具类
@Component
public class RedisShardedPoolUtil {
private static Logger log = LoggerFactory.getLogger(RedisShardedPoolUtil.class);
@Autowired
private RedisShardedPool redisShardedPool;
public JedisCluster getJedis(){
return redisShardedPool.initPool();
}
/**
* 设置key的有效期,单位是秒
* @param key
* @param exTime
* @return
*/
public Long expire(String key,int exTime){
JedisCluster jedis = null;
Long result = null;
try {
jedis = getJedis();
result = jedis.expire(key,exTime);
} catch (Exception e) {
log.error("expire key:{} error",key,e);
return result;
}
return result;
}
/**
* 设置key value exTime的单位是秒
* @param key
* @param value
* @param exTime
* @return
*/
public String setEx(String key,String value,int exTime){
JedisCluster jedis = null;
String result = null;
try {
jedis = getJedis();
result = jedis.setex(key,exTime,value);
} catch (Exception e) {
log.error("setex key:{} value:{} error",key,value,e);
return result;
}
return result;
}
/**
* 获取
* @param key
* @return
*/
public String get(String key){
JedisCluster jedis = null;
String result = null;
try {
jedis = getJedis();
result = jedis.get(key);
} catch (Exception e) {
log.error("get key:{} error",key,e);
return result;
}
return result;
}
/**
* 设置值
* @param key
* @param value
* @return
*/
public String set(String key,String value){
JedisCluster jedis = null;
String result = null;
try {
jedis = getJedis();
result = jedis.set(key,value);
} catch (Exception e) {
log.error("set key:{} value:{} error",key,value,e);
return result;
}
return result;
}
/**
* 获取旧的value,设置新的value;可用于锁操作
* @param key
* @param value
* @return
*/
public String getSet(String key,String value){
JedisCluster jedis = null;
String result = null;
try {
jedis = getJedis();
result = jedis.getSet(key,value);
} catch (Exception e) {
log.error("getset key:{} value:{} error",key,value,e);
return result;
}
return result;
}
/**
* 删除key
* @param key
* @return
*/
public Long del(String key){
JedisCluster jedis = null;
Long result = null;
try {
jedis = getJedis();
result = jedis.del(key);
} catch (Exception e) {
log.error("del key:{} error",key,e);
return result;
}
return result;
}
/**
* 当且仅当key不存在,将key的值设置为value,并且返回1;
* 若是给定的key已经存在,则setnx不做任何动作,返回0。
* 可用于锁操作
* @param key
* @param value
* @return
*/
public Long setnx(String key,String value){
JedisCluster jedis = null;
Long result = null;
try {
jedis = getJedis();
result = jedis.setnx(key,value);
} catch (Exception e) {
log.error("setnx key:{} value:{} error",key,value,e);
return result;
}
return result;
}
/**
* 查询有多少KEY
* @param pattern
* @return
*/
public int keys(final String pattern) {
try {
final Set<String> keySet = new HashSet();
final Map<String, JedisPool> nodes = getJedis().getClusterNodes();
for (String k : nodes.keySet()) {
JedisPool pool = nodes.get(k);
//获取Jedis对象,Jedis对象支持keys模糊查询
Jedis jedis = pool.getResource();
final Set<String> set = jedis.keys(pattern);
keySet.addAll(set);
}
return keySet.size();
} catch (Exception e) {
log.error("异常信息", e);
throw e;
}
}
/**
* 模糊查询给定的pattern的所有keys列表
*
* @param pattern 模糊查询
* @return 返回当前pattern可匹配的对象keys列表
*/
public Set<String> keyList(final String pattern) {
try {
final Set<String> keySet = new HashSet<String>();
final Map<String, JedisPool> nodes = getJedis().getClusterNodes();
for (String k : nodes.keySet()) {
JedisPool pool = nodes.get(k);
//获取Jedis对象,Jedis对象支持keys模糊查询
Jedis jedis = pool.getResource();
final Set<String> set = jedis.keys(pattern);
keySet.addAll(set);
}
return keySet;
} catch (Exception e) {
log.error("异常信息", e);
throw e;
}
}
}
4)控制器中实际应用jediscluster操作缓存
@Controller
@RequestMapping("/account-info")
public class AccountInfoController {
@Autowired
private IAccountInfoService accountInfoService;
@Autowired
private RedisShardedPoolUtil redisShardedPoolUtil;
@Value("${server.port}")
private String port;
/**
* 存储缓存数据
* @param jsonObject
* @return
*/
@ResponseBody
@PostMapping(value = "/redisOption")
public String redisOption(@RequestBody JSONObject jsonObject){
Map<String,String> map = JSONObject.toJavaObject(jsonObject,Map.class);
//循环转换
for (Map.Entry<String, String> entry : map.entrySet()) {
redisShardedPoolUtil.setEx(entry.getKey(), entry.getValue(),1111111111);
}
return null;
}
/**
* 获取缓存数据
* @param jsonObject
* @return
*/
@ResponseBody
@PostMapping(value = "/redisOptionGet")
public String redisOptionGet(@RequestBody JSONObject jsonObject){
Map<String,String> map = JSONObject.toJavaObject(jsonObject,Map.class);
//循环转换
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(redisShardedPoolUtil.get(entry.getKey()));
}
return "111111111111111";
}
二,在使用JedisCluster操作分布式缓存的基础上,使用redisson实现分布式锁
1)使用maven依赖redisson jar包
<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.15.1</version>
</dependency>
2)redis cluster节点配置
#redisson
spring.redisson.cluster.nodes[0]=127.0.0.1:6380
spring.redisson.cluster.nodes[1]=127.0.0.1:6381
spring.redisson.cluster.nodes[2]=127.0.0.1:6382
spring.redisson.cluster.nodes[3]=127.0.0.1:6383
spring.redisson.cluster.nodes[4]=127.0.0.1:6384
spring.redisson.cluster.nodes[5]=127.0.0.1:6385
3)redisson自动装配
①redisson配置自动装配
@ConfigurationProperties(prefix = "spring.redisson")
//@ConditionalOnProperty("redisson.cluster")
public class RedissonClusterProperties {
private String password;
private cluster cluster;
public static class cluster {
private List<String> nodes;
public List<String> getNodes() {
return nodes;
}
public void setNodes(List<String> nodes) {
this.nodes = nodes;
}
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public RedissonClusterProperties.cluster getCluster() {
return cluster;
}
public void setCluster(RedissonClusterProperties.cluster cluster) {
this.cluster = cluster;
}
}
②redisson客户端自动装配
@Configuration
@ConditionalOnClass(Config.class)
@EnableConfigurationProperties(RedissonClusterProperties.class)
public class RedissonAutoConfiguration {
@Autowired
private RedissonClusterProperties redssionProperties;
/**
* 哨兵模式自动装配
* @return
*/
// @Bean
// @ConditionalOnProperty(name="redisson.master-name")
// RedissonClient redissonSentinel() {
// Config config = new Config();
// SentinelServersConfig serverConfig = config.useSentinelServers().addSentinelAddress(redssionProperties.getSentinelAddresses())
// .setMasterName(redssionProperties.getMasterName())
// .setTimeout(redssionProperties.getTimeout())
// .setMasterConnectionPoolSize(redssionProperties.getMasterConnectionPoolSize())
// .setSlaveConnectionPoolSize(redssionProperties.getSlaveConnectionPoolSize());
//
// if(StringUtils.isNotBlank(redssionProperties.getPassword())) {
// serverConfig.setPassword(redssionProperties.getPassword());
// }
// return Redisson.create(config);
// }
/**
* 单机模式自动装配
* @return
*/
// @Bean
// @ConditionalOnProperty(name="redisson.address")
// RedissonClient redissonSingle() {
// Config config = new Config();
// SingleServerConfig serverConfig = config.useSingleServer()
// .setAddress(redssionProperties.getAddress())
// .setTimeout(redssionProperties.getTimeout())
// .setConnectionPoolSize(redssionProperties.getConnectionPoolSize())
// .setConnectionMinimumIdleSize(redssionProperties.getConnectionMinimumIdleSize());
//
// if(StringUtils.isNotBlank(redssionProperties.getPassword())) {
// serverConfig.setPassword(redssionProperties.getPassword());
// }
//
// return Redisson.create(config);
// }
@Bean
// @ConditionalOnProperty(name="redisson.cluster")
public RedissonClient redissonClient() {
//redisson版本是3.5,集群的ip前面要加上“redis://”,不然会报错,3.2版本可不加
List<String> clusterNodes = new ArrayList<>();
for (int i = 0; i < redssionProperties.getCluster().getNodes().size(); i++) {
// clusterNodes.add("redis://" + redssionProperties.getCluster().getNodes().get(i));
clusterNodes.add("redis://" + redssionProperties.getCluster().getNodes().get(i));
}
Config config = new Config();
// 添加集群地址
ClusterServersConfig clusterServersConfig = config.useClusterServers().addNodeAddress(clusterNodes.toArray(new String[clusterNodes.size()]));
// 设置密码
// clusterServersConfig.setPassword(redssionProperties.getPassword());
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
// /**
// * 装配locker类,并将实例注入到RedissLockUtil中
// * @return
// */
// @Bean
// DistributedLocker distributedLocker(RedissonClient redissonClient) {
// RedissonDistributedLocker locker = new RedissonDistributedLocker();
// locker.setRedissonClient(redissonClient);
// RedissLockUtil.setLocker(locker);
// return locker;
// }
}
4)控制器实现
①不加锁代码
@ResponseBody
@PostMapping(value = "/redisOptionReduce")
public String redisOptionReduce(@RequestBody JSONObject jsonObject) throws ExecutionException, InterruptedException {
String returnStr = "";
//RLock lock = redissonClient.getLock("xxxxxxxxxxxxxxxxxx");
//boolean isLock = lock.tryLock(3,5,TimeUnit.SECONDS);
//if(isLock){
AccountInfo accountInfo = accountInfoService.getById(1);
if(accountInfo.getAccountBalance() > 0){
Thread.sleep(30); //延长程序操作时间增加线程并发操作时,多个线程获取相同余额的可能性
double accountBalance = accountInfo.getAccountBalance() - 1;
UpdateWrapper<AccountInfo> accountInfoUpdateWrapper2 = new UpdateWrapper<>();
accountInfoUpdateWrapper2.setSql("account_balance = account_balance - 1");
accountInfoService.update(accountInfo,accountInfoUpdateWrapper2);
returnStr = "当前剩余余额:" + accountBalance;
}else {
returnStr = "当前剩余余额: 0";
}
//lock.unlock(); //释放锁
//}
return returnStr;
}
并发操作前账户余额信息
30个线程并发操作后,根据账户余额信息和jmter运行结果我们可以看出该程序代码造成了,库存超卖情况
②代码加上分布式锁实现
程序代码
@Autowired
private RedissonClient redissonClient;
@ResponseBody
@PostMapping(value = "/redisOptionReduce")
public String redisOptionReduce(@RequestBody JSONObject jsonObject) throws ExecutionException, InterruptedException {
String returnStr = "";
RLock lock = redissonClient.getLock("xxxxxxxxxxxxxxxxxx");
boolean isLock = lock.tryLock(3,5,TimeUnit.SECONDS);
if(isLock){
AccountInfo accountInfo = accountInfoService.getById(1);
if(accountInfo.getAccountBalance() > 0){
Thread.sleep(30); //延长程序操作时间增加线程并发操作时,多个线程获取相同余额的可能性
double accountBalance = accountInfo.getAccountBalance() - 1;
UpdateWrapper<AccountInfo> accountInfoUpdateWrapper2 = new UpdateWrapper<>();
accountInfoUpdateWrapper2.setSql("account_balance = account_balance - 1");
accountInfoService.update(accountInfo,accountInfoUpdateWrapper2);
returnStr = "当前剩余余额:" + accountBalance;
}else {
returnStr = "当前剩余余额: 0";
}
lock.unlock(); //释放锁
}
return returnStr;
}
程序运行前账户余额情况
30个线程并发运行后,根据账户余额和jmter运行结果可知分布式锁成功避免了线程超卖情况