public Future<Void> replicateBlob(String blobId, String serviceId, DataNodeId sourceDataNode,
      Callback<Void> callback) {
    if (blobId == null || sourceDataNode == null) {
      throw new IllegalArgumentException("blobId or sourceHost must not be null");
    }

    currentOperationsCount.incrementAndGet();
    routerMetrics.replicateBlobOperationRate.mark();
    routerMetrics.operationQueuingRate.mark();
    if (serviceId.equals(DeleteOperation.class.getSimpleName())) {
      routerMetrics.replicateBlobOperationOnDeleteRate.mark();
    } else if (serviceId.equals(TtlUpdateOperation.class.getSimpleName())) {
      routerMetrics.replicateBlobOperationOnTtlUpdateRate.mark();
    }

    FutureResult<Void> futureResult = new FutureResult<>();
    if (isOpen.get()) {
      getOperationController().replicateBlob(blobId, serviceId, sourceDataNode, futureResult, callback);
    } else {
      RouterException routerException =
          new RouterException("Cannot accept operation because Router is closed", RouterErrorCode.RouterClosed);
      routerMetrics.operationDequeuingRate.mark();
      completeOperation(futureResult, callback, null, routerException);
    }
    return futureResult;
  }
Requests for a new blob to be replicated on-demand asynchronously and invokes the {@link Callback} when the request completes.
@param blobId The ID of the blob to be replicated.
@param serviceId The service ID of the service replicating the blob. This can be null if unknown.
@param sourceDataNode The source {@link DataNodeId} to get the blob from.
@param callback The {@link Callback} which will be invoked on the completion of the request.
@return A future that would contain information about whether the replicateBlob succeeded or not, eventually.
ambry-router/src/main/java/com/github/ambry/router/NonBlockingRouter.java