Scala实例教程:Kestrel

要学习一门新语言,最好的方法之一莫过于从代码实例学习。现在Scala对于很多人来说还是一门新语言,所以有机会通过Scala实例教程多看多学都是很有帮助的。

Scala实例教程:走读Kestrel,了解Scala

Scala的一些语言特性是很令人着迷的,尤其对于那些厌倦了异常处理和冗长的类型转换的Java工程师来说,Scala可以把代码写的更加地简洁。但是对于Scala来说,尤其是没有 Python, Ruby 等语言基础的Java工程师来说,学习Scala是一件比较痛苦的事情,而将Scala的优势发挥出来,则需要更多的努力。

为了让这个事情变得不那么困难,我们可以从优秀的一个Scala代码的实例来开始――Kestrel。我们可以从新闻中了解到,作为Twitter核心的消息转发机制,使用了Kestrel。也许是因为Kestrel的集群架构实现简单――Kestrel的说明中提到,只要运行多个独立的Kestrel,随机set和get这些实例就能做到集群了(其实牺牲了消息队列的时序)。

Kestrel的原型是来自于Ruby的Starling。一个兼容memcached接口的消息队列,把这个消息队列看成是一个基础的service。客户端可以通过queueName,向队列添加或者获取消息队列中的消息,依次处理。Kestrel项目中,使用Apache Mina项目来实现Socket的链接管理。

同时,Kestrel还支持当消息队列过多的时候,把消息队列存放到本地硬盘上的策略。以提高整体的稳定性。当然,最好的情况,还是能够及时将队列中的消息处理完毕。在测试的过程中,会发生OutOfMemory的情况,这是因为在配置中,允许驻留在内存中的消息数量,超过了JVM开启的时候,所能够申请的数量,这在实际的使用中,是需要考虑清楚的。

另外,还需要注意的是Kestrel只能在Java 1.6的环境下使用。

不过还是主要通过走读Kestrel代码,来了解Scala。也能从中学到一些对Scala非常好的使用方法。等到对代码有一定认识之后,在回来看Scala的语法手册,可能会有更多的新发现。

下载编译不多说,注意要用Java 1.6就可以了。我们先从简单的开始,我们在Kestrel的官方网站上看到一个压力测试的用例。如下文所示:

$ ant -f tests.xml put-many-1 -Ditems=5000000 

先从测试案例开始,然后再看核心代码,入门会比较容易。在测试案例里面,我们比较容易猜出来,整个程序大概做了一些什么,通过test.xml所述,我们找到了net.lag.kestrel.load.PutMany,在src/test/net/lang/kestrel/load里面可以找到这个程序,PutMany.scala,相对来说ManyClients.scala的功能可能更全面一些,我们就拿它作为例子吧:

package net.lag.kestrel.load  


 



import _root_.java.net._  




import _root_.java.nio._  




import _root_.java.nio.channels._  




import _root_.java.util.concurrent.atomic._  




import _root_.net.lag.extensions._  

这段一点都不难懂,嗯……我们可以大胆地猜的 _ 在这里的涵义等同于 Java 里面的 *。事实上就是这样的,在后来,你会发现 _ 不仅仅代表的是 * ,它会出其不意地出现在很多地方,而在Scala中,它的涵义接近于default。

object ManyClients {  



  private val SLEEP = System.getProperty("sleep", "100").toInt  




  private val COUNT = System.getProperty("count", "100").toInt  



  ……  


} 

类定义也很清楚,object 等同于 Java 中的 class。变量声明中Scala的格式和Java有点不同,需要说明一下的是var表示变量,而val表示常量,在声明的时候必须被赋值,不过这个常量是在创建的时候执行的结果。就好像例子中写的那样。注意toInt,这是一个Scala的特点,通过推断的方式来确定变量的类型,所有在有些时候,变量的类型声明就可以被省略了。

这种对变量类型的省略是Scala的核心理念――”Type Less, Do more”,有些事情编译器直到就好啦。

函数来了:

def put(socket: SocketChannel, queueName: String, n: Int) = {  


    ……  


} 

def 表示一个方法,每个Scala的函数都有返回,这是Scala对”=”的理解,返回的结果就是 {} 中最后一条指令执行的返回值,所以,Scala没有return函数。返回值类型和参数的类型说明都是用 name : type 的格式来体现的,如果我们制定了 put 的返回是一种特殊的类型,比如Unit,那么我们的定义就是 def put ( a : Int) : Unit = …… 。这样的表达是可以理解的,但是对于Java工程师来说,看起来很别扭,尤其是 = 后面的 {} 被省略掉之后。

接下来我们就阅读到了main函数,是的,它和Java的main函数作用是完全相同的,但是其中有一段比较难以理解的代码,而这段代码体现了Scala易用性的特点:

var threadList: List[Thread] = Nil  


……  



for (i <- 0 until clientCount) {  




  val t = new Thread {  



    override def run = {  



      val socket = SocketChannel.open(new InetSocketAddress("localhost", 22133))  




      getStuff(socket, "spam")  



    }  


  }  


  threadList = t :: threadList  


  t.start  


}  


……  



for (t <- threadList) {  



  t.join  


}  


 

先解释一下对List的操作。var threadList : List[Thread],比较好理解,就是声明一个List,每个成员都是Thread,类似于Java声明ArrayList。使用var而不使用val,因为前面提到过var是会被改变的,val是常量。而 threadList = t :: threadList 是一个对List的操作,把t放在threadList的表头……是不是很形象呢?

再解释一下for循环的一个语法 for (t <- threadList) {} 就是遍历所有的threadList都执行一遍,等同于Java中的iterator,是否看起来很简洁呢?而另一个语法 for (t <- 0 until clientCount) {} 就不用解释了。

Scala的精彩部分之一,隆重登场了:

val t = new Thread {  


    override def run = {  



      val socket = SocketChannel.open(new InetSocketAddress("localhost", 22133))  




      getStuff(socket, "spam")  



    }  


  } 

首先,t 是一个Thread类,因为 val t = new Thread ….。

然后我们发现这个Thread的run函数被重载了。这就是为什么def 后面那个 = 的深意,函数是可以被赋值的。然后繁琐的创建线程的语句,被简化成了简单的几行代码。非常直观。

相关推荐