finagle简单使用


在看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的感觉。