基础篇
消息中间件概述
消息中间件和消息队列是同一个概念的两种名称,是分布式系统中的一个重要组件。
举个例子:比如现在有一个商城系统使用的是微服务架构,其中有订单服务、库存服务、物流服务、积分服务。业务场景是如果下单了一个商品需要调用库存服务减库存、需要调用物流服务发送商品、需要调用积分服务添加响应的积分。这中间牵扯到了服务和服务之间的调用问题,为了解决这两种问题有两种方案:
- 通过RPC框架(也就是http请求)
- 通过消息中间件
消息中间件就是生产者只需要将需要处理的消息丢进来即可不需要等到消息执行完成的回调,然后其他服务再去监听消息,有消息来了就执行消息
消息中间件的应用场景
-
异步解耦
比如上文说到的例子,一个请求如果使用RPC框架同步处理的话是需要400ms的,如果是使用消息中间件异步处理这些消息的话只需要110ms。
还有一种情况是,加入此时积分服务挂掉了,那么这个订单的请求就不能完成,可能造成订单下了但是积分没有加上去的bug、要么就是订单下不成功,加入使用的是消息中间件,即使积分服务挂了,但是消息成功加入到了消息队列,这次加积分的消息就一直没有被消费掉,等到积分服务重启的时候会再去消息队列中读取消息并处理对应的积分业务。
-
削峰填谷
如上图所示用户的请求是根据时间段的不同有所不同的,但是服务器的处理事务的能力限于配置的问题肯定也是有限的比如上图所示的1w/qps,如果用户的请求处于1w/qps一下还好说服务器都能处理的过来,如果超过1w/qps服务器就没办法处理,此时处理不掉的请求可能就会直接丢掉,但是如果引入消息中间件,用户发过来的请求不要直接给系统让系统处理,而是添加到消息中间件,那么如果用户请求超过1w/qps之后多余的消息就会丢到MQ中,等待被处理,当用户请求降下来了此时系统再赶紧处理MQ中还没处理的消息,这样处理的结果是用户端可能慢一点收到响应但是不至于报错。所以此时消息中间件就提到了削峰填谷的作用
-
消息分发
如上图所示:当前有个系统有个商家端还有三个用户端,商家端发布一个商品需要在三个用户端上显示,如果此时商家修改一个商品的价格,三个系统都需要同步修改对应的价格。此时就可以使用消息中间件,商家端修改了价格只需要往MQ中丢三条消息(包含商品的最终价格),然后三个分系统分别监听,拿到消息时候在修改对应的价格(应为消息中就有对应的价格所以这里还不需要访问数据库)
常见的一些消息中间件
- ActiveMQ
- RabbitMQ
- KafKa
- RocketMQ
消息中间件对比
RocketMQ的核心组件
- 运行模型
- Producer
负责生产消息,生产完消息会丢到MQ中 - Comsumer
负责消费消息,从MQ中读取对应的消息并执行相应的任务 - Message
MQ中的最小单位,也就是Producer需要传输的内容载体 - Queue
生产者发送的消息会放到队列中,然后消费者读取队列中的消息并执行对应任务 - Topic 表示一类消息的集合,每个主题包含若干个队列,队列中又包含着若干个消息,每条消息只能属于一个主题,是RocketMQ进行消息订阅的基本单位。
- Broker
就是一个代理服务器,消息中转角色,负责存储消息、转发消息。代理服务器在RocketMQ系统中负责接收从生产者发送来的消息并存储、同时为消费者的拉取请求作准备。代理服务器也存储消息相关的元数据,包括消费者组、消费进度偏移和主题和队列消息等。 - NameServer
名称服务充当路由消息的提供者。生产者或消费者能够通过名字服务查找各主题相应的Broker IP列表。多个NameServer实例组成集群,但相互独立,没有信息交换。
单机环境安装
这里采取的是二进制文件安装(安装的是4.7.0版本)
- 下载二进制文件 wget archive.apache.org/dist/rocket…
- 解压 unzip rocketmq-all-4.7.0-bin-release.zip
- 移动文件夹 mv rocketmq-all-4.7.0-bin-release rocketmq-4.7
- 修改配置参数
为了保证RocketMQ可以正常启动, 默认情况会使用比较大的内存, 建议给NameServer和Broker设置1G的内存。修改runbroker.sh和runserver.sh脚本如下图: - 启动NameServer:nohup bin/mqnamesrv &
- 启动Broker:nohup bin/mqbroker -n 127.0.0.1:9876 &
- 查看状态 jdk命令 jps
- 关闭服务 bin/mqshutdown broker 、bin/mqshutdown nameserver
管理控制台的安装:
- 下载项目 git clone gitee.com/heshengjun/…
- 进入到管理控制台项目 cd rocketmq-externals/rocketmq-console
- 编译项目:mvn package -Dmaven.test.skip=true
- 启动目录创建配置文件application.properties(配置端口和nameserver地址)
server.port=9999
rocketmq.config.namesrvAddr=127.0.0.1:9876 - 启动控制台:nohup java -jar rocketmq-console-ng-1.0.1.jar &
基本使用
-
入门案例(同步发送消息)
-
生产者
- 创建生产者(
DefaultMQProducer
) - 指定NameServer的地址
- 启动生产者
- 创建消息对象(
Message
) - 发送消息
- 关闭应用程序
//1 创建一个生产者 DefaultMQProducer producer = new DefaultMQProducer("tudou"); //2 指定NameServer的地址 producer.setNamesrvAddr("192.168.88.130:9876"); //3 启动生产者 producer.start(); for (int i = 0; i < 100; i++) { // 4 创建消息对象 Message msg = new Message("tudou", ("hello tudou" + i).getBytes()); // 5 发送消息 SendResult result = producer.send(msg); System.out.println(result.getSendStatus()); System.out.println("result.getMsgId() = " + result.getMsgId()); } //6 关闭应用程序 producer.shutdown(); 复制代码
控制台结果:
- 创建生产者(
-
消费者
- 创建一个消费者对象
- 设置NameServer的地址
- 指定消费的主题
- 指定从哪个位置开始消费
- 指定一个监听器, 并发消费消息
- 启动消费者
//1. 创建消费者对象 DefaultMQPushConsumer defaultMQPushConsumer = new DefaultMQPushConsumer("tudou"); //2. 设置NameServer地址 defaultMQPushConsumer.setNamesrvAddr("192.168.88.130:9876"); //3. 指定消费主题 defaultMQPushConsumer.subscribe("tudou","*"); //4. 冲那个位置开始消费 defaultMQPushConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); //5. 指定一个监听器,并发消费消息 defaultMQPushConsumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) { for (MessageExt msg:list) { System.out.println("msgId =" + msg.getMsgId() + "---
-