io.zeebe.gateway.impl.broker.BrokerRequestManager Maven / Gradle / Ivy
/*
* Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH under
* one or more contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright ownership.
* Licensed under the Zeebe Community License 1.0. You may not use this file
* except in compliance with the Zeebe Community License 1.0.
*/
package io.zeebe.gateway.impl.broker;
import io.zeebe.gateway.cmd.BrokerErrorException;
import io.zeebe.gateway.cmd.BrokerRejectionException;
import io.zeebe.gateway.cmd.BrokerResponseException;
import io.zeebe.gateway.cmd.ClientResponseException;
import io.zeebe.gateway.cmd.IllegalBrokerResponseException;
import io.zeebe.gateway.cmd.NoTopologyAvailableException;
import io.zeebe.gateway.cmd.PartitionNotFoundException;
import io.zeebe.gateway.impl.ErrorResponseHandler;
import io.zeebe.gateway.impl.broker.cluster.BrokerClusterState;
import io.zeebe.gateway.impl.broker.cluster.BrokerTopologyManagerImpl;
import io.zeebe.gateway.impl.broker.request.BrokerPublishMessageRequest;
import io.zeebe.gateway.impl.broker.request.BrokerRequest;
import io.zeebe.gateway.impl.broker.response.BrokerResponse;
import io.zeebe.gateway.metrics.GatewayMetrics;
import io.zeebe.protocol.Protocol;
import io.zeebe.protocol.impl.SubscriptionUtil;
import io.zeebe.protocol.record.ErrorCode;
import io.zeebe.protocol.record.MessageHeaderDecoder;
import io.zeebe.transport.ClientRequest;
import io.zeebe.transport.ClientTransport;
import io.zeebe.util.sched.Actor;
import io.zeebe.util.sched.future.ActorFuture;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeoutException;
import java.util.function.Supplier;
import java.util.function.ToIntFunction;
import org.agrona.DirectBuffer;
final class BrokerRequestManager extends Actor {
private static final TransportRequestSender SENDER_WITH_RETRY =
(c, s, r, t) -> c.sendRequestWithRetry(s, BrokerRequestManager::responseValidation, r, t);
private static final TransportRequestSender SENDER_WITHOUT_RETRY = ClientTransport::sendRequest;
private final ClientTransport clientTransport;
private final RequestDispatchStrategy dispatchStrategy;
private final BrokerTopologyManagerImpl topologyManager;
private final Duration requestTimeout;
BrokerRequestManager(
final ClientTransport clientTransport,
final BrokerTopologyManagerImpl topologyManager,
final RequestDispatchStrategy dispatchStrategy,
final Duration requestTimeout) {
this.clientTransport = clientTransport;
this.dispatchStrategy = dispatchStrategy;
this.topologyManager = topologyManager;
this.requestTimeout = requestTimeout;
}
private static boolean responseValidation(final DirectBuffer responseContent) {
final ErrorResponseHandler errorHandler = new ErrorResponseHandler();
final MessageHeaderDecoder headerDecoder = new MessageHeaderDecoder();
headerDecoder.wrap(responseContent, 0);
if (errorHandler.handlesResponse(headerDecoder)) {
errorHandler.wrap(
responseContent,
headerDecoder.encodedLength(),
headerDecoder.blockLength(),
headerDecoder.version());
final ErrorCode errorCode = errorHandler.getErrorCode();
// we only want to retry partition leader mismatch all other errors
// should be directly returned
return errorCode != ErrorCode.PARTITION_LEADER_MISMATCH;
} else {
return true;
}
}
CompletableFuture> sendRequestWithRetry(final BrokerRequest request) {
return sendRequestWithRetry(request, requestTimeout);
}
CompletableFuture> sendRequest(final BrokerRequest request) {
return sendRequest(request, requestTimeout);
}
CompletableFuture> sendRequest(
final BrokerRequest request, final Duration timeout) {
return sendRequestInternal(request, SENDER_WITHOUT_RETRY, timeout);
}
CompletableFuture> sendRequestWithRetry(
final BrokerRequest request, final Duration requestTimeout) {
return sendRequestInternal(request, SENDER_WITH_RETRY, requestTimeout);
}
private CompletableFuture> sendRequestInternal(
final BrokerRequest request,
final TransportRequestSender sender,
final Duration requestTimeout) {
final CompletableFuture> responseFuture = new CompletableFuture<>();
request.serializeValue();
actor.run(() -> sendRequestInternal(request, responseFuture, sender, requestTimeout));
return responseFuture;
}
private void sendRequestInternal(
final BrokerRequest request,
final CompletableFuture> returnFuture,
final TransportRequestSender sender,
final Duration requestTimeout) {
final BrokerAddressProvider nodeIdProvider;
try {
nodeIdProvider = determineBrokerNodeIdProvider(request);
} catch (final PartitionNotFoundException e) {
returnFuture.completeExceptionally(e);
GatewayMetrics.registerFailedRequest(
request.getPartitionId(), request.getType(), "PARTITION_NOT_FOUND");
return;
} catch (final NoTopologyAvailableException e) {
returnFuture.completeExceptionally(e);
GatewayMetrics.registerFailedRequest(
request.getPartitionId(), request.getType(), "NO_TOPOLOGY");
return;
}
final ActorFuture responseFuture =
sender.send(clientTransport, nodeIdProvider, request, requestTimeout);
final long startTime = System.currentTimeMillis();
actor.runOnCompletion(
responseFuture,
(clientResponse, error) -> {
RequestResult result = null;
try {
if (error == null) {
final BrokerResponse response = request.getResponse(clientResponse);
result = handleResponse(response, returnFuture);
if (result.wasProcessed()) {
final long elapsedTime = System.currentTimeMillis() - startTime;
GatewayMetrics.registerSuccessfulRequest(
request.getPartitionId(), request.getType(), elapsedTime);
return;
}
} else {
returnFuture.completeExceptionally(error);
}
} catch (final RuntimeException e) {
returnFuture.completeExceptionally(new ClientResponseException(e));
}
registerFailure(request, result, error);
});
}
private void registerFailure(
final BrokerRequest request, final RequestResult result, final Throwable error) {
if (result != null && result.getErrorCode() == ErrorCode.RESOURCE_EXHAUSTED) {
return;
}
final String code;
if (result != null && result.getErrorCode() != ErrorCode.NULL_VAL) {
code = result.getErrorCode().toString();
} else if (error != null && error.getClass().equals(TimeoutException.class)) {
code = "TIMEOUT";
} else {
code = "UNKNOWN";
}
GatewayMetrics.registerFailedRequest(request.getPartitionId(), request.getType(), code);
}
/**
* Returns a successful RequestResult, if the request was successfully processed or rejected.
* Otherwise, it returns a RequestResult with the returned error code or with {@link
* ErrorCode#NULL_VAL} if something unexpected occurred.
*/
private RequestResult handleResponse(
final BrokerResponse response, final CompletableFuture> responseFuture) {
try {
if (response.isResponse()) {
responseFuture.complete(response);
return RequestResult.processed();
} else if (response.isRejection()) {
responseFuture.completeExceptionally(new BrokerRejectionException(response.getRejection()));
return RequestResult.processed();
} else if (response.isError()) {
responseFuture.completeExceptionally(new BrokerErrorException(response.getError()));
return RequestResult.failed(response.getError().getCode());
} else {
responseFuture.completeExceptionally(
new IllegalBrokerResponseException(
"Expected broker response to be either response, rejection, or error, but is neither of them"));
}
} catch (final RuntimeException e) {
responseFuture.completeExceptionally(new BrokerResponseException(e));
}
return RequestResult.failed(ErrorCode.NULL_VAL);
}
private BrokerAddressProvider determineBrokerNodeIdProvider(final BrokerRequest> request) {
if (request.addressesSpecificPartition()) {
final BrokerClusterState topology = topologyManager.getTopology();
if (topology != null && !topology.getPartitions().contains(request.getPartitionId())) {
throw new PartitionNotFoundException(request.getPartitionId());
}
// already know partition id
return new BrokerAddressProvider(request.getPartitionId());
} else if (request.requiresPartitionId()) {
if (request instanceof BrokerPublishMessageRequest) {
determinePartitionIdForPublishMessageRequest((BrokerPublishMessageRequest) request);
} else {
// select next partition id for request
int partitionId = dispatchStrategy.determinePartition();
if (partitionId == BrokerClusterState.PARTITION_ID_NULL) {
// could happen if the topology is not set yet, let's just try with partition 0 but we
// should find a better solution
// https://github.com/zeebe-io/zeebe/issues/2013
partitionId = Protocol.DEPLOYMENT_PARTITION;
}
request.setPartitionId(partitionId);
}
return new BrokerAddressProvider(request.getPartitionId());
} else {
// random broker
return new BrokerAddressProvider();
}
}
private void determinePartitionIdForPublishMessageRequest(
final BrokerPublishMessageRequest request) {
final BrokerClusterState topology = topologyManager.getTopology();
if (topology == null || topology.getPartitionsCount() == 0) {
throw new NoTopologyAvailableException(
String.format(
"Expected to pick partition for message with correlation key '%s', but no topology is available",
request.getCorrelationKey()));
}
final int partitionId =
SubscriptionUtil.getSubscriptionPartitionId(
request.getCorrelationKey(), topology.getPartitionsCount());
request.setPartitionId(partitionId);
}
private interface TransportRequestSender {
ActorFuture send(
ClientTransport transport,
Supplier nodeAddressSupplier,
ClientRequest clientRequest,
Duration timeout);
}
private class BrokerAddressProvider implements Supplier {
private final ToIntFunction nodeIdSelector;
BrokerAddressProvider() {
this(BrokerClusterState::getRandomBroker);
}
BrokerAddressProvider(final int partitionId) {
this(state -> state.getLeaderForPartition(partitionId));
}
BrokerAddressProvider(final ToIntFunction nodeIdSelector) {
this.nodeIdSelector = nodeIdSelector;
}
@Override
public String get() {
final BrokerClusterState topology = topologyManager.getTopology();
if (topology != null) {
return topology.getBrokerAddress(nodeIdSelector.applyAsInt(topology));
} else {
return null;
}
}
}
private static class RequestResult {
private boolean processed;
private ErrorCode errorCode;
RequestResult(final boolean processed, final ErrorCode errorCode) {
this.processed = processed;
this.errorCode = errorCode;
}
boolean wasProcessed() {
return processed;
}
public ErrorCode getErrorCode() {
return errorCode;
}
static RequestResult processed() {
return new RequestResult(true, null);
}
static RequestResult failed(final ErrorCode code) {
return new RequestResult(false, code);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy