RocketMQ消息队列,专业消息中间件,既可为分布式应用系统提供异步解耦和削峰填谷的能力,同时也具备互联网应用所需的海量消息堆积、高吞吐、可靠重试等特性,是应对企业业务峰值时刻必备的技术。

云片由于业务特点,对消息队列的使用十分频繁,由此云片服务号从本期推文开始将发布“云片RocketMQ实战”系列文章,讲述云片根据短信业务的特点,运用RocketMQ消息队列实战经验。

本期推文《Stargate的前世今生》,由云片资深Java开发工程师周凯帆提供。

本文字数3025,预计需要阅读20分钟

云片由于其业务特点对应消息队列的使用十分频繁,这里以云片短信业务为例,短信业务的逻辑十分简单,我们只看主流程,本质就是接受用户请求,寻找合适的通道,使用cmpp/smpp协议提交给运营商。

我们可以发现,用户的每次请求要一直等待运营商的响应,这样主要的问题是:

  • 云片的服务器再运营商返回时需要一直维护http连接

  • 运营商的处理速率直接限制了云片的处理速率

  • 云片的最大并发为所有供应商并发之和

这种情况下我们无法提供稳定的服务。

实际上对于用户来说并不关心具体流程他们只需将短信提交给云片即可,所以我们可以异步的处理这些发送过程,确认收到短信后就可以给用户返回结果以提高响应速度。

日常情况我们的系统流量会是一个比较平稳的值X,所以我们提供能满足的当前流量的消费能力,这样消息就不会积压。

不过随着流量的增加最终实践流量会超过我们的消费能力,这样就会出现短信送达延迟,图中虚线右边是我们不希望看到的。

所以我们在流量到达虚线前提高系统的消费能力。

我们可以看到云片对应消息队列的重度依赖,使得在微服务化的时候没有找到一个适合云片的好用的annotation组件,当时SpringCloud框架并没有对RocketMQ支持的相关组件,而RocketMQ官方github上仅有一个不成熟的项目。

对于目前重度依赖RocketMQ的短信业务我们需要一个简单易用并且能够与我们老项目中代码兼容的注解,同时还要满足各团队的不同需求,于是我们开始一个名为Stargate的组件来支持我们后续的服务化推进。

Stargate组件为什么出现?

事实上在有Stargate之前我们的项目中有一个对RocketMQ SDK的封装,它确实解决的许多问题,但是面对越来越多的生产者和消费者我们越来越难以维护,甚至于碰到不熟悉的代码我曾经花了半小时去找一个生产者的消费者在哪里,这个消费者被各种继承重写,生产者的topic是以一种极其复杂的规则生成的,而且各种配置文件散落在代码的各各角落。

然后旧的组件也十分难以迁移到新的微服务项目中,以至于有的业务线开始自己从新封装一套组件,而且他们生产者的消息难以被其他团队的消费。于是我希望做一套能够避免这些不良使用习惯的组件,并且提供强大的兼容性让大家迁移过来。

设计Stargate的目标是?

所以在设计Stargate的时候主要考虑以下几点:

  • 简单易用

注解的方式相比直接使用RocketMQ更加清晰,上手更快,更易用,避免各种不良写法。

  • 扩展性强

SG1提供的扩展插件能够丰富Stargate的功能,而且这个插件的开发能力是开放的,后续会提到。实际上插件功能是在2.0增加应为我发现在限制大家的使用方式后,我需要定制许多的注解来兼容各种使用场景,于是我开放了这部分能力让大家选择性的开发和使用自己需要的功能。

  • 兼容各种老项目

通过编解码器我们可以兼容各种不同的老项目,不需要修改老项目的代码。

  • 单元测试更方便

另外再单元测试和开发阶段,无需对外部依赖可以方便的进行mock。


Stargate组件的价值

最初的时候我简单的认为这个组件的价值在于提供了一个更方便使用RocketMQ的方式,但后续的开发中慢慢的我发现并不是这样,目前来说我认为最有价值的两点在于:

  • 服务间异步调用的“规范”

由于它的扩展性和兼容性被各各业务线团队采用,似乎成了一个“规范”,服务之间的通信多了一种可选项。我们可以把自己的StargateProducer接口定义放在一个jar包中提供给其他人

  • “使用心得”的分享中心

插件的开发能力使得大家会把自己的使用模式封装成一个注解发布出来,这样许多十分巧妙的使用方法会被发布出来,而且这些都是开箱即用的,使用者只需要知道这个注解能实现什么,在发布要求上让开发者符上文档,这样就能形成一个生态,把大家的经验使用沉淀下来。

另外,Stargate对于代码结构方面的帮助也是巨大的,现在我们可以很快速的找到一个生产者的消费者在什么地方,后续甚至考虑提供IDE插件来更好的维护这些代码。

Stargate的初始化

那么我们接下来,看一下Stargate的这些设计目标是如何实现的,首先我们来看一下组件的入口,我们如何初始化Stargate的。

在应用启动时我们处理部分注解获得一个Bean的配置信息,然后向Spring注册这些Bean,我们为Producer生成代理类,创建Consumer客户端监听消息并且调用StargateConsumer处理这些消息。

生成这些的Bean的入口是从一个工厂类开始的,通常一个StargateProducer|StargateConsumer的创建会经过以下几个步骤:

事实上Stargate并不负责初始化这些生产者消费者bean,Stargate仅仅提供了创建的过程,我们把这些bean注册到Spring中然后提供一个工厂方法,由spring在适当的时候创建这些bean,维护这些bean。

这样我们在spring中就有了这些生产者接口的实例,我们可以把他注入到任何地方然后使用它们发送消息。监听他的消费者就会调用事先配置好的StargateConsumer。

编解码器

每个发送者和消费者都会在收发消息前进行编解码,这也是兼容原有项目的关键。大家可以思考一下,所有项目的都是使用RocketMQ的,本质的直接调用SDK的Send方法就能发送消息,但是老项目对于如何将一个消息变成二进制数组这是不一样的,所以我们提供编解码器的接口让大家可以替换这些转换过程的实现。

扩展接口

但是消息的编解码只能实现兼容性但是对于扩展能力的需求无法满足,所以我们再初始化过程和发送消费过程中抽象出了6个接口,让用户可以扩展自己的逻辑。

这个些接口主要用于处理”注解解析“,”RocketMQ Client创建“,”消息加工“,我们可以从下图中看到,工厂在返回一个bean前会调用这些接口的实现。消息收发阶段也会调用相应的实现。

通常情况下我们实现一个新的注解@DemoModel有这么几个流程:

  • 实现注解处理器处理注解数据

  • 实现Client处理器,根据注解解析的数据加工Client

  • 实现消息处理器,根据注解解析的数据加工消息

上下文

这时会有另一个问题,我们通常的流程在解析注解获得的数据需要保存给另外两个处理器使用,我们当然不希望让用户自己处理这些数据,这会增加用户的使用成本。

于是我们定义了一个上下文的概念,上下文有这几个特点:

  • 每个生成消费者有自己的上下文

  • 上下文是会被继承的

举一个例子,假如我们现在所有的生产者的topic加上一个公共前缀,那么我们只需要在生产者根上下文中的topic加上这个前缀的内容,所有的生产者都会有这个前缀。

事实上在Stargate2.0开始提供扩展功能后,我停止了对core项目的功能迭代,所有的新功能以插件方式在一个名为SG1的项目的发布,而每个用户也可以扩展自己的插件上传到SG1中,以此期望形成一个生态。

另外在云片国际版YCloud上线之后我们又有一些新的挑战,YCloud主要服务海外客户我们的服务器并不在国内,但是我们的消息需要提交回国内节点消费,而且MQ都部署在国内,从香港节点到国内节点的延时让人无法接受,因为这个延时是用户能感知到的,所以我的目标是把这个延时交给消费者来承担。

如果简单的在部署一套集群或许实施起来是最快的,但是这样成本非常大,而且消费者1和消费者2的负载通常是不均衡的,如果有了第三的机房,难道每个消费者都要部署3套?

所以我们的方向是在香港节点部署一个broker,重写客户端的队列选择器,让生产者找出离自己最近的broker中的队列,而消费者消费所有队列。

其实这里对应消费者也是一样的,我们可以通过改变消费者的客户端负载均衡器来消费指定的队列,这样我们就能实现一个隔离的环境。

在后续的发展中我们将消费者使用同样的方式去指定消费指定的broker上的队列,于是就形成了图中这样的隔离的环境,这样做的好处是:

  • 多个环境隔离正常消息不互通,达到隔离的目标

  • 多个环境的broker仍然是一体的,如果一个消费者出现故障,另一个消费者可以代替

如果发现消费者1出现异常的话,可以临时让消费者2代替消费者1的工作保证功能正常,但是目前来说这部分功能仍然无法实现,应为我们需求有一个指挥中心告诉消费者2去消费环境1的消息。

为了实现生产者消费者之间的协作,我们需要一个指挥中心,去收集和协调全部Stargate应用的工作。

StargateCommand会充当一个指挥中心,但是它只是一个协调机制,如果没有它Stargate应用仍然会按照其原型设定的方式去运行。

云片RocketMQ实战:Stargate的前世今生的更多相关文章

  1. RocketMQ实战快速入门

    转自:https://www.jianshu.com/p/824066d70da8 一.RocketMQ 是什么      Github 上关于 RocketMQ 的介绍:RcoketMQ 是一款低延 ...

  2. RocketMQ实战:生产环境中,autoCreateTopicEnable为什么不能设置为true

    1.现象 很多网友会问,为什么明明集群中有多台Broker服务器,autoCreateTopicEnable设置为true,表示开启Topic自动创建,但新创建的Topic的路由信息只包含在其中一台B ...

  3. RocketMQ 实战之快速入门

    原文地址:https://www.jianshu.com/p/824066d70da8 最近 RocketMQ 刚刚上生产环境,闲暇之时在这里做一些分享,主要目的是让初学者能快速上手RocketMQ. ...

  4. RocketMQ系列实战

    RocketMQ实战(一)RocketMQ实战(二)RocketMQ实战(三):分布式事务RocketMQ实战(四)

  5. RocketMQ ACL使用指南

    目录 1.什么是ACL? 2.ACL基本流程图 3.如何配置ACL 3.1 acl配置文件 3.2 RocketMQ ACL权限可选值 3.3.权限验证流程 4.使用示例 4.1 Broker端安装 ...

  6. RocketMQ吐血总结

    RocketMQ吐血总结 架构 概念模型 最基本的概念模型与扩展后段概念模型 存储模型 RocketMQ吐血总结 User Guide RocketMQ是一款分布式消息中间件,最初是由阿里巴巴消息中间 ...

  7. RocketMQ扫盲篇

    本篇博客主要参考: <浅入浅出>-RocketMQ 敖丙 APACHE-RocketMQ Gitee RocketMQ官方文档 RocketMQ 实战与进阶 GitChat 又是好久没有写 ...

  8. 面渣逆袭:RocketMQ二十三问

    基础 1.为什么要使用消息队列呢? 消息队列主要有三大用途,我们拿一个电商系统的下单举例: 解耦:引入消息队列之前,下单完成之后,需要订单服务去调用库存服务减库存,调用营销服务加营销数据--引入消息队 ...

  9. 精华!一张图进阶 RocketMQ

    前 言 大家好,我是三此君,一个在自我救赎之路上的非典型程序员. "一张图"系列旨在通过"一张图"系统性的解析一个板块的知识点: 三此君向来不喜欢零零散散的知识 ...

随机推荐

  1. Bzoj 3170[Tjoi 2013]松鼠聚会 曼哈顿距离与切比雪夫距离

    3170: [Tjoi 2013]松鼠聚会 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1318  Solved: 664[Submit][Stat ...

  2. Socket网络编程系列教程序

    C语言的用途相当多,可以用在数据结构.数据库.网络.嵌入式等方面,历经40多年不衰,真是厉害!最近一直想从某一应用方面写一个系列教程,好好地把某一方面讲深讲透.         正好博主对网络方面的编 ...

  3. Java 内存映射文件

    import java.io.*; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import jav ...

  4. o2优化(手动)

    #pragma GCC optimize(2) 将这句话放到程序开头即可

  5. 一个测试文件与源文件位于不同模块时Jacoco覆盖率配置的例子

    问题描述: 我们有个多模块项目,由于种种原因(更常见的可能是需要集成测试)测试文件和源文件不在一个模块,Jacoco的覆盖率无法正确显示,查询了一些资料,发现中文的例子比较少,就把我自己的Demo贴一 ...

  6. mysql之char、varchar、text对比

    mysql5.0.3以后,n都表示字符数(varchar(n)) 检索效率 char > varchar > text 当varchar长度超过255之后,跟text一致,但是设置varc ...

  7. Python基础之str常用方法、for循环

    初学python,有些地方可能还不够明白,希望各位看官发现我的错误后留言指正! 一.字符串的索引与切片 注:字符串的第一位的索引值是0 1.索引案例 s = 'abcd' s1 = s[0] prin ...

  8. flex布局知识总结

    flex-direction:决定主轴的排列方向flex-wrap:项目都排列在一条轴线上,若排不下,如何换行flex-flow=flex-direction+flex-wrap align-item ...

  9. google、谷歌浏览截图

    对于前端好用的浏览器---谷歌浏览器(没有插件)截取全屏很难受! 特备是前端,想截图下来,好好的量一下容器之前的尺寸(手动恼火) 对于程序员来说不一定需要插件,有很多大佬应该都知道, 小白记忆不好,每 ...

  10. django第二次 (转自刘江)

    除了我们前面说过的普通类型字段,Django还定义了一组关系类型字段,用来表示模型与模型之间的关系. 一.多对一(ForeignKey) 多对一的关系,通常被称为外键.外键字段类的定义如下: clas ...