誰得: finagle-mysql + quill code reading
誰得情報ですが、せっかく読んだので公開しておきます。
Version
Clientの生成
io.getquill.FinagleMysqlContextConfig
にtypesafe Configを渡して#client
が出発点io.getquill.FinagleMysqlContext
のコンストラクタに生成したClientを渡すことで、DaoなどはこのContextで定義されている#executeQuery
や#transaction
を経由してClientにアクセスする
com.twitter.finagle.Mysql#client
が呼ばれる- =
Mysql.Client#apply
- stack & params 引数は渡さないのでデフォルトが指定される
- stackパラメータには
Mysql.Client.stack
が設定される。これにはStackClient#newStack
をベースにしてTracingFilterとConnection管理をちょっと便利にする系のヘルパーを組み込んだStackが設定されている。 - paramsパラメータには
StackClient.defaultParams
をベースにしてDefaultPoolの設定のデフォルト値などが設定されている。
- stackパラメータには
- Stackのなかには
ClosableService
がある。- https://github.com/twitter/finagle/commit/c64bea0939a2c41dcc2addd8fcea3ad6f2af63e2
- (DeepL翻訳)newClientの
ServiceFactory
で作成されたセッションを閉じても、接続は接続プール内で生きているため、再利用を妨げることはありません。新しいクライアントスタックモジュールはDefaultPool
の上にあります。FactoryToServiceが有効になっていない場合、DefaultPoolから返されるサービスはClosableService
でラップされ、閉じたセッションが再利用されないようにします。
- (DeepL翻訳)newClientの
- https://github.com/twitter/finagle/commit/c64bea0939a2c41dcc2addd8fcea3ad6f2af63e2
- Stackにコネクションプールの設定が含まれる
- Finagle標準の仕組みでServiceインスタンスをプールできる。=DefaultPool
- DefaultPoolもServiceFactory
Mysql.Client
はMySQLサーバとの間のTransportを設定するために存在するcom.twitter.finagle.client.StdStackClient
をextendsしていて、TransporterとDispatcherを実装することでendpointer: EndpointerModule
が動くよう定義されている。この endpointer が後述のEndpointerStackClient#newClient
のなかで呼ばれて、com.twitter.finagle.ServiceFactory
が生成される。
- stack & params 引数は渡さないのでデフォルトが指定される
FinagleMysqlContextConfig
に処理が戻って、Mysql.Client#withXxx
を使ってConfigを反映- 最後に
Mysql#newRichClient
を呼んでClient
インスタンスを生成する- =
com.twitter.finagle.mysql.Client#apply
が呼ばれる ※小文字のmysqlに注意 - 第一引数に
ServiceFactory
をとり、ここでClient#newClient
を呼ぶことで、そのImplであるMysql#newClient
が呼ばれる。これはそのままMysql.Client#newClient
にデリゲートされる。 Mysql.Client#newClient
はEndpointerStackClient#newClient
そのものであり、このなかではMysql.Client.stack
に前述のendpointerをjoinしてStack#make
によってServiceFactory
が生成される。これで、Stackに載せていた各種機能ががっちゃんこされる。- また、
Mysql#newRitchClient
に渡されるdest: String
はcom.twitter.finagle.Resolver#evalLabeled
によって名前解決されてcom.twitter.finagle.Name
になる。これがアドレスを指す。EndpointerStackClient#newClient
ではこのNameをclientParams
として他のparamsとくっつけてStack#make
に渡す- 謎の魔法によって
EndpointAddr
になり、Transporterに設定される。
- また、
- 生成した
ServiceFactory
といくらかの設定(StatsReceiverなど)をmysql.Client#apply
に渡す
- =
- =
Points
- コネクションまわりの制御は
Mysql.Client
- Stackを組み立てて、MySQLトランスポートを組み込んで、ServiceFactoryをつくるところまで
- 実行部分の制御は
mysql.Client
- ServiceFactoryからServiceをとって、クエリなどを投げる
DBコネクションの確立
Mysql.Client
がextendsしているcom.twitter.finagle.client.StdStackClient#endpointer
でEndpointerModule
をStackに載せるEndpointerModule
でStack#make
が呼ばれたとき、com.twitter.finagle.mysql.MysqlTransporter
を取得しServiceFactory
を生成するServiceFactory#apply
が呼ばれたときMysqlTransporter#apply
メソッドを呼ぶ- = Service を取得するタイミング
- https://github.com/twitter/finagle/blob/finagle-19.12.0/finagle-core/src/main/scala/com/twitter/finagle/client/StdStackClient.scala#L75
- netty4 でコネクション(Transport)を取得して
com.twitter.finagle.mysql.transport.MysqlTransport
をインスタンス化 com.twitter.finagle.mysql.Handshake#apply
を呼び設定に従ってHandshakeオブジェクトを取得する- PlainかSecureかはparamから解決される
Handshake.connectionPhase
が呼ばれMysqlTransport
を通してネゴシエーションがされる
クエリ実行
StdClient
が生成されるタイミングでServiceFactory#toService
が呼ばれる- val で束縛しているが、
FactoryToService#apply(Request)
では都度ServiceFactory#apply()
によってServiceを取得している #prepare
や#transaction
も新しくServiceFactory#apply()
によってServiceを都度取得する
- val で束縛しているが、
ServiceFactory#toService
でServiceを取得- =
new com.twitter.finagle.FactoryToService
- =
#query
などではService#apply
にRequestオブジェクトを投げる- このRequestオブジェクトは
com.twitter.finagle.mysql.QueryRequest
など com.twitter.finagle.mysql.Request#toPacket
によってPacketオブジェクトを取り出し、先述のMysql.Client
で定義されているTransporterによってプロトコル制御に渡される。
- このRequestオブジェクトは
トランザクション
StdClient#session
が呼ばれるServiceFactory#apply()
によって(恐らくService自体にコネクションは組み込まれているので、ClientConnectionはnilでよい)Future[Service[..]]
を取得- sessionブロックのなかで使うClientインスタンスを用意しており、これは↑のServiceインスタンスをSingletonに利用する。これによって、前述のとおり
FactoryToService#apply
が返すServiceが同じになるため、トランザクションセッション中に同じコネクションが使われることが担保される。- sessionブロックのなかでもう一段ならセッションを開始できるみたい(だけど、あまりやるべきでなはない)
- sessionブロックを抜けるときにServiceインスタンスは
#close
が呼ばれるClient#close
→ServiceFactory#close
→Service#close
で回収される
- sessionメソッドに
f
ブロックを渡す- Optional
SET TRANSACTION ISOLATION LEVEL
->START TRANSACTION
->f(client)
->COMMIT
- 途中のどこかで
Throw(e @ WrappedChannelClosedException())
結果となるとROLLBACKせずに終わるChannelClosedException
を cause まで再帰的に見てたどり着けばこの分岐
- それ以外の
Throw
でROLLBACKが実行される- このROLLBACKにも失敗すると、
Client#discard
(実体はStdClient#session
のなかでつくられるStdClient with Session
)を呼んでPoisonConnectionRequest
をServiceに渡して自死させる。=コネクションを再利用されないよう閉じる- Client生成時点でStackに
PoisonConnection.module
が積まれており、ServiceはPoisonableService
に包まれている。PoisonConnectionRequest
を受け取ると、Service#status
をClosedにし、#apply(Request)
が呼ばれるとPoisonedConnectionException
例外を投げるようになる。実際の close メソッドはこのなかでは呼ばず、回収されるのを待つ感じになる。トランザクションに関しては前述の session 出口の close 地点。
- Client生成時点でStackに
- このROLLBACKにも失敗すると、
- Optional