Scala 箭头函数 => By-Name Parameters

Scala 中允许使用高阶函数, 高阶函数可以使用其他函数作为参数,或者使用函数作为输出结果。通常情况下,函数的参数是传值参数;即参数的值在它被传递给函数之前被确定。但是,如果我们需要编写一个接收参数不希望马上计算,直到调用函数内的表达式才进行真正的计算的函数。对于这种情况,Scala提供按名称参数调用函数。

闲话少说,先撸代码:

object Tests {
  def main(args: Array[String]): Unit = {
    val a = stepOne(param())
    println(s"stepOne 返回的结果: --> ${a}")

  }
  def param(): Long ={
    println("进入param函数!每次调用都触发")
    System.nanoTime()
  }

  def stepOne(x: => Long): Long ={
    println("进入函数stepOne!")
    println(s"先调用参数x --> ${x} 打印出来")
    x
  }
}
Scala箭头函数 By-Name Parameters

从上图中我们看出来整个代码的执行顺序。在代码中,如果定义函数的时候,传入参数不是传入的值,而是传入的参数名称(如代码中使用x: => Long而不是x: Long),在调用该函数时,不会立即执行和参数有关的计算,而是到参数真正使用到的时候才进行计算。

使用“按名称传递参数”方式的优点是:1.减少不必要的计算; 2.减少异常。

LSM浅析

1、简介

定义:Log-Structured Merge Tree,简称 LSM-Tree.

遥想当年,谷歌发表了 “BigTable” 的论文, 它所使用的文件组织方式,这个方法更一般的名字叫 Log Structured-Merge Tree。

LSM是当前被用在许多产品的文件结构策略:比如 Apache HBase, Apache Cassandra, MongoDB 的 Wired Tiger 存储引擎, LevelDB 存储引擎, RocksDB 存储引擎等、 SQLite,甚至在mangodb3.0中也带了一个可选的LSM引擎(Wired Tiger 实现的)。

LSM 有趣的地方是他抛弃了大多数数据库所使用的传统文件组织方法,LSM把一棵大树拆分成N棵小树,它首先写入内存中,随着小树越来越大,内存中的小树会flush到磁盘中,磁盘中的树定期可以做merge操作,合并成一棵大树,以优化读性能。将对数据的修改增量保持在内存中,达到指定的大小限制后将这些修改操作批量写入磁盘,不过读取的时候稍微麻烦,需要合并磁盘中历史数据和内存中最近修改操作,所以写入性能大大提升,读取时可能需要先看是否命中内存,否则需要访问较多的磁盘文件。

磁盘和内存中的树合并

简单地说,LSM 的设计目标是提供比传统的 B+ 树更好的写性能。LSM 通过将磁盘的随机写转化为顺序写来提高写性能 ,而付出的代价就是牺牲部分读性能、写放大(B+树同样有写放大的问题)。

磁盘随机读写和顺序读写性能对比

从上图中我们可以直观的看出来,顺序读写磁盘(不管是SATA还是SSD)快于随机读写主存,而且快至少N个数量级。这就要我们在设计的时候要避免随机读写,最好是顺序读写。通常LSM-tree适用于索引插入比检索更频繁的应用系统。 而LSM-tree通过滚动合并和多页块的方法推迟和批量进行索引更新,充分利用内存来存储近期或常用数据以降低查找代价,利用硬盘来存储不常用数据以减少存储代价。

2、读性能优化

  • 二分查找: 将文件数据有序保存,使用二分查找来完成特定key的查找。
  • 哈希:用哈希将数据分割为不同的bucket
  • B+树:使用B+树 或者 ISAM 等方法,可以减少外部文件的读取
  • 外部文件: 将数据保存为日志,并创建一个hash或者查找树映射相应的文件

所有的方法都可以有效的提高了读操作的性能,都强加了总体的结构信息在数据上,数据被按照特定的方式放置,可以很快的找到特定的数据,但是却对写操作不友善,让写操作性能下降。更糟糕的是,当我们需要更新hash或者B+树的结构时,需要同时更新文件系统中特定的部分,这就是上面说的比较慢的随机读写操作。

3、权衡读写

LevelDB 为代表的 LSM 存储引擎实现的是优化后的 LSM,LevelDB 的写速度非常快,写操作主要由两步组成:

  • 写日志并持久化(Append Only)。
  • Apply 到内存中的 memtable(SkipList)。

基本的思路与原始的LSM类似,memtable 写“满”后,会转换为 immutable memtable,然后被后台线程 compaction 成按 Key 有序存储的 sst 文件(顺序写)。由于 sst 文件会有多个,所以 LevelDB 的读操作可能会有多次磁盘 IO(LevelDB 通过 table cache、block cache 和 bloom filter 等优化措施来减少读操作的磁盘 IO 次数)。

参考链接: http://www.benstopford.com/2015/02/14/log-structured-merge-trees/