博主专注于做Java程序开发相关技术分享,旨在与各路大神做技术交流,觉得不错的朋友,点个关注,有想深度交流,也可参考博主其他文章:java架构师知识技能图谱-CSDN博客
Spring boot 简化了基于 Spring 开发的项目配置
开启对异步任务的支持,可以放在springboot的启动类上,也可以放在自定义线程池的配置类上,此时,可以在该配置类中声明一个线程池对象
来声明一个或多个异步任务,可以加在方法或者类上
在方法上加入这个注解,spring会从定义的线程池中获取一个新的线程来执行方法
当没有自定义线程池时,@Async默认的线程池是SimpleAsyncTaskExecutor
任意方法上标注@EventListener 注解,指定 classes,即需要处理的事件类型,一般就是 ApplicationEven 及其子类
可以搭配@Async使用
缓存某个beanName对应的经过了完整生命周期的bean,循环依赖中的对象是不能放入一级缓存中的
缓存的是还未完成属性注入和后续的BeanPostProcessor等生命周期的bean,如果涉及到AOP,那这里缓存的是原始对象进行了AOP之后的代理对象
缓存的是每个bean对象的ObjectFactory,主要用来去生成原始对象进行了AOP之后得到的代理对象,在每个Bean的生成过程中,都会提前暴露一个工厂,如果没有出现循环依赖依赖本bean,那么这个工厂无用,但如果出现了循环依赖依赖了本bean,则另外那个bean执行ObjectFactory提交得到一个AOP之后的代理对象(如果有AOP的话,如果无需AOP,则直接得到一个原始对象)。
Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象earlySingletonObjects,三级缓存为早期曝光对象工厂(singletonFactories)。当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取,第一步,先获取到三级缓存中的工厂;第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。如果A在之前已经经历了AOP代理,那么在后续的生命周期中会跳过AOP代理,从而保证A的对象与B的引用是同一个代理对象,至此,循环依赖结束!
如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理
为什么用消息队列?什么场景可以用到消息队列来解决
通过一个 MQ,Pub/Sub 发布订阅的消息模型,可以做到系统间解耦,A 系统产生一条数据,发送到 MQ 里面去,哪个系统需要数据自己去 MQ 里面消费
A 系统接收一个请求,需要在自己本地写库,还需要在 BCD 三个系统写库,自己本地写库要 3ms,BCD 三个系统分别写库要 300ms、450ms、200ms。最终请求总延时是 3 + 300 + 450 + 200 = 953ms,一般互联网类的企业,对于用户直接的操作,一般要求是每个请求都必须在 200 ms 以内完成
使用 MQ,那么 A 系统可发送 3 条消息到 MQ 队列中,然后直接返回降低响应时常,其他系统异步去队列中消费消息即可。
短时间内,系统的并发请求量增大,大量的请求涌入 MySQL,容易造成数据库瘫痪
使用 MQ,可以避免高峰期流量增大对系统的影响
单机吞吐量不高,社区不活跃
erlang语言开发,开源,社区活跃度高,延迟最低,性能好,但吞吐量一般,消息可靠性一般,可以做到基本不丢数据
阿里出品,目前已捐给 Apache,有黄的风险,吞吐量高,topic对吞吐量的影响不大,可以支持几百、几千的topic,时效性中,毫秒级,消息可靠性高,通过配置可以消息0丢失
适用大数据领域的实时计算、日志采集等场景,功能比较简单,高吞吐,消息可靠性高
基于主从(非分布式)做高可用性的
1) 单机-生产环境没人用
2) 普通集群
可提高吞吐量,不具备高可用性
多台机器启动多个RabbitMQ实例,创建的 queue,只会放在一个 RabbitMQ 实例上,但是每个实例都同步 queue 的元数据(元数据可以认为是 queue 的一些配置信息,通过元数据,可以找到 queue 所在实例),但当某个实例宕机之后,其他实例就无法获取到该实例所保存的queue中的消息了
3) 镜像集群
创建的 queue,无论是元数据还是 queue 里的消息都会存在于多个实例上,就是说,每个 RabbitMQ 节点都有这个 queue 的一个完整镜像,包含 queue 的全部数据的意思。然后每次写消息到 queue 的时候,都会自动把消息同步到多个实例的 queue 上。
由多个 broker 组成,每个 broker 是一个节点;你创建一个 topic,这个 topic 可以划分为多个 partition,每个 partition 可以存在于不同的 broker 上,每个 partition 就放一部分数据
首先Kafka实现了一个 topic 的数据,可分散放在多个机器上的,每个机器就放一部分数据,同时0.8 以后,提供了 HA 机制,就是 replica(复制品) 副本机制,每个partition的数据会同步到其它机器上,形成多个 replica 副本,然后所有 replica 会选举一个 leader 出来,生产和消费这部分partition的数据都只跟这个 leader 打交道即可
写数据只写leader,其他 follower 自己主动从 leader 来 pull 数据,完成后返回ack,leader收到所有ack后。返回写成功的消息给生产者;读数据,只会从 leader 去读,但是只有当一个消息已经被所有 follower 都同步成功返回 ack 的时候,这个消息才会被消费者读到
消费消息时,保证幂等性
方案一:开启RabbitMQ事务(同步会阻塞,不推荐,消耗性能,影响吞吐量)
方案二:开启confirm模式(异步,推荐)
生产者可以通过回调方法来处理该confirm消息,从而确保消息正常生产
开启持久化,防止宕机引起消息丢失
关闭 RabbitMQ 的自动 ack,等消息处理完之后,手动ack
关闭自动提交 offset,在处理完之后自己手动提交 offset
1) topic 设置 replication.factor 参数:这个值必须大于 1,要求每个 partition 必须有至少 2 个副本
2) 在 Kafka 服务端设置 min.insync.replicas 参数:这个值必须大于 1,这个是要求一个 leader 至少感知到有至少一个 follower 还跟自己保持联系,没掉队,这样才能确保 leader 挂了还有一个 follower 吧
3) 在 producer 端设置 acks=all :这个是要求每条数据,必须是写入所有 replica 之后,才能认为是写成功了。
4) 在 producer 端设置 retries=MAX (很大很大很大的一个值,无限次重试的意思):这个是要求一旦写入失败,就无限重试,卡在这里了
场景:一般只有一个 queue,但是会有多个 consumer,消费消息时,会导致顺序性出错
解决:一个 queue 只对应一个 consumer,然后这个 consumer 内部用内存队列做排队,然后分发给底层不同的 worker 来处理。
将不同的消息,进行分区,放入到不同的topic中,保证消息在队列中的顺序性,消费消息时,使用单线程消费
DLX,dead-letter-exchange,当消息在一个队列中变成死信 (dead message) 之后,它能被重新publish到另一个Exchange,这个Exchange就是DLX
消息被拒绝(basic.reject / basic.nack),并且requeue = false
消息TTL过期
队列达到最大长度
DLX也是一个正常的Exchange,和一般的Exchange没有区别,它能在任何的队列上被指定,实际上就是设置某个队列的属性。
当这个队列中有死信时,RabbitMQ就会自动的将这个消息重新发布到设置的Exchange上去,进而被路由到另一个队列。
可以监听这个队列中的消息做相应的处理
排查原因,是否消费端出现了异常,如果是发生异常,进行处理,处理之后视情况是否做处理
提高消费并行度,绝大部分消息消费行为都属于 IO 密集型,使用多线程用多个Consumer消费消息
批量方式消费,通过设置 consumer 的 consumeMessageBatchMaxSize 返个参数,默认是 1,即一次只消费一条消息,例如设置为 N,那么每次消费的消息数小于等于 N。
如果并未发生异常,只是单纯的消费速度一直追不上发送速度,如果业务对数据要求不高的话,可以选择丢弃不重要的消息