【归档文章】
项目中用到【Kafka】作消息的发送和接收,一次因为【Kafka队列连接】出错,导致系统问题,业务代码执行完成后,发送消息出现异常,结果导致业务系统请求超时,没有做出正确响应。
经过这次的事故,总结了使用MQ时需要注意的地方:
1、一般使用MQ发送数据消息,是在【业务逻辑处理完成】,发送消息的逻辑要从业务逻辑代码中剥离,以接口的方式暴露,做到核心业务对消息框架解耦;
2、如果【使用Java线程池】做消息的异步发送,一定要谨慎,不能使用【Executors】自带的【工厂方法创建线程池】,因为
- 【newFixedThreadPool】创建的线程池的队列是【LinkedBlockingQueue】,这个队列最大深度为【Integer.MAX_VALUE】,消如果消息队列发送缓慢,消息将堆积,造成内存消耗;
- 【newCachedThreadPool】的【最大线程数】为【Integer.MAX_VALUE】,如果消息较多,将创建很多线程,占用系统资源;
- 使用自定义线程池,一定【要根据业务最大并发】、【服务器资源】等,评估好 核心线程数、最大线程数 和 线程中的队列深度,最好自定义 线程工厂 和 拒绝策略,一旦消息发送失败,可以做好异常处理;
3、消息发布和订阅的逻辑,一定要与业务逻辑隔离,不能因为消息发布和订阅影响系统服务;
4、如果消息订阅和发布对业务影响较小,当MQ服务宕机,或者因为网络问题,导致连接异常,可以增加配置开关(如果使用分布式配置管理),等待MQ服务恢复后再打开;
5、MQ消息虽然可以【对系统间的依赖进行解耦】,但【对系统自身】增加消息队列的依赖,使系统自身复杂性增强,因为要处理包括:
- 消息不丢;
- 消息发送失败;
- 不重复发送;
- 不重复消费;
- 保证消息有序;
- ....
等一系列问题,增加了系统设计难度,提升系统维护成本,需要考虑消息队列服务出现问题,如何保证系统不受影响(通常做法会增加一个开关,判断是否要发送消息);
6、虽然【分布式事务】中有【事务消息】这个方案,但目前只有RocketMQ支持事务消息,如果没有使用RocketMQ,则需要在本地建立一张消息表,用于存放消息发送状态,一旦事务成功,则发送消息,并修改消息状态;
对于消息表的设计,如果发送的数据格式比较接近,通常可以采用ORM设计,将发送消息的字段与表的column作一一映射;但是如果发送的消息内容异构,则需要维护多张消息表,用于存放不同类型的消息,或者使用一个大json,来维护发送消息的数据;
总结:
MQ不是银弹,一般使用消息队列,为了解决【写操作比较慢】问题,保持系统吞吐量稳定,能【消峰填谷】等问题,比如 系统自身的写操作慢,或者 系统依赖下游的 写操作比较慢(不需要下游系统立刻对写请求给出业务响应的场景),可以使用MQ。