正如在拾遗笔记中提及的,小白的公司正在对自身产品环境做一个全面升级改造。其中最重要的一环是选择openjdk替换所有oracle jdk 1.8作为底层java环境运行。之所以使用openjdk主要出于商业版权,系统安全以及新的特性引入三个方面的考虑,具体情况就不在本章节balabala做展开。本章主要介绍入坑kafka的一些具体步骤,以备不时之需,毕竟上年纪了,记性开始不奥利给了。
前述
小白公司产品已经运行两年多了。这两年来随着业务的增长,新的功能或者应用不断增多,势必需要涉及分布式架构。那么消息队列的应用也成为必不可少的一环。大约在1年半之前,小白选择了rocketMQ作为产品环境的消息队列。其实那时候小白也犹豫过选择大名鼎鼎的kafka还是新晋小贵rocketMQ。最后出于几个方面的理由,小白那时最终选择了rocketMQ。首先kafka默认依赖于zookeeper等第三方应用,而rocketMQ比较干净,没有第三方平台的依赖,安装简单。其次,是rocketMQ性能据说比kafka相对优越(但其实这个是有代价的)。然后是小白公司投入的资源有限例如服务器。基于上述原因,小白选择rocketMQ。确实,rocketMQ也有非常出色的表现,在此也感谢rocketMQ团队的辛勤奉献。
为何放弃rocketMQ,拥抱Kafka
在这次小白公司对自身产品做升级时,小白最后忍痛放弃rocketMQ。其中最大一个理由当然是openjdk的选择。小白曾经尝试对rocketMQ做openjdk的适配,但是由于对rocketMQ深层机制不是很熟悉,加上官方没有这方面的详细文档,小白不得不放弃。因为rocketMQ对CMS垃圾回收器的深度依赖,而openJDK 9之后已经因为CMS的复杂度已经放弃对CMS的支持,从而导致rocketMQ无法简单在openJDK上运行,具体可见rocketMQ的运行脚本runserver.sh, 代码如下:
图1 rocketmq 运行脚本
其中-XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSClassUnloadingEnabled参数依赖于CMS垃圾回收器
另外,机缘巧合之下,小白公司产品需要使用到zookeeper,所以真的可能是天意吧。再加上kafka支持openjdk运行,所以不选kafka选谁呢。当然还有pulsar,小白还没着手研究,所以不敢冒险使用。
消息队列的使用目标
在配置运行kafka服务之前,我们通常需要确定需要消息队列达成的目标:
-
需要消息队列客户端可以异步批量发送信息。
-
需要消息队列客户端可以拉取消息而不是被动接收。
-
需要消息队列支持权限认证。
这三点rocketmq也能做到,kafka也没有这方面的问题,足够胜任。前两点在这里不做详述,具体请查看org.apache.kafka.clients.producer.KafkaProducer.send和org.apache.kafka.clients.consumer.Consumer.poll的源码。
配置kafka权限认证
目前kafka支持下授权验证方式:
- SASL/GSSAPI (Kerberos) - starting at version 0.9.0.0
- SASL/PLAIN - starting at version 0.10.0.0
- SASL/SCRAM-SHA-256 and SASL/SCRAM-SHA-512 - starting at version 0.10.2.0
- SASL/OAUTHBEARER - starting at version 2.0
小白目前只会SASL/PLAIN这一种方式,SASL/PLAIN是最简单的设置用户名密码的授权方式,对于其他的授权验证方式就知之不详。在这里只记录SASL/PLAIN的授权验证方式配置。
JAAS配置文件
在kafka的config目录下新建一个kafka_server_jaas.conf的文件。当然你也可以取任何其他的名字。文件的内容如下:
KafkaServer {
org.apache.kafka.common.security.plain.PlainLoginModule required
username="admin"
password="admin-secret"
user_admin="admin-secret"
user_alice="alice-secret";
};
其中org.apache.kafka.common.security.plain.PlainLoginModule用于指定验证方式是Plain还是其他的。username和password是kafka集群broker节点之间内部使用的。而对我们真正有用的后面两个像user_admin=“admin-secret”。这是一种模式user_{username}=“{password}”. 像user_admin=“admin-secret”意思是你可以使用用户名为admin,通过密码admin-secret验证登录kafka集群。
配置Kafka服务端
配置完jaas文件我们需要配置每个kafka节点的server.properties文件以对外暴露生效SASL/PLAIN验证模式:
首选先确定当前kafka节点的编号,例如:
broker.id=0
然后配置Listeners,注意最好配置上advertised.listeners, 例如:
# The address the socket server listens on. It will get the value returned from
# java.net.InetAddress.getCanonicalHostName() if not configured.
# FORMAT:
# listeners = listener_name://host_name:port
# EXAMPLE:
# listeners = PLAINTEXT://your.host.name:9092
listeners=SASL_PLAINTEXT://:9229
# Hostname and port the broker will advertise to producers and consumers. If not set,
# it uses the value for "listeners" if configured. Otherwise, it will use the value
# returned from java.net.InetAddress.getCanonicalHostName().
advertised.listeners=SASL_PLAINTEXT://{公网ip}:9229
然后配置SASL/PLAIN协议:
##Security && SASL
#
security.inter.broker.protocol=SASL_PLAINTEXT
sasl.mechanism.inter.broker.protocol=PLAIN
sasl.enabled.mechanisms=PLAIN
最后如果你有自己的zookeeper,还需要修改zookeeper.connect.
配置JVM参数生效JAAS
前面我们生成了JAAS文件,要让JAAS生效,我们还有最后一步:在bin目录下找到kafka-server-start.sh文件在java运行代码参数部分添加上-Djava.security.auth.login.config=/opt/kafka/config/kafka_server_jaas.conf, 如下图所示:
图2 kafka运行脚本配置
结论
最后,你可以运行kafka服务节点,然后你就可以在客户端通过用户名密码的方式使用kafka:
Properties props = new Properties();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, MAX_LIMIT);
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
props.put(ConsumerConfig.GROUP_ID_CONFIG, group);
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
if (StringUtils.isNotEmpty(user) && StringUtils.isNotEmpty(secretKey)) {
props.put("security.protocol", "SASL_PLAINTEXT");
props.put("sasl.mechanism", "PLAIN");
props.put("sasl.jaas.config", this.getSASLPlainText(user, secretKey));
}
return new KafkaConsumer<>(props);
private String getSASLPlainText(String user, String secretKey) {
return String.format(
"org.apache.kafka.common.security.plain.PlainLoginModule required username=\"%s\" password=\"%s\";",
user, secretKey);
}