Twilio的企业洞察力团队利用Apache Kafka在我们的仓库中收集数据,Twilio的其他团队使用这些数据进行数据探索并创造有意义的洞察力。
我们的团队正在建立一项服务,对Twilio的突发流量进行近乎实时的提醒--一天中的某些时段流量很大,而其他时段则很冷清。这些流量模式给流媒体系统带来了挑战。在我们的设计和实验阶段,我们学到了很多关于调整Kafka流的知识,这篇博文概述了我们学到的知识。
这篇博文将只关注我们在调整Kafka流时学到的东西。我们将在未来的博文中解释我们选择Kafka流的理由以及警报系统的设计。
应用设计
近乎实时的警报应用由两个阶段组成,通过滚动单个记录并检查它们是否违反了客户定义的阈值来测量指标。如果指标值超过了阈值,我们就向客户发出警报。该应用程序的阶段是。

1.公制计算--该阶段对单个记录进行公制计算,并将其产生到具有相同时间窗口的Kafka主题。
2.2.指标汇总和针对警报阈值的评估--该阶段采用单个指标并将其汇总到时间窗口。然后,这些聚合被卷起来,并根据警报阈值进行检查。
实验
Kafka流围绕着流、生产者和消费者暴露了很多细粒度的设置。在我们开始讨论对我们有用的东西之前,让我们来谈谈这些实验的测试平台。
测试平台
虽然我们不会谈论Kafka流集群中有多少个实例,Kubernetes pod中有多少个容器,但你需要根据你的基础设施,将这些设置与一个用例最终使用的CPU核心数和磁盘空间数相匹配。
这些实验是在具有直接连接SSD存储的EC2实例上运行的。
我们优化的两个输入流量配置文件是。
- 每秒约100条记录的稳定流。
- 爆发性的数据流,最高每秒约40000条记录,最低接近每秒5000条。
Kafka设置
为了整合Kafka,我们需要修改一些Kafka的设置。值得注意的是,我们调整的Kafka流设置是。
- num.stream.threads
- commit.interval.ms
- enable_auto_commit_config
- POLL.MS
- fetch_max_wait_ms
- fetch_min_bytes
- max_poll_records
- max_partition_fetch_bytes
- fetch_max_bytes
- LINGER.ms
- CustomRocksDB配置--显示通过使用块缓存减少处理器时间。
让我们详细了解一下这些设置。
1.StreamsConfig。NUM_STREAM_THREADS_CONFIG (num.stream.threads )
顾名思义,NUM.STREAM.THREADS ,就是驱动任务记录处理的流线程(Java线程)的数量。
只要有工作要由流任务(为每个输入主题分区创建)并发完成,流线程将处理创建、分区分配(standby 和active)和流任务需要的其他事情。
关于流线程的一个很好的文件可以在这里找到。
只要有核心(包括超线程)可供Kafka流应用程序使用,你就可以增加流线程的数量进行处理。然而,在以下情况下,线程将被闲置。
- 分区的数量不够多或
- 一些分区根本就没有得到数据
2.2. StreamsConfig.COMMIT_INTERVAL_MS_CONFIG (commit.interval.ms)
COMMIT.INTERVAL.MS 设置用于保存处理器在Kafka偏移管理系统中的位置(偏移)。
请注意,这与将Kafka记录提交到应用程序中的不同主题是不一样的。
30000如果processing.guarantee ,默认值是exactly_once ,否则默认值是100 。只有当enable.auto.commit 也被设置为false ,这个设置才会被应用。
我们有几个主题要存储指标和它们的卷积,同时期望exactly_once 处理,所以我们试验了1到20秒之间的值,以避免用这些偏移的状态更新给经纪人带来负担。
根据你对风险的规避程度,有可能让系统在出现故障时处理重复的处理。增加这个设置的时间值以避免任何重复处理。
3.StreamsConfig.POLL_MS_CONFIG (poll.ms)
POLL.MS 这个设置代表了我们在等待来自经纪商的数据时要阻塞的时间。我们必须为两个目标优化这个设置--我们不希望阻塞时间太长,但我们也需要在每次轮询时获取足够的数据。
后者是由max.poll.interval.ms ,以及围绕获取多少字节和记录的消费者获取设置来控制的。
poll.ms 的这一设置基本上避免了线程在没有数据的情况下从代理处获取分区的繁忙等待。
我们为这个设置确定了一个50ms的值。
4.ConsumerConfig。FETCH_MIN_BYTES_CONFIG (fetch.min.bytes)
如果我们可以在两次获取数据之间等待几毫秒,那么将FETCH_MIN_BYTES 从其默认值(1 )改变是一个好主意--我们的任务正在处理刚刚获取的数据。
如果有更多的数据可以被获取,我们不希望获取的频率太高。在这种情况下,我们可以等待我们的记录被处理。
否则,如果你希望在接近实时的情况下工作,并希望立即获取哪怕是一个新的字节的数据,请将此设置保留为默认值。
对于我们的测试平台A(每秒100条记录),这个设置在1000 和10000 字节的值之间运行良好,但在测试平台B(每秒4万条记录),应用程序在默认值下表现得更加出色。
5.5.ConsumerConfig.FETCH_MAX_WAIT_MS_CONFIG (fetch.max.wait.ms)
我们对FETCH_MAX_WAIT_MS_CONFIG 的经验和对FETCH_MIN_BYTES 的经验相似--在测试平台A上,介于1 秒和5 秒之间的地方给了我们满意的结果,但是我们在测试平台B上坚持使用默认的500ms。
6.StreamsConfig.MAX_POLL_RECORDS_CONFIG (max.poll.records)
MAX_POLL_RECORDS 的设置在我们试图快速减少偏移滞后的情况下很有用。它决定了每次轮询要获取多少条记录,如果从代理处获取的记录超过了指定的数量,它们会被存储在一个内部缓冲区,以避免RPC(请求),直到已经获取的记录被处理。
MAX_POLL_RECORDS 基本上让Kafka Streams知道应用程序在再次调用poll() 之前会处理指定数量的记录。在修改这个设置时,我们没有看到消费者的吞吐量有任何明显的改善,所以我们最终没有改变它。
7.ConsumerConfig.MAX_PARTITION_FETCH_BYTES_CONFIG (max.partition.fetch.bytes) 和 ConsumerConfig.FETCH_MAX_BYTES_CONFIG (fetch.max.bytes)
当调整MAX_PARTITION_FETCH_BYTES 或FETCH_MIN_BYTES 时,我们没有看到消费者的吞吐量有任何明显的改善。
8.ProducerConfig.LINGER_MS (linger.ms)
LINGER.ms 的设置允许我们在将记录发送到Kafka经纪商之前在应用程序中进行缓冲。虽然默认值是0 (一点击发送就把记录发送给经纪人),但当同时产生过多的主题时,我们不能不考虑0 给经纪人带来的负担。
因此,增加LINGER.ms 的设置来创建一批记录是更好的,以保障系统不被意外淹没。
尽管建议的徘徊值在5到10ms之间,对于我们的测试平台B(每秒40k条记录的测试平台),我们能够将其增加到1000ms,而不会对我们的底线造成任何伤害(以Kafka主题上的结果延迟来衡量)。基于你计划对你的服务端到端的积极性,你可以优化这个值,在生产者一方批处理事情。
9.自定义RocksDB配置
我们的应用程序使用六个不同的状态存储。Facebook有很多围绕RocksDB可以改变的设置的文档,所以我们将集中在我们做了最多优化的地方。
我们更专注于READ方面的事情,因此对RocksDB的每个阶段发生的读取进行了优化。
RocksDB的内存端。
- MemStore
- BloomFilter
RocksDB磁盘端。
- 索引
- SSTable
- BlockCache(我们最后加入了这个配置
你可以通过将设置指向类来设置一个自定义的RocksDB配置。
config.put(StreamsConfig.ROCKSDB_CONFIG_SETTER_CLASS_CONFIG, AlerterRocksDBConfig.class);
我们使用BlockCache ,而不是默认的FileCache --在连接到实例的Storage是远程的情况下,最好有一个不外挂的二级缓存,而是使用本地的非易失性磁盘。缓存的大小要明显高于实例上的 DRAM。
BlockBasedTableConfig tableConfig = (BlockBasedTableConfig) options.tableFormatConfig();
tableConfig.setBlockCache(new org.rocksdb.LRUCache(16 * 1024L * 1024L));
tableConfig.setBlockSize(16 * 1024L);
接下来的几行显示了如何根据你所拥有的实例类型和你能支持的程度来调整你的缓存和块大小。这可以确保没有来自RocksDB的无法处理的内存压力。
tableConfig.setPinL0FilterAndIndexBlocksInCache(true);
tableConfig.setCacheIndexAndFilterBlocks(false);
接下来的这些设置看起来是一样的,但是我们不希望使用BlockCache来存储索引和过滤器(在我们的例子中是bloom过滤器)。这些查询可以为MemStore(L0)来做,这就是我们最后要做的。
tableConfig.setFilter(new BloomFilter(10));
如果做Key-Value的查找,最好把bloom filter设置为过滤器,10 ,作为代表一个key的默认位数。
tableConfig.setIndexType(IndexType.kHashSearch);
对于键值查询,一个很好的优化是使用哈希搜索而不是默认的二进制搜索。这可以优化第一阶段,使其更接近结果。如果有相同前缀的键,RocksDB使用二进制搜索来缩小到结果。
我们在RocksDB的选项中设置了上述表格配置。
options.setTableFormatConfig(tableConfig);
其他需要设置的RocksDB选项有。
options.useCappedPrefixExtractor(4);
在上面的设置中,4字节意味着_4 X 8 = 32位_,这基本上意味着我们可以在32位中支持2^32种前缀组合
options.setMemtablePrefixBloomSizeRatio(0.25);
这一行设置了MemStore或MemTable的布鲁姆前缀比率。默认情况下,它是0 ,我们可以通过设置0 和0.25 之间的任何数值来启用它。
上面的设置并不是一个详尽的列表。基于RocksDB的文档,你可以根据你的使用情况来调整设置。
在写入方面,你希望压缩发生的频率以及你希望写入缓冲区的大小是关键的设置。你需要在你的应用程序中调整SSTables和MemTables的大小来优化这些。
其他Kafka流的特性
- 标点符号
- 输入主题的分区数量
1.Kafka标点符号
Punctuators是一种特殊形式的流处理器,它可以在用户定义的时间间隔或挂钟时间内进行调度。在Punctuator的特定运行中,Kafka流应用程序的处理会停止,以便Punctuator能够完成其对应用程序当前状态的工作。只有在Punctuator的特定运行结束后,应用程序才会恢复处理。
这可能会使标点器成为一个瓶颈,或者是一个缓慢滴答的定时炸弹,当记录的数量超过标点器,无法快速完成时,就会失败。
只要快速完成(类似于快速快照),标点器就是有用的,这可以通过使单次运行中处理的记录数量--读或写--确定或恒定而实现。
我们通过以下方式使我们的Punctuator更具有性能。
- 使用Java多线程从状态存储中读取数据。
- 使用相同的多线程范式来进行并行的快速评估。
我们还需要确保Punctuator的长时间运行被标记为违规。你需要确保你的应用逻辑避免让事情变得没有反应。
2.输入主题的分区数量
一个主题的分区数量定义了我们在处理该主题的记录时得到的并行程度。在Kafka Streams中,它定义了从应用程序的拓扑结构中创建的任务数量,以处理记录。
为了实现最佳的并行性,我们要避免数据倾斜,因为数据倾斜会使一些分区(也就是一些流任务)变热,而其他分区则保持闲置。如果你面临热点,想想为什么你的数据会偏向于某个分区的子集(由于你的记录键),以及你是否可以使用盐化来在各分区之间均匀地分配数据。
总结
让Kafka流大规模工作,同时以近乎实时的方式发出警报,给我们带来了一些围绕管理获取和处理状态存储的挑战,我们也学到了不少东西。以下是我们从这一过程中获得的高级经验。
-
RocksDB调整--范围查询在窗口和会话存储上是很好的,但要尽可能避免在上面进行点查询。
用自定义配置来调整RocksDB,使其尽可能与BlockCache一起使用bloom过滤器。
-
尽可能地轮询更多的记录和获取更多的字节。这里需要达到的关键平衡是,在不经常轮询的情况下批量轮询,以防没有足够的数据(这将取决于你的主题量概况)。保持你的轮询间隔特别短,如果每次轮询都能保证有大量的数据,那么你的代理获取就会更接近实时。
-
尽可能地进行批处理操作--通过批处理,我们可以在生产者一方徘徊,一起发送许多记录。
Punctuate也表示批处理过程,用于检查是否触发了警报标准。
-
Max_Poll_Records和Auto_Commit_Ms_Config如果后者的设置太高,就不能很好地配合。如果你为Auto_Commit_Ms_Config设置了一个相当高的值,建议你删除你的Max_Poll_Records,让默认值(500)来选择。 -
如果你的数据没有严重偏向某些分区(热点),你的应用程序的处理吞吐量与输入主题的分区数量成正比。
Twilio的分析和洞察力
希望这篇关于我们在Twilio的Kafka流实验的文章能帮助你理解其中的一些权衡--以及何时修补自己的Kafka设置。如果你对解决这类问题感兴趣并希望加入我们,这里是Twilio的招聘页面。
你今天的作者。
企业洞察力团队是由一群不同的工程师组成的,他们构建的解决方案提供了对Twilio客户数据的洞察力,团队可以利用这些数据满足他们的业务需求。我们使用Apache Kudu等技术。 阿帕奇Calcite__, Apache Kafka, Apache Spark, React.js, 和 _粘贴_在我们的堆栈中,为我们的客户提供实时的洞察力。