消息在通过send()方法发往broker的过程中,有可能需要经过拦截器(Interceptor)、序列化器(Serializer)和分区器(Partitioner)的一系列作用之后才能被真正地发往 broker。拦截器一般不是必需的,而序列化器是必需的。消息经过序列化之后就需要确定它发往的分区,如果消息ProducerRecord中指定了partition字段,那么就不需要分区器的作用,因为partition代表的就是所要发往的分区号。如果消息ProducerRecord中没有指定partition字段,那么就需要依赖分区器,根据key这个字段来计算partition的值。分区器的作用就是为消息分配分区
生产者需要用序列化器(Serializer)把对象转换成字节数组才能通过网络发送给Kafka。而在对侧,消费者需要用反序列化器(Deserializer)把从 Kafka 中收到的字节数组转换成相应的对象。在代码清单2-1中,为了方便,消息的key和value都使用了字符串,对应程序中的序列化器也使用了客户端自带的org.apache.kafka.common.serialization.StringSerializer,除了用于String类型的序列化器,还有ByteArray、ByteBuffer、Bytes、Double、Integer、Long这几种类型,它们都实现了org.apache.kafka.common.serialization.Serializer接口,此接口有3个方法:
configure()方法用来配置当前类,serialize()方法用来执行序列化操作。而close()方法用来关闭当前的序列化器,一般情况下 close()是一个空方法,如果实现了此方法,则必须确保此方法的幂等性,因为这个方法很可能会被KafkaProducer调用多次。生产者使用的序列化器和消费者使用的反序列化器是需要一一对应的,如果生产者使用了某种序列化器,比如StringSerializer,而消费者使用了另一种序列化器,比如IntegerSerializer,那么是无法解析出想要的数据的。
下面就以StringSerializer为例来看看Serializer接口中的3个方法的使用方法,StringSerializer类的具体实现如代码清单2-2所示。代码清单2-2 StringSerializer的代码实现
首先是configure()方法,这个方法是在创建KafkaProducer实例的时候调用的,主要用来确定编码类型,不过一般客户端对于 key.serializer.encoding、value.serializer.encoding和serializer.encoding这几个参数都不会配置,在KafkaProducer的参数集合(ProducerConfig)里也没有这几个参数(它们可以看作用户自定义的参数),所以一般情况下encoding的值就为默认的“UTF-8”。serialize()方法非常直观,就是将String类型转为byte[]类型。如果 Kafka 客户端提供的几种序列化器都无法满足应用需求,则可以选择使用如 Avro、JSON、Thrift、ProtoBuf和Protostuff等通用的序列化工具来实现,或者使用自定义类型的序列化器来实现。下面就以一个简单的例子来介绍自定义类型的使用方法。假设我们要发送的消息都是Company对象,这个Company的定义很简单,只有名称name和地址address,示例代码参考如下(为了构建方便,示例中使用了lombok[2]工具):
下面我们再来看一下Company对应的序列化器CompanySerializer,示例代码如代码清单2-3所示。
上面的这段代码的逻辑很简单,configure()和close()方法也都为空。与此对应的反序列化器CompanyDeserializer的详细实现参见3.2.3节。如何使用自定义的序列化器CompanySerializer呢?只需将KafkaProducer的value.serializer参数设置为CompanySerializer类的全限定名即可。假如我们要发送一个Company对象到Kafka,关键代码如代码清单2-4所示。代码清单2-4 自定义序列化器使用示例
注意,示例中消息的 key 对应的序列化器还是 StringSerializer,这个并没有改动。其实key.serializer和value.serializer并没有太大的区别,读者可以自行修改key对应的序列化器,看看会不会有不一样的效果。