Skip to content

快速开始 ​

Simba 5 需要 Java 21 或更高版本(任务和后端调用运行在虚拟线程上)。starter 需要 Spring Boot 4(4.1 或更高版本); 各库按 Kotlin 2.2 或更高版本编译。

依赖 ​

添加 Spring Boot starter 和 一个 后端模块。

kotlin
implementation("me.ahoo.simba:simba-spring-boot-starter:${simbaVersion}")

// JDBC: MySQL 8 or PostgreSQL 14+
implementation("me.ahoo.simba:simba-jdbc:${simbaVersion}")
implementation("org.springframework.boot:spring-boot-starter-jdbc")
runtimeOnly("com.mysql:mysql-connector-j") // or org.postgresql:postgresql

// or Redis 7 (through Spring Data Redis)
implementation("me.ahoo.simba:simba-spring-redis:${simbaVersion}")
implementation("org.springframework.boot:spring-boot-starter-data-redis")

// or Zookeeper
implementation("me.ahoo.simba:simba-zookeeper:${simbaVersion}")

// 可选(5.1 起):挂起版的锁调用、withLock、事件 Flow
implementation("me.ahoo.simba:simba-coroutines:${simbaVersion}")
xml
<dependency>
    <groupId>me.ahoo.simba</groupId>
    <artifactId>simba-spring-boot-starter</artifactId>
    <version>${simba.version}</version>
</dependency>
<!-- plus one of: simba-jdbc, simba-spring-redis, simba-zookeeper -->
<dependency>
    <groupId>me.ahoo.simba</groupId>
    <artifactId>simba-spring-redis</artifactId>
    <version>${simba.version}</version>
</dependency>
<!-- 可选(5.1 起):协程适配 -->
<dependency>
    <groupId>me.ahoo.simba</groupId>
    <artifactId>simba-coroutines</artifactId>
    <version>${simba.version}</version>
</dependency>

simba-bom 统一所有 Simba 模块的版本。simba-coroutines(5.1 起)在任意后端之上提供挂起版的锁调用、withLock 和事件 Flow,参阅 锁。不使用 Spring 时,依赖 simba-core 加一个后端模块:simba-jdbc、 simba-zookeeper,或者 Redis 用 simba-spring-redis(它会带上 simba-redis)。单独的 simba-redis 只定义了 RedisScripting 端口:只有在你为其他 Redis 客户端自行实现适配器时,才单独使用它。

后端准备 ​

后端你需要提供说明
JDBC一个 DataSource bean 和三张表执行一次 init-simba-mysql.sql 或 init-simba-postgresql.sql;方言根据 DataSource 自动识别
Redis一个 RedisConnectionFactory bean(Spring Boot 根据 spring.data.redis.* 创建)Redis 7;单机、Sentinel 或 Cluster
Zookeepersimba.zookeeper.connect-stringSimba 自行打开 Zookeeper 客户端,每个会话一个

这些脚本位于 simba-jdbc 的 classpath 上,因此 Spring Boot 可以替你执行:

yaml
spring:
  sql:
    init:
      mode: always
      schema-locations: classpath:me/ahoo/simba/jdbc/init-simba-mysql.sql

当 classpath 上有多个后端模块时,请设置 simba.backend=jdbc|redis|zookeeper,否则启动会失败。 后端 页面列出了每个后端的表结构、键和运维说明。

starter 会创建一个 Simba bean。在需要使用下面任一 API 的地方注入它即可。

选举 ​

基础 API:本节点获得和失去任期时的回调。

kotlin
val election = simba.election("order-dispatcher")
election.start(object : LeadershipListener {
    override fun onElected(term: Term) = dispatcher.start(term.token)
    override fun onRevoked(term: Term, reason: RevokeReason) = dispatcher.stop()
})

// on shutdown: completes after the last onRevoked ran
election.stop().toCompletableFuture().join()

每个 onElected(term) 之后恰好跟随一个 onRevoked(term, reason)。保持回调简短;同一选举的回调逐个执行。

Leader 任务 ​

通常你希望代码在本节点担任 leader 期间 运行。runWhileLeader 每个任期运行一次代码块,运行在它自己的虚拟线程上,并在任期结束时中断它:

kotlin
val task = simba.election("report").runWhileLeader { term ->
    while (term.isValid) {
        reportStore.writeChunk(nextChunk(), fencingToken = term.token)
    }
}
// later
task.close()

只在 leader 上运行的周期性任务:

kotlin
simba.election("report").schedule(Schedule.fixedDelay(Duration.ofMinutes(1))) { term ->
    reportService.generate(fencingToken = term.token)
}
java
@Service
public class ReportJobs {
    @SimbaScheduled(name = "report", fixedDelay = "1m")
    public void generate(Term term) {
        reportService.generate(term.getToken());
    }
}

调度、交接和分区组详见 Leader 任务与分区。

锁 ​

acquire 返回一个 LockTerm:锁的凭证。关闭它即释放租约。

kotlin
simba.lock("invoice-42").acquire(Duration.ofSeconds(5)).use { term ->
    term.ensureValid(Duration.ofSeconds(2))      // enough time left for this step?
    invoiceStore.write(invoice, fencingToken = term.token)
}

如果在超时之前没有赢得任期,acquire 抛出 LockTimeoutException;tryAcquire() 只尝试一次,失败时返回 null。 详见 锁。

分区组 ​

把分区分散到存活的节点上;每个分区同一时刻只在一个节点上运行。

kotlin
simba.group("billing", partitions = 16).schedule(Schedule.fixedDelay(Duration.ofMinutes(1))) { term, partition ->
    billing.run(shard = partition, fencingToken = term.token)
}
java
@SimbaPartitioned(group = "billing", partitions = 16, fixedDelay = "1m")
public void bill(Term term, int partition) {
    billing.run(partition, term.getToken());
}

不使用 Spring ​

基于一个后端自行构建客户端;Simba 实现了 AutoCloseable,关闭它会立即释放所有租约。

kotlin
val backend = JdbcLeaseBackend.postgres(dataSource)          // or JdbcLeaseBackend.mysql(dataSource)
// val backend = RedisLeaseBackend(SpringRedisScripting(redisConnectionFactory))
// val backend = ZookeeperLeaseBackend("zk1:2181,zk2:2181,zk3:2181")

Simba.builder()
    .backend(backend)
    .config(SimbaConfig.builder().leaseDuration(Duration.ofSeconds(15)).build())
    .listener { event -> log.debug { "simba: $event" } }
    .build()
    .use { simba ->
        simba.election("report").schedule(Schedule.fixedRate(Duration.ofMinutes(1))) { term -> report(term.token) }
        awaitShutdown()
    }
backend.close()

每个进程只创建一个 Simba:所有选举、锁和组共享它的会话,因此一次续约 (keep-alive) 就能为它们全部续期。 Spring Boot 页面列出了 builder 的选项。

运行示例 ​

示例应用 针对你选择的后端使用了 @SimbaScheduled、 @SimbaPartitioned 和一个选举:

bash
./gradlew simba-example:run -PexampleBackend=redis   # jdbc | redis | zookeeper

启动两个实例,再停止其中一个,观察另一个接管。

基于 Apache License 2.0 发布。