Spray + Akka高性能异步IO并发
如何使用Java建立像Node.js那样非堵塞异步事件并发IO服务器呢?Spray是基于NIO2高并发框架,虽然Tomcat 8也是基于NIO2,但是Spary的线程数要低得到,降低CPU上下文切换的负载;Akka和其Mysql库包都是相同线程执行上下文( execution context),因为在非堵塞前提下,性能拼的就不是线程数目越多越好,正好相反,线程数目越低,越接近理论理论最佳点。
启动设置
为了启动Spary和Akka,需要一个main启动函数,在main函数中,我们创建一个Actor系统:
ActorSystem system=ActorSystem.create("system");
ActorRef listener=system.actorOf(Props.create(HttpActor.class),"httpActor");
listener是使用Actor用来处理Http请求。然后要设定监听Http的端口:
InetSocketAddress endpoint=newInetSocketAddress(3000);
intbacklog=100;
Listoptions=JavaConversions.asScalaBuffer(newArrayList()).toList();
Optionsettings=scala.Option.empty();
最后,绑定Actor到监听的Http端口:
Bind bind=newHttp.Bind(listener, endpoint, backlog, options, settings, sslEngineProvider);
IO.apply(spray.can.Http$.MODULE$, system).tell(bind, ActorRef.noSender());
整个main函数主要代码如下:
publicstaticfinalActorSystem system=ActorSystem.create("system");
publicstaticvoidmain(String[] args) {
...
ActorRef listener=system.actorOf(Props.create(HttpActor.class),"httpActor");
InetSocketAddress endpoint=newInetSocketAddress(3000);
intbacklog=100;
Listoptions=JavaConversions.asScalaBuffer(newArrayList()).toList();
Optionsettings=scala.Option.empty();
ServerSSLEngineProvider sslEngineProvider=null;
Bind bind=newHttp.Bind(listener, endpoint, backlog, options, settings, sslEngineProvider);
IO.apply(spray.can.Http$.MODULE$, system).tell(bind, ActorRef.noSender());
...
}
设置好启动函数以后,下面是真正开始在Actor里处理进来Http请求了。
请求处理器
首先,我们因为使用的是原生Java代码,不是Scala,因此需要将Scala集成到Java中,可能比较丑陋,可以用专门类包装一下,引入Scala的Http协议:
HttpProtocolHTTP_1_1=HttpProtocols.HTTP$div1$u002E1();
Http Actor为了响应Http请求做三件事,第一件是创建一个路由router,这样能够根据请求URL:http://xxx/path中不同的/path分别处理:
Router router=partitionAndCreateRouter();
第二件是处理新的连接,告诉Spray这个actor不仅接受Http连接,也处理实际http连接:
}).match(Tcp.Connected.class, r->{
sender().tell(newHttp.Register(self(), Http.EmptyFastPath$.MODULE$), self());//tell that connection will be handled here!
})
第三件事就是处理实际的Http连接,将http请求委托给另外一个actor处理。
.match(HttpRequest.class, r->{
intid=Constants.ID.getAndIncrement();
String path=String.valueOf(r.uri().path());
if("/sell".equals(path)){
...//逻辑处理
}elseif("/buy".equals(path)){
...//逻辑处理
}else{
handleUnexpected(r);
}
})
整个HttpActor代码如下,业务逻辑以买卖为模型:
privatestaticclassHttpActorextendsAbstractActor{
privatestaticfinalHttpProtocolHTTP_1_1=HttpProtocols.HTTP$div1$u002E1();
publicHttpActor() {
finalRouter router=partitionAndCreateRouter();
receive(ReceiveBuilder
.match(HttpRequest.class, r->{
intid=Constants.ID.getAndIncrement();
String path=String.valueOf(r.uri().path());
if("/sell".equals(path)){
String productId=r.uri().query().get("productId").get();
...
SalesOrder so=newSalesOrder(price, productId, quantity, id);
so.setSeller(newSeller(who));
router.route(so, self());
replyOK(id);
}elseif("/buy".equals(path)){
...
}else{
handleUnexpected(r);
}
}).match(Tcp.Connected.class, r->{
sender().tell(newHttp.Register(self(), Http.EmptyFastPath$.MODULE$), self());//tell that connection will be handled here!
}).build());
}