Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.
Akka HTTP
安田裕介
@TanUkkii007
2016/3/4
Akka Stream & HTTP
リリースおめでとう!
ノンブロッキングで背圧制御に満ちたエコシステム
形成の幕開けになることを期待します
Akka HTTPとは
• akka IOとAkka Streamを使って、NIOかつ背圧制御
に基づいたHTTPモジュール
• 低レベルAPIのakka-http-coreと高レベルAPIのakka-
http-experimentalがある...
マイクロサービスの難しさ
“every single one of your peer teams suddenly becomes a
potential DOS attacker”
周りの全ての同僚チームが、突如 DOS アタッカーになりう...
アジェンダ
• Akka Streamの復習
• Akka HTTP API
• HTTPレイヤーとTCPレイヤーとの接合
• TCPレイヤーの内部構造
Akka Streamの復習
val intSource: Source[Int, NotUsed] = Source(List(1, 2, 3)) //Sourceは1つの出力をもつ。Sourceなどをグラフという。
val toString...
Akka Streamの復習
intSource.via(toStringFlow).toMat(seqSink)((sourceMatValue, sinkMatValue) => sinkMatValue).run()
すべては下流から始ま...
Akka HTTP
クライアントサイドAPI
val connectionFlow: Flow[HttpRequest, HttpResponse, Future[OutgoingConnection]] =
Http().outgoingConnection(h...
サーバーサイドAPI
val serverSource: Source[Http.IncomingConnection, Future[Http.ServerBinding]] = Http().bind(host, port) //bindで...
HttpRequest, HttpResponse
final case class HttpRequest(method: HttpMethod,
uri: Uri,
headers: immutable.Seq[HttpHeader],
e...
• Futureなのでコネクションプールの数を超えて並列に呼ぶことができる
• IOの方がCPUより遅いのでコネクションが足りなくなる
• NIOなのでスレッドプールのスレッド数=コネクション数にしても解決しない
リソース間調整
import ...
HTTPレイヤーとTCPレイヤーとの接合
TCPコネクションにHTTPのセマンティクスをのせる
val transportFlow: Flow[ByteString, ByteString, Future[Tcp.OutgoingConnection]] = Tcp().outgoin...
val transportFlow: Flow[ByteString, ByteString, Future[Tcp.OutgoingConnection]] = Tcp().outgoingConnection(new
InetSocketA...
val clientLayer: BidiFlow[HttpRequest, SslTlsOutbound, SslTlsInbound, HttpResponse, NotUsed] =
Http().clientLayer(Host(hos...
val outgoingConnection: Flow[HttpRequest, HttpResponse, Future[Http.OutgoingConnection]] =
clientLayer.joinMat(outgoingTls...
TCPコネクションにHTTPのセマンティクスをのせる
val connections: Source[Tcp.IncomingConnection, Future[Tcp.ServerBinding]] = Tcp().bind(host, p...
val bindingFuture: Future[ServerBinding] = serverSource.to(Sink.foreach { connection =>
val connFlow: Flow[HttpResponse, H...
TCPレイヤーの内部構造
TCPレイヤーの内部構造
クライアント編
akka.io.Tcpの復習
case object Ack extends Tcp.Event
class PullModeClient(remote: InetSocketAddress) extends Actor with ActorL...
akka.io.Tcpの復習
case object Ack extends Tcp.Event
class PullModeClient(remote: InetSocketAddress) extends Actor with ActorL...
Akka Streamのステージ
final case class Map[In, Out](f: In ⇒ Out) extends GraphStage[FlowShape[In, Out]] {
val in = Inlet[In]("M...
class TcpStreamLogic(val shape: FlowShape[ByteString, ByteString], val role: TcpRole) extends GraphStageLogic(shape) {
imp...
class TcpStreamLogic(val shape: FlowShape[ByteString, ByteString], val role: TcpRole) extends
GraphStageLogic(shape) {
imp...
private def connecting(ob: Outbound)(evt: (ActorRef, Any)): Unit = {
val (sender, msg) = evt
msg match {
case c: akka.io.T...
private def connected(evt: (ActorRef, Any)): Unit = {
val (sender, msg) = evt
msg match {
case akka.io.Tcp.Received(data) ...
private def connected(evt: (ActorRef, Any)): Unit = {
val (sender, msg) = evt
msg match {
case akka.io.Tcp.Received(data) ...
TCPレイヤーの内部構造
サーバー編
class TcpStreamLogic(val shape: FlowShape[ByteString, ByteString], val role: TcpRole) extends GraphStageLogic(shape) {
imp...
Tcp().bindステージ
実はデータの受信のみでなく、
コネクションの受付けまで背圧制御されています
https://github.com/akka/akka/blob/v2.4.2/akka-
stream/src/main/scala/...
まとめ
Akka HTTPでは
• 自分が処理できなければ読み込まない
• 相手が処理できなければ書き込まない
システム同士はみんなともだち
Thank you!
Prochain SlideShare
Chargement dans…5
×

Akka HTTP

2 301 vues

Publié le

2016/3/4 @Akkaを語る会

Publié dans : Ingénierie
  • Soyez le premier à commenter

Akka HTTP

  1. 1. Akka HTTP 安田裕介 @TanUkkii007 2016/3/4
  2. 2. Akka Stream & HTTP リリースおめでとう! ノンブロッキングで背圧制御に満ちたエコシステム 形成の幕開けになることを期待します
  3. 3. Akka HTTPとは • akka IOとAkka Streamを使って、NIOかつ背圧制御 に基づいたHTTPモジュール • 低レベルAPIのakka-http-coreと高レベルAPIのakka- http-experimentalがある • Akka Streamがシステム内の調和を保つとしたら、 Akka HTTPはシステム間の調和を実現する
  4. 4. マイクロサービスの難しさ “every single one of your peer teams suddenly becomes a potential DOS attacker” 周りの全ての同僚チームが、突如 DOS アタッカーになりうる ようになった 和訳原文 Stevey の “Google プラットフォームぶっちゃけ話”にでてくる Amazonのマイクロサービス化によるAWS誕生のくだり システム間のインターフェースとしてもっとも使われるHTTPに 背圧制御をもたらす必要がある
  5. 5. アジェンダ • Akka Streamの復習 • Akka HTTP API • HTTPレイヤーとTCPレイヤーとの接合 • TCPレイヤーの内部構造
  6. 6. Akka Streamの復習 val intSource: Source[Int, NotUsed] = Source(List(1, 2, 3)) //Sourceは1つの出力をもつ。Sourceなどをグラフという。 val toStringFlow: Flow[Int, String, NotUsed] = Flow[Int].map(_.toString) //Flowは1つの入力と1つの出力をもつ。mapなどをステージという 。 val seqSink: Sink[String, Future[Seq[String]]] = Sink.seq[String] //Sinkは1つの入力をもつ。第2型引数がMaterialized Value。 /** * +-----------------------------------------------------------------------------------+ * | runnableGraph | * | | * | +------------+ +--------------+ +---------+ | * | | | | | | | | * | | intSource ~ Int ~> ~ Int ~> toStringFlow ~ String ~> ~ String ~> seqSink | | * | | | | | | | | * | +------------+ +--------------+ +---------+ | * +-----------------------------------------------------------------------------------+ */ // すべてのポートが閉じたグラフはRunnableGraph[Mat]になり、マテリアライズ化が可能になる val runnableGraph: RunnableGraph[Future[Seq[String]]] = intSource.via(toStringFlow).toMat(seqSink)((sourceMatValue, sinkMatValue) => sinkMatValue) // RunnableGraphのrunを呼んでマテリアライズ化する。ここからストリームが動き出す。マテリアライズ化の結果としてMaterialized Valueが 返る。 val materializedValue: Future[Seq[String]] = runnableGraph.run()(ActorMaterializer()) // Materialized Valueからストリームの結果を受け取れる場合がある val streamResult: Seq[String] = Await.result(materializedValue, 10 seconds) //Seq("1", "2", "3")
  7. 7. Akka Streamの復習 intSource.via(toStringFlow).toMat(seqSink)((sourceMatValue, sinkMatValue) => sinkMatValue).run() すべては下流から始まる Source Flow Sink pull(in) pull(in) onPull onPull push(out, 1) onPush push(out, “1”) pull(in) pull(in) onPull push(out, 2) onPush ※アクターモデルとの最大の違い onPush onPull
  8. 8. Akka HTTP
  9. 9. クライアントサイドAPI val connectionFlow: Flow[HttpRequest, HttpResponse, Future[OutgoingConnection]] = Http().outgoingConnection(host, port) //単一のHTTPコネクションストリーム val responseFuture1: Future[HttpResponse] = Source.single(HttpRequest(uri = "/")) .via(connectionFlow) .runWith(Sink.head) val poolClientFlow: Flow[(HttpRequest, Int), (Try[HttpResponse], Int), HostConnectionPool] = Http().cachedHostConnectionPool[Int](host, port) //ホスト単位でコネクションプールをもつストリーム val responseFuture2: Future[(Try[HttpResponse], Int)] = Source.single(HttpRequest(uri = "/") -> 1) .via(poolClientFlow) .runWith(Sink.head)
  10. 10. サーバーサイドAPI val serverSource: Source[Http.IncomingConnection, Future[Http.ServerBinding]] = Http().bind(host, port) //bindでコネクション のSourceを得る val bindingFuture: Future[ServerBinding] = serverSource.to(Sink.foreach { connection => val connFlow: Flow[HttpResponse, HttpRequest, NotUsed] = connection.flow //それぞれのコネクションのデータの送受信を表す フロー val requestHandler: Flow[HttpRequest, HttpResponse, NotUsed] = Flow[HttpRequest].map { case HttpRequest(GET, Uri.Path("/"), _, _, _) => HttpResponse(entity = HttpEntity(ContentTypes.`text/html(UTF-8)`, "<html><body>Hello world!</body></html>")) } /** * +----------+ +----------------+ * | | ~HttpRequest~> | | * | connFlow | | requestHandler | * | | <~HttpResponse~ | | * +----------+ +----------------+ */ connFlow.joinMat(requestHandler)(Keep.right).run() //コネクションのフローにリクエストハンドラーを接続してリクエストを消 費する }).run()
  11. 11. HttpRequest, HttpResponse final case class HttpRequest(method: HttpMethod, uri: Uri, headers: immutable.Seq[HttpHeader], entity: RequestEntity, protocol: HttpProtocol) sealed trait RequestEntity extends HttpEntity final case class HttpResponse(status: StatusCode, headers: immutable.Seq[HttpHeader], entity: ResponseEntity, protocol: HttpProtocol) sealed trait ResponseEntity extends HttpEntity sealed trait HttpEntity { def dataBytes: Source[ByteString, Any] } なんとHttpEntityの中身はSource[ByteString, Any]だ これが効率的なデータの送受信と WebsoketやSSEなどの異なるプロトコルを統一的に扱うことを可能にしている
  12. 12. • Futureなのでコネクションプールの数を超えて並列に呼ぶことができる • IOの方がCPUより遅いのでコネクションが足りなくなる • NIOなのでスレッドプールのスレッド数=コネクション数にしても解決しない リソース間調整 import akka.io.IO import spray.can.Http import spray.client.pipelining._ trait RequestProvider { this: Actor => import context.system import context.dispatcher lazy val pipeline = { sendReceive(IO(Http)(system)) ~> unmarshal[String] } def request(path: String): Future[String] = pipeline(Get(s"$requestUrl/$path")) } スレッドプールとコネクションプール val safeRequest: Flow[String, String, NotUsed] = Flow[String].mapAsync(maxConnection)(request) Akka StreamのmapAsync(parallelism)(asyncFunction)を使えば parallelism以上にasyncFunctionが呼ばれることはない Akka HTTPでもコネクションプールをもつクライアントは mapAsyncで制御している Spray clientの例
  13. 13. HTTPレイヤーとTCPレイヤーとの接合
  14. 14. TCPコネクションにHTTPのセマンティクスをのせる val transportFlow: Flow[ByteString, ByteString, Future[Tcp.OutgoingConnection]] = Tcp().outgoingConnection(new InetSocketAddress(host, port)) //TCPコネクションのステージ val tlsStage: BidiFlow[SslTlsOutbound, ByteString, ByteString, SessionBytes, NotUsed] = TLSPlacebo() //TLSのプラシーボ効果ステージ。ByteStringをTLSの型にラップしているだけで何もしていない。HTTPスキーム用。 val outgoingTlsConnectionLayer: Flow[SslTlsOutbound, SessionBytes, Future[Http.OutgoingConnection]] = tlsStage.joinMat(transportFlow) { (_, tcpConnFuture: Future[Tcp.OutgoingConnection]) => tcpConnFuture map { tcpConn => Http.OutgoingConnection(tcpConn.localAddress, tcpConn.remoteAddress) } //TCPコネクションステージにTLSステージを接続する。Materialized ValueをTcp.OutgoingConnectionから Http.OutgoingConnectionに変換する。 } val clientLayer: BidiFlow[HttpRequest, SslTlsOutbound, SslTlsInbound, HttpResponse, NotUsed] = Http().clientLayer(Host(host, port)) //HttpRequest -> SslTlsOutbound、SslTlsInbound -> HttpResponseへの変換ステージ val outgoingConnection: Flow[HttpRequest, HttpResponse, Future[Http.OutgoingConnection]] = clientLayer.joinMat(outgoingTlsConnectionLayer)(Keep.right) //TCP/TLSステージとHTTPステージを接続 クライアント編
  15. 15. val transportFlow: Flow[ByteString, ByteString, Future[Tcp.OutgoingConnection]] = Tcp().outgoingConnection(new InetSocketAddress(host, port)) //TCPコネクションのステージ val tlsStage: BidiFlow[SslTlsOutbound, ByteString, ByteString, SessionBytes, NotUsed] = TLSPlacebo() //TLSのプラシーボ効果ステージ。ByteStringをTLSの型にラップしているだけで何もしていない。HTTPスキーム用。 /** * +------------------------------------------------+ * | outgoingTlsConnectionLayer | * | | * | +----------+ +---------------+ | * SslTlsOutbound ~~> | ~ByteString~> | | | * | | tlsStage | | transportFlow | | * SessionBytes <~~ | <~ByteString~ | | | * | +----------+ +---------------+ | * +------------------------------------------------+ */ val outgoingTlsConnectionLayer: Flow[SslTlsOutbound, SessionBytes, Future[Http.OutgoingConnection]] = tlsStage.joinMat(transportFlow) { (_, tcpConnFuture: Future[Tcp.OutgoingConnection]) => tcpConnFuture map { tcpConn => Http.OutgoingConnection(tcpConn.localAddress, tcpConn.remoteAddress) } //TCPコネクションステージにTLSステージを接続する。Materialized ValueをTcp.OutgoingConnectionから Http.OutgoingConnectionに変換する。 } TCPコネクションにHTTPのセマンティクスをのせる ① TCPコネクションにTLSの解釈を接続する
  16. 16. val clientLayer: BidiFlow[HttpRequest, SslTlsOutbound, SslTlsInbound, HttpResponse, NotUsed] = Http().clientLayer(Host(host, port)) //HttpRequest -> SslTlsOutbound、SslTlsInbound -> HttpResponseへの変換ステージ /** * +-------------------------------------------------------------------+ * | outgoingConnection | * | | * | +-------------+ +----------------------------+ | * HttpRequest ~~> | ~SslTlsOutbound~> | | | * | | clientLayer | | outgoingTlsConnectionLayer | | * HttpResponse <~~ | <~SslTlsInbound~ | | | * | +-------------+ +----------------------------+ | * +-------------------------------------------------------------------+ */ val outgoingConnection: Flow[HttpRequest, HttpResponse, Future[Http.OutgoingConnection]] = clientLayer.joinMat(outgoingTlsConnectionLayer)(Keep.right) //TCP/TLSステージとHTTPステージを接続 TCPコネクションにHTTPのセマンティクスをのせる ② HTTPのセマンティクスを解釈する
  17. 17. val outgoingConnection: Flow[HttpRequest, HttpResponse, Future[Http.OutgoingConnection]] = clientLayer.joinMat(outgoingTlsConnectionLayer)(Keep.right) //TCP/TLSステージとHTTPステージを接続 Source.single(httpRequest).via(outgoingConnection).toMat(Sink.head)(Keep.right).run() Source.single(httpRequest).via(Http().outgoingConnection(host, port)).toMat(Sink.head)(Keep.right).run() これら一連の処理は Http().outgoingConnection(host, port) と等価です TCPコネクションにHTTPのセマンティクスをのせる 動かしてみる
  18. 18. TCPコネクションにHTTPのセマンティクスをのせる val connections: Source[Tcp.IncomingConnection, Future[Tcp.ServerBinding]] = Tcp().bind(host, port) //TCPのbind val tlsStage: BidiFlow[SslTlsOutbound, ByteString, ByteString, SessionBytes, NotUsed] = TLSPlacebo() //TLSのプラシーボ効果ステージ。ByteStringをTLSの型にラップしているだけで何もしていない。HTTPスキーム用。 val serverLayer: BidiFlow[HttpResponse, SslTlsOutbound, SslTlsInbound, HttpRequest, NotUsed] = Http().serverLayer() //HttpResponse -> SslTlsOutbound、SslTlsInbound -> HttpRequestへの変換ステージ val serverSource: Source[IncomingConnection, Future[ServerBinding]] = connections.map { case Tcp.IncomingConnection(localAddress, remoteAddress, flow) => /** * +----------------------------------------------------------------------------+ * | IncomingConnection.flow | * | | * | +-------------+ +----------+ +----------+ | * HttpResponse ~~> | ~SslTlsOutbound~> | | ~ByteString~> | | | * | | serverLayer | | tlsStage | | identity | | * HttpRequest <~~ | <~SslTlsInbound~ | | <~ByteString~ | | | * | +-------------+ +----------+ +----------+ | * +----------------------------------------------------------------------------+ */ // TCP/TLSステージとHTTPステージを接続して、Tcp.IncomingConnectionからHttp.IncomingConnectionに変換 Http.IncomingConnection(localAddress, remoteAddress, serverLayer atop tlsStage join Flow[ByteString].map(identity) ) }.mapMaterializedValue { bindFuture: Future[Tcp.ServerBinding] => bindFuture.map(tcpBinding => Http.ServerBinding(tcpBinding.localAddress)(unbindAction = () => tcpBinding.unbind())) // Tcp.ServerBindingからHttp.ServerBindingに変換 } サーバー編
  19. 19. val bindingFuture: Future[ServerBinding] = serverSource.to(Sink.foreach { connection => val connFlow: Flow[HttpResponse, HttpRequest, NotUsed] = connection.flow val requestHandler: Flow[HttpRequest, HttpResponse, NotUsed] = Flow[HttpRequest].map { case HttpRequest(GET, Uri.Path("/"), _, _, _) => HttpResponse(entity = HttpEntity(ContentTypes.`text/html(UTF-8)`, "<html><body>Hello world!</body></html>")) } connFlow.joinMat(requestHandler)(Keep.right).run() }).run() val serverSource: Source[Http.IncomingConnection, Future[Http.ServerBinding]] = Http().bind(host, port) これら一連の処理は Http().bind(host, port) と等価です TCPコネクションにHTTPのセマンティクスをのせる 動かしてみる
  20. 20. TCPレイヤーの内部構造
  21. 21. TCPレイヤーの内部構造 クライアント編
  22. 22. akka.io.Tcpの復習 case object Ack extends Tcp.Event class PullModeClient(remote: InetSocketAddress) extends Actor with ActorLogging { import context.system override def preStart: Unit = { val manager: ActorRef = IO(Tcp) //IOエクステンションからTCPマネージャーを取得 manager ! Tcp.Connect(remote, pullMode = true) //TCPマネージャーに接続要求を送る } def receive: Receive = connecting def connecting: Receive = { case Tcp.Connected(remote, local) => { //TCP接続の完了 val connection = sender() //senderがコネクションWorkerアクター context.become(connected(connection)) connection ! Tcp.Register(self) //自分自身をコネクションWorkerアクターに登録 connection ! Tcp.ResumeReading //サーバーからの受信を再開する } } def connected(connection: ActorRef): Receive = { case Tcp.Received(data) => log.info("received {}", data) //サーバーからデータを受信 case data: ByteString => connection ! Tcp.Write(data, Ack) //ユーザーからデータの送信要求がきたら書き込む。成功したらAckを返 してもらう。Ackが返るまで受け付けてはいけません!! case Ack => connection ! Tcp.ResumeReading //データの送信が完了したら受信を再開する } } Pull Mode を使う読み込み側の背圧制御のために
  23. 23. akka.io.Tcpの復習 case object Ack extends Tcp.Event class PullModeClient(remote: InetSocketAddress) extends Actor with ActorLogging { import context.system override def preStart: Unit = { val manager: ActorRef = IO(Tcp) //IOエクステンションからTCPマネージャーを取得 manager ! Tcp.Connect(remote, pullMode = true) //TCPマネージャーに接続要求を送る } def receive: Receive = connecting def connecting: Receive = { case Tcp.Connected(remote, local) => { //TCP接続の完了 val connection = sender() //senderがコネクションWorkerアクター context.become(connected(connection)) connection ! Tcp.Register(self) //自分自身をコネクションWorkerアクターに登録 connection ! Tcp.ResumeReading //サーバーからの受信を再開する } } def connected(connection: ActorRef): Receive = { case Tcp.Received(data) => log.info("received {}", data) //サーバーからデータを受信 case data: ByteString => connection ! Tcp.Write(data, Ack) //ユーザーからデータの送信要求がきたら書き込む。成功したらAckを返 してもらう。Ackが返るまで受け付けてはいけません!! case Ack => connection ! Tcp.ResumeReading //データの送信が完了したら受信を再開する } } Pull Mode を使う読み込み側の背圧制御のために _人人人人人人人人人人人人人人人人_ > 書き込み側にも背圧制御欲しい <  ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄ そこでAkka Streamだ
  24. 24. Akka Streamのステージ final case class Map[In, Out](f: In ⇒ Out) extends GraphStage[FlowShape[In, Out]] { val in = Inlet[In]("Map.in") //入力ポート val out = Outlet[Out]("Map.out") //出力ポート override def shape: FlowShape[In, Out] = FlowShape.of(in, out) override def createLogic(inheritedAttributes: Attributes): GraphStageLogic = new GraphStageLogic(shape) { setHandler(in, new InHandler { //入力ポート`in`のイベントハンドラ override def onPush(): Unit = { //PUSHされた時のフック val v = f(grab(in)) //pushされた要素を取得してfを適用 push(out, v) //`out`にpush } }) setHandler(out, new OutHandler { //出力ポート`out`のイベントハンドラ override def onPull(): Unit = { //PULLされた時のフック pull(in) //`in`からpull } }) } } //使い方 def mapFlow[In, Out](f: In => Out): Flow[In, Out, NotUsed] = Flow.fromGraph(Map(f)) ストリームのステージの実装例としてMapを見てみよう Source.map()やFlow.map()はこのように実装されている
  25. 25. class TcpStreamLogic(val shape: FlowShape[ByteString, ByteString], val role: TcpRole) extends GraphStageLogic(shape) { implicit def self: ActorRef = stageActor.ref private def bytesIn = shape.in //読み込み用のポート private def bytesOut = shape.out //書き込み用のポート private var connection: ActorRef = _ //TCPコネクションのworkerアクター override def preStart(): Unit = { role match { case ob @ Outbound(manager, cmd: akka.io.Tcp.Connect, _, _) ⇒ getStageActor(connecting(ob)) //このステージのアクターのreceiveをconnectingで初期化 manager ! cmd // managerはIO(Tcp)と同じ。Tcp.Connectコマンドを送る。 } } private def connecting(ob: Outbound)(evt: (ActorRef, Any)): Unit = { val (sender, msg) = evt msg match { case c: akka.io.Tcp.Connected => role.asInstanceOf[Outbound].localAddressPromise.success(c.localAddress) //Materialized ValueのPromiseにlocalAddressを書き込む connection = sender // senderがTCPコネクションのworkerアクター setHandler(bytesOut, readHandler) //bytesOutのイベントハンドラをreadHandlerに設定 stageActor.become(connected) //このステージのアクターのreceiveをconnectedにする(context.become(connected)と同じ) connection ! akka.io.Tcp.Register(self) if (isAvailable(bytesOut)) connection ! ResumeReading pull(bytesIn) //bytesInからpullして要素を要求 } } private def connected(evt: (ActorRef, Any)): Unit = { val (sender, msg) = evt msg match { case akka.io.Tcp.Received(data) => push(bytesOut, data) //データを受信したらbytesOutにpushする case WriteAck => pull(bytesIn) //書き込み成功のAckが返ってきたらbytesInにpullして要素を要求 } } setHandler(bytesOut, new OutHandler { override def onPull(): Unit = () }) val readHandler = new OutHandler { override def onPull(): Unit = { //pullされたときに呼ばれるイベントハンドラー connection ! ResumeReading } } setHandler(bytesIn, new InHandler { override def onPush(): Unit = { //pushされたときに呼ばれるイベントハンドラー val elem = grab(bytesIn) //bytesInにpushされた要素を取得 connection ! Write(elem.asInstanceOf[ByteString], WriteAck) } }) } Tcp().outgoingConnectionステージの実装 https://github.com/akka/akka/blob/v2.4.2/akka- stream/src/main/scala/akka/stream/impl/io/TcpStages.scala#L171-L287 長いけどakka.io.Tcpの復習でみた コードと比較すると 理解しやすい
  26. 26. class TcpStreamLogic(val shape: FlowShape[ByteString, ByteString], val role: TcpRole) extends GraphStageLogic(shape) { implicit def self: ActorRef = stageActor.ref private def bytesIn = shape.in //読み込み用のポート private def bytesOut = shape.out //書き込み用のポート private var connection: ActorRef = _ //TCPコネクションのworkerアクター override def preStart(): Unit = { role match { case ob @ Outbound(manager, cmd: akka.io.Tcp.Connect, _, _) ⇒ getStageActor(connecting(ob)) //このステージのアクターのreceiveをconnectingで初期化 manager ! cmd // managerはIO(Tcp)と同じ。Tcp.Connectコマンドを送る。 } } } class PullModeClient(remote: InetSocketAddress) extends Actor with ActorLogging { import context.system override def preStart: Unit = { val manager: ActorRef = IO(Tcp) //IOエクステンションからTCPマネージャーを取得 manager ! Tcp.Connect(remote, pullMode = true) //TCPマネージャーに接続要求を送る } def receive: Receive = connecting } akka.io.Tcpの対応する部分 Tcp().outgoingConnectionステージの実装
  27. 27. private def connecting(ob: Outbound)(evt: (ActorRef, Any)): Unit = { val (sender, msg) = evt msg match { case c: akka.io.Tcp.Connected => role.asInstanceOf[Outbound].localAddressPromise.success(c.localAddress) //Materialized ValueのPromise にlocalAddressを書き込む connection = sender // senderがTCPコネクションのworkerアクター setHandler(bytesOut, readHandler) //bytesOutのイベントハンドラをreadHandlerに設定 stageActor.become(connected) //このステージのアクターのreceiveをconnectedにする( context.become(connected)と同じ) connection ! akka.io.Tcp.Register(self) if (isAvailable(bytesOut)) connection ! ResumeReading pull(bytesIn) //bytesInからpullして要素を要求 } } } def connecting: Receive = { case Tcp.Connected(remote, local) => { //TCP接続の完了 val connection = sender() //senderがコネクションWorkerアクター context.become(connected(connection)) connection ! Tcp.Register(self) //自分自身をコネクションWorkerアクターに登録 connection ! Tcp.ResumeReading //サーバーからの受信を再開する } } akka.io.Tcpの対応する部分 Tcp().outgoingConnectionステージの実装
  28. 28. private def connected(evt: (ActorRef, Any)): Unit = { val (sender, msg) = evt msg match { case akka.io.Tcp.Received(data) => push(bytesOut, data) //R② データを受信したらbytesOutにpushする case WriteAck => pull(bytesIn) //W② 書き込み成功のAckが返ってきたらbytesInにpullして要素を要求 } } val readHandler = new OutHandler { override def onPull(): Unit = { //bytesOutでpullされたときに呼ばれるイベントハンドラー connection ! ResumeReading //R① bytesOutにpull要求がきたら読み込みを再開する } } setHandler(bytesIn, new InHandler { override def onPush(): Unit = { //bytesInでpushされたときに呼ばれるイベントハンドラー val elem = grab(bytesIn) //bytesInにpushされた要素を取得 connection ! Write(elem.asInstanceOf[ByteString], WriteAck) //W① pushされたデータを書き込む } }) def connected(connection: ActorRef): Receive = { case Tcp.Received(data) => log.info("received {}", data) //サーバーからデータを受信 case data: ByteString => connection ! Tcp.Write(data, Ack) //ユーザーからデータの送信要求がきたら case Ack => connection ! Tcp.ResumeReading //データの送信が完了したら受信を再開する } akka.io.Tcpの対応する部分 Tcp().outgoingConnectionステージの実装
  29. 29. private def connected(evt: (ActorRef, Any)): Unit = { val (sender, msg) = evt msg match { case akka.io.Tcp.Received(data) => push(bytesOut, data) //R② データを受信したらbytesOutにpushする case WriteAck => pull(bytesIn) //W② 書き込み成功のAckが返ってきたらbytesInにpullして要素を要求 } } val readHandler = new OutHandler { override def onPull(): Unit = { //bytesOutでpullされたときに呼ばれるイベントハンドラー connection ! ResumeReading //R① bytesOutにpull要求がきたら読み込みを再開する } } setHandler(bytesIn, new InHandler { override def onPush(): Unit = { //bytesInでpushされたときに呼ばれるイベントハンドラー val elem = grab(bytesIn) //bytesInにpushされた要素を取得 connection ! Write(elem.asInstanceOf[ByteString], WriteAck) //W① pushされたデータを書き込む } }) def connected(connection: ActorRef): Receive = { case Tcp.Received(data) => log.info("received {}", data) //サーバーからデータを受信 case data: ByteString => connection ! Tcp.Write(data, Ack) //ユーザーからデータの送信要求がきたら case Ack => connection ! Tcp.ResumeReading //データの送信が完了したら受信を再開する } akka.io.Tcpの対応する部分 欲しいと言うまでデータは来ない ようになった Tcp().outgoingConnectionステージの実装
  30. 30. TCPレイヤーの内部構造 サーバー編
  31. 31. class TcpStreamLogic(val shape: FlowShape[ByteString, ByteString], val role: TcpRole) extends GraphStageLogic(shape) { implicit def self: ActorRef = stageActor.ref private def bytesIn = shape.in //読み込み用のポート private def bytesOut = shape.out //書き込み用のポート private var connection: ActorRef = _ //TCPコネクションのworkerアクター setHandler(bytesOut, new OutHandler { override def onPull(): Unit = () }) override def preStart(): Unit = { role match { case Inbound(conn, _) => setHandler(bytesOut, readHandler) //bytesOutのイベントハンドラをreadHandlerに設定 connection = conn getStageActor(connected) //このステージのアクターのreceiveをconnectedにする(context.become(connected)と同じ) connection ! Register(self) pull(bytesIn) //bytesInからpullして要素を要求 } } private def connected(evt: (ActorRef, Any)): Unit = { val (sender, msg) = evt msg match { case akka.io.Tcp.Received(data) => push(bytesOut, data) //R② データを受信したらbytesOutにpushする case WriteAck => pull(bytesIn) //W② 書き込み成功のAckが返ってきたらbytesInにpullして要素を要求 } } val readHandler = new OutHandler { override def onPull(): Unit = { //bytesOutでpullされたときに呼ばれるイベントハンドラー connection ! ResumeReading //R① bytesOutにpull要求がきたら読み込みを再開する } } setHandler(bytesIn, new InHandler { override def onPush(): Unit = { //bytesInでpushされたときに呼ばれるイベントハンドラー val elem = grab(bytesIn) //bytesInにpushされた要素を取得 connection ! Write(elem.asInstanceOf[ByteString], WriteAck) //W① pushされたデータを書き込む } }) } Tcp.IncomingConnection#flowステージの実装 開始の仕方が違うだけで 実装は同じ https://github.com/akka/akka/blob/v2.4.2/akka- stream/src/main/scala/akka/stream/impl/io/TcpStages.scala#L171-L287
  32. 32. Tcp().bindステージ 実はデータの受信のみでなく、 コネクションの受付けまで背圧制御されています https://github.com/akka/akka/blob/v2.4.2/akka- stream/src/main/scala/akka/stream/impl/io/TcpStages.scala#L30-L141
  33. 33. まとめ Akka HTTPでは • 自分が処理できなければ読み込まない • 相手が処理できなければ書き込まない システム同士はみんなともだち
  34. 34. Thank you!

×