队列有哪些特点

1,先进先出
2,发布订阅
3,持久化
4,分布式高可用 cluster

消息队列的通讯模式

点对点:一对一、一对多、多对多、多对一等

为什么要使用MQ

异步、削峰、解耦

消息队列有什么缺点

系统可用性降低
系统复杂性升高
一致性问题

消息队列如何选型

特性

activeMq

RabbitMq

kafka

RocketMq

PRODUCER-CONSUMER

支持

支持

支持

支持

PUBLISH_SUBSCRIBE

支持

支持

支持

支持

request-retry

支持

支持

-

支持

API完备性





多语言支持

支持,JAVA优先

语言无关

支持java优先

支持

单机吞吐量

万级

万级

十万级

单机万级

消息延迟

毫秒级

微秒级

毫秒级

毫秒级

可用性

毫秒级

微秒级

毫秒级

毫秒级

消息丢失

-


理论上不会丢失

理论上不会丢失

消息重复

-

可控制

理论上会有重复

允许重复

文档的完备性





提供快速入门





首次部署难度

-




中小公司技术一般,推荐rabbitMQ
大型公司,基础架构研发实力较强,用rocketMQ
实时计算、日志、binlog:kafka

rabbitMq:erlang开发,对消息堆积的支持并不好,当大量消息积压的时候,会导致rabbitMq的性能急剧下降,每秒钟可以处理几万到十几万条消息
rocketMq:java开发,面向互联网集群化功能丰富,对在线业务的响应时延做了很多优化,大多数情况下可以做到好苗子的响应,每秒大概能处理几十万条消息
kafka:scala开发,面向日志功能丰富,性能最高,当每秒钟流量不多的时候,kafka的时延反而较高,所以kafka不太适合在线业务

rabbitMQ消息可靠性传输

生产者
发送方确认:开启消息确认机制publisher-confirm-type:correlated 发送到交换器 返回ack
失败通知 :publisher-returns:true、mandatory=true
broker
发送时设置消息的deliveryMode=2(springboot默认就是)
搭配ack只有持久化磁盘 才返回ack
消费者
消费者手动确认:acknowledge-mode:manual

kafka

特性

高吞吐量、低延迟:每秒可以处理几十万条消息,延迟最低只有几号秒
可扩张性:集群支持热扩展
持久性、可靠性:消息持久化到磁盘,并且支持数据备份防止数据丢失
容错性:允许几群众节点失败(若副本数量n,允许n-1节点失败)
高并发:支持上千个客户端同事读写

角色

provider:生产者 发送消息的
consumer:消费者消费消息的
brocker:节点 你看到的机器
zookeeper:注册中心,记录kafka各种信息的地方
controller:主broker,以leader身份负责整个集群,如果挂掉 zk重新选举

逻辑组件

topic:消费主题
partition:分区 每个主题 可以发送多个分区,吞吐量更大(消费者数量小于等于分区数量)
replicas:副本 每个分区可以设置多个副本,副本之间数据一致 相当于 备份,有备胎 更加可靠
leader & follow:主从

副本:AR=ISR+OSR
AR:所有副本的统称
ISR:同步中的副本 (配置 数量滞后 时间滞后)滞后过了阈值的踢到OSR
OSR:追赶中的副本 (不参与选举)

首先树立一个观点 kafka并不是 你发送了这个消息 他就能消费 首先 它会同步到副本 所谓HW就是副本中的最小的leo
主:leo>=HW>=offset 还会 保存副本的这些信息
offset:偏移量 消费方消费到那一条了 有两种配置
各个分区有已提交的offset,从提交的offset开始消费
earliest:无提交的offset从头开始消费
latest(默认使用):从尾部开始消费 (消费新产生的数据)
HW(high waterMark):客户端最多能消费到的位置(已提交 已备份 )
leo:日志末端位移,表示下一个待写入的消息的offset,这个offset实际上 没有信息,主副本 都有这个值

kafka的集群信息 存在哪里?
zookeeper,还有每个broker也会保存在本地,发生变更 通过controller通知
高版本 可能已经移除zookeeper,由kafka自己管理

kafka发送消息 、消费、位移提交

一般有三种模式:
发送消息的时候指定分区
指定key,kafka会取模 相同的key会落到 一个partition(最常用)
默认 轮询
自定义分区策略(你想怎么玩都行)

指定消费组
同组 集群消费,不同的组 广播
例如topic:test 消费组 group1,group2
两个消费组 那就是广播
如果你写两个方法 消费组都是group1,方法A,方法B都是消费test,并且消费组都是group1
那就是集群消费

位移提交
自动提交
配置:
enable-auto-commit:true ##是否自动提交
auto-commit-interval:100 ## 提交offset的延迟(接收到offset消息之后多久提交offset)

手动提交:打开 一定要提交偏移量 否则重复消费
防止丢失消息 和重复,需要手动控制偏移量的提交

低版本 kafka是怎么丢消息的?高版本kafka又是如何解决的?

有空在写,比较复杂

kafka的客户端KM有空自己玩吧,里面就是把你通过敲命令的方式创建topic、partition、副本等 可视化工具 自己页面上创建;kafka监控eagle监控kafka集群的工具

kafka底层架构

1,分段存储

第一个文件
00000000000000000000.index
00000000000000000000.log
第二个文件
00000000000000369769.index
00000000000000369769.log
第三个文件
00000000000000769769.index
00000000000000769769.log
根据offset去找的时候 方便快速找到
a,kafka直接根据文件名大小,发现他在哪个文件里,例如offset:6 他在000000.log文件里面
b,文件找到了 需要找哪个位置,根据index文件 发现6,9807说明消息藏在这里
c,从log文件的9807位置读取
d,读取多长呢?读到下一条消息的偏移量停止就行

2,日志删除

a,按照时间 超过一段时间删除过期数据
b,按照消息大小,消息数量超过一定的大小删除最旧的数据

每次删除 的最小单位:segment 也就是直接干掉文件,一删 就是一个log和一个index

3,kafka执行消息写入和读取那么快,其中一个原因就是用了零拷贝?

传统:数据 从磁盘 读入 到内核 在读入到用户空间 用户空间数据在回到内核空间 最后再复制到网卡
零拷贝:硬件层面使用DMA技术控制芯片,网卡等外部设备直接去读取内存,不用cpu来回拷贝传输
不用经过用户空间
java中的零拷贝通过:Java.nio.channels.FileChannel的transferTo实现
它底层通过native方法调用操作系统的sendFile
操作系统负责把数据从某个fd传输到另一个fd(fd file dresciption) linux下的所有设备都是一个fd 文件描述符

Mq顺序性消费方案

1,阻塞队列 + 多线程实现

LinkedBlockingQueue[] queues = new LinkedBlockingQueue[4];

    @PostConstruct
    void execute(){
        //遍历队列数组,初始化每一个元素,同时让线程启动
        for (int i = 0; i < 4; i++) {
            logger.info("thread started , index = {}", i);
            final int current = i;
            queues[current] = new LinkedBlockingQueue();
            new Thread( () -> {
                try {
                    //循环4次,起4个线程一直监听自己的队列,不停获取数据
                    //取到就执行打印log,取不到阻塞等待
                    while (true) {
                        Order order = (Order) queues[current].take();
                        logger.info("get from queue, queue:{},order:[id={},status={}]",
                                current,
                                order.getId(),
                                order.getStatus());
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

    @KafkaListener(topics = {"sorted"},topicPattern = "0",groupId = "sorted-group-test-1")
    public void onMessage(ConsumerRecord<?, ?> consumerRecord) throws InterruptedException {
        Optional<?> optional = Optional.ofNullable(consumerRecord.value());
        if (optional.isPresent()) {
            Object msg = optional.get();
//            从kafka里获取到消息
//            注意分发方式,kafka有两个分区0和1,对应两个消费者
//            当前分区是0,也就是偶数的id,0、2、4、6会在这里被消费
            Order order = JSON.parseObject(String.valueOf(msg),Order.class);


//            而队列是4个。也就是每个消费者再分到两个队列里去
//            队列另一端分别对应4个线程在等待
//            所以,按4取余数
            int index = order.getId() % 4;
//            logger.info("put to queue,queue={},order:[id={},status={}]",index,order.getId(),order.getStatus());
            queues[index].put(order);
        }
    }

    @KafkaListener(topics = {"sorted"},topicPattern = "1",groupId = "sorted-group-test-1")
    public void onMessage1(ConsumerRecord<?, ?> consumerRecord) throws InterruptedException {
        //相同的实现,现实中为另一台机器,这里用两个listener模拟
        //奇数的id会被分到这里,也就是1、3、5、7
        onMessage(consumerRecord);
    }

`2,使用线程池

//队列数量,根据业务情况决定
    //本案例,队列=4,线程=4,一一对应
    ExecutorService[] queues = new ExecutorService[4];

    @PostConstruct
    void execute(){
        //遍历队列数组,初始化每一个元素,同时让线程启动
        for (int i = 0; i < 4; i++) {
            logger.info("thread started , index = {}", i);
            final int current = i;
            queues[current] = Executors.newSingleThreadExecutor();
        }
    }

    @KafkaListener(topics = {"sorted"},topicPattern = "0",groupId = "sorted-group-test-2")
    public void onMessage(ConsumerRecord<?, ?> consumerRecord) throws InterruptedException {
        Optional<?> optional = Optional.ofNullable(consumerRecord.value());
        if (optional.isPresent()) {
            Object msg = optional.get();
//            从kafka里获取到消息
//            注意分发方式,kafka有两个分区0和1,对应两个消费者
//            当前分区是0,也就是偶数的id,0、2、4、6会在这里被消费
            Order order = JSON.parseObject(String.valueOf(msg),Order.class);


//            而队列是4个。也就是每个消费者再分到两个队列里去
//            队列另一端分别对应4个线程在等待
//            所以,按4取余数
            int index = order.getId() % 4;
//            logger.info("put to queue,queue={},order:[id={},status={}]",index,order.getId(),order.getStatus());

            queues[index].execute(()->{
                logger.info("get from queue, queue:{},order:[id={},status={}]",
                        index,
                        order.getId(),
                        order.getStatus());
            });

        }
    }

    @KafkaListener(topics = {"sorted"},topicPattern = "1",groupId = "sorted-group-test-2")
    public void onMessage1(ConsumerRecord<?, ?> consumerRecord) throws InterruptedException {
        //相同的实现,现实中为另一台机器,这里用两个listener模拟
        //奇数的id会被分到这里,也就是1、3、5、7
        onMessage(consumerRecord);
    }

海量数据 同步场景

使用cannel 伪装成一个Mysql的slave实例 选用消息队列 发送binlog,持久化到其他数据源 es,大数据等