基于传统的单机模式下的并发锁,已远远不能满足当下高并发大负载的情况,当下常用的并发处理如下
1、使用synchronized关键字
2、select for update 乐观锁
3、使用redis实现同步锁
方案一 适合单机模式,
方案二 虽然满足多节点服务实例但 对变更操作的吞吐量有影响
方案三 基于redis nosql数据库 在效率与横向扩展方面都大大优于前两种方案
redis 单线程 在自身设计上一定程度可避免想成不安全 再者其效率高于关系型数据库
本次实现锁机制 基于redis 的两个指令 查询 redis.cn 网站
指令一:SETNX key value
将key
设置值为value
,如果key
不存在,这种情况下等同SET命令。 当key
存在时,什么也不做。SETNX
是”SET if Not eXists”的简写。
Integer reply, 特定值:
1
如果key被设置了0
如果key没有被设置
指令二:GETSET key value
自动将key对应到value并且返回原来key对应的value。如果key存在但是对应的value不是字符串,就返回错误。
bulk-string-reply: 返回之前的旧值,如果之前Key
不存在将返回nil
。
步骤一, pom文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
步骤二,RedisCacheAutoConfiguration
package com.wh.whcloud.df.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wh.whcloud.core.redis.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* Redis缓存配置
*
* @author
*/
@Slf4j
@Configuration
@AutoConfigureAfter({RedisAutoConfiguration.class})
public class RedisCacheAutoConfiguration {
/**
* 重新配置一个RedisTemplate
*
* @param factory
* @return
*/
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
RedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
RedisSerializer<String> stringSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.setDefaultSerializer(jackson2JsonRedisSerializer);
log.info("RedisTemplate配置 [{}]", template);
return template;
}
@Bean
@ConditionalOnMissingBean(RedisUtil.class)
@ConditionalOnBean(RedisTemplate.class)
public RedisUtil redisUtils(RedisTemplate redisTemplate) {
RedisUtil redisUtil = new RedisUtil(redisTemplate);
log.info("RedisUtil [{}]", redisUtil);
return redisUtil;
}
}
步骤三, RedisLock 加解锁实现
package com.wh.whcloud.util;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
public class RedisLock {
private static final Long SUCCESS = 1L;
private long timeout = 9999; //获取锁的超时时间
/**
* 加锁,无阻塞
*
* @param
* @param
* @return
*/
public static Boolean tryLock(RedisTemplate redisTemplate, String key, String value, long expireTime) {
try {
//SET命令返回OK ,则证明获取锁成功
Boolean ret = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.MILLISECONDS);
return ret;
} catch (Exception e) {
e.printStackTrace();
return false;
}
return false;
}
/*
Long start = System.currentTimeMillis();
for(;;){
//SET命令返回OK ,则证明获取锁成功
Boolean ret = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
return ret;
//否则循环等待,在timeout时间内仍未获取到锁,则获取失败
long end = System.currentTimeMillis() - start;
if (end>=timeout) {
return false;
}
}*/
/**
* 解锁
*
* @param
* @param
* @return
*/
public static Boolean unlock(RedisTemplate redisTemplate, String key, String value) {
try {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);
//redis脚本执行
//Object result = redisTemplate.execute(redisScript, Collections.singletonList(key), value))
Object result = redisTemplate.delete(Collections.singletonList(key));
if (SUCCESS.equals(result)) {
return true;
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
return false;
}
}
注意:
redisTemplate.execute 是redis脚本执行,可能报错, 具体参考: https://blog.csdn.net/cevery/article/details/108303919
测试:我这里自己创建两个 线程测试
@Slf4j
@Component
public class Schedule {
@Resource
private RedisTemplate redisTemplate;
@Scheduled(cron = "0 0/1 * * * ? ")
public void getDeviceStatus() throws InterruptedException {
//启动两个线程,测试哪一个能够悠闲抢到Redis锁
Test1Runnable test1 = new Test1Runnable();
new Thread(test1).start();
Test2Runnable test2 = new Test2Runnable();
new Thread(test2).start();
}
//自定义个唯一的Key值
public String key = "ZX123456789";
//保存数据方法
public boolean testSave(){
//获取锁
boolean isOK = RedisLock.tryLock(redisTemplate, key, "key+1", 2000);
//处理业务 ,然后释放锁
if(isOK){
System.out.println("处理完业务,释放锁==="+RedisLock.unlock(redisTemplate, key, "key+1"));
}
return isOK;
}
class Test1Runnable implements Runnable {
@Override
public void run() {
boolean result = testSave();
log.info(Thread.currentThread().getName()+"Test1获取锁:"+result);
}
}
class Test2Runnable implements Runnable {
@Override
public void run() {
boolean result = testSave();
log.info(Thread.currentThread().getName()+"Test2获取锁:"+result);
}
}
}
效果: