• Akka源码分析-Serialization


       今天我们来谈一下akka的序列化框架,其实序列化、反序列化是一个老生常谈的问题,那么我们为什么还要研究一下akka的序列化框架呢?不就是使用哪种序列化、反序列化方法的区别么?其实刚开始的时候我也是这么想的,但是针对性、系统性的分析一下akka的序列化、反序列化过程,就会发现这个问题其实还是挺有意思的。

      我们首先来看下什么是序列化、反序列化。序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程;反序列化,就是通过从存储区中读取或反序列化对象的状态,重新创建该对象。举个栗子,在跨网络传输的过程中,传输的都是二进制数,是无法直接传输对象的。对象是什么呢?数据和算法,状态和方法。数据就是对象的状态,比如一个整型字段的值,字符串的具体内容;算法就是对象所属的方法,简单来说就是二进制可执行代码。统一来看,他们都可以是二进制数据。那么为啥在序列化的定义中,没有说算法的。想想都知道,当然是为了减少传输的数据量啊。而且代码这部分不传输也是可以的,毕竟网络的两端代码这部分是需要预先协商好的。

      那既然序列化就是用来保存、传输对象的状态的,那为啥还要单独研究akka的序列化框架呢?因为我喜欢!啊哈哈,其实也不是。主要是akka的序列化是一个框架,而不是只单独的某个算法或jar包。因为在这个框架内,你可以单独制定每个class的序列化、反序列化对应的实现。

      记得之前在分析remote的时候,我们有谈到过序列化,下面还从这部分开始分析,因为这比较直接。如果上来就分析整个框架,比较容易迷失。

      上面是跨网咯传输数据的相关代码,很明显,这里调用了codec.constructMessage,生成了pdu,然后调用handle.write把数据发送出去了。

      def constructMessage(
        localAddress:      Address,
        recipient:         ActorRef,
        serializedMessage: SerializedMessage,
        senderOption:      OptionVal[ActorRef],
        seqOption:         Option[SeqNo]       = None,
        ackOption:         Option[Ack]         = None): ByteString
    

       也就是说constructMessage把待发送的消息转化成了ByteString,这就是可以跨网络传输的二进制数据流。不过还调用了serializeMessage这个方法,把用户待发送的消息序列化了。其实我们应该特别注意这一点,为啥呢?因为这意味着用户待发送数据的序列化和系统消息的序列化分开了,或者说是两个层面的东西。啥意思呢?简单来说就是,akka先调用序列化方法(可以是任意自定义)把用户待发送数据转成ByteString,然后把这个数据设置成系统消息的某个字段,再调用系统序列化器把系统消息序列化。

    override def constructMessage(
        localAddress:      Address,
        recipient:         ActorRef,
        serializedMessage: SerializedMessage,
        senderOption:      OptionVal[ActorRef],
        seqOption:         Option[SeqNo]       = None,
        ackOption:         Option[Ack]         = None): ByteString = {
    
        val ackAndEnvelopeBuilder = AckAndEnvelopeContainer.newBuilder
    
        val envelopeBuilder = RemoteEnvelope.newBuilder
    
        envelopeBuilder.setRecipient(serializeActorRef(recipient.path.address, recipient))
        senderOption match {
          case OptionVal.Some(sender) ⇒ envelopeBuilder.setSender(serializeActorRef(localAddress, sender))
          case OptionVal.None         ⇒
        }
    
        seqOption foreach { seq ⇒ envelopeBuilder.setSeq(seq.rawValue) }
        ackOption foreach { ack ⇒ ackAndEnvelopeBuilder.setAck(ackBuilder(ack)) }
        envelopeBuilder.setMessage(serializedMessage)
        ackAndEnvelopeBuilder.setEnvelope(envelopeBuilder)
    
        ByteString.ByteString1C(ackAndEnvelopeBuilder.build.toByteArray) //Reuse Byte Array (naughty!)
      }
    

       上面是constructMessage的具体实现,可看到envelopeBuilder.setMessage(serializedMessage)这行代码确实把序列化后的数据赋值给了某个字段。

    public Builder setMessage(akka.remote.WireFormats.SerializedMessage value) {
            if (messageBuilder_ == null) {
              if (value == null) {
                throw new NullPointerException();
              }
              message_ = value;
              onChanged();
            } else {
              messageBuilder_.setMessage(value);
            }
            bitField0_ |= 0x00000002;
            return this;
          }
    

       这是setMessage的代码。需要特别注意的是它的参数是SerializedMessage类型,而不是ByteString。这意味着什么呢?这意味着,还有一层。

      目前来看,序列化的过程大概是上面这个样子。那上面的系统消息又是啥呢?我们来分析ackAndEnvelopeBuilder.build.toByteArray这段代码。首先来看build返回了什么。

    public akka.remote.WireFormats.AckAndEnvelopeContainer build() {
            akka.remote.WireFormats.AckAndEnvelopeContainer result = buildPartial();
            if (!result.isInitialized()) {
              throw newUninitializedMessageException(result);
            }
            return result;
          }
    

       它返回了一个AckAndEnvelopeContainer,那就可以再补充一下上面的图。

      下面是AckAndEnvelopeContainer的继承关系。

      其实AckAndEnvelopeContainer内部还有其他的对象,但也不需要再过多深入分析,因为他们的序列化方法都是一样的。

      从上面红框开始,相关的序列化好像都是akka框架自定义的,那么它又是如何支持用户自定义序列化方法的呢?答案就在前面的serializeMessage方法中。

    private def serializeMessage(msg: Any): SerializedMessage = handle match {
        case Some(h) ⇒
          Serialization.currentTransportInformation.withValue(Serialization.Information(h.localAddress, extendedSystem)) {
            MessageSerializer.serialize(extendedSystem, msg.asInstanceOf[AnyRef])
          }
        case None ⇒
          throw new EndpointException("Internal error: No handle was present during serialization of outbound message.")
      }
    

       很明显,最终调用了MessageSerializer.serialize把用户自定义消息转化成了SerializedMessage。那SerializedMessage是用来干啥的呢?其实直观上来说,它还是用来保存用户序列化后的数据的,跟前面的伎俩一样,把ByteString赋值给某个字段。目前猜测来看,的确是这样的,但又感觉那里不对。4

      从SerializedMessage的继承关系和字段来看,好像确实是这样的。但需要注意这个类又两个ByteString的字段,分别是message_ 和 messageManifest_。一个是序列化后的消息,一个是序列化后消息的声明。

    /**
       * Uses Akka Serialization for the specified ActorSystem to transform the given message to a MessageProtocol
       * Throws `NotSerializableException` if serializer was not configured for the message type.
       * Throws `MessageSerializer.SerializationException` if exception was thrown from `toBinary` of the
       * serializer.
       */
      def serialize(system: ExtendedActorSystem, message: AnyRef): SerializedMessage = {
        val s = SerializationExtension(system)
        val serializer = s.findSerializerFor(message)
        val builder = SerializedMessage.newBuilder
    
        val oldInfo = Serialization.currentTransportInformation.value
        try {
          if (oldInfo eq null)
            Serialization.currentTransportInformation.value = system.provider.serializationInformation
    
          builder.setMessage(ByteString.copyFrom(serializer.toBinary(message)))
          builder.setSerializerId(serializer.identifier)
    
          val ms = Serializers.manifestFor(serializer, message)
          if (ms.nonEmpty) builder.setMessageManifest(ByteString.copyFromUtf8(ms))
    
          builder.build
        } catch {
          case NonFatal(e) ⇒
            throw new SerializationException(s"Failed to serialize remote message [${message.getClass}] " +
              s"using serializer [${serializer.getClass}].", e)
        } finally Serialization.currentTransportInformation.value = oldInfo
      }
    

       这两个字段先忽略,先来看serialize这个方法。需要注意这段代码builder对象的三个set方法。它分别设置了序列化后的消息、消息的声明,还有一个是序列化器的identifier。

      上面是Serializer的trait。抛开与序列化、反序列化相关的方法定义,第一个字段是一个identifier。从中文释义和官方注释来看,这是一个序列化器的数字标志,用以区分不同的序列化器。0~40是官方预留值,标志内部序列化用途,那为啥是40呢?你猜,哈哈哈,我也不知道,大概是官方觉得预留40个内部序列化器应该够用了吧。啥?如果不够用?鬼知道。

      那为什么每个序列化器都需要一个identifier呢?后面再说吧。

    /**
       * Returns whether this serializer needs a manifest in the fromBinary method
       */
      def includeManifest: Boolean
    

       其实Serializer还有一个非常重要的方法:includeManifest。它标志当前序列化器是否包含消息声明。那这个消息声明具体有啥用呢?

    def manifestFor(s: Serializer, message: AnyRef): String = s match {
        case s2: SerializerWithStringManifest ⇒ s2.manifest(message)
        case _                                ⇒ if (s.includeManifest) message.getClass.getName else ""
      }
    

       从获取消息声明的代码来看,它首先判断是否为SerializerWithStringManifest类型,如果是就调用manifest返回消息声明;否则就判断是否需要消息声明,如果需要则获取当前class的名称,否则返回空字符串。目前我们知道当前class的名称可以是消息的声明。那我们应该能大胆的猜测这个字段的意义了。

      它是用来给反序列化提供相关的参数或信息的。因为序列化后的对象都是二进制数据,在接收端怎么知道该用哪个序列化器进行反序列化呢?如果有这部分二进制数据对应的类名,那就可以从配置里面查找到了!!!这有什么意义呢?其实我们在开发系统的时候,一般都非常具有针对性,或者说不会考虑通用性。比如会在系统设计阶段,使用固定的序列化器,比如kryo,或Hessian。这就意味着我们已经提前知道了该用哪种序列化框架,不需要进行选择。拿过来的数据,用对应的序列化器反序列化就好了,不用担心格式不兼容!!!所以一般是没有manifest的。所以我觉得manifest是通用序列化框架的关键。

      其实SerializerWithStringManifest继承了Serializer,就是多提供了一个manifest方法。

      /**
       * Return the manifest (type hint) that will be provided in the fromBinary method.
       * Use `""` if manifest is not needed.
       */
      def manifest(o: AnyRef): String
    

       就是返回给定消息的类型提示字符串。

      那么在序列化之前是如何知道改用那个序列化器呢?答案就在SerializationExtension.findSerializerFor这个方法上。

      /**
       * Returns the Serializer configured for the given object, returns the NullSerializer if it's null.
       *
       * Throws akka.ConfigurationException if no `serialization-bindings` is configured for the
       *   class of the object.
       */
      def findSerializerFor(o: AnyRef): Serializer =
        if (o eq null) NullSerializer else serializerFor(o.getClass)
    
    /**
       * Returns the configured Serializer for the given Class. The configured Serializer
       * is used if the configured class `isAssignableFrom` from the `clazz`, i.e.
       * the configured class is a super class or implemented interface. In case of
       * ambiguity it is primarily using the most specific configured class,
       * and secondly the entry configured first.
       *
       * Throws java.io.NotSerializableException if no `serialization-bindings` is configured for the class.
       */
      @throws(classOf[NotSerializableException])
      def serializerFor(clazz: Class[_]): Serializer =
        serializerMap.get(clazz) match {
          case null ⇒ // bindings are ordered from most specific to least specific
            def unique(possibilities: immutable.Seq[(Class[_], Serializer)]): Boolean =
              possibilities.size == 1 ||
                (possibilities forall (_._1 isAssignableFrom possibilities(0)._1)) ||
                (possibilities forall (_._2 == possibilities(0)._2))
    
            val ser = {
              bindings.filter {
                case (c, _) ⇒ c isAssignableFrom clazz
              } match {
                case immutable.Seq() ⇒
                  throw new NotSerializableException(s"No configured serialization-bindings for class [${clazz.getName}]")
                case possibilities ⇒
                  if (unique(possibilities))
                    possibilities.head._2
                  else {
                    // give JavaSerializer lower priority if multiple serializers found
                    val possibilitiesWithoutJavaSerializer = possibilities.filter {
                      case (_, _: JavaSerializer)         ⇒ false
                      case (_, _: DisabledJavaSerializer) ⇒ false
                      case _                              ⇒ true
                    }
                    if (possibilitiesWithoutJavaSerializer.isEmpty) {
                      // shouldn't happen
                      throw new NotSerializableException(s"More than one JavaSerializer configured for class [${clazz.getName}]")
                    }
    
                    if (!unique(possibilitiesWithoutJavaSerializer)) {
                      _log.warning(LogMarker.Security, "Multiple serializers found for [{}], choosing first of: [{}]",
                        clazz.getName,
                        possibilitiesWithoutJavaSerializer.map { case (_, s) ⇒ s.getClass.getName }.mkString(", "))
                    }
                    possibilitiesWithoutJavaSerializer.head._2
    
                  }
    
              }
            }
    
            serializerMap.putIfAbsent(clazz, ser) match {
              case null ⇒
                if (shouldWarnAboutJavaSerializer(clazz, ser)) {
                  _log.warning(LogMarker.Security, "Using the default Java serializer for class [{}] which is not recommended because of " +
                    "performance implications. Use another serializer or disable this warning using the setting " +
                    "'akka.actor.warn-about-java-serializer-usage'", clazz.getName)
                }
                log.debug("Using serializer [{}] for message [{}]", ser.getClass.getName, clazz.getName)
                ser
              case some ⇒ some
            }
          case ser ⇒ ser
        }
    

       简单来说这个方法就是从SerializationExtension这个扩展配置的序列化器中根据类的claz信息找到一个序列化器,如果找不到就招父类对应的序列化器,如果还找不到那就用Java默认的序列化器。

      其实序列化的过程可以简单总结为上面这样的流程图。下面我们简要分析一下反序列化的过程。

      上面是收到消息时的处理过程,很显然调用了tryDecodeMessageAndAck方法。

     private def tryDecodeMessageAndAck(pdu: ByteString): (Option[Ack], Option[Message]) = try {
        codec.decodeMessage(pdu, provider, localAddress)
      } catch {
        case NonFatal(e) ⇒ throw new EndpointException("Error while decoding incoming Akka PDU", e)
      }
    

       这个方法输入是一个ByteString,返回一个Tuple,第二个类型是Message。

    override def decodeMessage(
        raw:          ByteString,
        provider:     RemoteActorRefProvider,
        localAddress: Address): (Option[Ack], Option[Message]) = {
        val ackAndEnvelope = AckAndEnvelopeContainer.parseFrom(raw.toArray)
    
        val ackOption = if (ackAndEnvelope.hasAck) {
          import scala.collection.JavaConverters._
          Some(Ack(SeqNo(ackAndEnvelope.getAck.getCumulativeAck), ackAndEnvelope.getAck.getNacksList.asScala.map(SeqNo(_)).toSet))
        } else None
    
        val messageOption = if (ackAndEnvelope.hasEnvelope) {
          val msgPdu = ackAndEnvelope.getEnvelope
          Some(Message(
            recipient = provider.resolveActorRefWithLocalAddress(msgPdu.getRecipient.getPath, localAddress),
            recipientAddress = AddressFromURIString(msgPdu.getRecipient.getPath),
            serializedMessage = msgPdu.getMessage,
            senderOption =
              if (msgPdu.hasSender) OptionVal(provider.resolveActorRefWithLocalAddress(msgPdu.getSender.getPath, localAddress))
              else OptionVal.None,
            seqOption =
              if (msgPdu.hasSeq) Some(SeqNo(msgPdu.getSeq)) else None))
        } else None
    
        (ackOption, messageOption)
      }
    

       上面是decodeMessage的源码,第一行是用来反序列化的。

    public static akka.remote.WireFormats.AckAndEnvelopeContainer parseFrom(byte[] data)
            throws akka.protobuf.InvalidProtocolBufferException {
          return PARSER.parseFrom(data);
        }
    

       它返回一个AckAndEnvelopeContainer对象。怎么样这个类型是不是比较熟悉?没错,在序列化的过程中,用户消息的最终序列化数据传给了这个对象,然后序列化发送出去的。

      后面的代码就是在解析AckAndEnvelopeContainer的其他字段了,比如Recipient/Sender/Seq等。其中我们最关心的是Message。还记得这个Message的类型是什么吗?没错就是SerializedMessage。

      不过上面的代码并没有对用户消息进行反序列化,而是调用msgDispatch.dispatch(msg.recipient, msg.recipientAddress, msg.serializedMessage, msg.senderOption)对消息进行处理。

     

      很显然,用户消息最终调用了MessageSerializer.deserialize进行反序列化。

    /**
       * Uses Akka Serialization for the specified ActorSystem to transform the given MessageProtocol to a message
       */
      def deserialize(system: ExtendedActorSystem, messageProtocol: SerializedMessage): AnyRef = {
        SerializationExtension(system).deserialize(
          messageProtocol.getMessage.toByteArray,
          messageProtocol.getSerializerId,
          if (messageProtocol.hasMessageManifest) messageProtocol.getMessageManifest.toStringUtf8 else "").get
      }
    

       它还是通过SerializationExtension这个扩展来反序列化的。同样有三个参数:message,serializerID,Manifest。

    /**
       * Deserializes the given array of bytes using the specified serializer id,
       * using the optional type hint to the Serializer.
       * Returns either the resulting object or an Exception if one was thrown.
       */
      def deserialize(bytes: Array[Byte], serializerId: Int, manifest: String): Try[AnyRef] =
        Try {
          val serializer = try getSerializerById(serializerId) catch {
            case _: NoSuchElementException ⇒ throw new NotSerializableException(
              s"Cannot find serializer with id [$serializerId]. The most probable reason is that the configuration entry " +
                "akka.actor.serializers is not in synch between the two systems.")
          }
          deserializeByteArray(bytes, serializer, manifest)
        }
    

       首先根据serializerId找到本节点对应的序列化器,这意味着什么呢?这意味着所有的节点,相同的序列化器都必须具有相同的serializerId。这就是serializerId的意义。

    private def deserializeByteArray(bytes: Array[Byte], serializer: Serializer, manifest: String): AnyRef = {
    
        @tailrec def updateCache(cache: Map[String, Option[Class[_]]], key: String, value: Option[Class[_]]): Boolean = {
          manifestCache.compareAndSet(cache, cache.updated(key, value)) ||
            updateCache(manifestCache.get, key, value) // recursive, try again
        }
    
        withTransportInformation { () ⇒
          serializer match {
            case s2: SerializerWithStringManifest ⇒ s2.fromBinary(bytes, manifest)
            case s1 ⇒
              if (manifest == "")
                s1.fromBinary(bytes, None)
              else {
                val cache = manifestCache.get
                cache.get(manifest) match {
                  case Some(cachedClassManifest) ⇒ s1.fromBinary(bytes, cachedClassManifest)
                  case None ⇒
                    system.dynamicAccess.getClassFor[AnyRef](manifest) match {
                      case Success(classManifest) ⇒
                        val classManifestOption: Option[Class[_]] = Some(classManifest)
                        updateCache(cache, manifest, classManifestOption)
                        s1.fromBinary(bytes, classManifestOption)
                      case Failure(e) ⇒
                        throw new NotSerializableException(
                          s"Cannot find manifest class [$manifest] for serializer with id [${serializer.identifier}].")
                    }
                }
              }
          }
        }
      }
    

       很明显,首先判断Serializer是不是SerializerWithStringManifest,如果是就传入发送过来的manifest;如果不是且manifest是空字符串,则调用fromBinary时,第二个参数为空(也就是manifest);如果manifest不为空,则从manifestCache中找到对应的manifest信息,传入fromBinary;如果manifestCache没找到,则动态加载manifest信息,更新manifestCache后再传入fromBinary。

      至此,反序列化过程结束。

      上面是我们总结的akka的序列化框架。消息的序列化可以分为三层:用户消息、用户消息序列化后对应的SerializedMessage、SerializedMessage对应的AckAndEnvelopeContainer。其中AckAndEnvelopeContainer的序列化是系统提供的,用户无法自定义;SerializedMessage是系统序列化和用户自定义序列化的中间层,提供一个适配的功能,比如提供序列化器的ID以及消息的声明信息;用户消息是基础层,可以使用自定义序列化器进行序列化。在反序列化时先反序列化城AckAndEnvelopeContainer,进行一些系统级的操作,然后根据SerializedMessage的序列化器ID查找对应的序列化器,反序列化用户消息。至此一个通用的序列化、反序列化流程设计完毕,而且还很通用的额。其实简单来说就是,解耦了“变”和“不变”。“变”是指用户的序列化器可能不变通,而且支持自定义;“不变”是指系统级消息的序列化过程不变,所有节点都一样。

      怎么样,你对akka设计的这个通用的序列化框架怎么看?是不是觉得挺好用的?我觉得这个分层设计的概念大家应该学会,这有助于我们照葫芦画瓢也设计一个类似的序列化框架。啥?你的序列化不需要通用?那你总会遇到schema变迁、升级的问题的。

  • 相关阅读:
    SQL 2005 开启OpenRowset/OpenDatasource的办法
    SQLSERVER存储过程查找所有数据表中某列存在空值
    SQLSERVER存储过程查找所有数据表中某列存在空值
    SQLSERVER存储过程查找数据表中某列存在空值
    SQLSERVER存储过程查找数据表中某列存在空值
    Memcached入门
    Memcached入门
    面试干货——年底干货大放送,你准备好了吗?
    面试干货——年底干货大放送,你准备好了吗?
    1.svn 彻底clear时,注意代码备份 2.借助vc助手加头文件
  • 原文地址:https://www.cnblogs.com/gabry/p/9606193.html
Copyright © 2020-2023  润新知