Kafka系列之消息过长问题的处理

kafka涉及的技术问题,计划用多篇文章来记录,这一篇先记录下工作中遇到的比较严重的问题和解决的方案。消息过长的问题在生产者和消费者端都有可能发生:
生产者: kafka对发送的消息长度有限制,不能发送任意长度的数据,默认长度设置的是1M,超过1M就不允许发送。
消费者:kafka拉取数据长度也有限制,默认拉取最大的长度为50M,超过的如果一次拉取的消息长度超过了fetch.max.bytes的限制,那么Kafka Broker会返回一个RecordTooLargeException异常,告诉消费者消息太大了,无法拉取。

一、修改默认长度限制

既然kafka配置设置默认长度为1M和50M,那么解决这一问题最直接的方案就是修改默认的长度限制,把message.max.bytes和fetch.max.bytes的值调大,例如日常业务中单条数据最大的长度小于60M,最大的长度限制就可以设置为60M。在docker模式下,修改环境变量:

enviroment:
    KAFKA_MESSAGE_MAX_BYTES: 12695150    # 设置生产者端发送消息的最大长度
    KAFKA_MAX_PARTITION_FETCH_BYTES: 4194304 # 设置消费者端接收数据的最大长度
二、更细粒度的消息推送

方案一是解决消息的长度过长的最直接方式,但是如果消息本身的长度很长,使用方案一来解决就会带来以下三个问题:

  1. 内存占用:如果消息长度很长,消费者端需要一次缓存消息的所有数据,很可能大量占用消费者端的内存;
  2. 消息处理延迟:消息长度过长,消费者拉取数据的时间也会变长,会延迟消息的处理,影响时效性;
  3. 消费者不均衡:如果消息过长,一个消费者在处理这条数据的时候需要消耗不少的网络带宽和CPU资源,可能导致其他消费者无法获取资源,导致不均衡的问题;

既然单纯使用方案一会带来这么多的问题,那么我们就换一个解决思路:减小单条消息的长度,把消息的粒度控制到更细。以IP和端口扫描为例子:
如果以IP为单位推送数据,IP(比如192.168.111.1)下的所有端口探活、扫描完成之后的结果一次推送,可能资产、漏洞等信息会非常多,超过了长度限制。那么我们可以在业务层面把推送数据的粒度设置的更细,可以以端口为单位,这个IP下的一个端口干完了所有的工作就推送一次数据,粒度从原来的IP为单位细化到以端口为单位,推送的数据自然要小的多,基本不会不限消息超多长度限制的问题。

三、数据压缩

方案一和方案二可以作为一个相辅相成的解决方案,可以解决很多场景下的推送长度过长的问题,但是我们需要继续思考下还有没有其他的方案?答案是有,可以使用数据压缩。数据压缩也是大消息常用的处理方案,如果一个消息长度过长在推送前使用压缩算法把数据压缩到很小的长度推送到kafka,消费者拉取到数据的时候解压数据消费即可。常用的压缩算法有两种:
1.GZIP压缩
GZIP压缩的比例比较高,网上技术文章写的数据压缩比在50%(本人实测压缩比在90%左右,10M的数据,压缩之后长度不到1M,这点需要具体情况确认),但是压缩的时间和占用的CPU资源比较高,常用于日志、文件的压缩。
2.SNNAPY压缩
SNNAPY压缩的比例比GZI要低不少,能达到60%左右(本人实例6.5M的数据可以压缩到1M以下,超过6.5就基本上会出现长度超过限制的问题),压缩的效率和占用的CPU资源比GZIP压缩更低,常用于图片、音频、大数据等二进制的压缩。
如果要使用解压缩,则需要配置kafka的压缩算法,kakfa生产者端和消费端都需要支持对应的算法。

  1. docker环境下配置kakfa,修改配置后重启kakfa容器即可生效。

    environment:
     KAFKA_COMPRESSION_CODEC: snappy   # 压缩算法
  2. 以生产者端为例子配置,下边是Go sarama包的配置代码:

    // config 获取kafka配置,初始化配置
    func config(conf Config) (*sarama.Config, error) {
     config := sarama.NewConfig()
     config.Producer.Return.Successes = true
     config.Producer.Return.Errors = true
     config.Producer.Retry.Max = 5
    
     if conf.Compression.Enabled {
         // 配置压缩的算法
         config.Producer.Compression = conf.Compression.CompressionCodec 
         // 根据压缩的算法设置sarama包中的数据最大长度限制,这点不能忘了
         switch conf.Compression.CompressionCodec {
         case sarama.CompressionGZIP:
             config.Producer.MaxMessageBytes = GzipMaxMessages * 1024 * 1024
         case sarama.CompressionSnappy:
             config.Producer.MaxMessageBytes = SnappyMaxMessages * 1024 * 1024
         }
     } else {
         config.Producer.MaxMessageBytes = SnappyMaxMessages
     }
    
     return config, nil
    }

    sarama源码中的配置项: MaxMessageBytes默认为1000000,Compression压缩默认不开启

    type Producer struct {
     // The maximum permitted size of a message (defaults to 1000000). Should be
     // set equal to or smaller than the broker's `message.max.bytes`.
     MaxMessageBytes int
     // The type of compression to use on messages (defaults to no compression).
     // Similar to `compression.codec` setting of the JVM producer.
     Compression CompressionCodec
    }

    配置好之后可以测试往kafka中推送数据,通过wireshark抓包kafka数据,在过滤框中输入tcp.port == 9092 && kafka.message_value可以准确的抓取到每一条kafka数据:

    API Version:对应的是请求的接口版本
    Topic:kafka主题
    Partition:分区
    Compression Codec:使用的压缩算法
    Key:消息的键
    Value:消息的值
    Acknowledgment:确认级别,例如 0 表示不等待确认,1 表示等待 leader 确认,-1 表示等待所有副本确认。


TimeWtr
1 声望0 粉丝