java view
2020-03-27 10:33:32 1 举报
AI智能生成
通俗易懂,精炼的java面试题,内含消息队列、redis缓存、分布式事务、数据库优化等等面试知识,正逐渐完善
作者其他创作
大纲/内容
消息队列
队列入门
kafka
activeMQ
rabbitMQ
direct
子主题
rocketMQ
特点
Producer、Consumer队列都可以分布式
能够保证严格的消息顺序(因为性能原因,不能保证消息不重复,
因为总有网络不可达的情况发生,需业务端保证)。
因为总有网络不可达的情况发生,需业务端保证)。
丰富的消息拉取模式
较少的依赖
亿级消息堆积能力
实时的消息订阅机制
高效的订阅者水平扩展能力
支持集群部署,保证了高可用,数据不会丢失
概念
Name Server
它是一个几乎无状态节点,可集群部署,节点之间无任何信息同步
Broker
Broker分为Master与Slave
一个Master可以对应多个Slave,但是一个Slave只能对应一个Master
Master与Slave的对应关系通过指定相同的BrokerName
BrokerId为0表示Master,非0表示Slave
每个Broker与Name Server 集群中的所有节点建立长连接,
定时注册Topic信息到所有Name Server。
定时注册Topic信息到所有Name Server。
Consumer
Consumer与Name Server集群中的其中一个节点
(随机选择,但不同于上一次)建立长连接
(随机选择,但不同于上一次)建立长连接
定期从Name Server 取Topic路由信息
向提供Topic服务的Master、Slave建立长连接
定时向Master、Slave发送心跳
Producer
Producer 与Name Server集群中的其中一个节点
(随机选择,但不同于上一次)建立长连接
(随机选择,但不同于上一次)建立长连接
定期从Name Server取Topic路由信息
向提供Topic服务的Master建立长连接
且定时向Master发送心跳
问题
为什么使用消息队列
解耦
有一个服务专门给设备推送数据的
数据以Protobuf形式放入到消息队列中
公司不定期会有新的定制设备需要推送数据
这样新的设备就可以从消息队列获取数据
不需要更改原推送代码
异步
推送消息到设备
其中有一次 一次性要给几十万台设备推送消息
要是同步一条条去推送,那要等多久才能完成,最后推送的设备很久才能收到消息
如果使用了消息队列,我就可以把消息都放到多个mq中,
可以同时从多个消息队列中消费,大大提高了推送时间
可以同时从多个消息队列中消费,大大提高了推送时间
削峰
有一些特殊时段,服务器请求的并发量如果暴增,也就所谓的请求高峰期
服务器处理能力有限,如果高峰期并发量超过限定,服务器就会崩溃,所以先把请求都压在MQ中,然后分批消费
往往高峰期,只是某个时段,或者很短的时间
消息队列有什么优点和缺点
缺点
系统可用性降低
引入外部依赖越多,系统越容易挂掉
mq挂掉
系统复杂度提高
加个MQ进来,你考虑消息重复、丢失、传输顺序等问题
一致性问题
如果向多个系统写入数据,其中某个失败了,怎么保证这些系统的一致性
优点
解耦、异步、削峰
Kafka、ActiveMQ、RabbitMQ、RocketMQ
有什么优缺点?
有什么优缺点?
activeMQ
一最早大家都用 ActiveMQ,
现在用的不多了,
社区也不是很活跃,不推荐用这个
现在用的不多了,
社区也不是很活跃,不推荐用这个
RabbitMQ
erlang 语言阻止了大量的 Java 工程师去深入研究和掌控它,
对公司而言,几乎处于不可控的状态,但是确实人家是开源的,
比较稳定的支持,活跃度也高
对公司而言,几乎处于不可控的状态,但是确实人家是开源的,
比较稳定的支持,活跃度也高
RocketMQ
越来越多的公司会去用 RocketMQ,确实很不错,
毕竟是阿里出品,但社区可能有突然黄掉的风险
毕竟是阿里出品,但社区可能有突然黄掉的风险
kafka
大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的
中小型公司一般使用rabbitMQ,技术挑战不是特别高
大型公司基础架构研发实力较强,使用rocketMQ
如何保证消息队列的高可用?
RabbitMQ 的高可用性
基于主从(非分布式)做高可用性的
集群搭建
普通集群模式
无高可用
rabbitMQ部署多个实例分别在一个服务器上
每个rabbitMQ对应一个queue
只有一个queue放消息,其他queue只同步queue元数据
镜像集群模式
你创建的 queue,无论元数据还是 queue 里的消息
都会存在于多个实例上
都会存在于多个实例上
每次你写消息到 queue 的时候,
都会自动把消息同步到多个实例的 queue 上
都会自动把消息同步到多个实例的 queue 上
如何开启这个镜像集群模式
后台新增 镜像集群模式的策略
指定的时候是可以要求数据同步到所有节点的,
也可以要求同步到指定数量的节点
也可以要求同步到指定数量的节点
镜像的配置是通过 policy 策略的方式
all 所有的节点都将被同步
exactly 指定个数的节点被同步
nodes 指定的名称的节点被同步
exactly 指定个数的节点被同步
nodes 指定的名称的节点被同步
Kafka 的高可用性
天然的分布式消息队列
架构
kafka8.0后,提供了HA 机制(副本机制)
每个 partition 的数据都会同步到其它机器上,形成自己的多个副本(replica )
副本(replica )会选举一个 leader 出来,其他 副本 就是 follower
当leader挂掉时,从follower中重新选举出leader,读和写只由leader负责,follower只用来备份数据
在写的时候,leader 会负责把数据同步到所有 follower 上去
所有 follower 都同步成功返回 ack
搭建集群
如何保证消息不被重复消费
(使用消息队列如何保证幂等性)
(使用消息队列如何保证幂等性)
kafka消费数据的过程
consumer 消费了数据之后,每隔一段时间(定时定期),
会把自己消费过的消息的 offset 提交到zookeeper,又给kafka
会把自己消费过的消息的 offset 提交到zookeeper,又给kafka
幂等性
通俗点说,就一个数据,或者一个请求,给你重复来多次,
你得确保对应的数据是不会改变的,不能出错。
你得确保对应的数据是不会改变的,不能出错。
解决方案
如何保证 MQ 的消费是幂等性的,需要结合具体的业务来看
例
需要让生产者发送每条数据的时候,里面加一个全局唯一的 id
先根据这个 id 去比如 Redis 里查一下,如果存在跳过消费,如果不存在,消费并将id写入redis
如何处理消息丢失的问题
场景
绝对不会把计费消息给弄丢,弄丢会造成经济损失
消息丢失图解
消息丢失的情况
生产者弄丢数据
消息传入MQ过程中丢失
由于网络原因等问题
解决方案
rabbitMQ
方案一 事务机制(同步,损耗性能吞吐量下降)
使用 RabbitMQ 提供的事务功能
生产者发送数据之前开启 RabbitMQ 事务channel.txSelect
发送消息,如果消息没有成功被 RabbitMQ 接收到,那么生产者会收到异常报错
此时就可以回滚事务channel.txRollback,然后重试发送消息
如果收到了消息,那么可以提交事务channel.txCommit
方案二 confirm机制(异步,推荐使用)
生产者那里设置开启 confirm 模式
每次写的消息都会分配一个唯一的 id
如果写入了 RabbitMQ 中,RabbitMQ
会给你回传一个 ack(回执) 消息告诉你说这个消息 ok
会给你回传一个 ack(回执) 消息告诉你说这个消息 ok
如果 RabbitMQ 没能写入这个消息,会回调你的一个 nack 接口
告诉你这个消息接收失败,你可以重试
告诉你这个消息接收失败,你可以重试
kafka
acks=all
MQ弄丢数据
MQ挂掉还没消费的消息会丢失
解决方案
rabbitMQ
丢失可能性不大,可以开启rabbitMQ的持久化
设置持久化
创建queue的时候,设置其持久化
持久化 queue 的元数据,但是它是不会持久化 queue 里的数据的
发送消息的时候将消息的 deliveryMode 设置为 2
当写入rabbitMQ后,还没持久化就挂掉会丢数据
可以使用confirm机制,等持久化后在返回ack
kafka
场景
follower同步leader数据时,leader挂掉了
方案
给 topic 设置 replication.factor 参数:这个值必须大于 1,
要求每个 partition 必须有至少 2 个副本
要求每个 partition 必须有至少 2 个副本
Kafka 服务端设置 min.insync.replicas 参数
这个值必须大于 1
leader 至少感知到有至少一个 follower正常
producer 端设置 acks=all :消息写入所有 replica 之后,才能认为是写成功了
在 producer 端设置 retries=MAX,很大很大很大的一个值,无限次重试的意思
消费者弄丢数据
消费者消费消息的过程中挂掉,导致消息丢失
解决方案
rabbitMQ
ack机制
刚消费到,还没处理,结果进程挂了
使用 RabbitMQ 提供的 ack 机制
必须关闭 RabbitMQ 的自动 ack,可以通过一个 api 来调用就行
然后每次你自己代码里确保处理完的时候,再在程序里手动 ack
kafka
场景
自动提交了 offset消息还没处理完,消费者就挂掉了
关闭自动提交 offset
在处理完之后自己手动提交 offset
保证消息的顺序性
顺序会错乱的场景
rabbitMQ
场景1
一个queue,有多个consumer去消费
consumer从MQ里面读取数据是有序的
每个consumer的执行时间是不固定的
造成数据顺序错误
场景2
一个queue对应一个consumer,
但是consumer里面进行了多线程消费,
这样也会造成消息消费顺序错误。
但是consumer里面进行了多线程消费,
这样也会造成消息消费顺序错误。
kafka
场景
kafka一个topic,一个partition,一个consumer,
但是consumer内部进行多线程消费
但是consumer内部进行多线程消费
解决方案
方案一
在消费者处可以把消息写入多个内存queue,
需要顺序执行的消息写入同一个queue
然后N个线程分别消费一个内存queue
需要顺序执行的消息写入同一个queue
然后N个线程分别消费一个内存queue
方案二
把需要顺序执行的消息放入同一个queue中,然后一个queue对应一个消费者去消费
如何解决消息队列的延时导致消息大量积压
场景
消费者出现问题,或者直接挂掉,消息累积,大量积压
解决方案
临时紧急扩容
先修复 consumer 的问题,确保其恢复消费速度,然后将现有 consumer 都停掉
新建一个 topic,partition 是原来的 10 倍,临时建立好原先 10 倍的 queue 数量
然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,
消费之后不做耗时的处理,直接均匀轮询写入临时建立好的 10 倍数量的 queue
消费之后不做耗时的处理,直接均匀轮询写入临时建立好的 10 倍数量的 queue
接着临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据
等快速消费完积压数据之后,得恢复原先部署的架构,重新用原先的 consumer 机器来消费消息
MQ中的消息过期失效了
场景
RabbtiMQ 是可以设置过期时间TTL
消息在 queue 中积压超过一定的时间就会被 RabbitMQ 给清理掉
解决方案
批量重导
写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入 mq 里面去
缓存
redis入门
问题
为什么要用缓存
高性能
对于一些需要复杂操作耗时查出来的结果
且确定后面不怎么变化,但是有很多读请求
直接将查询出来的结果放在缓存中,后面直接读缓存
节省查询时间,提高性能
用了缓存之后会有什么不良后果?
缓存与数据库双写不一致
缓存雪崩、缓存穿透
缓存并发竞争
redis 的线程模型是什么?为什么 redis 单线程却能支撑高并发?
单线程模型
为什么不用多线程?
一次I/O读写的过程
I/O操作一般分为两个阶段
等待I/O准备就绪
在磁盘上数据是分磁道、分簇存储的
数据往往并不是连续排列在同一磁道上
磁头在读取数据时往往需要在磁道之间反复移动,因此这里就有一个寻道耗时
盘面旋转将请求数据所在扇区移至读写头下方也是需要时间,这里还存在一个旋转耗时
真正操作I/O资源
在等待I/O准备就绪阶段,线程阻塞,等待磁盘就绪,
此时操作系统可以将这个空闲的CPU核心用于服务其他线程
此时操作系统可以将这个空闲的CPU核心用于服务其他线程
因此在I/O操作中,使用多线程效率更高
但redis是基于内存,不涉及I/O操作,
使用多线程不仅不能提高效率,而且多线程上下文切换还需要耗费时间
使用多线程不仅不能提高效率,而且多线程上下文切换还需要耗费时间
redis瓶颈
内存大小
网络带宽
redis运行机制
多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件
IO 多路复用程序会监听多个 socket,会将产生事件的 socket 放入队列中排队
事件分派器每次从队列中取出一个 socket,
根据 socket 的事件类型交给对应的事件处理器进行处理
根据 socket 的事件类型交给对应的事件处理器进行处理
redis 单线程模型也能高效率
纯内存操作
基于非阻塞的I/O多路复用机制
节省了线程上下文切换时间
子主题
0 条评论
下一页