在看awseome-scala的时候,正好看到了twitter的finagle。自己公司里貌似也有那种关注load balance等的解决方案,不过不是很容易理解。考虑到自己之前几个月都在弄电子技术了,重新弄点网络相关的把。
这次的任务是做了一个thrift+finagle的远程服务调用例子。其实不是很难,如果不是连续碰到finagle的坑的话。finagle是twitter开源出来的一个分布式系统解决方案,毕竟是大公司出来的东西,更新频率特别是文档很低,很多文档基本都是几年前,好在有代码……
先给一个thrift的IDL
namespace java in.xnnyygn.dictservice exception DictServiceException { 1: string description } service DictService { string get(1: string key) throws(1: DictServiceException ex) void put(1: string key, 2: string value) }
恩,这个是twitter那个scala-bootstrapper的searchbird的例子,不过scala-bootstrapper对应的finagle什么的版本都很低。只能从头开始写。
生成thrift的对应scala服务客户端是用一个叫做scrooge的sbt插件,也是twitter的。scroooge据说兼容apache thrift默认的服务客户端生成程序,不过scrooge可以生成finagle的服务和客户端,所以更适合使用finagle的程序。
com.twitter.scrooge.ScroogeSBT.newSettings scroogeBuildOptions in Compile := Seq("--finagle", "--verbose") scalaVersion := "2.10.4" libraryDependencies ++= Seq( "org.apache.thrift" % "libthrift" % "0.8.0", "com.twitter" %% "scrooge-core" % "3.14.1", "com.twitter" %% "finagle-thrift" % "6.24.0", "com.rabbitmq" % "amqp-client" % "3.4.3", "com.typesafe.akka" % "akka-actor_2.10" % "2.3.9" )
上面是build.sbt的内容,注意scrooge的版本是3.14.1。这里的版本需要和scala以及sbt对应。可以到这里看对应版本的关系。
其次scroogeBuildOptions默认是finagle,没有verbose,这里这行配置删除也可以。库资料中前3个是需要的,后面两个是因为我后来又把rabbitmq加上了的原因。
相应的project/plugins下需要声明scrooge插件。
addSbtPlugin("com.twitter" %% "scrooge-sbt-plugin" % "3.14.1")
scrooge会在你compile的时候执行,你把thrift放在src/main/thrift目录下,它自动会生成文件并放到大致是target/scala_2.x/src_managed目录下。接下来是服务实现。
package in.xnnyygn.dictservice import com.twitter.util.Future import java.util.concurrent.ConcurrentHashMap class DictServiceImpl extends DictService.FutureIface { private val map = new ConcurrentHashMap[String, String] def get(key: String): Future[String] = Option(map.get(key)) match { case Some(value) => Future.value(value) case _ => Future.exception(new DictServiceException("no such key")) } def put(key: String, value: String) = { map.put(key, value) Future.Done } }
不是很复杂,原先twitter的searchbird例子中ConcurrentHashMap部分使用的是Map with SynchronizedMap,不过Scala 2.10.x说建议用ConcurrentHashMap,又一个deprecated的坑……
产生服务的部分为
package in.xnnyygn.dictservice import com.twitter.util.{Await, Future} import com.twitter.finagle.Thrift class DictServiceTestServer { def start = { val service = Thrift.serveIface("localhost:9999", new DictServiceImpl) Await.ready(service) } }
这里的代码是参考finagle.Thrift的scaladoc写的,其他地方的文档或多或少有问题,有事情还是看这段代码的scaladoc比较好。
相应的客户端为
package in.xnnyygn.dictservice import com.twitter.finagle.Thrift class DictServiceTestClient { val stub = Thrift.newIface[DictService.FutureIface]("localhost:9999") }
这里很简单,因为之后我是用sbt console测试的。如果你从scrooge还是其他什么地方看到这里的写法,貌似会出错,我只能参考Thrift的scaladoc了。
最后开两个sbt console,你可以体会下scala -thrift-> scala的感觉。