Bulkheading strategies for Akka actors

626 views Asked by At

I have a scenario where an important actor needs to make a call to a slow (15 - 20 seconds) remote system:

// Non-actor code equivalent
public Result makeSlowNetworkCall(Request request) {
    Result result = slowServiceClient.soooooSlow(request);      // Could be up to 15 - 20 SECONDS (mehhhh)
    return result;
}

The Akka equivalent to this is currently looking like:

// Groovy
class SlowServiceActor extends UntypedActor {
    @Override
    void onReceive(Object message) {
        if(message instanceof CallSlowService) {
            Request request = (message as CallSlowService).request
            Result result = makeSlowNetworkCall(request)
            // ...now do something with result, some 15 seconds later
        }
    }

    Result makeSlowNetworkCall(Request request) {
        slowServiceClient.soooooSlow(request)
    }
}

Obviously this is blocking and bad, bad, bad. After reading this excellent article on handling non-blocking DB calls, my main takeaway is that there are essentially two "bulkheading" strategies I can employ:

  • Place all SlowServiceActor instances in their own dispatcher, to isolate their latency/blocking-ness from other actors/threads that don't interact directly with the Slow Service; and
  • Invoke the Slow Service via Futures for true "asynchronicity"

So my best attempt thus far is:

// In application.conf:
slowServiceDispatcher {
    ...config here
}

class CallSlowService implements Callable<Result> {
    @Override
    Result call() throws Exception {
        slowServiceClient.soooooSlow(request)
    }
}

// Created using the "slowServiceDispatcher"
class SlowServiceActor extends UntypedActor {
    @Override
    void onReceive(Object message) {
        if(message instanceof CallSlowService) {
            Request request = (message as CallSlowService).request
            Future<Result> callSlowServiceFuture = Futures.future(new CallSlowService())

            Result result = ???

            // ...now do something with result, some 15 seconds later
        }
    }
}

But as you can see, I have a few problems:

  • I think I am misunderstanding the Futures.future(...) API; I don't think that's meant for constructing new Futures
  • How do I actually obtain the result in a non-blocking fashion?
  • And finally: am I missing anything here? Any strategies I'm not utilizing/leveraging that I should be?
1

There are 1 answers

0
Anton Sarov On BEST ANSWER

If I understand this correctly, you kind of have two options here: you listen to a Future being completed or you do something with the result:

If you want to listen, you can use some callback like

final ExecutionContext ec = system.dispatcher();

future.onSuccess(new OnSuccess<String>() {
  public void onSuccess(String result) {
    if ("bar" == result) {
      //Do something if it resulted in "bar"
    } else {
      //Do something if it was some other String
    }
  }
}, ec);

The other way would be to map the future's result. So you have the something like:

callSlowServiceFuture.map(new Mapper<ReturnType1, ReturnType2>() {
  public ReturnType2 apply(ReturnType1 s) {
    // do something with 's'
  }
}, ec);

This way you say "... the moment I get a result from the service call, please manipulate it as described in apply ..."