#scala #serialization #akka #protocol-buffers #scalapb
#scala #сериализация #akka #протокол-буферы #scalapb
Вопрос:
мы пытаемся использовать protobuf с Akka и сериализовать все сообщения Akka через protobuf. Для Scala у нас есть библиотека под названием ScalaPB, которая помогает нам сгенерировать класс, включающий такие методы, как parseFrom
, toByteArray
и т.д. Для сериализации или десериализации наших данных. Но, пока мы пытаемся запустить программу, получаем приведенное ниже исключение:
akka.actor.dungeon.SerializationCheckFailedException: Failed to serialize and deserialize message of type com.knoldus.akkaserialization.msg.example.Bang$ for testing. To avoid this error, either disable 'akka.actor.serialize-messages', mark the message with 'akka.actor.NoSerializationVerificationNeeded', or configure serialization to support this message
файл application.conf содержит приведенную ниже конфигурацию:
akka {
actor {
allow-java-serialization = off
serialize-messages = on
serializers {
proto = "akka.remote.serialization.ProtobufSerializer"
}
serialization-bindings {
"com.knoldus.akkaserialization.msg.example.Bang" = proto
"com.knoldus.akkaserialization.msg.example.Message" = proto
}
}
}
Эти классы com.knoldus.akkaserialization.msg.example.Bang
и com.knoldus.akkaserialization.msg.example.Message
генерируются через ScalaPB и содержат все требуемые методы.
Исходный код akka.remote.serialization.ProtobufSerializer
define,
This Serializer serializes `akka.protobuf.Message` and `com.google.protobuf.Message` It is using reflection to find the `parseFrom` and `toByteArray` methods to avoid dependency to `com.google.protobuf`
Итак, мы ожидаем, что это автоматически считывает наши классы case Bang
и Message
и выполняет сериализацию, но, к сожалению, получаем исключение сериализации.
Не могли бы вы помочь выяснить, в чем именно проблема со ScalaPB и ProtoBuff?
Ответ №1:
Используемый вами сериализатор был разработан для работы с Java protobufs, а не со ScalaPB. Вам необходимо включить свой собственный сериализатор. Вот один из них, который вы можете использовать:
package com.example.protoser
import java.util.concurrent.atomic.AtomicReference
import akka.actor.ExtendedActorSystem
import akka.serialization.BaseSerializer
import scalapb.GeneratedMessageCompanion
class ScalaPbSerializer(val system: ExtendedActorSystem) extends BaseSerializer {
private val classToCompanionMapRef = new AtomicReference[Map[Class[_], GeneratedMessageCompanion[_]]](Map.empty)
override def toBinary(o: AnyRef): Array[Byte] = o match {
case e: scalapb.GeneratedMessage => e.toByteArray
case _ => throw new IllegalArgumentException("Need a subclass of scalapb.GeneratedMessage")
}
override def includeManifest: Boolean = true
override def fromBinary(bytes: Array[Byte], manifest: Option[Class[_]]): AnyRef = {
manifest match {
case Some(clazz) =>
@scala.annotation.tailrec
def messageCompanion(companion: GeneratedMessageCompanion[_] = null): GeneratedMessageCompanion[_] = {
val classToCompanion = classToCompanionMapRef.get()
classToCompanion.get(clazz) match {
case Some(cachedCompanion) => cachedCompanion
case None =>
val uncachedCompanion =
if (companion eq null) Class.forName(clazz.getName "$", true, clazz.getClassLoader)
.getField("MODULE$").get().asInstanceOf[GeneratedMessageCompanion[_]]
else companion
if (classToCompanionMapRef.compareAndSet(classToCompanion, classToCompanion.updated(clazz, uncachedCompanion)))
uncachedCompanion
else
messageCompanion(uncachedCompanion)
}
}
messageCompanion().parseFrom(bytes).asInstanceOf[AnyRef]
case _ => throw new IllegalArgumentException("Need a ScalaPB companion class to be able to deserialize.")
}
}
}
Конфигурация должна быть примерно такой:
akka {
actor {
serializers {
scalapb = "com.example.protoser.ScalaPbSerializer"
}
serialization-bindings {
"scalapb.GeneratedMessage" = scalapb
}
serialization-identifiers {
"com.example.protoser.ScalaPbSerializer" = 10000
}
}
}
Вышесказанное было адаптировано из старого кода, поэтому правки и предложения приветствуются!
Ответ №2:
Вот простой способ сделать это, просто добавьте следующие строки в свою конфигурацию.
https://doc.akka.io/docs/akka/current/serialization.html
Akka предоставляет сериализаторы для нескольких примитивных типов и protobuf com.google.protobuf.Сгенерированное сообщение (protobuf2) и com.google.protobuf.По умолчанию генерируется messagev3 (protobuf3) (последнее зависит только от модуля akka-remote), поэтому обычно вам не нужно добавлять конфигурацию для этого, если вы отправляете необработанные сообщения protobuf в качестве сообщений участника.
Сгенерированный ScalaPB код также может быть сериализован в protobuf, поэтому нам просто нужно привязать сгенерированный ScalaPB признак класса case к serializer.
akka {
actor {
serialization-bindings {
"com.google.protobuf.Message" = proto
"scalapb.GeneratedMessage" = proto
"scalapb.GeneratedEnum" = proto
}
}
}
У меня это работает. Моя среда:
- akka-grpc: 2.1.4
- akka: 2.6.19
- Scala: 2.13.8