屏蔽底层消息中间件的差异,降低切换版本,统一消息的编程模型
通过定义绑定器Binder作为中间件,实现了应用程序与消息中间件细节之间的隔离
通过向应用程序暴露统一的Channel通道,使得应用程序不需要再考虑各种不同消息中间件实现
遵循发布-订阅模式,通过主题进行广播
一句话就是编码过程中看不到特定的某一个消息中间件代码的身影
1.Message 生产者/消费者之间靠消息媒介传递消息内容
2.MessageChannel 消息必须走特定的通道
3.MessageChannel的子接口SubscribableChannel,由MessageHandler消息处理器订阅(消息通道的消息如何被消费,谁负责收发处理)
Binder:很方便的连接中间件,屏蔽差异
Channel:是队列的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过对Channel对队列进行配置
Source和Sink:相当于Stream自身,从Stream发布消息就是输出,接收消息就是输入
1.下载RocketMQ压缩包
下载地址:
https://dlcdn.apache.org/rocketmq/4.9.4/rocketmq-all-4.9.4-bin-release.zip
这里的下载地址是最新的,我自己用的rocketmq-4.9.2
解压后给rocketmq操作权限
chmod -R 777 rocketmq-4.9.2
启动服务之前要配置一些东西 默认给程序分配的内存太大 跑不起来 要手动分配的小一点
修改bin目录下的runserver.sh和runbroker.sh
runserver.sh中的
71 JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m"
runbroker.sh中的
67 JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m"
修改conf目录下broker.conf
23 brokerIP1 = xxx.xxx.xxx.xxx(服务器公网地址) //声明broker1 IP地址
24 autoCreateTopicEnable = true //自动创建主题
后台启动rocketmq
所有命令都是先cd进rocketmq后再输的
还有就是如果是云服务器的话要开放安全组端口为rocketmq服务9876、10911、10909、10912四个
先启动namesrv
nohup sh ./bin/mqnamesrv -n 服务器公网地址:9876 & //后台启动namesrv
可以查看日志,查看日志信息是否启动成功
tail -f /root/logs/rocketmqlogs/namesrv.log
再启动mqbroker
nohup sh ./bin/mqbroker -n 服务器公网地址:9876 -c ./conf/broker.conf & //把broker注册给namesrv 指定配置文件位置
tail -f /root/logs/rocketmqlogs/broker.log
创建主题topic
sh ./bin/mqadmin updateTopic -n 服务器公网地址:9876 -c DefaultCluster -t Test1 //不能重复创建主题
注:以上步骤操作失误,可以杀掉rocketmq重新启动
# 1.关闭NameServer
sh ./bin/mqshutdown namesrv
# 2.关闭Broker
sh ./bin/mqshutdown broker
rocketmq环境配置好之后进入编码环节:
本次使用服务注册中心为nacos
1.创建父工程project
父工程pom
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<maven.compiler.source>1.8maven.compiler.source>
<maven.compiler.target>1.8maven.compiler.target>
<junit.version>4.12junit.version>
<lombok.version>1.18.10lombok.version>
<log4j.version>1.2.17log4j.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>2.2.2.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Hoxton.SR1version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2.1.0.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>${junit.version}version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>${log4j.version}version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>${lombok.version}version>
dependency>
dependencies>
dependencyManagement>
<build>
<finalName>cloud2020finalName>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<fork>truefork>
<addResources>trueaddResources>
configuration>
plugin>
plugins>
build>
2.创建三个子模块
分别是消息生产者8801、消息消费者8802、8803
建module:cloud-stream-rocketmq-provider8801
改pom
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-stream-rocketmqartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
yml:
server:
port: 8801
#rocketmq
spring:
application:
name: cloud-stream-provider
cloud:
stream:
bindings:
output: #生产消息
destination: Test1
rocketmq:
binder:
name-server: 服务器公网地址:9876
#nacos
nacos:
discovery:
server-addr: localhost:8848 #配置nacos地址
management:
endpoints:
web:
xposure:
include: '*'
主启动类:
@EnableDiscoveryClient
@SpringBootApplication
public class StreamMQMain8801 {
public static void main(String[] args) {
SpringApplication.run(StreamMQMain8801.class,args);
}
}
业务类:
1.发送消息接口
package com.hz.springcloud.service;
public interface IMessageProvider {
public String send();
}
2.接口实现类
package com.hz.springcloud.service.impl;
import cn.hutool.core.lang.UUID;
import com.hz.springcloud.service.IMessageProvider;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.common.message.MessageConst;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageBuilder;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
@EnableBinding(Source.class)
@Slf4j
public class MessageProviderImpl implements IMessageProvider {
@Resource
private MessageChannel output;
@Override
public String send() {
String serial = UUID.randomUUID().toString();
Map<String,Object> headers = new HashMap<>();
headers.put(MessageConst.PROPERTY_TAGS,"testTag");
MessageHeaders messageHeaders = new MessageHeaders(headers);
Message<String> message = MessageBuilder.createMessage(serial, messageHeaders);
boolean b = output.send(message);
if (b){
log.info(" ----- 消息发送成功: " + serial);
}else{
log.info(" ----- 消息发送失败: ");
}
return "200 OK";
}
}
3.controller
package com.hz.springcloud.contrroller;
import com.hz.springcloud.service.IMessageProvider;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
public class SendMessageController {
@Resource
private IMessageProvider messageProvider;
@GetMapping(value = "/sendMessage")
public String sendMessage(){
return messageProvider.send();
}
}
测试:
1.启动8801项目确认服务已经注册进nacos注册中心
2.访问 http://localhost:8801/sendMessage
3.查看控制台
建module:cloud-stream-rocketmq-consumer8802
改pom
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-stream-rocketmqartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
yml:
server:
port: 8802
#rocketmq
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
bindings:
input: #消费消息
destination: Test1
group: TestGroup
rocketmq:
binder:
name-server: 服务器公网地址:9876
#nacos
nacos:
discovery:
server-addr: localhost:8848 #配置nacos地址
service-url:
nacos-user-service: http://cloud-stream-provider
主启动类:
@EnableDiscoveryClient
@SpringBootApplication
public class StreamMQMain8802 {
public static void main(String[] args) {
SpringApplication.run(StreamMQMain8802.class,args);
}
}
由于是消费者,所以没有业务类
测试:
1.启动8801,8802项目
2.访问 http://localhost:8801/sendMessage
3.8802接收消息
再创建消费者子模块
cloud-stream-rocketmq-consumer8803
用来测试消息重复消费以及消息持久化
的问题
由于和8802一样,只有yml不同,故只展示yml
server:
port: 8803
#rocketmq
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
bindings:
input: #消费者
destination: Test1
group: TestGroup1 #这里和8802不同 为了展示不同组消息重复消费
rocketmq:
binder:
name-server: 服务器公网地址:9876
#nacos
nacos:
discovery:
server-addr: localhost:8848 #配置nacos地址
service-url:
nacos-user-service: http://cloud-stream-provider
测试:
发了五条消息8802,8803服务都消费了
如何避免重复消费?
微服务应用放置于同一个group中,就能够保证消息只会被其中一个应用消费一次。(轮询)同一个组内会发生竞争关系,只有其中一个可以消费。避免了重复消费
8802、8803两个服务的group组名都声明成一样的
发了五条消息,8802消费了4条、8803消费了1条,成功避免重复消费消息
消息只会在有分组属性的服务上被消费,而没有声明分组的服务不会消费发送的消息
测试:
1.停掉8802、8803两个服务
2.去掉8802服务的group属性,8803group属性还在
3.8801先发送五条消息
4.先启动8802服务,无分组属性配置,后台无打印消费信息,并报错
5.再启动8803服务,有分组属性配置,后台有打印消息,消息被8803消费(消息持久化)
此次整合仅仅是一个小demo,真正的实际开发中这样是远远不够的,还需要结合具体业务操作。还有广播消费、顺序消费、延时消息、过滤消息、事务消息等等…