@[TOC](目录标题)
场景及代码示例
1、list集合存储到Redis以及读取
import org.springframework.data.redis.core.StringRedisTemplate;
@Autowired
private StringRedisTemplate redisTemplate;
// 存入缓存,过期时间1h
String cacheValue = JSONObject.parseArray(JSON.toJSONString(lsit)).toJSONString();
redisTemplate.opsForValue().set(key, cacheValue, 1, TimeUnit.HOURS);
/* 缓存中读取 */
String childTemplatesStr = redisTemplate.opsForValue().get(key);
List<TemplateInfo> cacheChildTemplates = JSONObject.parseArray(childTemplatesStr, TemplateInfo.class);
分布式锁及代码示例
参考文章:分布式系统 - 分布式锁及实现方案
1、Redisson实现分布式锁
关键点声明
各加锁方法对比
● 申明方法有无InterruptedException:方法申明时抛出 InterruptedException,表示当前方法在等待时,支持其他线程通过调用interrupt方法,中断当前线程方法的执行。
● 有无leaseTime:设置leaseTime即设置锁的过期时间,若无或leaseTime=-1L,则通过watchDog自动续锁。
● 有无waitTime:设置锁阻塞时的最大阻塞时间。
● lock和tryLock:往往lock是获取不到锁时阻塞,tryLock获取不到锁时也会立即返回,但如果包含waitTime参数,则另算。
● lock.unlock():如果直接lock.unlock(),可能会抛出java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id. 从报错信息可知:尝试解锁,而不是由当前线程按节点 ID 锁定。 这是因为redisson的unlock方法内部对释放锁的线程进行校验,当发现不是当前线程释放锁的时候会报以上错误
。 解决方式:unlock之前判断是否是当前线程(lock.isLocked() && lock.isHeldByCurrentThread()
)。
有个Redisson分布式锁实现原理可参考文章:https://segmentfault.com/a/1190000040813126
Redisson分布式锁代码模板
1、
设置等待的时间,加锁的时间:
● 因为设置了等待的时间waitTime,那么当没有获取到锁的时候,会最大等待500ms。
● 因为设置了加锁的时间leaseTime,那么当1500ms之后,如果业务还没有执行完毕,则会释放锁。
RLock lock = redissonClient.getLock("LOCK_KEY");
long waitTime=500L;
long leaseTime=15000L;
try {
boolean isLock = lock.tryLock(waitTime, leaseTime, TimeUnit.MILLISECONDS);
if (!isLock){
log.info("lock conflict");
return;
}
// do something ...
} catch (InterruptedException e) {
log.warn("InterruptedException", e);
Thread.currentThread().interrupt();
}
} catch (Exception e) {
log.error("error", e);
throw e;
}finally {
//判断要解锁的key是否已被锁定;判断要解锁的key是否被当前线程持有
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
2、
没有设置等待时间,加锁的时间:
● 因为没有设置等待时间,当获取不到锁的时候,不会阻塞等待,而是直接返回。
● 因为没有设置加锁的时间,默认redisson会给30s的加锁时间。
RLock lock = redissonClient.getLock("LOCK_KEY");
try {
boolean isLock = lock.tryLock();
if (!isLock){
log.info("lock conflict");
return;
}
// do something ...
} catch (InterruptedException e) {
log.warn("InterruptedException", e);
Thread.currentThread().interrupt();
}
} catch (Exception e) {
log.eror("error", e);
} finally {
//判断要解锁的key是否已被锁定;判断要解锁的key是否被当前线程持有
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
2、Redis实现分布式锁
加锁: set NX PX + 重试 + 重试间隔
向Redis发起如下命令:
SET productId:lock 0xx9p03001 NX PX 30000
其中,"productId"由自己定义,可以是与本次业务有关的id,"0xx9p03001"是一串随机值,必须保证全局唯一(原因在后文中会提到),“NX"指的是当且仅当key(也就是案例中的"productId:lock”)在Redis中不存在时,返回执行成功,否则执行失败。"PX 30000"指的是在30秒后,key将被自动删除。执行命令后返回成功,表明服务成功的获得了锁。
@Override
public boolean lock(String key, long expire, int retryTimes, long retryDuration) {
// use JedisCommands instead of setIfAbsense
boolean result = setRedis(key, expire);
// retry if needed
while ((!result) && retryTimes-- > 0) {
try {
log.debug("lock failed, retrying..." + retryTimes);
Thread.sleep(retryDuration);
} catch (Exception e) {
return false;
}
// use JedisCommands instead of setIfAbsense
result = setRedis(key, expire);
}
return result;
}
private boolean setRedis(String key, long expire) {
try {
RedisCallback<String> redisCallback = connection -> {
JedisCommands commands = (JedisCommands) connection.getNativeConnection();
String uuid = SnowIDUtil.uniqueStr();
lockFlag.set(uuid);
return commands.set(key, uuid, NX, PX, expire); // 看这里
};
String result = redisTemplate.execute(redisCallback);
return !StringUtil.isEmpty(result);
} catch (Exception e) {
log.error("set redis occurred an exception", e);
}
return false;
}
加锁:setIfAbsent
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void method() {
// eventId就是订单id
log.info("[MallOrderMsgConsumer]. eventId:[{}], message:[{}]", event.getEventId(), JsonUtils.toJson(event));
try {
String orderId = event.getEventId();
AssertUtil.isTrue(StringUtils.hasText(orderId));
/* 幂等处理,缓存存储1天 */
Boolean lock = stringRedisTemplate.opsForValue()
.setIfAbsent(AiToolConstant.MALL_ORDER_LOCK_KEY_PREFIX + orderId, orderId, 24, TimeUnit.HOURS);
log.info("[MallOrderMsgConsumer]. lock:[{}], eventId:[{}]", lock, orderId);
if (BooleanUtils.isTrue(lock)) {
log.info("[MallOrderMsgConsumer]. start handler. message:[{}]", JsonUtils.toJson(event));
/* 核心业务处理 */
handler(event);
log.info("[MallOrderMsgConsumer]. end handler. message:[{}]", JsonUtils.toJson(event));
}
} catch (Exception exception) {
log.error("[MallOrderMsgConsumer]. ERROR. eventId:[{}], message:[{}]",
event.getEventId(), JsonUtils.toJson(event), exception);
} finally {
// 根据业务场景,判断是否解锁
}
解锁:采用lua脚本
在删除key之前,一定要判断服务A持有的value与Redis内存储的value是否一致。如果贸然使用服务 A持有的key来删除锁,则会误将服务B的锁释放掉。
if redis.call("get", KEYS[1])==ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end