消息队列削峰和分发

削峰演示场景

一个模块A接受一波流量,收到之后,立刻调用另一个模块B,那么此时B承担了基本相同的流程。举个例子,模块A一瞬间收到100个请求,那么B基本也是一瞬间收到100个请求,如果B的承压能力非常差,或者B有什么资源限制,那么这100个请求下来,B可能就挂了或者报错了。

面对这种下游扛不住的场景,我们还可以有第二种流程:模块A不用一次性把消息打给B,而是只用将信息传递到一个中转站,B按自身的消费能力从中转站拉取消息,再自己去做就可以了,这个中转站,就是消息队列,可以起到削峰的作用。

在之前给的代码其实已经实现了削峰的功能。在 Spring Kafka 中,起到削峰作用的其实是隐藏的默认配置:

  • 单线程消费 :默认情况下,@KafkaListener 是单线程执行的。这意味着无论 A 塞得多快,B 都是一个一个按顺序做,数据库永远不会过载。

  • 并发控制 :如果想让 B 快一点,但又怕把 MySQL 压垮,可以配置**concurrency** 参数。比如设置 concurrency = 3,就相当于给 B 开了 3 个窗口

java 复制代码
// 示例:设置并发数为 3,既加快了处理速度,又保证了 MySQL 不会因为瞬时 100 个请求而挂掉
@KafkaListener(topics = "decoupling-topic", concurrency = "3")
public void listen(String message) {
    countService.doHeavyWork(); 
}

最理想的状态:线程数concurrency = 分区数partition

总结:消息队列削峰,一般解决的是突发流量激增额情况。如果是流量持续增大,并且单机性能优化达到极致,那么只能水平扩容。削峰,也可以理解为在解耦的基础上,进一步考虑了流量激增的情况;而解耦更侧重考虑整体的相应时间

分发演示场景

一个模块A需要发送同一条消息给模块B,C,D

先来看一下使用消息队列进行分发前的代码是怎么样的:

java 复制代码
@PostMapping(value = "/dispatch", consumes = "application/json; charset=utf-8")
public ResponseEntity<String> dispatch(@RequestBody IncrCountReq dat) {
    String msg = "to the moon!";
    countService.msgAll("svr1", msg);
    countService2.msgAll("svr2", msg);
    countService3.msgAll("svr3", msg);
    return ResponseEntity.ok().build();
}

public void msgAll(String svrName, String msg) {
    System.out.println("svr " + svrName + " received msg : " + msg);
}

svr1,svr2,svr3就可以看作B,C,D三个模块。结果如下:

功能上没有任何问题,但是这样3个模块,我就得写三个对应的代码。那如果20个模块要感知这条消息,我们就需要写20个。所以我们用消息队列进行分发:

java 复制代码
@PostMapping(value = "/dispatch_with_mq", consumes = "application/json"; charset=utf-8")
public ResponseEntity<String> dispatchWithMQ(@RequestBody IncrCountReq dat) {
    String msg = "to the moon!";
    kafkaTemplate.send("tp-mq-dispatch", msg);
    return ResponseEntity.ok().build();
}

对应的要有消费者逻辑:

java 复制代码
@KafkaListener(topics = "tp-mq-dispatch", groupId = "TEST_GROUP1")
public void dispatchForSvr1(ConsumerRecord<?, ?> record, Acknowledgment ack) {
    Optional<?> message = Optional.ofNullable(record.value());
    if (message.isPresent()) {
        Object msg = message.get();
        String topic = record.topic();
        System.out.println("svr1 收到Kafka消息! Topic:" + topic + ", Message:" + msg);
        try {
            countService.incrManyTimes(10000);
            ack.acknowledge();
            log.info("Kafka消费成功! Topic:" + topic + ", Message:" + msg);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("Kafka消费失败! Topic:" + topic + ", Message:" + msg);
        }
    }
}

@KafkaListener(topics = "tp-mq-dispatch", groupId = "TEST_GROUP2")
public void dispatchForSvr2(ConsumerRecord<?, ?> record, Acknowledgment ack) {
    Optional<?> message = Optional.ofNullable(record.value());
    if (message.isPresent()) {
        Object msg = message.get();
        String topic = record.topic();
        System.out.println("svr2 收到Kafka消息! Topic:" + topic + ", Message:" + msg);
        try {
            countService.incrManyTimes(10000);
            ack.acknowledge();
            log.info("Kafka消费成功! Topic:" + topic + ", Message:" + msg);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("Kafka消费失败! Topic:" + topic + ", Message:" + msg);
        }
    }
}

@KafkaListener(topics = "tp-mq-dispatch", groupId = "TEST_GROUP3")
public void dispatchForSvr3(ConsumerRecord<?, ?> record, Acknowledgment ack) {
    Optional<?> message = Optional.ofNullable(record.value());
    if (message.isPresent()) {
        Object msg = message.get();
        String topic = record.topic();
        System.out.println("svr3 收到Kafka消息! Topic:" + topic + ", Message:" + msg);
        try {
            countService.incrManyTimes(10000);
            ack.acknowledge();
            log.info("Kafka消费成功! Topic:" + topic + ", Message:" + msg);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("Kafka消费失败! Topic:" + topic + ", Message:" + msg);
        }
    }
}

看到这里可能会问,消费者代码不也是不断在增加么?对,但是这是消费端的事情,A模块的维护者就需要升级,并且这里我们为了简单起见,模块都在同一个服务内,事实上,在生产环境生产端和消费端基本都是不同的服务,甚至是不同的团队在维护。

总结: 消息队列分发,既然是分发,一般就是发给多个消费者,而且发完之后啥也不管,就是解耦

相关推荐
014-code9 小时前
kafka + springboot快速入门
java·spring boot·分布式·kafka
⑩-9 小时前
RabbitMQ与Kafka的区别?
分布式·kafka·rabbitmq
没有bug.的程序员10 小时前
支付扣款成功却未发货?Spring Boot 整合 Kafka 事务消息的物理级防丢防重生死局
spring boot·spring·kafka·linq·事务消息·支付扣款
Jackyzhe10 小时前
从零学习Kafka:副本机制
分布式·学习·kafka
后季暖10 小时前
kafka优化
数据库·分布式·kafka
future02101 天前
Kafka积压急救:根治方案全解析
分布式·kafka
飞Link1 天前
Kafka~本地Python Kafka发送数据,服务端Kafka消费不到
java·分布式·kafka
面向Google编程1 天前
从零学习Kafka:副本机制
大数据·后端·kafka
ezreal_pan1 天前
Kafka Docker 部署避坑指南:监听器配置与客户端连接问题深度解析
分布式·docker·kafka