Scala / Akka / Protobuf: не удалось сериализовать и десериализовать сообщение

#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