Ticketing Java/Kotlin-Bibliothek
GitHub Maven CentralEin einziger TicketBroker bietet drei Aufrufstile zusammen an — Kotlin-Coroutines verwenden acquire, Java-Async verwendet acquireAsync (CompletableFuture), und Java-Blocking verwendet acquireBlocking. Egal welchen du nutzt, es ist derselbe Broker und dieselben Verbindungen.
Repository
Beispiel
Einfaches Beispiel
Einen Broker beim App-Start erstellen und teilen. wait=0 ist ein sofortiger Versuch ohne Queue, maximal 255 s; lease liegt bei 1–250 s. Das letzte Minimum-Work-Budget ist Pflicht und darf null sein. Teilsekunden werden aufgerundet; Werte außerhalb des Bereichs oder Budget über der normalisierten Lease werden vor dem Senden abgelehnt und nie geclampt.
Kotlin (coroutines):
val broker = TicketBroker.connect("127.0.0.1:5225")
broker.waitReady(Duration.ofSeconds(5))
val ticket = broker.acquire("key", Duration.ofSeconds(5), Duration.ofSeconds(30), Duration.ofSeconds(2))
val token = ticket.token
// In the same DB transaction: verify/update token high-water and perform the business write.
ticket.release()Java (blocking):
TicketBroker broker = TicketBroker.connect("127.0.0.1:5225");
broker.waitReadyBlocking(Duration.ofSeconds(5));
try (Ticket ticket = broker.acquireBlocking(
"key", Duration.ofSeconds(5), Duration.ofSeconds(30), Duration.ofSeconds(2))) {
long token = ticket.getToken();
// In the same DB transaction: verify/update token high-water and perform the business write.
}Automatisches Close/Drop ist ein begrenzter Best-effort-Release. Wenn das Ergebnis zählt, die explizite Release-API nutzen. Der Token muss den geschützten DB-Write in derselben Transaktion fencen.
Wissenswertes zum Verhalten
- Sobald ein Request-Byte gesendet worden sein könnte, führt eine verlorene definitive Antwort zu Indeterminate. Kein automatisches
Amit demselben Owner; der Caller betritt die Critical Section nicht. - Cancel vor Send ist unsent; nach possible-send wird die Session geschlossen. Wurde gleichzeitig ein Grant-Token geparst, folgt ein begrenzter kompensierender exact-token Release.
M, jedesEund malformed/oversized/unknown Responses sind session-fatal. Ungeklärte possible-send Acquires werden Indeterminate.Bist definitive Capacity-Ablehnung und kommt sofort. Kein interner Retry; der Caller kann mit App-Backoff und neuem Owner einen neuen Acquire starten.- Explizite/kompensierende Releases versuchen exact token nur bis absolut 5 Sekunden ab call/enqueue.
Rist Erfolg,Nbereits weg/nicht aktuell; keine finale Antwort ist Fehler, nie angenommener Erfolg. - Ticket gibt es nur bei positiver konservativer Restzeit und ausreichendem Work-Budget. Arbeit über 250 s ist vor Send unsupported.
- Token-DB-Fencing ist Pflicht: in derselben Transaktion
token <= stored_high_waterablehnen, High-water und Business Write aktualisieren, dann commit/rollback und release.
Sicherheitsoptionen (Token · TLS)
Alle Optionen sind optional. token sollte mit den client_tokens des Servers übereinstimmen, und TLS hat vier Modi: aus / System-Vertrauensspeicher / eine angegebene CA / Verifikation überspringen (nur für Tests).
val broker = TicketBroker.builder()
.addrs("10.0.0.1:5225", "10.0.0.2:5225", "10.0.0.3:5225")
.token("123")
.tls(TlsMode.SystemRoots)
// .tls(TlsMode.Ca("ca.crt"))
// .tls(TlsMode.InsecureSkipVerify)
.connect()In Java verwendest du die statischen Fabrikmethoden TlsMode.systemRoots() / TlsMode.ca("ca.crt") / TlsMode.insecureSkipVerify().
Die serverseitige Token-, TLS- und Cluster-Konfiguration kannst du auf der Seite Ticketing-Serverbereitstellung erzeugen.
Spring Virtual Threads
Läuft Spring MVC auf virtuellen Threads, nutze die *Blocking-Aufrufe — in Java wie in Kotlin. Ein nicht-suspendierender Controller kann acquire nicht aufrufen, daher ist acquireBlocking auch in Kotlin der normale Weg. Sie laufen nicht über Coroutines und parken nur auf der Antwort, der Carrier-Thread bleibt also frei.
@RestController
class OrderController(private val broker: TicketBroker) {
@PostMapping("/orders/{id}")
fun place(@PathVariable id: String): String {
broker.acquireBlocking("order-$id", Duration.ofSeconds(5), Duration.ofSeconds(30)).use {
// kritischer Abschnitt
}
return "ok"
}
}Die Hintergrundfreigabe hinter close() (try-with-resources / use) läuft standardmäßig auf einem Virtual-Thread-Executor und staut sich daher nicht hinter einem Pool fester Größe. Übergib deinen eigenen Executor, um stattdessen den von Spring zu nutzen.
TicketBroker.builder()
.addrs("127.0.0.1:5225")
.executor(applicationTaskExecutor)
.connect()Mit WebFlux oder Coroutine-Controllern verwendest du weiterhin die suspend-Aufrufe.