Karafka框架1.3.0发布说明(Ruby + Kafka)

235 阅读7分钟

注意:这些发布说明只包括主要的变化。

注意:上述变化不包括为你的非Rails项目设置Zeitwerk。

注意:如果你使用Sidekiq后端,请记住,在升级之前,你需要消耗所有已经在Redis中的消息。

变化(功能、不兼容等)

自动重新加载开发中的代码变更

到现在为止,为了在Karafka进程中看到你的代码变化,你必须重新启动它。这真的很麻烦,因为对于更大、更复杂的Kafka集群来说,重新启动与重新连接和重新平衡可能需要大量的时间。幸运的是,这些时间已经过去了

你所需要做的就是在你的karafka.rb 文件中的App.boot 之前启用这部分代码。

# For non-Rails app with Zeitwerk loader
if Karafka::App.env.development?
  Karafka.monitor.subscribe(
    Karafka::CodeReloader.new(
      APP_LOADER
    )
  )
end

# Or for Ruby on Rails
if Karafka::App.env.development?
  Karafka.monitor.subscribe(
    Karafka::CodeReloader.new(
      *Rails.application.reloaders
    )
  )
end

而你的代码修改将在每次消息/消息批处理后被应用。

但请记住,它有一些限制。

  • 路由的变化不会被反映出来。这将需要重新连接,并使重新加载变得非常复杂。
  • 你在Karafka框架之外但仍在其中运行的任何后台工作,可能不会在重载时被发现。
  • 如果你使用跨越多个批次的内存消费者数据缓冲(或单一消息获取模式下的消息),它将无法工作,因为代码重载意味着重新初始化所有的消费者实例。在这种情况下,你最好不要使用重载模式。

同样值得指出的是,如果你有一个代码,在重载阶段应该以任何方式被重新初始化,你可以把它传递给Karafka::CodeReloader 初始化器。

if Karafka::App.env.development?
  Karafka.monitor.subscribe(
    Karafka::CodeReloader.new(
      *Rails.application.reloaders
    ) { Dry::Events::Publisher.registry.clear }
  )
end

解析器现在是路由中的反序列化器,接受整个Karafka::Params::Params对象

解析器作为一个概念,它将负责对违反SRP的数据进行序列化和反序列化(详见这里)。从现在开始,它们是独立的实体,你可以独立使用。在升级时,只需将你在路由中使用的每个主题的解析器重命名为反序列化器

App.consumer_groups.draw do
  consumer_group :batched_group do
    batch_fetching true

    topic :xml_data do
      consumer XmlMessagesConsumer
      batch_consuming false
      # parser XmlDeserializer.new
      deserializer XmlDeserializer.new
    end
  end
end

并确保,你自己提取消息的payload

class XmlDeserializer
  # @param params [Karafka::Params::Params] params to de-serialize
  # @return [Hash] deserialized xml
  # @example:
  #   XmlDeserializer.new.call('<node>n</node>')
  def call(params)
    ::Hash.from_xml(params.payload)
  end
end

Zeitwerk支持Karafka::Loader

注意:如果你在Ruby on Rails上使用Karafka,你可以跳过这一节。

我们并不是最擅长加载东西的人。Zeitwerk才是。这就是为什么我们放弃了我们的自定义加载器,而选择了它。

只要在配置应用之前,在你的karafka.rb文件中加载你的应用代码,你就可以开始使用了。

APP_LOADER = Zeitwerk::Loader.new

%w[
  lib
  app/consumers
  app/responders
  app/workers
].each(&APP_LOADER.method(:push_dir))

APP_LOADER.setup
APP_LOADER.eager_load

class App < Karafka::App
  # config here...
end

不要忘记eager_load代码,否则一些Karafka组件可能无法按预期工作。

消息的有效载荷现在可以在'payload'键下使用,不需要root合并

这可能是这个版本中最大的变化。

到目前为止,你的数据在收到时是在#params_batch中的每个params实例的根范围内可用。

这意味着,当你发送一条消息时,你可以像下面这样访问它

WaterDrop::SyncProducer.call(
  { login: 'maciek', id: '1' },
  topic: 'users'
)

你会像这样访问它。

def consume
  params_batch.each do |params|
    puts "Hello #{params['login']}!\n"
  end
end

Karafka曾经在Karafka::Params::Params对象根范围内直接合并你的数据。这很方便,但不够灵活。在根params范围内有一些元数据细节可能会被覆盖,另外,如果你发送的不是JSON哈希值,比方说一个数组,你会得到一个异常,你必须使用一个自定义解析器来绕过它(见这个FAQ问题)。

由于这个原因,并且为了更好地将你的传入数据与有效载荷的其他部分(标题、元数据信息等)分开,从现在开始,你的所有数据都将在有效载荷参数键下可用。

def consume
  params_batch.each do |params|
    puts "Hello #{params['payload']['login']}!\n"
    # or
    puts "Hello #{params.payload['login']}!\n"
  end
en

这同样适用于你想访问未解析数据的情况。

def consume
  params_batch.to_a.each |params|
    puts "Unparsed details: #{params['payload']}"
  end
end

元数据支持

batch_fetching模式下,当从Kafka集群中获取数据时,会收到额外的信息。这些细节可以通过**#metadata**消费者方法获得。

class UsersConsumer < ApplicationConsumer
  def consume
    puts metadata
    #=> { batch_size: 200, topic: 'events', partition: 2 }
  end
end

消息头支持

在大多数消息系统(JMS、QPID等)、流媒体系统和大多数传输系统(HTTP、TCP)中,通常都有一个头和有效载荷的概念。

传统上,有效载荷是用于业务对象的,而报头传统上是用于传输路由、过滤等。头信息是最典型的key=value对。

WaterDropKarafka现在都支持消息头。

WaterDrop::SyncProducer.call(
  { login: 'maciek', id: '1' },
  topic: 'users',
  headers: { event: 'created' }
)

# Karafka consumer
def consume
  puts params_batch.last.headers #=> { 'event' => 'created' }
end

RSpec帮助器使消费者测试更加容易

到现在为止,为了测试消费者,你必须知道Karafka存储Kafka消息的内部格式。这不再是真的了

我们创建了一个新的库,叫做Karafka-Testing,它将为你提供所有的方法,使你的消费者测试更加容易。

安装

将这个gem添加到你的Gemfile中的测试组中。

group :test do
  gem 'karafka-testing'
  gem 'rspec'
end

然后在你的spec_helper.rb文件中。

require 'karafka/testing/rspec/helpers'

RSpec.configure do |config|
  config.include Karafka::Testing::RSpec::Helpers
end

使用方法

一旦包含在你的RSpec设置中,这个库将为你提供两个方法,你可以在你的规格中使用。

-#karafka_consumer_for- 这个方法将为所需主题创建一个消费者实例。它需要被设置为规格主题。
-#publish_for_karafka- 这个方法将 "发送 "消息到消费者实例。

注意:使用`#publish_for_karafka`方法发送的消息将不会被发送到Kafka。它们将被 "虚拟 "委托给创建的消费者实例,所以你的规范可以在没有Kafka设置的情况下运行。

RSpec.describe InlineBatchConsumer do
  # This will create a consumer instance with all the
  # settings defined for the given topic
  subject(:consumer) do
    karafka_consumer_for(:inline_batch_data)
  end

  let(:nr1_value) { rand }
  let(:nr2_value) { rand }
  let(:sum) { nr1_value + nr2_value }

  before do
    # Sends first message to Karafka consumer
    publish_for_karafka({ 'number' => nr1_value }.to_json)
    # Sends second message to Karafka consumer
    publish_for_karafka({ 'number' => nr2_value }.to_json)
    allow(Karafka.logger).to receive(:info)
  end

  it 'expects to log a proper message' do
    expect(Karafka.logger)
      .to receive(:info).with(
        "Sum of 2 elements equals to: #{sum}"
      )
    consumer.consume
  end
end

仪器仪表的统一

我们对默认监听器和Karafka运行时流程执行期间发布的事件名称做了一些小改动。更多细节请见提交。

Karafka::Instrumentation::Listener现在是Karafka::Instrumentation::StdoutListener

这个监听器有了重命名,并切换到实例化版本。

Karafka.monitor.subscribe(
  # Old
  Karafka::Instrumentation::Listener
  # New
  Karafka::Instrumentation::StdoutListener.new
)

Karafka::Instrumentation::ProctitleListener已被添加

添加了名为Karafka::Instrumentation::ProctitleListener的新工具。它的目的是为你提供一个带有描述性数值的更漂亮的proc标题。为了使用它,请在你的karafka.rb引导文件中加入以下一行。

Karafka.monitor.subscribe(
  Karafka::Instrumentation::ProctitleListener.new
)

mark_as_consumed分为mark_as_consumed和mark_as_consumed!

一个阻塞的**#mark_as_consumed**方法被分成了两个。

  • #mark_as_consumed- 用于非阻塞的最终偏移承诺。
  • #mark_as_consumed!- 是一个阻塞的偏移量承诺,它将停止处理流程,以确保偏移量已经被存储。

#payloads用于params_batch,从params_batch中只提取对象的有效载荷。

如果你对额外的`#params`元数据不感兴趣,你可以使用`#payloads`方法,只访问Kafka消息反序列化的有效载荷。

class EventsConsumer < ApplicationConsumer
  def consume
    EventStore.store params_batch.payloads
  end
end

单个消费者类支持一个以上的主题

从现在开始,你可以为多个主题使用同一个消费者类。

App.consumer_groups.draw do
  consumer_group :default do
    topic :users do
      consumer UsersConsumer
    end

    topic :admins do
      consumer UsersConsumer
    end
  end
end

注意:你仍然会在每个主题分区中拥有独立的实例。

关键故障时的延迟重新连接

如果发生关键故障(网络断开或任何类似的情况),Karafka将退缩并等待reconnect_timeout(默认为10s)后再尝试重新连接。这应该可以防止你在出现严重问题时被错误和日志所堵塞。

放弃对Kafka 0.10的支持,转而支持Kafka 0.11的本机。

对Kafka 0.10的支持已被放弃。如果你决定将Kafka 0.10与Karafka 1.3一起使用,可能会发生奇怪的事情,所以只要升级即可。

重组响应者--multiple_usage constrain不再可用

multiple_usage已经被移除。如果你决定向同一个主题发送多条消息而不声明的话,响应者不会引发任何异常。这个功能是个坏主意,在长期运行的分批流中使用响应器时,会产生很多麻烦。

以下代码在Karafka 1.2中会引发Karafka::Errors::InvalidResponderUsageError错误,但在Karafka 1.3中可以继续运行。

class ExampleResponder < ApplicationResponder
  topic :regular_topic

  def respond(user, profile)
    respond_to :regular_topic, user
    respond_to :regular_topic, user
  end
end

异常名称标准化

所有Karafka内部框架的异常名称现在都以Error后缀结尾。请看这个文件,了解整个异常列表

默认的fetcher_max_queue_size从100改为10,以降低最大内存使用量

当Karafka在处理时,ruby-kafka会在一个单独的线程中预先缓冲更多数据。如果你有一个大的消费者滞后,这可能会导致你的Karafka进程在前期预缓冲数百或更多的数据。降低队列大小使得Karafka在默认情况下更容易预测。

开始使用Karafka

如果你想尽快开始使用Kafka和Karafka,那么最好的办法就是直接克隆我们的示例仓库。

git clone https://github.com/karafka/example-app ./example_app

然后,捆绑安装所有的依赖项。

cd ./example_app
bundle install