ActiveMQ
P2P,P/S
jms集成
默认存储 kahadb
P2P:点对点,
P/S : create topic 订阅者都可以收到消息
主备模式, master -> slave(高可用)使用zk协调 配置network
集群:
master1和master2是一套组合,组成一个集群。 共享一个队列
共享队列,
RabbitMQ
exchange
X是作为一个路由器,例如 把 a c 给 c1 ,d b 给c2
队列binding到路由器
如果多个c监听一个mq,将会自动负载均衡。
镜像:
shovel远程通信,远程复制: 类似于Oracle异地备份
clustor集群模式
信息从生产到消费的过程
Spring Boot整合RabbitMQ
依赖如下
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
接收方使用@RabbitListener注解,后面跟队列名(前提是有这个队列,没有会报找不队列异常)
//1.@RabbitListener(queues = "MyQue")
//2.改为 @RabbitListener(queuesToDeclare = @Queue("MyQue")) 如果没有找到则会创建该队列
//3. 如下会自动创建队列,且会绑定路由
@RabbitListener(bindings = @QueueBinding(
value = @Queue("MyQue"),
exchange = @Exchange("MyExchange")
))
public void process(String message){
System.out.println("接收到"+message);
}
// 绑定路由后设置只接收路由中key为food的消息,并创建MyQueFood队列
//
@RabbitListener(bindings = @QueueBinding(
value = @Queue("MyQueFood"),
key = "food", // 只接受路由中key为food的消息
exchange = @Exchange("MyExchangeFood")
))
//自动创建,队列与exchange绑定
public void processFood(String message){
System.out.println("MyQueFood接收到"+message);
}
发送方代码示例
@RestController
public class RabbitMQTest {
@Autowired
private AmqpTemplate amqpTemplate;
@GetMapping("/send")
public void send(){
amqpTemplate.convertAndSend("MyQue","new MMMMMM!!!");
}
@GetMapping("/sendFood")
public String sendFood() {
// 参数1 路由,参数2 key,参数3 消息
amqpTemplate.convertAndSend("MyExchangeFood","food","new MMMMMM!!!");
return "发送成功";
}
}
Spring Cloud Stream 封装
Spring Cloud Stream 就是应用与MQ的粘合剂,就是封装了MQ,目前使用的是RabbitMQ,不知道以后会不会支持其他。
代码示例:
先导依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
发送方
public interface StreamClient {
final static String INPUT = "streamInput";
final static String OUTPUT = "streamOutput";
@Input(INPUT)
public SubscribableChannel input();
//注意input和output不能绑定同一个队列,在sprint boot 2.0.0.M3之后必须绑定不同的队列。
@Output(OUTPUT)
public MessageChannel output();
}
接收方
@Slf4j
@Component
@EnableBinding(StreamClient.class)
public class StreamReceiver {
@StreamListener(StreamClient.INPUT)
public void process(Object message){
log.info("StreamListener: {}",message);
}
@StreamListener(StreamClient.OUTPUT)
public void out(Object message){
log.info("StreamListener: {}",message);
}
}
写好了这样两个Bean,注入。
@Resource
private StreamClient streamClient;
调用stream的方式
@GetMapping("/sendMessageByStream")
public void sendMsgByStream(){
streamClient.output().send(MessageBuilder.withPayload(new Date()+"now").build());
}
如果一个服务部署了多个实例的话,为了保证一条消息只被一个服务消费,可以进行以下配置
spring:
application:
name: eureka
cloud:
stream:
bindings:
MyMessage:
group: food
# 分组,防止多个实例创建多个队列,每个队列都消费消息。
# 分组之后在这个组内,多个实例监听一个队列,一条消息只会被一个实例消费
@SendTo 注解,可以在函数执行完之后,将返回值作为消息发送到另一个队列。注意:在sprint boot 2.0.0.M3之后函数必须有返回值,否则会报以下错误。
@Slf4j
@Component
@EnableBinding(StreamClient.class)
public class StreamReceiver {
@StreamListener(StreamClient.INPUT)
@SendTo(INPUT2)
// 注意,默认使用json格式,传Object形式会报错
public JSONObject process(JSONObject message){
log.info("StreamListener: input收到了{}",message.toJSONString());
return message;
}
@StreamListener(StreamClient.OUTPUT)
public void out(Object message){
log.info("StreamListener: output收到了{}",message.toString());
}
@StreamListener(INPUT2)
public void input2(Object message){
log.info("StreamListener: input2收到了{}",message.toString());
}
}
RabbitMQ+websocket实现
后端代码
public void kickOutByAccount(String account){
String destination = "/topic/layout_xx_" + account;
String websocketContent = "您的账号被管理员强制下线";
JSONObject msg = new JSONObject();
msg.put("title", "系统通知");
msg.put("content", websocketContent);
msg.put("type", "info");
try {
stompRabbitMqClient.connectAndSend(destination, msg);
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.yunzainfo.cloud.common.config;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.messaging.converter.StringMessageConverter;
import org.springframework.messaging.simp.stomp.StompHeaders;
import org.springframework.web.socket.WebSocketHttpHeaders;
import org.springframework.web.socket.client.WebSocketClient;
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
import org.springframework.web.socket.messaging.WebSocketStompClient;
import org.springframework.web.socket.sockjs.client.Transport;
import org.springframework.web.socket.sockjs.client.WebSocketTransport;
public class StompRabbitMqClient {
private static final String URL_TEMPLATE = "http://%s:%s/stomp";
@Value("${rabbit.stomp.login:guest}")
private String login;
@Value("${rabbit.stomp.password:guest}")
private String passCode;
@Value("${rabbit.stomp.url}")
private String url;
public StompRabbitMqClient() {
}
public <T> void connectAndSend(String dest, T toSend) throws ExecutionException, InterruptedException {
WebSocketClient client = new StandardWebSocketClient();
List<Transport> transports = new ArrayList(1);
transports.add(new WebSocketTransport(client));
WebSocketStompClient stompClient = new WebSocketStompClient(client);
stompClient.setMessageConverter(new StringMessageConverter());
CustomStompSessionHandler sessionHandler = new CustomStompSessionHandler(dest, toSend);
WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
headers.setSecWebSocketProtocol("13");
StompHeaders sHeaders = new StompHeaders();
sHeaders.add("login", this.login);
sHeaders.add("passcode", this.passCode);
stompClient.connect(this.url, headers, sHeaders, sessionHandler, new Object[0]);
}
}
stomp配置
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketStompConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws/stomp").setAllowedOrigins("*").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
//订阅广播 Broker(消息代理)名称(前缀)
// Enables a simple in-memory broker
registry.enableSimpleBroker("/topic");
//全局使用的订阅前缀(客户端订阅路径上会体现出来)
registry.setApplicationDestinationPrefixes("/app/");
//点对点使用的订阅前缀(客户端订阅路径上会体现出来),不设置的话,默认也是/user/
registry.setUserDestinationPrefix("/user/");
}
}
前段代码:Angular
const baseClient: BaseClient = {
gateway: `${environment.gateway}`,
ignores: [`${environment.gateway}/cas-proxy/app/validate_full?callback=${window.location.href}`],
stomp: {
brokerURL: `${environment.stomp_server_url}`,
connectHeaders: {login: 'guest', passcode: 'guest'},
heartbeatIncoming: 5,
heartbeatOutgoing: 20000,
reconnectDelay: 200
},
layout: {
show_sider: true,
show_header: true,
},
systemcode: 'message-center-3',
type: BaseClientType.CAS_SYSTEM,
dev: false
};
<nz-modal nzTitle="系统通知" nzContent="您的账号被管理员强制下线。"
[(nzVisible)]="visible"
(nzOnCancel)="logOut()"
(nzOnOk)="logOut()"></nz-modal>
ngOnInit() {
this.getUserInfo().subscribe(res => {
this.account = res.data.account;
console.log('获取账号成功'+this.account);
this.rxStompService.watch('/topic/layout_xx_'+this.account).subscribe(msg =>{
this.visible = true;
this.msgList.push(msg.body);
this.logOut();
},error1 => {
console.log('stomp error');
this.logOut();
});
},error1 => {
console.log('error auth');
});
}
Kafka
topic
空中接力,在内存中操作,不走硬盘,所以性能强,但数据容易丢失,适合做推荐,数据分析之类的对数据完整性要求不太严格的功能。
RocketMQ
能承受阿里双十一考验的MQ,分布式事务(两阶段提交)