1:setnx方式

  1. public String redisLock() throws InterruptedException{
  2. String lockKey = "locke_key";
  3. String clientId = UuidUtils.generateUuid();
  4. try{
  5. Boolean locked = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.MICROSECONDS);
  6. if (!locked){
  7. return "已被其他线程锁定";
  8. }
  9. //业务逻辑扣除数据的
  10. }finally {
  11. String value = stringRedisTemplate.opsForValue().get(lockKey);
  12. if (value.equals(clientId)){
  13. stringRedisTemplate.delete(lockKey);
  14. }
  15. }
  16. return "end";
  17. }

存在的问题:

  1. 超时问题,已经超时,逻辑还没执行完成
  2. 设置vlaue的原因,防止线程3,删除线程2的锁
  3. 锁延长的机制,开启timer

jedis执行luna脚本:
image.png

2:Redisson方式

底层依靠Luna脚本,保证原子操作实现的分布式锁
依赖redis 的单线程保证分布式是锁\

官网: https://redisson.org/
分布式场景下的应用
分布式锁,bloom Filter

锁支持重入

使用:

导包:


org.redisson
redisson-spring-boot-starter
3.17.0

redisson方式分布式锁

  1. public String redisLock2() throws InterruptedException{
  2. String lockKey = "locke_key";
  3. RLock lock = redisson.getLock(lockKey);
  4. try{
  5. lock.lock();
  6. //业务逻辑扣除数据的
  7. }finally {
  8. lock.unlock();
  9. }
  10. return "end";
  11. }
  1. private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
  2. if (leaseTime != -1) {
  3. return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
  4. }
  5. RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
  6. ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
  7. if (e != null) {
  8. return;
  9. }
  10. // lock acquired
  11. if (ttlRemaining == null) {
  12. //负责锁超时的续费功能
  13. scheduleExpirationRenewal(threadId);
  14. }
  15. });
  16. return ttlRemainingFuture;
  17. }
  1. <T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
  2. internalLockLeaseTime = unit.toMillis(leaseTime);
  3. return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
  4. "if (redis.call('exists', KEYS[1]) == 0) then " +
  5. "redis.call('hset', KEYS[1], ARGV[2], 1); " +
  6. "redis.call('pexpire', KEYS[1], ARGV[1]); " +
  7. "return nil; " +
  8. "end; " +
  9. "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
  10. "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
  11. "redis.call('pexpire', KEYS[1], ARGV[1]); " +
  12. "return nil; " +
  13. "end; " +
  14. "return redis.call('pttl', KEYS[1]);",
  15. Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
  16. }
  1. private void renewExpiration() {
  2. ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
  3. if (ee == null) {
  4. return;
  5. }
  6. Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
  7. @Override
  8. public void run(Timeout timeout) throws Exception {
  9. ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
  10. if (ent == null) {
  11. return;
  12. }
  13. Long threadId = ent.getFirstThreadId();
  14. if (threadId == null) {
  15. return;
  16. }
  17. RFuture<Boolean> future = renewExpirationAsync(threadId);
  18. future.onComplete((res, e) -> {
  19. if (e != null) {
  20. log.error("Can't update lock " + getName() + " expiration", e);
  21. return;
  22. }
  23. if (res) {
  24. // reschedule itself
  25. renewExpiration();
  26. }
  27. });
  28. }
  29. }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
  30. ee.setTimeout(task);
  31. }

实现事务的原理:Luna脚本

主节点挂了,导致的不在可用的解决方案:
换组件 使用zk, zk 强一致性CP架构

image.png

3:RedLock

解决redis的主从挂断的问题

原理是:

redlock对多个redisson节点发送lock ,只有半数加锁成功,返回true,认为加锁成功

问题:

性能较差,各个节点存在网络延迟,失败回滚的场景

  1. public String redisLock2() throws InterruptedException{
  2. String lockKey = "locke_key";
  3. RLock lock1 = redisson1.getLock(lockKey);
  4. RLock lock2 = redisson2.getLock(lockKey);
  5. RLock lock3 = redisson3.getLock(lockKey);
  6. RedissonRedLock redissonRedLock = new RedissonRedLock(lock1,lock2,lock3);
  7. try{
  8. boolean b = redissonRedLock.tryLock();
  9. if (b){
  10. //业务逻辑扣除数据的
  11. }
  12. }finally {
  13. redissonRedLock.unlock();
  14. }
  15. return "end";
  16. }

image.png

优化方案:
分段:

在不同的节点存储不同的分段