分布式锁

MACULA框架可运用在分布式系统中,分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,分布式锁则实现了这一要求。MACULA框架默认支持数据库分布锁与redis分布式锁。
  • redis分布式锁实现

获取锁例子代码

//lockkey 互斥锁的key,相同
//identifier 请求标识,后期释放锁只能由申请锁的请求端释放,保证可靠性
//timeoutRelease key超时时间,防止死锁
private Boolean lock(String lockKey, String identifier, Long timeoutRelease) {
  Boolean acquire = redisTemplate.opsForValue().setIfAbsent(lockKey,
          identifier, timeoutRelease, TimeUnit.SECONDS);
  if(acquire) {
    return true;
  }
  return false;
}

释放锁例子代码

//使用lua脚本保证reids执行的原子性
private static final String UNLOCK_LUA = "if redis.call(\"get\",KEYS[1]) == ARGV[1] "
        + "then "
        + "    return redis.call(\"del\",KEYS[1]) "
        + "else "
        + "    return 0 "
        + "end ";
...
private Boolean unlock(String lockKey, String identifier) {
  RedisScript<Long> script = RedisScript.of(UNLOCK_LUA, Long.class);
  return redisTemplate.execute(script, Arrays.asList(lockKey), identifier) == 1;
}
  • redis分布式锁应用代码实例
String lockKey = "redis:xx:xxx:lock";
String identifier = "uuid";
try {
    // 取锁
    if (lock(lockKey, identifier, timeoutRelease)) {
        //互斥共享资源访问
        ...
    }
} catch (Exception e) {
    ...
} finally {
    unlock(lockKey, identifier);
}

幂等

幂等是保证了只要调用接口成功,外部多次调用对系统的影响是一致的,也就是一个请求多次重试的问题。MACULA框架主要通过控制数据表的约束唯一性来实现幂等性和通过redis的key值判断实现幂等性

  • 例子1:

通过数据表实现幂等性

微信用户信息表,一个unionid针对一个用户,为unionId字段定义唯一属性,防止多次同数据调用保存也只添加一个用户信息

示例保存新增代码

public void baseInfo(WechatBaseVo baseInfo, boolean syncFlag) {
    WechatBaseInfoEntity baseInfoEntity = wechatBaseInfoRepository.findByUnionId(baseInfo.getUnionId());
    if (baseInfoEntity == null) {
      baseInfoEntity = new WechatBaseInfoEntity();
    }
    BeanUtils.copyProperties(baseInfo, baseInfoEntity);
    ...
    wechatBaseInfoRepository.save(baseInfoEntity);
}
  • 例子2:

通过redis的key值判断实现幂等性

进行登录/注册信息发送时,当发送成功后会生成一个redis的key,在该key为消失之前不允许重复发送相同的登录/注册信息

示例短信发送代码

public void sendCode(String mobile, String templateName) {
  //幂等验证
  if (isSentTooFast(mobile)) {
    throw new OAuthSmsException("sms.sendSms.sms.sendingTooFast", "您的操作过于频繁,请稍后再试");
  }
  //提供幂等验证条件
  setSendIntervalFlag(mobile);
  smsCodeSender.send(mobile, code, templateName);
}
...
private boolean isSentTooFast(String mobile) {
  if (sendMinIntervalInSeconds <= 0) {
    return false;
  }
  String smsCodeSentRedisKey = getSmsCodeSentRedisKey(mobile);
  return redisTemplate.hasKey(smsCodeSentRedisKey);
}
...
private void setSendIntervalFlag(String mobile) {
  String smsCodeSentRedisKey = getSmsCodeSentRedisKey(mobile);
  redisTemplate.opsForValue().set(smsCodeSentRedisKey, "sent",
          sendMinIntervalInSeconds, TimeUnit.SECONDS);
}