posts - 22, comments - 32, trackbacks - 0, articles - 73
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

分布式锁实现-redis,zk

Posted on 2021-03-24 20:11 为自己代言 阅读(192) 评论(0)  编辑  收藏 所属分类: java/J2EE
1:分布锁有好多实现方式
  •  基于数据库实现
      这个实现方式比较复杂,考虑因素比较多,比如:超时,非公平锁,非重入等会有各种各样的问题,在解决问题的过程中会使整个方案变得越来越复杂。操作数据库需要一定的开销,性能问题需要考虑      
  • 基于redis实现(这个对于不太敏感的场景可以使用,由于redis集群和单机,还有客户端,版本等多方面因素考虑情况比较多)
       性能好。使用缓存实现分布式锁的缺点 其数据库一样
  • 基于zookeeper实现(这个是最终也是最好最可靠的)
       创建临时节点,可以解决单机,锁无法释放,非阻塞,不可冲入,非公平的问题
 
    总结
从理解的难易程度角度(从低到高)

数据库 > 缓存 > Zookeeper

从实现的复杂性角度(从低到高)

Zookeeper > 缓存 > 数据库

从性能角度(从高到低)

缓存 > Zookeeper >= 数据库

从可靠性角度(从高到低)

Zookeeper > 缓存 > 数据库
下面讲基于redis实现分布锁代码:RedisTemplate 客户端 lettuce


@Service
public class RedisDistributedLockUtils {

    @Autowired
    
private RedisTemplate redisTemplate;

    
private static final Long RELEASE_SUCCESS = 1L;

    
private static final long DEFAULT_TIMEOUT = 1000 * 10;
    
//因为要使用lua 脚本是因为 redis 执行lua脚本是原子操作
    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";

    
/**
     * 实时获取锁
     *
     * 尝试获取分布式锁 将redis版本升级到2.1以上(spring-boot-starter-data-redis 版本 2.X以上),然后使用setIfAbsent 不存在
     * 当setIfAbsent成功之后断开连接,下面设置过期时间的代码 stringRedisTemplate.expire(key,timeout);是无法执行的,这时候就会有大量没有过期时间的数据存在数据库
     * 
@param lockKey    锁
     * 
@param requestId  请求标识
     * 
@param expireTime 超期时间
     * 
@return 是否获取成功
     
*/
    
public boolean trySetDistributedLock(String lockKey, String requestId, long expireTime) {
        
return redisTemplate.opsForValue().setIfAbsent(lockKey, requestId,0 == expireTime ? DEFAULT_TIMEOUT : expireTime, TimeUnit.MILLISECONDS);
    }

    
/**
     * 以阻塞方式的获取锁
     * 
@param key
     * 
@param value
     * 
@param timeout
     * 
@return
     
*/
    
public boolean setDistributedLock(String key, String value, long timeout) {
        Boolean lock 
= false;
        
long start = System.currentTimeMillis();
        
while (!lock && (System.currentTimeMillis() - start < timeout)) {
            
//执行set命令
            lock = redisTemplate.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.MILLISECONDS);
            
//不频繁去获取锁
            try {
                
if (!lock) {
                    Thread.sleep(
60);
                }
            } 
catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
return lock;
    }

    
public boolean releaseLock(String key, String value) {
        
// 使用Lua脚本:先判断是否是自己设置的锁,再执行删除
        
// 使用lua脚本删除redis中匹配value的key,可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁
        
// spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本的异常EvalSha is not supported in cluster environment
        
// 所以只能拿到原redis的connection来执行脚本

        List
<String> keys = new ArrayList<>();
        keys.add(key);
        List
<String> args = new ArrayList<>();
        args.add(value);
        Long result 
= (Long)redisTemplate.execute(new RedisCallback<Long>() {
            @Override
            
public Long doInRedis(RedisConnection connection) throws DataAccessException {
                Object nativeConnection 
= connection.getNativeConnection();
                
// 集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
                
// 集群模式
                if (nativeConnection instanceof JedisCluster) {
                    
return (Long)((JedisCluster)nativeConnection).eval(UNLOCK_LUA, keys, args);
                }
                
//客户端是Jedis时候(单机模式)
                else if (nativeConnection instanceof Jedis) {
                    
return (Long)((Jedis)nativeConnection).eval(UNLOCK_LUA, keys, args);
                }
                
//这里使用 redisTemplate 中lettuce 客户端
                else{
                    DefaultRedisScript
<Long> redisScript = new DefaultRedisScript<>();
                    redisScript.setScriptText(UNLOCK_LUA);
                    redisScript.setResultType(Long.
class);
                    
return (Long)redisTemplate.execute(redisScript, keys, value);
                }
            }
        });
        
//返回最终结果
        return RELEASE_SUCCESS.equals(result);
    }
}
基于zookeeper实现下期补上:


介绍分布式锁文章写的比较详细:
https://blog.csdn.net/u010963948/article/details/79006572

只有注册用户登录后才能发表评论。


网站导航: