Skip to content
Ahoo-WangPublic

About

Distributed lock service | 分布式锁服务

Topics

Resources

Contributing

Security policy

Stars

33 stars

Watchers

1 watching

Forks

Latest commit

 

History

805 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Simba

License GitHub release Maven Central Version Codacy Badge codecov Ask DeepWiki

English Documentation | 中文文档 | 中文 README

Simba is a JVM library for leader election and distributed locks. Each process opens one session on a backend you already operate — MySQL 8 or PostgreSQL 14+ (JDBC), Redis 7, or Zookeeper — and contends for named leases on it. The node that wins a lease gets a Term: its fencing token and how long it is still valid. There is no Simba server to run.

  • At most one valid term per name, as far as the local clock can tell: the local deadline is computed from when each keep-alive was sent and always passes before the backend can hand the lease to someone else.
  • Fencing tokens. Every term carries a token that strictly increases per name. A process paused by GC can resume after its term ended; resources that must reject such a stale owner check the token. Simba cannot do that for you, and does not promise "no split-brain".
  • One session per node. One keep-alive renews every election, lock and partition of the process.
  • Work bound to the term. Leader work runs on virtual threads, is interrupted when the term ends, and finishes before the lease is released.

Read Fencing and Failures before protecting anything that must never be written twice. Coming from 4.x? See Migrating to 5.0.

Installation

Java 21+; the starter needs Spring Boot 4 (4.1+). Add the Spring Boot starter and one backend:

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

// JDBC (MySQL 8 or PostgreSQL 14+): also create the tables once with
// me/ahoo/simba/jdbc/init-simba-mysql.sql or init-simba-postgresql.sql from simba-jdbc
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 with maxmemory-policy noeviction (an evicted lease would reset its fencing token)
implementation("me.ahoo.simba:simba-spring-redis:${simbaVersion}")
implementation("org.springframework.boot:spring-boot-starter-data-redis")

// or Zookeeper: set simba.zookeeper.connect-string (a node keeps its terms through pauses of about 5/12 of
// simba.session.lease-duration, about 6 s by default: raise it to tolerate longer GC or network stalls)
implementation("me.ahoo.simba:simba-zookeeper:${simbaVersion}")

// optional, since 5.1: suspending lock calls, withLock and an event Flow (kotlinx-coroutines)
implementation("me.ahoo.simba:simba-coroutines:${simbaVersion}")

The starter builds one Simba bean over the backend on the classpath. With several backend modules, set simba.backend=jdbc|redis|zookeeper. Without Spring:

val simba = Simba.builder()
    .backend(JdbcLeaseBackend.postgres(dataSource))   // or RedisLeaseBackend / ZookeeperLeaseBackend
    .build()                                          // AutoCloseable: close() frees every lease at once

Usage

Election

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

Every onElected(term) is followed by exactly one onRevoked(term, reason).

Leader work

simba.election("ingest").runWhileLeader { term ->
    while (term.isValid) sink.write(source.next(), fencingToken = term.token)
}

The block runs once per term and is interrupted when the term ends; stop() waits for it before releasing the lease.

Leader-only scheduling

@Service
public class ReportJobs {
    @SimbaScheduled(name = "report", fixedDelay = "1m")
    public void generate(Term term) {
        reportService.generate(term.getToken());
    }
}

Without Spring: simba.election("report").schedule(Schedule.fixedDelay(Duration.ofMinutes(1))) { term -> ... }.

Partitions

@SimbaPartitioned(group = "billing", partitions = 16, fixedDelay = "1m")
public void bill(Term term, int partition) {
    billing.run(partition, term.getToken());
}

Each of the 16 partitions runs on one live node at a time. Without Spring: simba.group("billing", 16).schedule(...).

Lock

simba.lock("invoice-42").acquire(Duration.ofSeconds(5)).use { term ->
    term.ensureValid(Duration.ofSeconds(2))
    invoiceStore.write(invoice, fencingToken = term.token)
}

The returned LockTerm is the credential: closing it releases the lease. Since 5.1, acquireAsync waits without blocking a thread; cancelling its future abandons the attempt:

simba.lock("invoice-42").acquireAsync(Duration.ofSeconds(5))
    .thenCompose { term -> invoiceStore.writeAsync(invoice, term.token).whenComplete { _, _ -> term.releaseAsync() } }

With simba-coroutines (since 5.1), the same as a suspending call:

simba.lock("invoice-42").withLock(Duration.ofSeconds(5)) { term -> invoiceStore.write(invoice, term.token) }

Observability

simba.snapshot() shows the session and every election of the node; the starter adds Micrometer metrics, the simba Actuator endpoint and the simba health indicator.

Testing

simba-testing provides an in-memory backend and virtual time for your unit tests; simba-tck provides FencingSpec to verify your token-checking resource and LeaseBackendSpec for backends.

Documentation

Why Simba The problem from first principles, guarantees, choosing an API and a backend
Quick Start Dependencies, backend setup, first code for each API
How Simba Works Sessions, local deadline, terms, tokens, election lifecycle, handover, timing
Fencing and Failures Fencing protocol and recipes, token sources, failure modes
Backends JDBC, Redis, Zookeeper: schema, keys, paths, operating notes
Spring Boot simba.* properties, beans, shutdown order
Elections and Leader Work Elections, runWhileLeader, schedules, partition groups, annotations
Locks lock(name) and LockTerm
Observability Snapshot, events, metrics, Actuator endpoint, health
Testing In-memory backend, virtual time, TCK
Migrating from 4.x API, property and storage mapping
API Reference Public types
Architecture, ADRs Internals and design decisions

The example application runs against any backend.

Used By

Contributing

See CONTRIBUTING.md for the workflow, quality gates and release process, the contributor guide for tests and the backend TCK, and SECURITY.md to report vulnerabilities.

About

Distributed lock service | 分布式锁服务

Topics

Resources

Contributing

Security policy

Stars

33 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages