java分布式锁的原理以及实现方法

发布时间:2021-06-29 10:46:45 作者:chen
来源:亿速云 阅读:219
# Java分布式锁的原理以及实现方法

## 目录
1. [分布式锁概述](#分布式锁概述)
2. [分布式锁的核心特性](#分布式锁的核心特性)
3. [常见实现方案对比](#常见实现方案对比)
4. [基于数据库的实现](#基于数据库的实现)
5. [基于Redis的实现](#基于redis的实现)
6. [基于Zookeeper的实现](#基于zookeeper的实现)
7. [RedLock算法详解](#redlock算法详解)
8. [框架集成实践](#框架集成实践)
9. [典型应用场景](#典型应用场景)
10. [性能优化建议](#性能优化建议)
11. [常见问题解决方案](#常见问题解决方案)

---

## 分布式锁概述
在分布式系统中,当多个进程/服务需要互斥地访问共享资源时,分布式锁成为关键组件。与单机锁不同,分布式锁需要解决网络延迟、节点故障等分布式环境特有的问题。

**核心价值**:
- 保证跨JVM的互斥访问
- 避免重复处理(如定时任务)
- 防止超卖等业务场景

## 分布式锁的核心特性
| 特性          | 说明                                                                 |
|---------------|----------------------------------------------------------------------|
| 互斥性        | 同一时刻只有一个客户端持有锁                                         |
| 可重入性      | 同一客户端可多次获取同一把锁                                         |
| 锁超时        | 防止死锁,自动释放机制                                               |
| 高可用        | 集群环境下仍能正常工作                                               |
| 非阻塞        | 获取锁失败立即返回而非阻塞                                           |

## 常见实现方案对比
```mermaid
pie
    title 分布式锁实现方案占比
    "Redis" : 45
    "Zookeeper" : 35
    "数据库" : 15
    "其他" : 5

方案对比表

方案 优点 缺点 适用场景
数据库 实现简单 性能差,无自动过期 低并发简单场景
Redis 性能高,功能丰富 实现复杂,需处理脑裂问题 高并发场景
Zookeeper 可靠性高,原生支持 性能中等,依赖ZK集群 强一致性要求场景

基于数据库的实现

悲观锁实现

-- 建表语句
CREATE TABLE `distributed_lock` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `lock_key` varchar(64) NOT NULL,
  `lock_value` varchar(255) NOT NULL,
  `expire_time` datetime NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_lock_key` (`lock_key`)
);
// 获取锁
public boolean tryLock(String key, String value, long expireTime) {
    Connection conn = null;
    try {
        conn = dataSource.getConnection();
        conn.setAutoCommit(false);
        
        // 先尝试插入
        PreparedStatement ps = conn.prepareStatement(
            "INSERT INTO distributed_lock(lock_key, lock_value, expire_time) VALUES (?,?,?)");
        ps.setString(1, key);
        ps.setString(2, value);
        ps.setTimestamp(3, new Timestamp(System.currentTimeMillis() + expireTime));
        try {
            return ps.executeUpdate() == 1;
        } catch (SQLException e) {
            // 唯一键冲突说明锁已存在
            if (e.getErrorCode() == 1062) {
                // 检查锁是否过期
                PreparedStatement queryPs = conn.prepareStatement(
                    "SELECT * FROM distributed_lock WHERE lock_key=? FOR UPDATE");
                queryPs.setString(1, key);
                ResultSet rs = queryPs.executeQuery();
                if (rs.next()) {
                    Timestamp expire = rs.getTimestamp("expire_time");
                    if (expire.before(new Date())) {
                        // 锁已过期,可以获取
                        PreparedStatement updatePs = conn.prepareStatement(
                            "UPDATE distributed_lock SET lock_value=?, expire_time=? WHERE lock_key=?");
                        updatePs.setString(1, value);
                        updatePs.setTimestamp(2, new Timestamp(System.currentTimeMillis() + expireTime));
                        updatePs.setString(3, key);
                        return updatePs.executeUpdate() == 1;
                    }
                }
            }
            return false;
        }
    } finally {
        if (conn != null) conn.close();
    }
}

乐观锁实现

public boolean tryLockWithVersion(String key, String value, long expireTime) {
    int version = getCurrentVersion(key);
    int updated = jdbcTemplate.update(
        "UPDATE distributed_lock SET lock_value=?, expire_time=?, version=version+1 " +
        "WHERE lock_key=? AND version=?",
        value, 
        new Timestamp(System.currentTimeMillis() + expireTime),
        key,
        version
    );
    return updated == 1;
}

基于Redis的实现

SETNX方案

public boolean tryLock(Jedis jedis, String key, String value, int expireTime) {
    // SET if Not eXists
    String result = jedis.set(key, value, "NX", "PX", expireTime);
    return "OK".equals(result);
}

// 解锁Lua脚本
private static final String UNLOCK_SCRIPT =
    "if redis.call('get', KEYS[1]) == ARGV[1] then " +
    "   return redis.call('del', KEYS[1]) " +
    "else " +
    "   return 0 " +
    "end";

public boolean unlock(Jedis jedis, String key, String value) {
    Object result = jedis.eval(UNLOCK_SCRIPT, 
        Collections.singletonList(key), 
        Collections.singletonList(value));
    return result.equals(1L);
}

Redisson实现

// 获取锁
RLock lock = redisson.getLock("orderLock");
try {
    // 尝试加锁,最多等待100秒,上锁后30秒自动解锁
    boolean res = lock.tryLock(100, 30, TimeUnit.SECONDS);
    if (res) {
        // 业务逻辑
    }
} finally {
    lock.unlock();
}

基于Zookeeper的实现

临时顺序节点方案

public class ZkDistributedLock implements Watcher {
    private ZooKeeper zk;
    private String lockPath;
    private String currentPath;
    private String waitPath;
    
    public boolean tryLock() throws Exception {
        // 创建临时顺序节点
        currentPath = zk.create(lockPath + "/lock-", 
            null, 
            ZooDefs.Ids.OPEN_ACL_UNSAFE,
            CreateMode.EPHEMERAL_SEQUENTIAL);
            
        // 检查是否是最小序号节点
        List<String> children = zk.getChildren(lockPath, false);
        Collections.sort(children);
        
        if (currentPath.equals(lockPath + "/" + children.get(0))) {
            return true;
        } else {
            // 监听前一个节点
            int currentIndex = Collections.binarySearch(children, 
                currentPath.substring(currentPath.lastIndexOf('/') + 1));
            waitPath = lockPath + "/" + children.get(currentIndex - 1);
            zk.getData(waitPath, this, null);
            return false;
        }
    }
    
    @Override
    public void process(WatchedEvent event) {
        if (event.getType() == Event.EventType.NodeDeleted) {
            synchronized (this) {
                this.notifyAll(); // 唤醒等待线程
            }
        }
    }
}

RedLock算法详解

Redis官方推荐的分布式锁算法,解决单点Redis故障问题。

算法步骤: 1. 获取当前毫秒级时间戳 2. 依次尝试从N个独立Redis实例获取锁 3. 计算获取锁总耗时,当且仅当在多数节点上获取成功且总耗时小于锁有效期时才算成功 4. 如果获取失败,需在所有节点上释放锁

public boolean tryRedLock(List<Jedis> jedisList, String key, String value, 
    int expireTime, int retryCount, long retryDelay) {
    
    int successCount = 0;
    long startTime = System.currentTimeMillis();
    
    for (int i = 0; i < retryCount; i++) {
        for (Jedis jedis : jedisList) {
            if (tryLock(jedis, key, value, expireTime)) {
                successCount++;
            }
        }
        
        // 检查是否获得多数锁
        if (successCount >= jedisList.size() / 2 + 1) {
            // 检查获取时间是否有效
            long elapsed = System.currentTimeMillis() - startTime;
            if (elapsed < expireTime) {
                return true;
            }
            break;
        }
        
        // 释放已获取的锁
        for (Jedis jedis : jedisList) {
            unlock(jedis, key, value);
        }
        
        // 重试延迟
        try {
            Thread.sleep(retryDelay);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }
    return false;
}

框架集成实践

Spring集成示例

@Configuration
public class LockConfig {
    
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer()
            .setAddress("redis://127.0.0.1:6379")
            .setDatabase(0);
        return Redisson.create(config);
    }
    
    @Bean
    public DistributedLockTemplate distributedLockTemplate(RedissonClient redisson) {
        return new DistributedLockTemplate(redisson);
    }
}

@Component
public class OrderService {
    
    @Autowired
    private DistributedLockTemplate lockTemplate;
    
    public void createOrder(String orderId) {
        lockTemplate.execute("order:" + orderId, 3000, () -> {
            // 业务逻辑
            return null;
        });
    }
}

典型应用场景

秒杀系统实现

public boolean seckill(Long productId, Long userId) {
    String lockKey = "seckill:" + productId;
    RLock lock = redisson.getLock(lockKey);
    
    try {
        // 尝试获取锁,等待时间100ms,持有锁1秒
        if (lock.tryLock(100, 1000, TimeUnit.MILLISECONDS)) {
            // 1. 查询库存
            int stock = getStockFromDB(productId);
            if (stock <= 0) {
                return false;
            }
            
            // 2. 扣减库存
            if (reduceStock(productId) > 0) {
                // 3. 创建订单
                createOrder(productId, userId);
                return true;
            }
        }
        return false;
    } finally {
        lock.unlock();
    }
}

性能优化建议

  1. 锁粒度控制

    • 细粒度锁(如按订单ID加锁)
    • 避免全局锁
  2. 锁等待时间

    // 建议设置合理的等待时间
    lock.tryLock(50, 300, TimeUnit.MILLISECONDS);
    
  3. 锁分段技术

    // 将商品库存分为10段
    int segment = productId.hashCode() % 10;
    RLock lock = redisson.getLock("stock:" + segment);
    

常见问题解决方案

锁续期问题

private ThreadLocal<Long> lockHoldTime = new ThreadLocal<>();

public boolean tryLock(String key, long waitTime, long leaseTime) {
    long start = System.currentTimeMillis();
    while (System.currentTimeMillis() - start < waitTime) {
        if (redis.setnx(key, "1")) {
            // 启动守护线程续期
            scheduleExpirationRenewal(key, leaseTime);
            lockHoldTime.set(System.currentTimeMillis());
            return true;
        }
        Thread.sleep(50);
    }
    return false;
}

private void scheduleExpirationRenewal(String key, long leaseTime) {
    new Thread(() -> {
        while (true) {
            try {
                // 每leaseTime/3时间续期一次
                Thread.sleep(leaseTime / 3);
                if (lockHoldTime.get() == null) break;
                redis.expire(key, leaseTime);
            } catch (Exception e) {
                break;
            }
        }
    }).start();
}

脑裂问题解决方案

  1. 使用RedLock多节点方案
  2. 设置合理的超时时间
  3. 增加fencing token机制

总结:分布式锁的选择需要根据业务场景在CP和AP之间做出权衡,建议: - 强一致性场景:Zookeeper - 高并发场景:Redis+Redisson - 简单场景:数据库乐观锁 “`

推荐阅读:
  1. redis实现分布式锁的原理是什么
  2. redis分布式锁的实现原理

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

java

上一篇:Bootstrap3.3.7如何实现导航栏下拉菜单鼠标滑过展开效果

下一篇:怎么使用JavaScript触发过渡效果

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》