18

如果我有一个案例类的嵌套对象图,类似于下面的示例,并且我想将它们的集合存储在一个 redis 列表中,我应该查看哪些库或工具可以提供最快的整体往返雷迪斯?

这将包括:

  • 是时候序列化项目了
  • 传输序列化数据的网络成本
  • 检索存储的序列化数据的网络成本
  • 是时候反序列化回案例类了

    case class Person(name: String, age: Int, children: List[Person]) {}
    
4

4 回答 4

28

更新(2018 年):不再积极维护 scala/pickling。有大量其他库作为替代方案出现,它们采用类似的方法,但往往专注于特定的序列化格式;例如,JSON、二进制、protobuf。

您的用例正是scala/pickling ( https://github.com/scala/pickling ) 的目标用例。免责声明:我是作者

Scala/pickling 旨在成为 Java 或 Kryo 等自动框架的更快、更安全、更开放的替代方案。它是专为分布式应用程序而构建的,因此序列化/反序列化时间和序列化数据大小占据首位。它采用不同的方法进行序列化 - 它在编译时在使用站点内联生成酸洗(序列化)代码,因此速度非常快。

最新的基准在我们的OOPSLA 论文中- 对于二进制 pickle 格式(您也可以选择其他格式,例如 JSON),scala/pickling 始终比 Java 和 Kryo 快,并且生成的二进制表示与 Kryo 相同或更小,这意味着更少通过网络传递腌制数据时的延迟。

欲了解更多信息,有一个项目页面: http: //lampwww.epfl.ch/~hmiller/pickling

还有一个ScalaDays 2013 演讲,从 6 月开始在 Parley 的.

我们还将在 Strange Loop 2013 上展示一些新的发展,特别是与处理通过网络发送闭包相关的,以防这也可能成为您的用例的痛点。

在撰写本文时,scala/pickling 处于预发布状态,我们的第一个稳定版本计划于 8 月 21 日发布。

于 2013-08-10T12:11:20.860 回答
9

更新:

您必须小心使用 JDK 中的序列化方法。性能不是很好,你的类中的一个小变化就会使数据无法反序列化。


我使用了 scala/pickling,但它在序列化/反序列化时有一个全局锁。

因此,我没有使用它,而是编写了自己的序列化/反序列化代码,如下所示:

import java.io._

object Serializer {

  def serialize[T <: Serializable](obj: T): Array[Byte] = {
    val byteOut = new ByteArrayOutputStream()
    val objOut = new ObjectOutputStream(byteOut)
    objOut.writeObject(obj)
    objOut.close()
    byteOut.close()
    byteOut.toByteArray
  }

  def deserialize[T <: Serializable](bytes: Array[Byte]): T = {
    val byteIn = new ByteArrayInputStream(bytes)
    val objIn = new ObjectInputStream(byteIn)
    val obj = objIn.readObject().asInstanceOf[T]
    byteIn.close()
    objIn.close()
    obj
  }
}

下面是一个使用它的例子:

case class Example(a: String, b: String)

val obj = Example("a", "b")
val bytes = Serializer.serialize(obj)
val obj2 = Serializer.deserialize[Example](bytes)
于 2015-12-28T10:55:09.753 回答
0

根据upickle基准测试:“uPickle 在读/写方面比 Circe 快 30-50%,在序列化案例类方面比 play-json 快约 200%”。

它很容易使用,下面是如何将案例类序列化为 JSON 字符串:

case class City(name: String, funActivity: String, latitude: Double)
val bengaluru = City("Bengaluru", "South Indian food", 12.97)
implicit val cityRW = upickle.default.macroRW[City]
upickle.default.write(bengaluru) // "{\"name\":\"Bengaluru\",\"funActivity\":\"South Indian food\",\"latitude\":12.97}"

您还可以序列化为二进制或其他格式。

于 2020-12-23T03:30:27.843 回答
0

2013 年接受的答案提出了一个不再维护的库。StackOverflow 上有很多类似的问题,但我真的找不到符合以下标准的好答案:

  • 序列化/反序列化应该很快
  • 通过网络进行高性能数据交换,您只需对所需的元数据进行编码
  • 支持模式演变,以便更改序列化对象(例如:)case class不会破坏过去的反序列化

我建议不要使用低级 JDK SerDes(如ByteArrayOutputStreamByteArrayInputStream)。支持模式演变变得很痛苦,并且很难使其与外部服务(例如:Thrift)一起工作,因为您无法控制被发送回的数据是否使用相同类型的流。

有些人使用 JSON 规范,使用json4s之类的库,但它不适合分布式计算消息传输。它将数据编组为 JSON 字符串,因此速度较慢且存储效率低,因为它将使用 8 位来存储字符串中的每个字符。

我强烈推荐使用MessagePack二进制序列化格式。我建议阅读规范以了解编码细节。它具有多种不同语言的实现,这是我为 Scala 编写的通用示例case class,您可以将其复制粘贴到代码中。

import java.nio.ByteBuffer
import java.util.concurrent.TimeUnit

import org.msgpack.core.MessagePack

case class Data(message: String, number: Long, timeUnit: TimeUnit, price: Long)

object Data extends App {

  def serialize(data: Data): ByteBuffer = {
    val packer = MessagePack.newDefaultBufferPacker
    packer
      .packString(data.message)
      .packLong(data.number)
      .packString(data.timeUnit.toString)
      .packLong(data.price)
    packer.close()
    ByteBuffer.wrap(packer.toByteArray)
  }

  def deserialize(data: ByteBuffer): Data = {
    val unpacker = MessagePack.newDefaultUnpacker(convertDataToByteArray(data))
    val newdata = Data.apply(
      message = unpacker.unpackString(),
      number = unpacker.unpackLong(),
      timeUnit = TimeUnit.valueOf(unpacker.unpackString()),
      price = unpacker.unpackLong()
    )
    unpacker.close()
    newdata
  }

  def convertDataToByteArray(data: ByteBuffer): Array[Byte] = {
    val buffer = Array.ofDim[Byte](data.remaining())
    data.duplicate().get(buffer)
    buffer
  }

  println(deserialize(serialize(Data("Hello world!", 1L, TimeUnit.DAYS, 3L))))
}

它将打印:

Data(Hello world!,1,DAYS,3)
于 2021-10-08T02:38:02.393 回答