微服务架构-分布式消息中间件-088:Kafka-kafka如何保证消息的顺序性

1 MQ遵循投递消息先进先出原则

课程内容:

  1. kafka集群,生产者如何将消息投递那个Broker
  2. kafka与rabbitMQ如何实现顺序效率
  3. kafka为何能支撑高吞吐量
  4. kafka副本消息存放机制

MQ如何保证消息顺序的问题?
队列遵循的原则:先进先出、后进后出

队列与主题有哪些区别?
主题实际是对队列实现一层封装,类似一个仓库,里面存放很多不同的消息。

为什么MQ会产生消息顺序的问题?
当生产者往同一个队列中存放的消息的行为不统一,可能会存在消息顺序的问题。
下单(insert)、修改订单(update)、删除订单(delete)
{orderType:”insert”,orderId:”123456”}
{orderType:”update”,orderId:”123456”}
{orderType:”delete”,orderId:”123456”}
单个消费者的情况下,MQ的队列会根据先进先出的原则,消费的顺序是不会打乱的。
消费者集群的情况下,可能出现消费顺序的问题。

2 MQ顺序消息产生的背景有哪些

为什么MQ会产生消息顺序的问题?

  1. 消费者集群在这里插入图片描述
  2. MQ服务器端集群
    在这里插入图片描述

3 MQ如何保证消息的顺序性

为什么MQ服务器集群的话,单个消费者消费消息顺序会被打乱?
因为投递的消息分别存放到多个不同的Broker存放,单个消费者获取所有的Broker建立长连接,获取顺序可能是随机的。

什么情况下,消费者获取消息顺序不会被打乱?
同一个Broker,同一个消费者;根据消息key计算hash值。
如何解决kafka或者rocketmq消息顺序问题?
对相同的业务逻辑设置相同的消息key,存放到同一个broker中,最终对应一个消费者实现消费。

Kafka保证消息顺序性应用
application.yml

spring:
  kafka:
    bootstrap-servers: 192.168.0.58:9092,192.168.0.59:9092,192.168.0.60:9092
    #生产者的配置,大部分我们可以使用默认的,这里列出几个比较重要的属性
    producer:
      #每批次发送消息的数量
      batch-size: 300
      #设置大于0的值将使客户端重新发送任何数据,一旦这些数据发送失败。注意,这些重试与客户端接收到发送错误时的重试没有什么不同。允许重试将潜在的改变数据的顺序,如果这两个消息记录都是发送到同一个partition,则第一个消息失败第二个发送成功,则第二条消息会比第一条消息出现要早。
      retries: 0
      #producer可以用来缓存数据的内存大小。如果数据产生速度大于向broker发送的速度,producer会阻塞或者抛出异常,以“block.on.buffer.full”来表明。这项设置将和producer能够使用的总内存相关,但并不是一个硬性的限制,因为不是producer使用的所有内存都是用于缓存。一些额外的内存会用于压缩(如果引入压缩机制),同样还有一些用于维护请求。
      buffer-memory: 33554432
      #key序列化方式
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
    #消费者的配置
    consumer:
      concurrency: 10
      #Kafka中没有初始偏移或如果当前偏移在服务器上不再存在时,默认区最新 ,有三个选项 【latest, earliest, none】
      auto-offset-reset: earliest
      #是否开启自动提交
      enable-auto-commit: false
      ack-mode: MANUAL_IMMEDIATE
      #自动提交的时间间隔
      #            auto-commit-interval: 100
      #key的解码方式
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      #value的解码方式
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      group-id: mayikt-consumer-group-1
server:
  port: 8087

KafkaController

@RestController
@SpringBootApplication
@EnableKafka
public class KafkaController {

    /**
     * 注入kafkaTemplate
     */
    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    /**
     * 发送消息的方法
     *
     * @param key  推送数据的key
     * @param data 推送数据的data
     */
    private void send(String key, String data) {
        // topic 名称 key data 消息数据
        kafkaTemplate.send("mayikt-topic", key, data);

    }

    private void sendMsg(String data) {
        // topic 名称 key data 消息数据
        kafkaTemplate.send("mayikt-topic", data);

    }

    @RequestMapping("/getOrderKafka")
    public String getOrderKafka() {
        String orderId = System.currentTimeMillis() + "";
//        // 发送insertmsg
//        sendMsg(getSqlMsg("insert", orderId));
//        // 发送Updatemsg
//        sendMsg(getSqlMsg("update", orderId));
//        // 发送deletemsg
//        sendMsg(getSqlMsg("delete", orderId));

        // 发送insertmsg
        send(orderId, getSqlMsg("insert", orderId));
        // 发送Updatemsg
        send(orderId, getSqlMsg("update", orderId));
        // 发送deletemsg
        send(orderId, getSqlMsg("delete", orderId));
        return "success";
    }

    public String getSqlMsg(String type, String orderId) {
        JSONObject dataObject = new JSONObject();
        dataObject.put("type", type);
        dataObject.put("orderId", orderId);
        return dataObject.toJSONString();
    }


    public static void main(String[] args) {
        SpringApplication.run(KafkaController.class, args);
    }

    /**
     * 消费者使用日志打印消息
     */

//    @KafkaListener(topicPartitions = {@TopicPartition(topic = "mayikt", partitions = {"0"})})
    @KafkaListener(topics = "mayikt-topic")
    public void receive(ConsumerRecord<?, ?> consumer) {
        System.out.println("topic名称:" + consumer.topic() + ",key:" +
                consumer.key() + "," +
                "分区位置:" + consumer.partition()
                + ", 下标" + consumer.offset() + ",msg:" + consumer.value());
    }
}

运行结果:
在这里插入图片描述

4 解决MQ消息顺序的问题核心思路

几乎所有的mq都不支持全局的Broker顺序消息问题。
如果只有一个消费者消费消息,这时候整个吞吐量非常低。
如果既想保证mq消费的时候有严格的顺序,又要保证吞吐量比较高,这时候消费者应该批量获取整个broker消息。然后对每个消息计算hash,相同的key存放同一个内存队列,每个内存队列对应一个独立的线程处理消息。内存队列有多少个,相当于消费者有多少个。
在这里插入图片描述

5 解决MQ消息顺序的问题总结

总结:MQ什么情况产生消息的顺序问题?

  1. 如果生产者往MQ投递的消息行为不一样,可能会产生消息顺序的问题,如果投递消息到同一个队列中行为是一样的,没有必要在意消息顺序问题;
  2. MQ存放消息默认情况下本身就有一定顺序,遵循先进先出的原则(单个MQ单个消费者情况下);
  3. 如果有多个消费者订阅同一个队列,可能会产生消费者顺序被打乱;
  4. 如果Broker是集群的情况下,因为不同的消息可能会存放到多个不同的Broker中,单个消费者在获取消息的时候顺序可能会被打乱。

解决消息顺序的核心思路:
保证消息存放到同一个Broker中,对应只有一个消费者实现消费。
Kafka对消息设置相同的key,key可以根据业务来定;但是单个消费者消费消息吞吐量非常低,可以使消费者批量获取消息,然后采用内存队列存放。(也会根据消息的key计算hash,相同的key存放到同一个内存队列中,每个内存队列只会对应一个线程进行处理)

6 kafka的消费者分组的概念设计

消费者如果是分组的情况下,在同一个组中,最终只会有一个消费者消费同一个消息。
3个broker集群,假如有3个消费者在同一个分组,每个消费者对应消费一个固定的分区;假如有2个消费者在同一个分组,一个消费者固定消费一个分区,另一个消费者固定消费另外两个分区。
broker集群数量和消费者集群数量1:1,能更好的解决消费顺序问题。
在这里插入图片描述

7 为什么kafka吞吐高的原因

Kafka吞吐量为什么比较高的原因?

  1. 使用顺序写的形式存储数据,利用磁盘顺序读写的性能,减少相比寻常读写寻地址浪费的时间;
  2. 生产者和消费者都支持批量处理,异步+缓冲区(定时/缓存大小)投递消息,减少io操作;
    缺点:数据可能丢失
  3. 支持数据零拷贝,NIO本身支持;
  4. 根据Partition实现对topic数据分区存放;
  5. 对数据实现压缩,减少带宽传输;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值