谨以此记录学习redis并发锁学习笔记:
基于传统的单机模式下的并发锁,已远远不能满足当下高并发大负载的情况,当下常用的并发处理如下
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没有被设置
redis> SETNX mykey "Hello"
(integer) 1
redis> SETNX mykey "World"
(integer) 0
redis> GET mykey
"Hello"
redis>
指令二:GETSET key value
自动将key对应到value并且返回原来key对应的value。如果key存在但是对应的value不是字符串,就返回错误。
bulk-string-reply: 返回之前的旧值,如果之前Key
不存在将返回nil
。
redis> INCR mycounter
(integer) 1
redis> GETSET mycounter "0"
"1"
redis> GET mycounter
"0"
redis>
步骤一:引入依赖
org.springframework.boot
spring-boot-starter-data-redis
org.apache.commons
commons-pool2
2.6.0
步骤二: 配置redis连接
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
RedisTemplate template = new RedisTemplate<>();
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setConnectionFactory(lettuceConnectionFactory);
return template;
}
@Bean
@Override
public CacheErrorHandler errorHandler() {
return new CacheErrorHandler() {
@Override
public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
log.error("redis异常:key=[{}]", key, exception);
}
@Override
public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
log.error("redis异常:key=[{}]", key, exception);
}
@Override
public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
log.error("redis异常:key=[{}]", key, exception);
}
@Override
public void handleCacheClearError(RuntimeException exception, Cache cache) {
log.error("redis异常:", exception);
}
};
}
}
代码demo:将Redistemplate 注入需要使用的地方
public class RedisLock {
@Autowired
private RedisTemplate redisTemplate;
/**
* 加锁
* @param key 键
* @param value 当前时间 + 超时时间
* @return 是否拿到锁
*/
public boolean lock(String key, String value) {
if (redisTemplate.opsForValue().setIfAbsent(key, value)) {
return true;
}
String currentValue = redisTemplate.opsForValue().get(key);
//如果锁过期
if (!StringUtils.isEmpty(currentValue)
&& Long.parseLong(currentValue) < System.currentTimeMillis()) {
String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
//是否已被别人抢占
return StringUtils.isNotEmpty(oldValue) && oldValue.equals(currentValue);
}
return false;
}
/**
* 解锁
*
* @param key 键
* @param value 当前时间 + 超时时间
*/
public void unlock(String key, String value) {
try {
String currentValue = redisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
redisTemplate.opsForValue().getOperations().delete(key);
}
} catch (Exception e) {
log.error("redis解锁异常");
}
}
}
使用demo:
private void getJsapiTicketCatch(String appid, Map resultMap) {
long expr = 90 * 60 * 1000L;
long ex = 5 * 1000L;
String value = String.valueOf(System.currentTimeMillis() + ex);
boolean lock = redisLock.lock(appid, value);
//获取锁
if (lock) {
String tick = redisTemplate.opsForValue().get("zqs"+appid);
if (StringUtils.isNotBlank(tick)) {
resultMap.put("tick",tick);
} else {
//token 通过appid
BigAuthorizationInfo authorInfoByAppidService = authorizedService.getAuthorInfoByAppidService(appid);
if (authorInfoByAppidService == null
|| StringUtils.isBlank(authorInfoByAppidService.getAuthorizer_access_token())) {
resultMap.put("tick",null);
}else {
String token = authorInfoByAppidService.getAuthorizer_access_token();
String jsapiTicket = WeShareUtils.GetJsapiTicket(token);
if (StringUtils.isBlank(jsapiTicket)) {
resultMap.put("tick",null);
}else {
resultMap.put("tick",jsapiTicket);
redisTemplate.opsForValue().set("zqs"+appid, jsapiTicket, expr,TimeUnit.MILLISECONDS);
}
}
}
//释放锁
redisLock.unlock(appid, value);
}