多种MQ的探究-Kafka
Kafka
原理
简介:
kafka是一个支持高吞吐量的分布式消息服务。
特点:
kafka依赖于zookeeper进行节点注册。
kafka集群:
kafka集群与zk类似,leader拥有最新最全的信息,然后复制信息到从节点上。
springboot2.0版本集成的kafka2.x,消息offsets放置在zk节点topics上。
优缺点:
采用分区机制,吞吐量高,拥有ack容错机制,支持集群负载均衡,对日志文件的记录比较好。
但由于高效率的原因,kafka对死信队列、延迟队列等方面不如其他mq。
原理
kafka集群节点注册到zk上,服务加载初始化时加载kafka集群信息,然后进行消息处理。
kafka与zk类似,存在leader与follower,并实时监测服务状态。
模式:
kafka----broker1(topic1…topicn)–consumer
…
kafka----brokern(topic1…topicn)–consumer
详细:
kafka的topic会依据配置分区数量进行分区(partition),消息producer后push进入broker的各个分区中有序排列(offset)。消息消费监听到当前分区有自己的消息进来,然后进行处理,并记录offset。
producer
消息推送首先获取节点分片情况,然后进行序列化处理
clusterAndWaitTime = waitOnMetadata(record.topic(), record.partition(), maxBlockTimeMs);
...
serializedKey = keySerializer.serialize(record.topic(), record.headers(), record.key());
serializedValue = valueSerializer.serialize(record.topic(), record.headers(), record.value());
我们可以在application配置文件中配置序列化:
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
然后获取传入的分片id、时间戳等信息创建分片对象,然后创建producer进行提交:
ProducerBatch batch = new ProducerBatch(tp, recordsBuilder, time.milliseconds());
FutureRecordMetadata future = Utils.notNull(batch.tryAppend(timestamp, key, value, headers, callback, time.milliseconds()));
consumer
消息消费有两种方式:1-继承函数接口实现onMessage() 2-注册监听@KafkaListener,两种都是通过监听器来实现,consumer通过poll分片来获取消费对象。消息监听后会进行注册:
public class KafkaListenerEndpointRegistrar implements BeanFactoryAware, InitializingBean {
private final List<KafkaListenerEndpointDescriptor> endpointDescriptors = new ArrayList<>();
private KafkaListenerEndpointRegistry endpointRegistry;
...
通过bean工厂来管理监听器,当监听到消息时会即时进行处理消费。
与其他mq监听并无二致。
安装
环境:centos7
下载
1.从官方服务器上下载
2.直接在服务器上下载
例如:wget http://mirror.bit.edu.cn/apache/kafka/1.0.0/kafka_2.11-1.0.0.tgz
然后解压 tar -zxvf xxx.gz
配置
kafka拥有四种配置:
server.properties
作为服务的主配置,其中可配置信息如下:
- listeners=PLAINTEXT://192.168.99.128:9092 监听器监听的ip与端口,需要放开,否则无法识别连接
- num.partitions=1 kafka的分区,默认1个区,仅支持一个consumer,如果需要一个主题多消费者则需要放开分区,例如num.partitions=5
- log.dirs=/tmp/kafka-logs 日志存放路径
- zookeeper.connect=192.168.99.128:2181 kafka依赖的zookeeper的连接地址(一般使用自己的zk)
- 等等…
producer.properties
消息生产者的配置信息,主要的配置有bootstrap.servers,默认为当前机器单节点
consumer.properties
消息消费者,与生产者一致,其中配置了consumer.group-id作为分组id
zookeeper.properties
单机zk的配置,如果使用外置zk则不需要配置
启动
执行:./bin/kafka-server-start.sh -daemon ./config/server.properties
jps查询运行的应用:
停止:./bin/kafka-server-stop.sh
应用
采用springboot集成kafka模式进行开发
服务
1.kafka组件
将kafka服务作为独立组件jar来实现
引入依赖:
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
配置producer
@Service("kafkaProducer")
public class KafkaProducer
{
@Resource
private KafkaTemplate kafkaTemplate;
public Boolean sendMessage(String topic, Object message)
{
try
{
kafkaTemplate.send(topic, message);
} catch (Exception e)
{
return Boolean.FALSE;
}
return Boolean.TRUE;
}
}
2.业务
引入kafka组件包
主题:
public static final String KAFKA_MY_TOPIC = "MY_KAFKA_TOPIC";
public static final String KAFKA_MY_TOPIC_BAK = "MY_KAFKA_TOPIC_BAK";
服务启动后zk中可见:
说明:__consumer_offsets偏移量为消费者已消费的元数据,存储在消费者分区上,是在config配置中的num.partitions决定的,默认50个,0~49
消息推送:
@PostMapping("/sendBatchKafka")
@ResponseBody
public String sendBatchKafka(@RequestBody String request)
{
kafkaProducer.sendMessage(KafkaTopic.KAFKA_MY_TOPIC, request);
Map<String, String> params = (Map<String, String>) JSONObject.parse(request);
String context = params.get("context");
params.put("context", context + "123");
kafkaProducer.sendMessage(KafkaTopic.KAFKA_MY_TOPIC_BAK, JSONObject.toJSONString(params));
return "success";
}
消息消费:
@KafkaListener(topics = {KafkaTopic.KAFKA_MY_TOPIC})
public void onMessage(String message)
{
logger.info("KafkaConsumer主题消息:" + message);
}
@KafkaListener(topics = {KafkaTopic.KAFKA_MY_TOPIC, KafkaTopic.KAFKA_MY_TOPIC_BAK}, containerFactory = KafkaConfig.TOPIC_FACTORY)
public void dealMessage(String message)
{
logger.info("KafkaBatchConsumer主题消息(多主题):" + message);
}
调用推送接口,即可发送消息,多主题都可以接收。
单消费配置:
spring:
kafka:
bootstrap-servers: 192.168.99.128:9092
consumer:
group-id: test-consumer-group
enable-auto-commit: true
listener:
missing-topics-fatal: false
producer:
batch-size: 4096
说明:kafka默认单消费者,选取最新加载的消费者进行消费,如下:
此时查看zk,则可发现只有一个分区:
如果我们需要的就是一对一模式,那么在code的时候注意规范,完全可当做点对点来实现。
多消费者模式
但如果我们需要一对多模式,那么就需要进行调整:
spring:
kafka:
bootstrap-servers: 192.168.99.128:9092
consumer:
group-id: test-consumer-group
enable-auto-commit: true
listener:
missing-topics-fatal: false
concurrency: 5
type: batch
producer:
batch-size: 4096
listener.type改为batch批量模式
listener.concurrency线程数调整为依据server.properoties中的num.partitions=5来配置。
missing-topics-fatal默认会校验不存在的topic,此处false,否则需要在启动时创建,不然报错。
topic设置分区信息即可,默认1个分区(会依据规则找最新注册消费者消费),分区数不能大于配置的分区数:
@Bean(name = "MY_KAFKA_TOPIC")
public NewTopic myTopic()
{
return new NewTopic(KAFKA_MY_TOPIC, 5, (short)2);
}
可见按照配置设置了5个分区:
详细说明:
此处采用的是依据消费者的数量进行消息分区推送,因为分区数>=消费者数,那么自定义一个全局线程安全的容器来存放消费者的数量,故推送方法做出变化:
protected static Map<String, Integer> consumerCache = new ConcurrentHashMap<String, Integer>();
Integer concurrency = consumerCache.get(topic);
for (int i=0; i<concurrency; i++)
{
kafkaTemplate.send(topic, i, groupId , message);
}
说明:多消费者模式包含单消费,单主题默认分区为1个id为0。
测试:
这样三个消费者就都能收到消息了。
此处推送为一个topic对应多consumer模式,另一种是多topic对应多consumer模式,需要消息根据不同的topic进行推送,如果几千条就需要更多的topic,有点得不偿失。
mq-kafka-code源码
微信公众号:像是风