热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

游戏陪玩app开发,消息可靠性的实现

在游戏陪玩app开发中,随着业务的不断复杂和调用链路的不断增长,我们可能会慢慢引入越来越多的中间件来更好的服务于我们的系统,但是每样技术都

在游戏陪玩app开发中,随着业务的不断复杂和调用链路的不断增长,我们可能会慢慢引入越来越多的中间件来更好的服务于我们的系统,但是每样技术都是一把双刃剑,在提高游戏陪玩app开发系统性能的同时,也要想办法来减少它对系统带来稳定性的影响,今天要带来的是如何让RabbitMQ的可靠性达到保证。

要想了解如何保证RabbitMQ的可靠性,首先要从它的执行流程开始了解。

执行流程

在这里插入图片描述

  • 生产者发送消息或者消费者进行消费消息都会先与游戏陪玩app开发的主机建立起一条长连接,由长连接里的channal来传送消息。
  • 长连接优点:消费者如果出现宕机或者下线,mq会感知到,没法继续派发后会把这条消息再次存储起来,避免造成消息大面积丢失
  • 消息由消息头+消息体+路由键组成。
  • 消息发送出去后,首先进入游戏陪玩app开发的mq服务器指定的一个虚拟主机中,由虚拟主机中的exchange交换机收到后,通过消息的路由键和绑定关系,最终决定发往那个队列。
  • 消费者通过监听指定队列拿到消息。

由执行流程就可以看到,消息不管是在生产者发送到MQ服务器的过程中或者是在消费的过程中都存在着丢失的风险,那怎么办呢?

消息确认机制-可靠抵达

事务

提到保证可靠性的问题,小伙伴们肯定首先可以想到的是事务机制,RabbitMQ也提供了事务消息,不过官方文档也写到事务消息让MQ的性能下降250倍,所以说在游戏陪玩app开发对性能要求很高的情况下,显然不适合去使用事务消息。

那只能从其他几个方面来保证可靠性了。

在这里插入图片描述

这是一张消息发送到消费的简图,要保证可靠性的话,要从三个方面来来考虑。

publisher → Broker,confirmCallback机制

  • 在创建connectionFactory的时候设置publisherConfirm(true)选项,开始confirmCallback。
  • 消息只要被broker收到就会执行confirmCallback,如果是cluster模式,需要所有broker都接收到才会调用confirmCallback。
  • 被broker接收到只能表示message已经抵达服务器,并不能保证消息一定被投递到目标queue里。所以需要用到接下来的returnCallback。

#配置yml文件
spring:rabbitmq:#开启发送单确认publisher-confirms: true

//定制abbitTemplate
@PostConstruct //MyRabbitConfig初始化完成后,执行这个方法
public void initRabbitTemplate(){// 服务器收到消息确认回调/*correlationData 消息的唯一idack 消息是否成功收到cause 失败原因*/rabbitTemplate.setConfirmCallback(((correlationData, ack, cause) -> {log.info("confirm---->correlationData{},-------->ack{},-------->cause{}",correlationData,ack,cause);}));
}

Exchange → Queue,returnCallback 机制

  • confirm模式只能保证消息抵达broker,不能保证消息准确投递到目标queue里。在游戏陪玩app开发的一些业务场景下,需要保证消息一定要投递到目标queue里,此时就需要用到return退回模式。
  • 这样如果未能投递到目标queue里将会调用returnCallback
    ,可以记录下详细到投递数据,定期的巡检或者自动纠错都将需要这些数据。

#配置yml文件
spring:rabbitmq:#开启发送端消息抵达队列确认publisher-returns: true#只要抵达队列,以异步方式优先回调这个returnconfirmtemplate:mandatory: true

//定制abbitTemplate
@PostConstruct //MyRabbitConfig初始化完成后,执行这个方法
public void initRabbitTemplate(){//设置消息抵达queue的确认回调 (消息没有投递给指定队列,才会触发这个失败回调)/*message 投递失败的详细信息replyCode 回复的状态码replyText 回复的文本内容exchange 这个消息发送给哪个交换机routingKey 这个消息用的哪个路由键*/rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {log.info("return---->message{},-->replyCode{},-->replyText{},-->exchange{},-->routingKey{}",message,replyCode,replyText,exchange,routingKey);});
}

Queue → Consumer,ack消息确认机制(消费端)

  • 默认自动ack,消息被消费者收到,就会从broker的queue中移除
  • 问题:收到很多消息,自动回复给服务器ack,只处理一个消息,游戏陪玩app开发服务器就宕机了。这时消息就会全部丢失,所以要关闭默认的自动ack机制。
  • 消费者获取到消息,成功处理,可以回复ack给broker
  • basic.ack用于肯定确认;broker将移除此消息
  • basic.nack用于否定确认;可以指出broker是否丢弃此消息,可以批量
  • basic.reject用于否定确认;同上,但不能批量
  • queue无消费者,消息依然会被存储,直到消费者消费
  • 消费者收到消息,默认会自动ack。但是如果无法确认此消息是否被处理完成,或者成功处理。我们可以开启手动ack模式
  • 消息处理成功,ack(),接受下一个消息,此消息broker就会移除
  • 消息处理失败,nack()/reject(),重新发送给其他人进行处理,或者容错处理后ack
  • 消息一直都没有调用ack/nack方法,broker认为此消息正在被处理,不会投递给别人。此时客户端断开。消息不会被broker移除,会投递给别人
  • 手动ack机制下,只要没有明确告诉mq消息被消费,没有ack,游戏陪玩app开发消息就一直是unacked状态。即使consumer宕机,消息不会丢失,会变为ready状态,下次一有新的consumer连接进来就发给他

#配置yml文件
spring:rabbitmq:#切换为手动acklistener:direct:acknowledge-mode: manual

完成以上三个配置后,我们用于消费消息的代码就会变成这样

/*** @author lp* @date 2020/8/9 15:05*/
@Service
@Slf4j
@RabbitListener(queues = "demo.queue")
public class DemoListener {@Autowiredprivate DemoService service;@RabbitHandlerpublic void listener(DemoEntity entity, Channel channel, Message message) throws IOException {log.info("-----------开始消费消息----------");try {//具体业务service.doSomething(entity);//成功处理回复aclchannel.basicAck(message.getMessageProperties().getDeliveryTag(), false);} catch (Exception e) {//失败处理重新返回queuechannel.basicReject(message.getMessageProperties().getDeliveryTag(), true);}}
}

当然在游戏陪玩app开发中仅仅是这三步配置还是不够的,因为上述步骤并没有做到消息持久化,在做好持久化方案后,我们的消息将无敌。(夸张的修辞手法)

小结:


  • 消息发送出去,由于网络问题没有抵达游戏陪玩app开发服务器
  • 做好容错方法(try-catch),发送消息可能会网络失败,失败后要有重试机制,可记录到数据库,采用定期扫描重发的方式。
  • 做好日志记录(给数据库保存每一个消息的详细信息),每个消息状态是否都被服务器收到,应记录。
  • 做好定期重发,如果消息没有发送成功,定期去数据库扫描未成功的消息进行重发。
  • 消息抵达Broker,Broker要将消息写入磁盘(持久化)才算成功。此时Broker尚未持久化完成,宕机
  • publisher也必须加入确认回调机制,确认成功的消息,修改数据库消息状态。
  • 自动ack的状态下。消费者收到消息,但还没来得及处理消息,宕机
  • 一定开启手动ack,消息消费成功后才移除,失败或者没来得及处理就noAck并重新入队。

做好这些后,游戏陪玩app开发中消息丢失的可能性已经很小很小了,但是又会有新的问题出现,比如说重复消费,消息过多导致消费者宕机等。

防止消息重复


  • 消息消费成功,事务已经提交,ack时,机器宕机。导致没有ack成功,Broker的消息重新由unack变为ready,并发送给其他消费者
  • 消费失败时,由于重试机制,自动又将消息发送出去
  • 成功消费,ack时宕机,消息由unack变为ready,Broker又重新发送
  • 消费者的业务消费接口应该设计为幂等性的。比如扣库存有工作单的状态标志
  • 使用防重表(redis/mysql),发送消息每一个都有业务的唯一标识,处理过就不用处理
  • rabbitMq每一次消息都有redelivered字段,可以获取是否被重新投递过来的,而不是第一次被投递过来的

消息积压


  • 产生原因:
  • 消费者宕机
  • 消费者能力不足积压
  • 发送者发送流量太大
  • 如何解决:
  • 上线更多消费者,进行正常消费
  • 上线专门的消息队列服务,将消息先批量取出来,记录到数据库,离线慢慢处理

推荐阅读
  • 为了优化直播应用底部聊天框的弹出机制,确保在不同设备上的布局稳定性和兼容性,特别是在配备虚拟按键的设备上,我们对用户交互流程进行了调整。首次打开应用时,需先点击首个输入框以准确获取键盘高度,避免直接点击第二个输入框导致的整体布局挤压问题。此优化通过调整 `activity_main.xml` 布局文件实现,确保了更好的用户体验和界面适配。 ... [详细]
  • 本文将详细介绍在Android应用中添加自定义返回按钮的方法,帮助开发者更好地理解和实现这一功能。通过具体的代码示例和步骤说明,本文旨在为初学者提供清晰的指导,确保他们在开发过程中能够顺利集成返回按钮,提升用户体验。 ... [详细]
  • 深入解析 Vue.js 的设计与实现:第三章详解
    在《深入解析 Vue.js 的设计与实现》第三章中,详细探讨了 Vue.js 渲染器与虚拟 DOM 的机制。通过 JavaScript 对象来模拟实际的 DOM 结构,例如,`const vNode = { tag: 'div', props: { ... } }`,这种方式不仅提高了性能,还增强了组件的可维护性和灵活性。本章进一步分析了虚拟 DOM 的创建、更新及优化策略,为开发者提供了深入了解 Vue.js 内核工作的视角。 ... [详细]
  • 本文提供了 RabbitMQ 3.7 的快速上手指南,详细介绍了环境搭建、生产者和消费者的配置与使用。通过官方教程的指引,读者可以轻松完成初步测试和实践,快速掌握 RabbitMQ 的核心功能和基本操作。 ... [详细]
  • 本文深入探讨了 `ExpressionChangedAfterItHasBeenCheckedError` 错误的原因及其解决方案。通过分析 Angular 的变更检测机制,详细解释了该错误的发生条件,并提供了多种有效的应对策略,帮助开发者在实际开发中避免这一常见问题。 ... [详细]
  • 本文深入探讨了NDK与JNI技术在实际项目中的应用及其学习路径。通过分析工程目录结构和关键代码示例,详细介绍了如何在Android开发中高效利用NDK和JNI,实现高性能计算和跨平台功能。同时,文章还提供了从基础概念到高级实践的系统学习指南,帮助开发者快速掌握这些关键技术。 ... [详细]
  • 精通jQuery:深入解析事件处理机制与应用技巧
    本文详细探讨了jQuery的事件处理机制及其应用技巧,通过具体的代码示例,逐一解析了每个jQuery代码片段与其对应的HTML结构。文章以标记为基准,CSS作为通用样式,确保每段代码都能独立运行。HTML和CSS代码统一放置在文章末尾,方便读者参考和实践。 ... [详细]
  • 如何在微信公众平台集成新浪云服务应用摘要:新浪云服务平台SinaAppEngine(简称SAE)自2009年启动内部研发,并于同年对外开放。本文详细介绍了如何利用SAE的强大功能,在微信公众平台上构建高效、稳定的云服务应用程序,涵盖从环境配置到应用部署的全流程,为开发者提供详尽的技术指导与实践案例。 ... [详细]
  • 深入解析 Django 中用户模型的自定义方法与技巧 ... [详细]
  • 本文详细探讨了Java集合框架的使用方法及其性能特点。首先,通过关系图展示了集合接口之间的层次结构,如`Collection`接口作为对象集合的基础,其下分为`List`、`Set`和`Queue`等子接口。其中,`List`接口支持按插入顺序保存元素且允许重复,而`Set`接口则确保元素唯一性。此外,文章还深入分析了不同集合类在实际应用中的性能表现,为开发者选择合适的集合类型提供了参考依据。 ... [详细]
  • C#编程指南:实现列表与WPF数据网格的高效绑定方法 ... [详细]
  • 深入解析Gradle中的Project核心组件
    在Gradle构建系统中,`Project` 是一个核心组件,扮演着至关重要的角色。通过使用 `./gradlew projects` 命令,可以清晰地列出当前项目结构中包含的所有子项目,这有助于开发者更好地理解和管理复杂的多模块项目。此外,`Project` 对象还提供了丰富的配置选项和生命周期管理功能,使得构建过程更加灵活高效。 ... [详细]
  • 成功实现Asp.Net MVC3网站与MongoDB数据库的高效集成
    我们成功地构建了一个基于Asp.NET MVC3框架的网站,并实现了与MongoDB数据库的高效集成。此次更新不仅完善了基本的创建和显示功能,还全面实现了数据的增删改查操作。在创建功能方面,我们修复了之前代码中的错误,确保每个属性都能正确生成。此外,我们还对数据模型进行了优化,以提高系统的性能和稳定性。 ... [详细]
  • Mongoose E11000 错误:集合中出现重复键问题分析与解决 ... [详细]
  • Select2.js下拉框应用总结与实践要点
    在使用Select2.js下拉框插件的过程中,积累了诸多实践经验与心得。尽管最初觉得Select2在某些方面不尽如人意,但在对比了其他选项后,发现其仍是最优选择。本文将详细探讨Select2.js的配置、优化技巧及常见问题解决方法,帮助开发者更好地利用这一强大的前端工具。 ... [详细]
author-avatar
朱小小喵喵_972
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有