如果我有一个案例类的嵌套对象图,类似于下面的示例,并且我想将它们的集合存储在一个 redis 列表中,我应该查看哪些库或工具可以提供最快的整体往返雷迪斯?
这将包括:
- 是时候序列化项目了
- 传输序列化数据的网络成本
- 检索存储的序列化数据的网络成本
是时候反序列化回案例类了
case class Person(name: String, age: Int, children: List[Person]) {}
更新(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 日发布。
更新:
您必须小心使用 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)
根据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}"
您还可以序列化为二进制或其他格式。
2013 年接受的答案提出了一个不再维护的库。StackOverflow 上有很多类似的问题,但我真的找不到符合以下标准的好答案:
case class
不会破坏过去的反序列化我建议不要使用低级 JDK SerDes(如ByteArrayOutputStream
和ByteArrayInputStream
)。支持模式演变变得很痛苦,并且很难使其与外部服务(例如: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)