Tokyo Course Grained

カナダ西海岸ソフトウェアエンジニアのブログです

finagle の Contexts

以前発表した finagle 話 で request id をリクエストに振るのにフィルタを使うといいよ、 でも後続のフィルタまたはサービスで id を使いたい場合どうやって渡すかが問題で、 今のところ http のヘッダにぶっこんでるが悩む…って話をした。

finagle のドキュメント読んでたら Contexts という機構を見つけてこれがリクエストごとのid に使えそう。 サンプル見ただけじゃ意味がわからなかったんだけど、ちょっとしたコード書いてみたら完全に理解した。

case class MyContext(id: String) {
  def asCurrent[T](f: => T): T = MyContext.let(this)(f)
}

object MyContext {

  val ctx = new Contexts.broadcast.Key[MyContext]("com.github.iwag.mycontext") {

    override def marshal(value: MyContext): Buf = {
      value match {
        case MyContext(id) => Buf.Utf8(id)
        case _ => Buf.Empty
      }
    }

    override def tryUnmarshal(buf: Buf): Try[MyContext] =
      buf match {
        case b if buf.isEmpty => Throw(new IllegalArgumentException("illegal"))
        case Buf.Utf8(id) => Return(MyContext(id))
        case invalid => Throw(new IllegalArgumentException("illegal"))
      }
  }

  def current = Contexts.broadcast.get(ctx)

  private[iwag] def let[R](myContext: MyContext)(f: => R): R =
    Contexts.broadcast.let(ctx, myContext)(f)
}

class RequestIdFilterEx extends SimpleFilter[Request, Response] {
  override def apply(request: Request, service: Service[Request, Response]): Future[Response] = {
    MyContext("test").asCurrent { // コンテキスト作成
      service(request) map { res =>
        MyContext.current.map { c => // こんな感じでとれる
         res.headerMap.add("mycontext-id", c.id)
       }
        res
      }
    }
  }
}


class HTTPServiceImpl(log:Logger) extends Service[Request, Response] {
  override def apply(request: Request): Future[Response] = Future.value{
   val res = Response()
   MyContext.current.map { c=>
      res.headerMap.add("mycontext-id-http", c.id) // 別のサービスでも取れる!
    }
  }
}

// ... 略
curl -i -XPOST localhost:40080 
mycontext-id-http: test
mycontext-id: test
Content-Length: 0

ClinentId のソース 見るとわかりますがほぼパクってます、、ちょっと違うけど、、、ClientId.scala これ finagle.thrift じゃなくて finagle.core においてもよいんじゃという気がする。

結局、 if(contextsがあるなら)みたいな感じで汚いがヘッダに突っ込む方式ではフィルタサービスが HttpRequest じゃないといけなかったのでよりいろんなところで使えるようになっていると思う。

いろんなところで読めるようになると何がうれしいか。例えば私のfinagleサーバは elasticsearch を使ってるんだけど、elasticsearch へのHTTPリクエスト(JSON) にこの id を紛れ込ませれば elasticsearch のスロークエリログにもidが載ることになり、スロークエリがどのリクエストだったか照合とかができるようになる。 MySQLとかRedisとかにもどっかに突っ込めるのだろうか、、、

ちなみにこのContextsの応用がTrace(zipkinとかで使う処理のトレース用)でこんな感じでどこでも(!)取れる

Trace.id.traceId.toString()

これを RequestId ということにするのが早いという説も、、、

しかし Context の仕組みがわからない(例えば broadcastとlocalの違い)、たぶん Trace.scala とか見れば、サーバを越えてContextから値を取るとかできるはず。。。

Traceというか zipkin の仕組みも調べて書きたいな、、、

広告を非表示にする