我正在用Java学习Akka。我写了一个有两个演员的简单程序。
我的第一个演员 ActorA 被调用,列表包含1000个字符串。 ActorA 遍历列表,并为每个元素调用 ActorB 。
ActorB 使用从 ActorA 接收到的String参数对外部服务进行Http POST调用。
我希望 ActorB 能够成功进行1000个Http POST调用,并且将收到相同数量的响应。但是 ActorB 能够在80-120次之间随机发出POST请求,然后停止发出POST呼叫。
我尝试提供自定义调度程序,因为HTTP POST调用是一项阻止操作,但还是没有运气!
请参阅下面给出的代码和配置。
public class ActorA extends AbstractActor {
static public Props props() {
return Props.create(ActorA.class);
}
static public class IdWrapper {
List<String> ids;
public IdWrapper(List<String> ids) {
this.ids = ids;
}
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(IdWrapper.class, this::process)
.build();
}
private void process(IdWrapper msg) {
msg.ids.forEach(id -> {
context().actorSelection("actorB").tell(new MessageForB(id), ActorRef.noSender());
}
);
}
}
public class ActorB extends AbstractActor {
final Http http = Http.get(getContext().system());
final Materializer materializer = ActorMaterializer.create(context());
public static Props props() {
return Props.create(ActorB.class);
}
static public class MessageForB implements Serializable {
String id;
public MessageForB(String id) {
this.id = id;
}
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(MessageForB.class, this::process)
.build();
}
private void process(MessageForB messageForB) {
ExecutionContext ec = getContext().getSystem().dispatchers().lookup("my-blocking-dispatcher");
/**
* Get id from request
*/
String reqId = messageForB.id;
/**
* Prepare request
*/
XmlRequest requestEntity = getRequest(Stream.of(reqId).collect(Collectors.toList()));
String requestAsString = null;
try {
/**
* Create and configure JAXBMarshaller.
*/
JAXBContext jaxbContext = JAXBContext.newInstance(XmlRequest.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
/**
* Convert request entity to string before making POST request.
*/
StringWriter sw = new StringWriter();
jaxbMarshaller.marshal(requestEntity, sw);
requestAsString = sw.toString();
} catch (JAXBException e) {
e.printStackTrace();
}
/**
* Create RequestEntity from request string.
*/
RequestEntity entity = HttpEntities.create(
MediaTypes.APPLICATION_XML.toContentType(HttpCharsets.ISO_8859_1),
requestAsString);
/**
* Create Http POST with necessary headers and call
*/
final CompletionStage<HttpResponse> responseFuture =
http.singleRequest(HttpRequest.POST("http://{hostname}:{port}/path")
.withEntity(entity));
responseFuture
.thenCompose(httpResponse -> {
/**
* Convert response into String
**/
final CompletionStage<String> res = Unmarshaller.entityToString().unmarshal
(httpResponse.entity(), ec, materializer);
/**
* Consume response bytes
**/
httpResponse.entity().getDataBytes().runWith(Sink.ignore(), materializer);
return res;
})
.thenAccept(s -> {
try {
/**
* Deserialize string to DTO.
*/
MyResponse MyResponse = getMyResponse(s);
// further processing..
} catch (JAXBException e) {
e.printStackTrace();
}
});
}
private XmlRequest getRequest(List<String> identifiers){
XmlRequest request = new XmlRequest();
// Business logic to create req entity
return request;
}
private MyResponse getMyResponse(String s) throws JAXBException {
JAXBContext jaxbContext = JAXBContext.newInstance
(MyResponse.class);
javax.xml.bind.Unmarshaller jaxbUnmarshaller = jaxbContext
.createUnmarshaller();
StringReader reader = new StringReader(s);
return (MyResponse)
jaxbUnmarshaller.unmarshal(reader);
}
}
my-blocking-dispatcher {
type = Dispatcher
executor = "thread-pool-executor"
thread-pool-executor {
core-pool-size-min = 5
core-pool-size-max = 20
}
throughput = 1
}
我在哪里可以改善或更正我的代码,以便 ActorB 能够成功地为 ActorA 发送的所有项目进行Http POST调用?
答案 0 :(得分:1)
我看到您使用过http.singleReques
。
对于这些情况,Akka HTTP提供了Http()。singleRequest(...)方法,该方法只是将HttpRequest实例转换为Future [HttpResponse]。在内部,请求是通过(缓存的)主机连接池分派的,用于请求的有效URI。
http.singleRequest
使用连接池处理请求,因此您需要增加来自akka http config的连接池中的连接数。
“主机连接池”部分中的默认设置:
host-connection-pool {
max-connections = 4
min-connections = 0
max-retries = 5
max-open-requests = 32
pipelining-limit = 1
idle-timeout = 30 s
}
解决方案2:
根据akka-http docs,将为每个请求创建一个特定的连接。因此,您可以在没有连接池的情况下并行处理1000个连接。
使用连接级API,可以通过实现Http()。outgoingConnection(...)方法返回的Flow来打开与目标端点的新HTTP连接。这是一个示例:
def run(req:String): Unit ={
val apiBaseUrl = "example.com" //without protocol
val path = "/api/update"
val body = HttpEntity(ContentTypes.`application/json`,req.getBytes)
val request = HttpRequest(HttpMethods.POST, path,entity = body)
val connectionFlow = Http().outgoingConnection(apiBaseUrl)
val result = Source.single(request).via(connectionFlow).runWith(Sink.head)
result.onComplete{
case Success(value) =>
println(value)
case Failure(e)=>
e.printStackTrace()
}
}