Implement lanes
This commit is contained in:
parent
dfc393c953
commit
83c064220f
|
@ -9,6 +9,7 @@ import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -22,7 +23,6 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import reactor.core.Disposable;
|
import reactor.core.Disposable;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.core.publisher.Sinks.EmitFailureHandler;
|
|
||||||
import reactor.core.scheduler.Schedulers;
|
import reactor.core.scheduler.Schedulers;
|
||||||
|
|
||||||
public class AtomixReactiveApi implements ReactiveApi {
|
public class AtomixReactiveApi implements ReactiveApi {
|
||||||
|
@ -60,14 +60,17 @@ public class AtomixReactiveApi implements ReactiveApi {
|
||||||
@Nullable DiskSessionsManager diskSessions,
|
@Nullable DiskSessionsManager diskSessions,
|
||||||
@NotNull Set<ResultingEventTransformer> resultingEventTransformerSet) {
|
@NotNull Set<ResultingEventTransformer> resultingEventTransformerSet) {
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
var kafkaTDLibRequestProducer = new KafkaTdlibRequestProducer(kafkaParameters);
|
|
||||||
var kafkaTDLibResponseConsumer = new KafkaTdlibResponseConsumer(kafkaParameters);
|
|
||||||
var kafkaClientBoundConsumer = new KafkaClientBoundConsumer(kafkaParameters);
|
|
||||||
var kafkaTdlibClientsChannels = new KafkaTdlibClientsChannels(kafkaTDLibRequestProducer,
|
|
||||||
kafkaTDLibResponseConsumer,
|
|
||||||
kafkaClientBoundConsumer
|
|
||||||
);
|
|
||||||
if (mode != AtomixReactiveApiMode.SERVER) {
|
if (mode != AtomixReactiveApiMode.SERVER) {
|
||||||
|
var kafkaTDLibRequestProducer = new KafkaTdlibRequestProducer(kafkaParameters);
|
||||||
|
var kafkaTDLibResponseConsumer = new KafkaTdlibResponseConsumer(kafkaParameters);
|
||||||
|
var kafkaClientBoundConsumers = new HashMap<String, KafkaClientBoundConsumer>();
|
||||||
|
for (String lane : kafkaParameters.getAllLanes()) {
|
||||||
|
kafkaClientBoundConsumers.put(lane, new KafkaClientBoundConsumer(kafkaParameters, lane));
|
||||||
|
}
|
||||||
|
var kafkaTdlibClientsChannels = new KafkaTdlibClientsChannels(kafkaTDLibRequestProducer,
|
||||||
|
kafkaTDLibResponseConsumer,
|
||||||
|
kafkaClientBoundConsumers
|
||||||
|
);
|
||||||
this.kafkaSharedTdlibClients = new KafkaSharedTdlibClients(kafkaTdlibClientsChannels);
|
this.kafkaSharedTdlibClients = new KafkaSharedTdlibClients(kafkaTdlibClientsChannels);
|
||||||
this.client = new LiveAtomixReactiveApiClient(kafkaSharedTdlibClients);
|
this.client = new LiveAtomixReactiveApiClient(kafkaSharedTdlibClients);
|
||||||
} else {
|
} else {
|
||||||
|
@ -77,10 +80,13 @@ public class AtomixReactiveApi implements ReactiveApi {
|
||||||
if (mode != AtomixReactiveApiMode.CLIENT) {
|
if (mode != AtomixReactiveApiMode.CLIENT) {
|
||||||
var kafkaTDLibRequestConsumer = new KafkaTdlibRequestConsumer(kafkaParameters);
|
var kafkaTDLibRequestConsumer = new KafkaTdlibRequestConsumer(kafkaParameters);
|
||||||
var kafkaTDLibResponseProducer = new KafkaTdlibResponseProducer(kafkaParameters);
|
var kafkaTDLibResponseProducer = new KafkaTdlibResponseProducer(kafkaParameters);
|
||||||
var kafkaClientBoundProducer = new KafkaClientBoundProducer(kafkaParameters);
|
var kafkaClientBoundProducers = new HashMap<String, KafkaClientBoundProducer>();
|
||||||
|
for (String lane : kafkaParameters.getAllLanes()) {
|
||||||
|
kafkaClientBoundProducers.put(lane, new KafkaClientBoundProducer(kafkaParameters, lane));
|
||||||
|
}
|
||||||
var kafkaTDLibServer = new KafkaTdlibServersChannels(kafkaTDLibRequestConsumer,
|
var kafkaTDLibServer = new KafkaTdlibServersChannels(kafkaTDLibRequestConsumer,
|
||||||
kafkaTDLibResponseProducer,
|
kafkaTDLibResponseProducer,
|
||||||
kafkaClientBoundProducer
|
kafkaClientBoundProducers
|
||||||
);
|
);
|
||||||
this.kafkaSharedTdlibServers = new KafkaSharedTdlibServers(kafkaTDLibServer);
|
this.kafkaSharedTdlibServers = new KafkaSharedTdlibServers(kafkaTDLibServer);
|
||||||
} else {
|
} else {
|
||||||
|
@ -122,6 +128,7 @@ public class AtomixReactiveApi implements ReactiveApi {
|
||||||
return createSession(new LoadSessionFromDiskRequest(id,
|
return createSession(new LoadSessionFromDiskRequest(id,
|
||||||
diskSession.token,
|
diskSession.token,
|
||||||
diskSession.phoneNumber,
|
diskSession.phoneNumber,
|
||||||
|
diskSession.lane,
|
||||||
true
|
true
|
||||||
));
|
));
|
||||||
})
|
})
|
||||||
|
@ -152,41 +159,49 @@ public class AtomixReactiveApi implements ReactiveApi {
|
||||||
boolean loadedFromDisk;
|
boolean loadedFromDisk;
|
||||||
long userId;
|
long userId;
|
||||||
String botToken;
|
String botToken;
|
||||||
|
String lane;
|
||||||
Long phoneNumber;
|
Long phoneNumber;
|
||||||
if (req instanceof CreateBotSessionRequest createBotSessionRequest) {
|
if (req instanceof CreateBotSessionRequest createBotSessionRequest) {
|
||||||
loadedFromDisk = false;
|
loadedFromDisk = false;
|
||||||
userId = createBotSessionRequest.userId();
|
userId = createBotSessionRequest.userId();
|
||||||
botToken = createBotSessionRequest.token();
|
botToken = createBotSessionRequest.token();
|
||||||
phoneNumber = null;
|
phoneNumber = null;
|
||||||
|
lane = createBotSessionRequest.lane();
|
||||||
reactiveApiPublisher = ReactiveApiPublisher.fromToken(kafkaSharedTdlibServers, resultingEventTransformerSet,
|
reactiveApiPublisher = ReactiveApiPublisher.fromToken(kafkaSharedTdlibServers, resultingEventTransformerSet,
|
||||||
userId,
|
userId,
|
||||||
botToken
|
botToken,
|
||||||
|
lane
|
||||||
);
|
);
|
||||||
} else if (req instanceof CreateUserSessionRequest createUserSessionRequest) {
|
} else if (req instanceof CreateUserSessionRequest createUserSessionRequest) {
|
||||||
loadedFromDisk = false;
|
loadedFromDisk = false;
|
||||||
userId = createUserSessionRequest.userId();
|
userId = createUserSessionRequest.userId();
|
||||||
botToken = null;
|
botToken = null;
|
||||||
phoneNumber = createUserSessionRequest.phoneNumber();
|
phoneNumber = createUserSessionRequest.phoneNumber();
|
||||||
|
lane = createUserSessionRequest.lane();
|
||||||
reactiveApiPublisher = ReactiveApiPublisher.fromPhoneNumber(kafkaSharedTdlibServers, resultingEventTransformerSet,
|
reactiveApiPublisher = ReactiveApiPublisher.fromPhoneNumber(kafkaSharedTdlibServers, resultingEventTransformerSet,
|
||||||
userId,
|
userId,
|
||||||
phoneNumber
|
phoneNumber,
|
||||||
|
lane
|
||||||
);
|
);
|
||||||
} else if (req instanceof LoadSessionFromDiskRequest loadSessionFromDiskRequest) {
|
} else if (req instanceof LoadSessionFromDiskRequest loadSessionFromDiskRequest) {
|
||||||
loadedFromDisk = true;
|
loadedFromDisk = true;
|
||||||
userId = loadSessionFromDiskRequest.userId();
|
userId = loadSessionFromDiskRequest.userId();
|
||||||
botToken = loadSessionFromDiskRequest.token();
|
botToken = loadSessionFromDiskRequest.token();
|
||||||
phoneNumber = loadSessionFromDiskRequest.phoneNumber();
|
phoneNumber = loadSessionFromDiskRequest.phoneNumber();
|
||||||
|
lane = loadSessionFromDiskRequest.lane();
|
||||||
if (loadSessionFromDiskRequest.phoneNumber() != null) {
|
if (loadSessionFromDiskRequest.phoneNumber() != null) {
|
||||||
reactiveApiPublisher = ReactiveApiPublisher.fromPhoneNumber(kafkaSharedTdlibServers,
|
reactiveApiPublisher = ReactiveApiPublisher.fromPhoneNumber(kafkaSharedTdlibServers,
|
||||||
resultingEventTransformerSet,
|
resultingEventTransformerSet,
|
||||||
userId,
|
userId,
|
||||||
phoneNumber
|
phoneNumber,
|
||||||
|
lane
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
reactiveApiPublisher = ReactiveApiPublisher.fromToken(kafkaSharedTdlibServers,
|
reactiveApiPublisher = ReactiveApiPublisher.fromToken(kafkaSharedTdlibServers,
|
||||||
resultingEventTransformerSet,
|
resultingEventTransformerSet,
|
||||||
userId,
|
userId,
|
||||||
botToken
|
botToken,
|
||||||
|
lane
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -231,7 +246,7 @@ public class AtomixReactiveApi implements ReactiveApi {
|
||||||
|
|
||||||
if (!loadedFromDisk) {
|
if (!loadedFromDisk) {
|
||||||
// Create the disk session configuration
|
// Create the disk session configuration
|
||||||
var diskSession = new DiskSession(botToken, phoneNumber);
|
var diskSession = new DiskSession(botToken, phoneNumber, lane);
|
||||||
return Mono.<Void>fromCallable(() -> {
|
return Mono.<Void>fromCallable(() -> {
|
||||||
Objects.requireNonNull(diskSessions);
|
Objects.requireNonNull(diskSessions);
|
||||||
synchronized (diskSessions) {
|
synchronized (diskSessions) {
|
||||||
|
|
|
@ -109,11 +109,17 @@ public class Cli {
|
||||||
private static void createSession(ReactiveApi api, String commandArgs) {
|
private static void createSession(ReactiveApi api, String commandArgs) {
|
||||||
var parts = commandArgs.split(" ");
|
var parts = commandArgs.split(" ");
|
||||||
boolean invalid = false;
|
boolean invalid = false;
|
||||||
if (parts.length == 3) {
|
if (parts.length == 4 || parts.length == 3) {
|
||||||
|
String lane;
|
||||||
|
if (parts.length == 4) {
|
||||||
|
lane = parts[3];
|
||||||
|
} else {
|
||||||
|
lane = "";
|
||||||
|
}
|
||||||
CreateSessionRequest request = switch (parts[0]) {
|
CreateSessionRequest request = switch (parts[0]) {
|
||||||
case "bot" -> new CreateBotSessionRequest(Long.parseLong(parts[1]), parts[2]);
|
case "bot" -> new CreateBotSessionRequest(Long.parseLong(parts[1]), parts[2], lane);
|
||||||
case "user" -> new CreateUserSessionRequest(Long.parseLong(parts[1]),
|
case "user" -> new CreateUserSessionRequest(Long.parseLong(parts[1]),
|
||||||
Long.parseLong(parts[2]));
|
Long.parseLong(parts[2]), lane);
|
||||||
default -> {
|
default -> {
|
||||||
invalid = true;
|
invalid = true;
|
||||||
yield null;
|
yield null;
|
||||||
|
@ -129,7 +135,7 @@ public class Cli {
|
||||||
invalid = true;
|
invalid = true;
|
||||||
}
|
}
|
||||||
if (invalid) {
|
if (invalid) {
|
||||||
LOG.error("Syntax: CreateSession <\"bot\"|\"user\"> <userid> <token|phoneNumber>");
|
LOG.error("Syntax: CreateSession <\"bot\"|\"user\"> <userid> <token|phoneNumber> [lane]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,11 +11,14 @@ public class ClusterSettings {
|
||||||
|
|
||||||
public String id;
|
public String id;
|
||||||
public List<String> kafkaBootstrapServers;
|
public List<String> kafkaBootstrapServers;
|
||||||
|
public List<String> lanes;
|
||||||
|
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
public ClusterSettings(@JsonProperty(required = true, value = "id") String id,
|
public ClusterSettings(@JsonProperty(required = true, value = "id") String id,
|
||||||
@JsonProperty(required = true, value = "kafkaBootstrapServers") List<String> kafkaBootstrapServers) {
|
@JsonProperty(required = true, value = "kafkaBootstrapServers") List<String> kafkaBootstrapServers,
|
||||||
|
@JsonProperty(required = true, value = "lanes") List<String> lanes) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.kafkaBootstrapServers = kafkaBootstrapServers;
|
this.kafkaBootstrapServers = kafkaBootstrapServers;
|
||||||
|
this.lanes = lanes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,20 @@
|
||||||
package it.tdlight.reactiveapi;
|
package it.tdlight.reactiveapi;
|
||||||
|
|
||||||
import com.google.common.primitives.Ints;
|
|
||||||
import com.google.common.primitives.Longs;
|
|
||||||
import it.tdlight.reactiveapi.CreateSessionRequest.CreateBotSessionRequest;
|
import it.tdlight.reactiveapi.CreateSessionRequest.CreateBotSessionRequest;
|
||||||
import it.tdlight.reactiveapi.CreateSessionRequest.CreateUserSessionRequest;
|
import it.tdlight.reactiveapi.CreateSessionRequest.CreateUserSessionRequest;
|
||||||
import it.tdlight.reactiveapi.CreateSessionRequest.LoadSessionFromDiskRequest;
|
import it.tdlight.reactiveapi.CreateSessionRequest.LoadSessionFromDiskRequest;
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import org.apache.kafka.common.errors.SerializationException;
|
|
||||||
|
|
||||||
public sealed interface CreateSessionRequest permits CreateUserSessionRequest, CreateBotSessionRequest,
|
public sealed interface CreateSessionRequest permits CreateUserSessionRequest, CreateBotSessionRequest,
|
||||||
LoadSessionFromDiskRequest {
|
LoadSessionFromDiskRequest {
|
||||||
|
|
||||||
long userId();
|
long userId();
|
||||||
|
|
||||||
record CreateUserSessionRequest(long userId, long phoneNumber) implements CreateSessionRequest {}
|
record CreateUserSessionRequest(long userId, long phoneNumber, String lane) implements CreateSessionRequest {}
|
||||||
|
|
||||||
record CreateBotSessionRequest(long userId, String token) implements CreateSessionRequest {}
|
record CreateBotSessionRequest(long userId, String token, String lane) implements CreateSessionRequest {}
|
||||||
|
|
||||||
record LoadSessionFromDiskRequest(long userId, String token, Long phoneNumber, boolean createNew) implements
|
record LoadSessionFromDiskRequest(long userId, String token, Long phoneNumber, String lane,
|
||||||
CreateSessionRequest {
|
boolean createNew) implements CreateSessionRequest {
|
||||||
|
|
||||||
public LoadSessionFromDiskRequest {
|
public LoadSessionFromDiskRequest {
|
||||||
if ((token == null) == (phoneNumber == null)) {
|
if ((token == null) == (phoneNumber == null)) {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import java.util.Objects;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@JsonInclude(Include.NON_NULL)
|
@JsonInclude(Include.NON_NULL)
|
||||||
|
@ -13,12 +14,16 @@ public class DiskSession {
|
||||||
public String token;
|
public String token;
|
||||||
@Nullable
|
@Nullable
|
||||||
public Long phoneNumber;
|
public Long phoneNumber;
|
||||||
|
@Nullable
|
||||||
|
public String lane;
|
||||||
|
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
public DiskSession(@JsonProperty("token") @Nullable String token,
|
public DiskSession(@JsonProperty("token") @Nullable String token,
|
||||||
@JsonProperty("phoneNumber") @Nullable Long phoneNumber) {
|
@JsonProperty("phoneNumber") @Nullable Long phoneNumber,
|
||||||
|
@JsonProperty("lane") @Nullable String lane) {
|
||||||
this.token = token;
|
this.token = token;
|
||||||
this.phoneNumber = phoneNumber;
|
this.phoneNumber = phoneNumber;
|
||||||
|
this.lane = Objects.requireNonNullElse(lane, "");
|
||||||
this.validate();
|
this.validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package it.tdlight.reactiveapi;
|
package it.tdlight.reactiveapi;
|
||||||
|
|
||||||
import org.apache.kafka.common.serialization.Serializer;
|
public enum KafkaChannelCodec {
|
||||||
|
|
||||||
public enum KafkaChannelName {
|
|
||||||
CLIENT_BOUND_EVENT("event", ClientBoundEventSerializer.class, ClientBoundEventDeserializer.class),
|
CLIENT_BOUND_EVENT("event", ClientBoundEventSerializer.class, ClientBoundEventDeserializer.class),
|
||||||
TDLIB_REQUEST("request", TdlibRequestSerializer.class, TdlibRequestDeserializer.class),
|
TDLIB_REQUEST("request", TdlibRequestSerializer.class, TdlibRequestDeserializer.class),
|
||||||
TDLIB_RESPONSE("response", TdlibResponseSerializer.class, TdlibResponseDeserializer.class);
|
TDLIB_RESPONSE("response", TdlibResponseSerializer.class, TdlibResponseDeserializer.class);
|
||||||
|
@ -11,7 +9,7 @@ public enum KafkaChannelName {
|
||||||
private final Class<?> serializerClass;
|
private final Class<?> serializerClass;
|
||||||
private final Class<?> deserializerClass;
|
private final Class<?> deserializerClass;
|
||||||
|
|
||||||
KafkaChannelName(String kafkaName,
|
KafkaChannelCodec(String kafkaName,
|
||||||
Class<?> serializerClass,
|
Class<?> serializerClass,
|
||||||
Class<?> deserializerClass) {
|
Class<?> deserializerClass) {
|
||||||
this.name = kafkaName;
|
this.name = kafkaName;
|
|
@ -4,13 +4,27 @@ import it.tdlight.reactiveapi.Event.ClientBoundEvent;
|
||||||
|
|
||||||
public class KafkaClientBoundConsumer extends KafkaConsumer<ClientBoundEvent> {
|
public class KafkaClientBoundConsumer extends KafkaConsumer<ClientBoundEvent> {
|
||||||
|
|
||||||
public KafkaClientBoundConsumer(KafkaParameters kafkaParameters) {
|
private final String lane;
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
public KafkaClientBoundConsumer(KafkaParameters kafkaParameters, String lane) {
|
||||||
super(kafkaParameters);
|
super(kafkaParameters);
|
||||||
|
this.lane = lane;
|
||||||
|
if (lane.isBlank()) {
|
||||||
|
this.name = KafkaChannelCodec.CLIENT_BOUND_EVENT.getKafkaName();
|
||||||
|
} else {
|
||||||
|
this.name = KafkaChannelCodec.CLIENT_BOUND_EVENT.getKafkaName() + "-" + lane;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KafkaChannelName getChannelName() {
|
public KafkaChannelCodec getChannelCodec() {
|
||||||
return KafkaChannelName.CLIENT_BOUND_EVENT;
|
return KafkaChannelCodec.CLIENT_BOUND_EVENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getChannelName() {
|
||||||
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,16 +1,27 @@
|
||||||
package it.tdlight.reactiveapi;
|
package it.tdlight.reactiveapi;
|
||||||
|
|
||||||
import it.tdlight.reactiveapi.Event.ClientBoundEvent;
|
import it.tdlight.reactiveapi.Event.ClientBoundEvent;
|
||||||
import it.tdlight.reactiveapi.Event.OnRequest;
|
|
||||||
|
|
||||||
public class KafkaClientBoundProducer extends KafkaProducer<ClientBoundEvent> {
|
public class KafkaClientBoundProducer extends KafkaProducer<ClientBoundEvent> {
|
||||||
|
|
||||||
public KafkaClientBoundProducer(KafkaParameters kafkaParameters) {
|
private final String name;
|
||||||
|
|
||||||
|
public KafkaClientBoundProducer(KafkaParameters kafkaParameters, String lane) {
|
||||||
super(kafkaParameters);
|
super(kafkaParameters);
|
||||||
|
if (lane.isBlank()) {
|
||||||
|
this.name = KafkaChannelCodec.CLIENT_BOUND_EVENT.getKafkaName();
|
||||||
|
} else {
|
||||||
|
this.name = KafkaChannelCodec.CLIENT_BOUND_EVENT.getKafkaName() + "-" + lane;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KafkaChannelName getChannelName() {
|
public KafkaChannelCodec getChannelCodec() {
|
||||||
return KafkaChannelName.CLIENT_BOUND_EVENT;
|
return KafkaChannelCodec.CLIENT_BOUND_EVENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getChannelName() {
|
||||||
|
return name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,8 @@ import it.tdlight.common.Init;
|
||||||
import it.tdlight.common.utils.CantLoadLibrary;
|
import it.tdlight.common.utils.CantLoadLibrary;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
@ -20,7 +20,6 @@ import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.reactivestreams.Publisher;
|
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.SignalType;
|
import reactor.core.publisher.SignalType;
|
||||||
import reactor.kafka.receiver.KafkaReceiver;
|
import reactor.kafka.receiver.KafkaReceiver;
|
||||||
|
@ -37,7 +36,7 @@ public abstract class KafkaConsumer<K> {
|
||||||
this.kafkaParameters = kafkaParameters;
|
this.kafkaParameters = kafkaParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
public KafkaReceiver<Integer, K> createReceiver(@NotNull String groupId, @Nullable Long userId) {
|
public KafkaReceiver<Integer, K> createReceiver(@NotNull String kafkaGroupId) {
|
||||||
try {
|
try {
|
||||||
Init.start();
|
Init.start();
|
||||||
} catch (CantLoadLibrary e) {
|
} catch (CantLoadLibrary e) {
|
||||||
|
@ -46,10 +45,10 @@ public abstract class KafkaConsumer<K> {
|
||||||
}
|
}
|
||||||
Map<String, Object> props = new HashMap<>();
|
Map<String, Object> props = new HashMap<>();
|
||||||
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaParameters.bootstrapServers());
|
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaParameters.bootstrapServers());
|
||||||
props.put(ConsumerConfig.CLIENT_ID_CONFIG, kafkaParameters.clientId() + (userId != null ? ("_" + userId) : ""));
|
props.put(ConsumerConfig.CLIENT_ID_CONFIG, kafkaParameters.clientId());
|
||||||
props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
|
props.put(ConsumerConfig.GROUP_ID_CONFIG, kafkaGroupId);
|
||||||
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, IntegerDeserializer.class);
|
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, IntegerDeserializer.class);
|
||||||
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, getChannelName().getDeserializerClass());
|
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, getChannelCodec().getDeserializerClass());
|
||||||
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");
|
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");
|
||||||
props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, toIntExact(Duration.ofMinutes(5).toMillis()));
|
props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, toIntExact(Duration.ofMinutes(5).toMillis()));
|
||||||
if (!isQuickResponse()) {
|
if (!isQuickResponse()) {
|
||||||
|
@ -63,20 +62,16 @@ public abstract class KafkaConsumer<K> {
|
||||||
.commitBatchSize(isQuickResponse() ? 64 : 1024)
|
.commitBatchSize(isQuickResponse() ? 64 : 1024)
|
||||||
.maxCommitAttempts(5)
|
.maxCommitAttempts(5)
|
||||||
.maxDeferredCommits((isQuickResponse() ? 64 : 1024) * 5);
|
.maxDeferredCommits((isQuickResponse() ? 64 : 1024) * 5);
|
||||||
Pattern pattern;
|
|
||||||
if (userId == null) {
|
|
||||||
pattern = Pattern.compile("tdlib\\." + getChannelName() + "\\.\\d+");
|
|
||||||
} else {
|
|
||||||
pattern = Pattern.compile("tdlib\\." + getChannelName() + "\\." + userId);
|
|
||||||
}
|
|
||||||
ReceiverOptions<Integer, K> options = receiverOptions
|
ReceiverOptions<Integer, K> options = receiverOptions
|
||||||
.subscription(pattern)
|
.subscription(List.of("tdlib." + getChannelName()))
|
||||||
.addAssignListener(partitions -> LOG.debug("onPartitionsAssigned {}", partitions))
|
.addAssignListener(partitions -> LOG.debug("onPartitionsAssigned {}", partitions))
|
||||||
.addRevokeListener(partitions -> LOG.debug("onPartitionsRevoked {}", partitions));
|
.addRevokeListener(partitions -> LOG.debug("onPartitionsRevoked {}", partitions));
|
||||||
return KafkaReceiver.create(options);
|
return KafkaReceiver.create(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract KafkaChannelName getChannelName();
|
public abstract KafkaChannelCodec getChannelCodec();
|
||||||
|
|
||||||
|
public abstract String getChannelName();
|
||||||
|
|
||||||
public abstract boolean isQuickResponse();
|
public abstract boolean isQuickResponse();
|
||||||
|
|
||||||
|
@ -103,19 +98,15 @@ public abstract class KafkaConsumer<K> {
|
||||||
+ " with max.poll.records.")));
|
+ " with max.poll.records.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Flux<Timestamped<K>> consumeMessages(@NotNull String subGroupId, long userId) {
|
|
||||||
return consumeMessagesInternal(subGroupId, userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Flux<Timestamped<K>> consumeMessages(@NotNull String subGroupId) {
|
public Flux<Timestamped<K>> consumeMessages(@NotNull String subGroupId) {
|
||||||
return consumeMessagesInternal(subGroupId, null);
|
return consumeMessagesInternal(subGroupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Flux<Timestamped<K>> consumeMessagesInternal(@NotNull String subGroupId, @Nullable Long userId) {
|
private Flux<Timestamped<K>> consumeMessagesInternal(@NotNull String subGroupId) {
|
||||||
return createReceiver(kafkaParameters.groupId() + "-" + subGroupId, userId)
|
return createReceiver(kafkaParameters.groupId() + (subGroupId.isBlank() ? "" : ("-" + subGroupId)))
|
||||||
.receiveAutoAck(isQuickResponse() ? 1 : 4)
|
.receiveAutoAck(isQuickResponse() ? 1 : 4)
|
||||||
.concatMap(Function.identity())
|
.concatMap(Function.identity())
|
||||||
.log("consume-messages" + (userId != null ? "-" + userId : ""),
|
.log("consume-messages",
|
||||||
Level.FINEST,
|
Level.FINEST,
|
||||||
SignalType.REQUEST,
|
SignalType.REQUEST,
|
||||||
SignalType.ON_NEXT,
|
SignalType.ON_NEXT,
|
||||||
|
|
|
@ -1,10 +1,23 @@
|
||||||
package it.tdlight.reactiveapi;
|
package it.tdlight.reactiveapi;
|
||||||
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public record KafkaParameters(String groupId, String clientId, String bootstrapServers) {
|
public record KafkaParameters(String groupId, String clientId, String bootstrapServers, List<String> lanes) {
|
||||||
|
|
||||||
public KafkaParameters(ClusterSettings clusterSettings, String clientId) {
|
public KafkaParameters(ClusterSettings clusterSettings, String clientId) {
|
||||||
this(clientId, clientId, String.join(",", clusterSettings.kafkaBootstrapServers));
|
this(clientId,
|
||||||
|
clientId,
|
||||||
|
String.join(",", clusterSettings.kafkaBootstrapServers),
|
||||||
|
List.copyOf(clusterSettings.lanes)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getAllLanes() {
|
||||||
|
var lanes = new LinkedHashSet<String>(this.lanes.size() + 1);
|
||||||
|
lanes.add("");
|
||||||
|
lanes.addAll(this.lanes);
|
||||||
|
return lanes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package it.tdlight.reactiveapi;
|
package it.tdlight.reactiveapi;
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
@ -31,22 +30,22 @@ public abstract class KafkaProducer<K> {
|
||||||
props.put(ProducerConfig.LINGER_MS_CONFIG, "20");
|
props.put(ProducerConfig.LINGER_MS_CONFIG, "20");
|
||||||
props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy");
|
props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy");
|
||||||
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, IntegerSerializer.class);
|
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, IntegerSerializer.class);
|
||||||
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, getChannelName().getSerializerClass());
|
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, getChannelCodec().getSerializerClass());
|
||||||
SenderOptions<Integer, K> senderOptions = SenderOptions.create(props);
|
SenderOptions<Integer, K> senderOptions = SenderOptions.create(props);
|
||||||
|
|
||||||
sender = KafkaSender.create(senderOptions.maxInFlight(1024));
|
sender = KafkaSender.create(senderOptions.maxInFlight(1024));
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract KafkaChannelName getChannelName();
|
public abstract KafkaChannelCodec getChannelCodec();
|
||||||
|
|
||||||
public Mono<Void> sendMessages(long userId, Flux<K> eventsFlux) {
|
public abstract String getChannelName();
|
||||||
var userTopic = new UserTopic(getChannelName(), userId);
|
|
||||||
|
public Mono<Void> sendMessages(Flux<K> eventsFlux) {
|
||||||
|
var channelName = getChannelName();
|
||||||
return eventsFlux
|
return eventsFlux
|
||||||
.<SenderRecord<Integer, K, Integer>>map(event -> SenderRecord.create(new ProducerRecord<>(
|
.<SenderRecord<Integer, K, Integer>>map(event ->
|
||||||
userTopic.getTopic(),
|
SenderRecord.create(new ProducerRecord<>(channelName, event), null))
|
||||||
event
|
.log("produce-messages-" + channelName,
|
||||||
), null))
|
|
||||||
.log("produce-messages-" + userTopic,
|
|
||||||
Level.FINEST,
|
Level.FINEST,
|
||||||
SignalType.REQUEST,
|
SignalType.REQUEST,
|
||||||
SignalType.ON_NEXT,
|
SignalType.ON_NEXT,
|
||||||
|
|
|
@ -8,9 +8,12 @@ import it.tdlight.reactiveapi.Event.OnRequest;
|
||||||
import it.tdlight.reactiveapi.Event.OnResponse;
|
import it.tdlight.reactiveapi.Event.OnResponse;
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import reactor.core.Disposable;
|
import reactor.core.Disposable;
|
||||||
|
@ -32,16 +35,17 @@ public class KafkaSharedTdlibClients implements Closeable {
|
||||||
private final Disposable requestsSub;
|
private final Disposable requestsSub;
|
||||||
private final AtomicReference<Disposable> eventsSub = new AtomicReference<>();
|
private final AtomicReference<Disposable> eventsSub = new AtomicReference<>();
|
||||||
private final Flux<Timestamped<OnResponse<Object>>> responses;
|
private final Flux<Timestamped<OnResponse<Object>>> responses;
|
||||||
private final Flux<Timestamped<ClientBoundEvent>> events;
|
private final Map<String, Flux<Timestamped<ClientBoundEvent>>> events;
|
||||||
private final Many<OnRequest<?>> requests = Sinks.many().unicast()
|
private final Many<OnRequest<?>> requests = Sinks.many().unicast()
|
||||||
.onBackpressureBuffer(Queues.<OnRequest<?>>get(65535).get());
|
.onBackpressureBuffer(Queues.<OnRequest<?>>get(65535).get());
|
||||||
|
|
||||||
public KafkaSharedTdlibClients(KafkaTdlibClientsChannels kafkaTdlibClientsChannels) {
|
public KafkaSharedTdlibClients(KafkaTdlibClientsChannels kafkaTdlibClientsChannels) {
|
||||||
this.kafkaTdlibClientsChannels = kafkaTdlibClientsChannels;
|
this.kafkaTdlibClientsChannels = kafkaTdlibClientsChannels;
|
||||||
this.responses = kafkaTdlibClientsChannels.response().consumeMessages("td-responses");
|
this.responses = kafkaTdlibClientsChannels.response().consumeMessages("td-responses");
|
||||||
this.events = kafkaTdlibClientsChannels.events().consumeMessages("td-handler");
|
this.events = kafkaTdlibClientsChannels.events().entrySet().stream()
|
||||||
|
.collect(Collectors.toUnmodifiableMap(Entry::getKey, e -> e.getValue().consumeMessages(e.getKey())));
|
||||||
this.requestsSub = kafkaTdlibClientsChannels.request()
|
this.requestsSub = kafkaTdlibClientsChannels.request()
|
||||||
.sendMessages(0L, requests.asFlux())
|
.sendMessages(requests.asFlux())
|
||||||
.subscribeOn(Schedulers.parallel())
|
.subscribeOn(Schedulers.parallel())
|
||||||
.subscribe();
|
.subscribe();
|
||||||
}
|
}
|
||||||
|
@ -50,7 +54,15 @@ public class KafkaSharedTdlibClients implements Closeable {
|
||||||
return responses;
|
return responses;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Flux<Timestamped<ClientBoundEvent>> events() {
|
public Flux<Timestamped<ClientBoundEvent>> events(String lane) {
|
||||||
|
var result = events.get(lane);
|
||||||
|
if (result == null) {
|
||||||
|
throw new IllegalArgumentException("No lane " + lane);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Flux<Timestamped<ClientBoundEvent>>> events() {
|
||||||
return events;
|
return events;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ public class KafkaSharedTdlibServers implements Closeable {
|
||||||
public KafkaSharedTdlibServers(KafkaTdlibServersChannels kafkaTdlibServersChannels) {
|
public KafkaSharedTdlibServers(KafkaTdlibServersChannels kafkaTdlibServersChannels) {
|
||||||
this.kafkaTdlibServersChannels = kafkaTdlibServersChannels;
|
this.kafkaTdlibServersChannels = kafkaTdlibServersChannels;
|
||||||
this.responsesSub = kafkaTdlibServersChannels.response()
|
this.responsesSub = kafkaTdlibServersChannels.response()
|
||||||
.sendMessages(0L, responses.asFlux().log("responses", Level.FINEST, SignalType.ON_NEXT))
|
.sendMessages(responses.asFlux().log("responses", Level.FINEST, SignalType.ON_NEXT))
|
||||||
.subscribeOn(Schedulers.parallel())
|
.subscribeOn(Schedulers.parallel())
|
||||||
.subscribe();
|
.subscribe();
|
||||||
this.requests = kafkaTdlibServersChannels.request()
|
this.requests = kafkaTdlibServersChannels.request()
|
||||||
|
@ -47,9 +47,9 @@ public class KafkaSharedTdlibServers implements Closeable {
|
||||||
.log("requests", Level.FINEST, SignalType.REQUEST, SignalType.ON_NEXT);
|
.log("requests", Level.FINEST, SignalType.REQUEST, SignalType.ON_NEXT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Disposable events(Flux<ClientBoundEvent> eventFlux) {
|
public Disposable events(String lane, Flux<ClientBoundEvent> eventFlux) {
|
||||||
return kafkaTdlibServersChannels.events()
|
return kafkaTdlibServersChannels.events(lane)
|
||||||
.sendMessages(0L, eventFlux)
|
.sendMessages(eventFlux)
|
||||||
.subscribeOn(Schedulers.parallel())
|
.subscribeOn(Schedulers.parallel())
|
||||||
.subscribe();
|
.subscribe();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
package it.tdlight.reactiveapi;
|
package it.tdlight.reactiveapi;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public record KafkaTdlibClientsChannels(KafkaTdlibRequestProducer request,
|
public record KafkaTdlibClientsChannels(KafkaTdlibRequestProducer request,
|
||||||
KafkaTdlibResponseConsumer response,
|
KafkaTdlibResponseConsumer response,
|
||||||
KafkaClientBoundConsumer events) implements Closeable {
|
Map<String, KafkaClientBoundConsumer> events) implements Closeable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
package it.tdlight.reactiveapi;
|
package it.tdlight.reactiveapi;
|
||||||
|
|
||||||
import it.tdlight.jni.TdApi;
|
import it.tdlight.jni.TdApi;
|
||||||
import it.tdlight.reactiveapi.Event.ClientBoundEvent;
|
|
||||||
import it.tdlight.reactiveapi.Event.OnRequest;
|
import it.tdlight.reactiveapi.Event.OnRequest;
|
||||||
import it.tdlight.reactiveapi.Event.ServerBoundEvent;
|
|
||||||
|
|
||||||
public class KafkaTdlibRequestConsumer extends KafkaConsumer<OnRequest<TdApi.Object>> {
|
public class KafkaTdlibRequestConsumer extends KafkaConsumer<OnRequest<TdApi.Object>> {
|
||||||
|
|
||||||
|
@ -12,8 +10,13 @@ public class KafkaTdlibRequestConsumer extends KafkaConsumer<OnRequest<TdApi.Obj
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KafkaChannelName getChannelName() {
|
public KafkaChannelCodec getChannelCodec() {
|
||||||
return KafkaChannelName.TDLIB_REQUEST;
|
return KafkaChannelCodec.TDLIB_REQUEST;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getChannelName() {
|
||||||
|
return getChannelCodec().getKafkaName();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -9,7 +9,12 @@ public class KafkaTdlibRequestProducer extends KafkaProducer<OnRequest<?>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KafkaChannelName getChannelName() {
|
public KafkaChannelCodec getChannelCodec() {
|
||||||
return KafkaChannelName.TDLIB_REQUEST;
|
return KafkaChannelCodec.TDLIB_REQUEST;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getChannelName() {
|
||||||
|
return getChannelCodec().getKafkaName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package it.tdlight.reactiveapi;
|
package it.tdlight.reactiveapi;
|
||||||
|
|
||||||
import it.tdlight.jni.TdApi;
|
import it.tdlight.jni.TdApi;
|
||||||
import it.tdlight.reactiveapi.Event.ClientBoundEvent;
|
|
||||||
import it.tdlight.reactiveapi.Event.OnResponse;
|
import it.tdlight.reactiveapi.Event.OnResponse;
|
||||||
|
|
||||||
public class KafkaTdlibResponseConsumer extends KafkaConsumer<OnResponse<TdApi.Object>> {
|
public class KafkaTdlibResponseConsumer extends KafkaConsumer<OnResponse<TdApi.Object>> {
|
||||||
|
@ -11,8 +10,13 @@ public class KafkaTdlibResponseConsumer extends KafkaConsumer<OnResponse<TdApi.O
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KafkaChannelName getChannelName() {
|
public KafkaChannelCodec getChannelCodec() {
|
||||||
return KafkaChannelName.TDLIB_RESPONSE;
|
return KafkaChannelCodec.TDLIB_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getChannelName() {
|
||||||
|
return getChannelCodec().getKafkaName();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package it.tdlight.reactiveapi;
|
package it.tdlight.reactiveapi;
|
||||||
|
|
||||||
import it.tdlight.jni.TdApi;
|
import it.tdlight.jni.TdApi;
|
||||||
import it.tdlight.reactiveapi.Event.ClientBoundEvent;
|
|
||||||
import it.tdlight.reactiveapi.Event.OnResponse;
|
import it.tdlight.reactiveapi.Event.OnResponse;
|
||||||
|
|
||||||
public class KafkaTdlibResponseProducer extends KafkaProducer<OnResponse<TdApi.Object>> {
|
public class KafkaTdlibResponseProducer extends KafkaProducer<OnResponse<TdApi.Object>> {
|
||||||
|
@ -11,7 +10,12 @@ public class KafkaTdlibResponseProducer extends KafkaProducer<OnResponse<TdApi.O
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KafkaChannelName getChannelName() {
|
public KafkaChannelCodec getChannelCodec() {
|
||||||
return KafkaChannelName.TDLIB_RESPONSE;
|
return KafkaChannelCodec.TDLIB_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getChannelName() {
|
||||||
|
return getChannelCodec().getKafkaName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,23 @@
|
||||||
package it.tdlight.reactiveapi;
|
package it.tdlight.reactiveapi;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public record KafkaTdlibServersChannels(KafkaTdlibRequestConsumer request,
|
public record KafkaTdlibServersChannels(KafkaTdlibRequestConsumer request,
|
||||||
KafkaTdlibResponseProducer response,
|
KafkaTdlibResponseProducer response,
|
||||||
KafkaClientBoundProducer events) implements Closeable {
|
Map<String, KafkaClientBoundProducer> events) implements Closeable {
|
||||||
|
|
||||||
|
public KafkaClientBoundProducer events(String lane) {
|
||||||
|
var p = events.get(lane);
|
||||||
|
if (p == null) {
|
||||||
|
throw new IllegalArgumentException("No lane " + lane);
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
response.close();
|
response.close();
|
||||||
events.close();
|
events.values().forEach(KafkaProducer::close);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,28 @@
|
||||||
package it.tdlight.reactiveapi;
|
package it.tdlight.reactiveapi;
|
||||||
|
|
||||||
import it.tdlight.reactiveapi.Event.ClientBoundEvent;
|
import it.tdlight.reactiveapi.Event.ClientBoundEvent;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
|
|
||||||
public class LiveAtomixReactiveApiClient extends BaseAtomixReactiveApiClient {
|
public class LiveAtomixReactiveApiClient extends BaseAtomixReactiveApiClient {
|
||||||
|
|
||||||
private final Flux<ClientBoundEvent> clientBoundEvents;
|
private final KafkaSharedTdlibClients kafkaSharedTdlibClients;
|
||||||
|
|
||||||
LiveAtomixReactiveApiClient(KafkaSharedTdlibClients kafkaSharedTdlibClients) {
|
LiveAtomixReactiveApiClient(KafkaSharedTdlibClients kafkaSharedTdlibClients) {
|
||||||
super(kafkaSharedTdlibClients);
|
super(kafkaSharedTdlibClients);
|
||||||
this.clientBoundEvents = kafkaSharedTdlibClients.events().map(Timestamped::data);
|
this.kafkaSharedTdlibClients = kafkaSharedTdlibClients;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Flux<ClientBoundEvent> clientBoundEvents() {
|
public Flux<ClientBoundEvent> clientBoundEvents(String lane) {
|
||||||
return clientBoundEvents;
|
return kafkaSharedTdlibClients.events(lane).map(Timestamped::data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Flux<ClientBoundEvent>> clientBoundEvents() {
|
||||||
|
return kafkaSharedTdlibClients.events().entrySet().stream()
|
||||||
|
.collect(Collectors.toUnmodifiableMap(Entry::getKey, e -> e.getValue().map(Timestamped::data)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,15 @@ import it.tdlight.jni.TdApi.Function;
|
||||||
import it.tdlight.jni.TdApi.Object;
|
import it.tdlight.jni.TdApi.Object;
|
||||||
import it.tdlight.reactiveapi.Event.ClientBoundEvent;
|
import it.tdlight.reactiveapi.Event.ClientBoundEvent;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.Map;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
public interface ReactiveApiMultiClient {
|
public interface ReactiveApiMultiClient {
|
||||||
|
|
||||||
Flux<ClientBoundEvent> clientBoundEvents();
|
Flux<ClientBoundEvent> clientBoundEvents(String lane);
|
||||||
|
|
||||||
|
Map<String, Flux<ClientBoundEvent>> clientBoundEvents();
|
||||||
|
|
||||||
<T extends TdApi.Object> Mono<T> request(long userId, TdApi.Function<T> request, Instant timeout);
|
<T extends TdApi.Object> Mono<T> request(long userId, TdApi.Function<T> request, Instant timeout);
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@ import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.StringJoiner;
|
import java.util.StringJoiner;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
@ -77,6 +78,7 @@ public abstract class ReactiveApiPublisher {
|
||||||
|
|
||||||
private final AtomicReference<State> state = new AtomicReference<>(new State(LOGGED_OUT));
|
private final AtomicReference<State> state = new AtomicReference<>(new State(LOGGED_OUT));
|
||||||
protected final long userId;
|
protected final long userId;
|
||||||
|
protected final String lane;
|
||||||
|
|
||||||
private final Many<OnResponse<TdApi.Object>> responses;
|
private final Many<OnResponse<TdApi.Object>> responses;
|
||||||
|
|
||||||
|
@ -85,10 +87,11 @@ public abstract class ReactiveApiPublisher {
|
||||||
|
|
||||||
private ReactiveApiPublisher(KafkaSharedTdlibServers kafkaSharedTdlibServers,
|
private ReactiveApiPublisher(KafkaSharedTdlibServers kafkaSharedTdlibServers,
|
||||||
Set<ResultingEventTransformer> resultingEventTransformerSet,
|
Set<ResultingEventTransformer> resultingEventTransformerSet,
|
||||||
long userId) {
|
long userId, String lane) {
|
||||||
this.kafkaSharedTdlibServers = kafkaSharedTdlibServers;
|
this.kafkaSharedTdlibServers = kafkaSharedTdlibServers;
|
||||||
this.resultingEventTransformerSet = resultingEventTransformerSet;
|
this.resultingEventTransformerSet = resultingEventTransformerSet;
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
|
this.lane = Objects.requireNonNull(lane);
|
||||||
this.responses = this.kafkaSharedTdlibServers.responses();
|
this.responses = this.kafkaSharedTdlibServers.responses();
|
||||||
this.rawTelegramClient = ClientManager.createReactive();
|
this.rawTelegramClient = ClientManager.createReactive();
|
||||||
try {
|
try {
|
||||||
|
@ -114,18 +117,21 @@ public abstract class ReactiveApiPublisher {
|
||||||
public static ReactiveApiPublisher fromToken(KafkaSharedTdlibServers kafkaSharedTdlibServers,
|
public static ReactiveApiPublisher fromToken(KafkaSharedTdlibServers kafkaSharedTdlibServers,
|
||||||
Set<ResultingEventTransformer> resultingEventTransformerSet,
|
Set<ResultingEventTransformer> resultingEventTransformerSet,
|
||||||
long userId,
|
long userId,
|
||||||
String token) {
|
String token,
|
||||||
return new ReactiveApiPublisherToken(kafkaSharedTdlibServers, resultingEventTransformerSet, userId, token);
|
String lane) {
|
||||||
|
return new ReactiveApiPublisherToken(kafkaSharedTdlibServers, resultingEventTransformerSet, userId, token, lane);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ReactiveApiPublisher fromPhoneNumber(KafkaSharedTdlibServers kafkaSharedTdlibServers,
|
public static ReactiveApiPublisher fromPhoneNumber(KafkaSharedTdlibServers kafkaSharedTdlibServers,
|
||||||
Set<ResultingEventTransformer> resultingEventTransformerSet,
|
Set<ResultingEventTransformer> resultingEventTransformerSet,
|
||||||
long userId,
|
long userId,
|
||||||
long phoneNumber) {
|
long phoneNumber,
|
||||||
|
String lane) {
|
||||||
return new ReactiveApiPublisherPhoneNumber(kafkaSharedTdlibServers,
|
return new ReactiveApiPublisherPhoneNumber(kafkaSharedTdlibServers,
|
||||||
resultingEventTransformerSet,
|
resultingEventTransformerSet,
|
||||||
userId,
|
userId,
|
||||||
phoneNumber
|
phoneNumber,
|
||||||
|
lane
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,7 +209,7 @@ public abstract class ReactiveApiPublisher {
|
||||||
// Buffer requests to avoid halting the event loop
|
// Buffer requests to avoid halting the event loop
|
||||||
.onBackpressureBuffer();
|
.onBackpressureBuffer();
|
||||||
|
|
||||||
kafkaSharedTdlibServers.events(messagesToSend);
|
kafkaSharedTdlibServers.events(lane, messagesToSend);
|
||||||
|
|
||||||
publishedResultingEvents
|
publishedResultingEvents
|
||||||
// Obtain only cluster-bound events
|
// Obtain only cluster-bound events
|
||||||
|
@ -548,8 +554,9 @@ public abstract class ReactiveApiPublisher {
|
||||||
public ReactiveApiPublisherToken(KafkaSharedTdlibServers kafkaSharedTdlibServers,
|
public ReactiveApiPublisherToken(KafkaSharedTdlibServers kafkaSharedTdlibServers,
|
||||||
Set<ResultingEventTransformer> resultingEventTransformerSet,
|
Set<ResultingEventTransformer> resultingEventTransformerSet,
|
||||||
long userId,
|
long userId,
|
||||||
String botToken) {
|
String botToken,
|
||||||
super(kafkaSharedTdlibServers, resultingEventTransformerSet, userId);
|
String lane) {
|
||||||
|
super(kafkaSharedTdlibServers, resultingEventTransformerSet, userId, lane);
|
||||||
this.botToken = botToken;
|
this.botToken = botToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -579,8 +586,9 @@ public abstract class ReactiveApiPublisher {
|
||||||
public ReactiveApiPublisherPhoneNumber(KafkaSharedTdlibServers kafkaSharedTdlibServers,
|
public ReactiveApiPublisherPhoneNumber(KafkaSharedTdlibServers kafkaSharedTdlibServers,
|
||||||
Set<ResultingEventTransformer> resultingEventTransformerSet,
|
Set<ResultingEventTransformer> resultingEventTransformerSet,
|
||||||
long userId,
|
long userId,
|
||||||
long phoneNumber) {
|
long phoneNumber,
|
||||||
super(kafkaSharedTdlibServers, resultingEventTransformerSet, userId);
|
String lane) {
|
||||||
|
super(kafkaSharedTdlibServers, resultingEventTransformerSet, userId, lane);
|
||||||
this.phoneNumber = phoneNumber;
|
this.phoneNumber = phoneNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
package it.tdlight.reactiveapi;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class UserTopic {
|
|
||||||
|
|
||||||
private final String value;
|
|
||||||
|
|
||||||
public UserTopic(KafkaChannelName channelName, long userId) {
|
|
||||||
value = "tdlib.%s.%d".formatted(channelName.getKafkaName(), userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTopic() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return value.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (o == null || getClass() != o.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
UserTopic userTopic = (UserTopic) o;
|
|
||||||
|
|
||||||
return Objects.equals(value, userTopic.value);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user