【分布式-Redis应用】Spring中Redis使用项目实战(持续更新...)

@[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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@来杯咖啡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值