找回密码
立即注册
搜索
发回帖 发新帖

6570

积分

0

好友

832

主题
发表于 昨天 23:34 | 查看: 4| 回复: 0

前言

分布式锁是分布式系统中一个非常关键的组件。目前业界有多种分布式锁的实现方案,比如借助 Redis、MQ、数据库、ZooKeeper 等第三方服务系统来构建。tldb 给出的分布式锁方案,核心目标在于简化设计过程,让你像使用程序中的对象锁一样,简洁可靠地获取分布式锁。

tldb 分布式锁使用方式

tldb 提供了三类核心方法:

  • Lock:阻塞式请求锁
  • TryLock:尝试加锁,若锁已被占用则失败返回;反之则获取该锁
  • UnLock:释放已经获取的锁

tldb 的分布式锁功能主要在 MQ 模块中实现,调用的方法位于 MQ 客户端。客户端的实现其实非常轻量,除了目前已经支持的 Java、Golang、Python、JavaScript 等语言的 simpleClient 之外,有兴趣的开发者完全可以自行实现其他语言的 MQ 客户端,几乎没有技术门槛。

由于分布式锁由 tldb 服务器统一控制,它对客户端来说是跨语言的。例如,用 Java 客户端上锁的对象,其他语言同样无法获取该对象锁。

Lock(string, int) 方法的使用

tldb 提供的是以字符串为锁对象的独占锁。例如调用 lock("abc", 3),需要提供两个参数:

  1. 第一个参数为锁对象,即服务器对 "abc" 对象分配一把锁。所有对 "abc" 对象请求加锁的线程争用同一把独占锁。该方法为阻塞方法,请求到锁就返回;如果锁被其他线程占用,则一直阻塞直至获取到锁。

  2. 第二个参数表示持有该分布式锁的最长时间,单位为秒。比如 lock("abc", 3) 的意思是:如果超过 3 秒还没有调用 unlock 释放该锁,服务器会在服务端强制释放,并将锁继续分配给其他等待的线程。

UnLock(string) 方法的使用

UnLock 是释放分布式锁时调用的方法。客户端成功获取分布式锁后,服务器会返回一把锁的 key。客户端在执行完业务逻辑代码之后,必须显式调用 UnLock(key) 来释放该锁。如果没有调用 unlock 释放锁,tldb 会等待锁释放的超时时间,直至超时后强制释放该锁。

TryLock(string, int) 方法的使用

TryLock 与 Lock 相似,但行为有明显区别:

  • Lock 方法是阻塞的。调用 Lock 请求分布式锁时,如果该锁已经被占用,那么 Lock 方法会一直等待,直到 tldb 服务器将锁分配给它——这与程序中获取独占锁的方式一致。

  • TryLock 是非阻塞的。调用 TryLock 后会立即返回。如果成功获取到锁,tldb 会将标识该锁的 key 一并返回;如果该锁已经被占用,服务器将返回空数据。

Go 语言调用示例

因为 tldb 分布式锁的实现位于 MQ 模块,所以 Go 程序必须使用 tlmq-go 这个 MQ 客户端来调用锁方法。

导入依赖包:

import "github.com/donnie4w/tlmq-go/cli"

调用 Lock 的示例程序,Lock 方法是阻塞的:

sc := cli.NewMqClient("ws://127.0.0.1:5001", "mymq=123")
sc.Connect()
// 以上为客户端连接 MQ 服务器

key, err := sc.Lock("testlock", 3)
// Lock 中两个参数:第一个参数为字符串,即 tldb 服务器为 "testlock" 分配一个全局的分布式锁
// 第二个参数 3 为客户端持有该锁的最长时间,表示超过 3 秒没有释放锁时,
// tldb 服务器将在服务端强制释放该锁,并分配给其他请求锁的线程
if err != nil {
    // 获取锁失败,需检查 tldb 是否能正常访问
} else {
    defer sc.UnLock(key) // 获取锁成功后,必须在程序最后调用 UnLock
    // 执行业务逻辑程序
}

调用 TryLock 的示例程序,TryLock 是非阻塞的:

sc := cli.NewMqClient("ws://127.0.0.1:5001", "mymq=123")
sc.Connect()

if key, ok := sc.TryLock("testlock2", 3); ok {
    // ok 为 true,表示已经成功获取到分布式锁
    defer sc.UnLock(key) // 在程序最后释放锁对象
    // ...
}

Go 语言中,还可以用自旋方式搭配 TryLock 获取分布式锁,实现类似阻塞等待的效果:

sc := cli.NewMqClient("ws://127.0.0.1:5001", "mymq=123")
sc.Connect()

var key string
for {
    if v, ok := sc.TryLock("testlock", 3); ok {
        key = v
        break
    } else {
        <-time.After(100 * time.Millisecond)
    }
}
defer sc.UnLock(key)
// ...
// 业务逻辑代码

这段程序比较好理解:每隔 100 毫秒循环获取一次字符串 "testlock" 的分布式锁,直到成功为止。

Java 语言调用示例

Java 客户端为 tlmq-j,项目地址:https://github.com/donnie4w/tlmq-j

Maven 配置如下:

<dependency>
    <groupId>io.github.donnie4w</groupId>
    <artifactId>tlmq-j</artifactId>
    <version>0.0.2</version>
</dependency>

调用 Lock 方法:

MqClient mc = new SimpleClient("ws://127.0.0.1:5001", "mymq=123");
mc.connect();
// Java 连接服务器

String key = null;
try {
    key = mc.lock("testlock", 3); // 获取分布式锁
    // ... 执行业务逻辑程序
} finally {
    if (key != null) {
        mc.unLock(key); // 释放分布式锁
    }
}

调用 TryLock 方法:

MqClient mc = new SimpleClient("ws://127.0.0.1:5001", "mymq=123");
mc.connect();

String key = null;
try {
    key = mc.tryLock("testlock", 3); // 尝试获取分布式锁
    // ... 执行业务逻辑程序
} finally {
    if (key != null) {
        mc.unLock(key); // 释放分布式锁
    }
}

多线程并发测试数据

下面是 tldb 分布式锁的功能测试数据。

第一组测试:多线程并发调用 Lock 获取同一个对象锁

TestLock 多线程并发获取分布式锁测试结果

第二组测试:多线程并发使用自旋方式调用 TryLock 与 Lock 争用同一个对象锁

TryLock 自旋方式争用分布式锁测试结果

从测试数据可以看出,tldb 集群对分布式锁的正常加锁、排队等待、释放后再分配等行为都符合预期。TryLock 由于自旋间隔设置为 100ms,在获取锁时会有一定的时间延迟,这也是非阻塞锁的典型特征。




上一篇:十亿级用户名查重:从数据库到布隆过滤器的性能优化之路
下一篇:策略模式面试完整版:订单支付与工厂模式实战拆解
您需要登录后才可以回帖 登录 | 立即注册

关闭

站长推荐上一条 /1 下一条

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-10-28 13:09 , Processed in 0.084192 second(s), 43 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表