如何在循环中调用Akka Http POST(1000-10000次)?

时间:2019-01-08 17:25:59

标签: java akka-http

我正在用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调用?

1 个答案:

答案 0 :(得分:1)

我看到您使用过http.singleReques

根据akka-http docs

  

对于这些情况,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:

使用http.outgoingConnection

根据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()
  }
}