一、Stream(消息驱动)
消息驱动概述:
MQ(消息中间件):ActiveMQ、RabbitMQ、RocketMQ、kafkaMQ
解决问题:多种MQ适配(切换、维护、开发)
屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型。
官方连接:https://spring.io/projects/spring-cloud-stream
- 官方定义 Spring Cloud Stream 是一个构建消息驱动微服务的框架。
- 应用程序通过 inputs 或者 outputs 来与 Spring Cloud Stream 中binder 交互。
- 通过我们配置来 binding ,而 Spring Cloud Stream 的 binder 负责与消息中间件交互。所以,我们只需要搞清楚如何与 Spring Cloud Stream 交互就可以方便使用消息驱动的方式。
- 通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动。
- Spring Cloud Stream 为一些供应商的消息中间件产品提供了个性化的自动化配置实现,引用了发布-订阅、消费组、分区的三个核心概念。
- 目前仅支持RabbitMQ、Kafka。
中文操作手册:https://m.wang1314.com/doc/webapp/topic/20971999.html
标准流程套路:Binder、Channel、Source和Sink
编码API和常用注解:
案例说明:
RabbitMQ环境OK
新建三个模块:
- cloud-stream-rabbitmq-provider8801,作为生产者进行发消息模块
- cloud-stream-rabbitmq-consumer8802,作为消息接收模块
- cloud-stream-rabbitmq-consumer8803,作为消息接收模块
消息驱动生产者:
pom
<!-- stream rabbit -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
yml
server:
port: 8801
spring:
application:
name: cloud-stream-provider
cloud:
stream:
binders: #配置要绑定的rabbitmq的服务信息
defaultRabbit: #定义的名称,用于binding整合
type: rabbit #消息组件类型
environment: #设置rabbitmq的相关环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: #服务的整合处理
output: #一个通道名称
destination: studyExchange #表示要使用Exchange名称定义
content-type: application/json #设置消息类型,本次为json,文本为“text/plain”
binder: defaultRabbit #设置要绑定的消息服务的具体设置
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2 #心跳时间间隔
lease-expiration-duration-in-seconds: 5 #如果现在超过了5秒的间隔
instance-id: send-8801.com #在信息列表显示主机名称
prefer-ip-address: true #访问路径变为IP地址
主启动类StreamMQMain8801
package com.jiao.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author jyl
* @create 2021-3-15
*/
@SpringBootApplication
public class StreamMQMain8801 {
public static void main(String[] args) {
SpringApplication.run(StreamMQMain8801.class,args);
}
}
业务类
- 发送消息接口(IMessageProvider)
package com.jiao.springcloud.service;
public interface IMessageProvider {
public String send();
}
- 发送消息接口实现类(IMessageProviderImpl)
package com.jiao.springcloud.service.impl;
import com.jiao.springcloud.service.IMessageProvider;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.MessageChannel;
import javax.annotation.Resource;
import java.util.UUID;
@EnableBinding(Source.class) //定义消息的推送管道
public class IMessageProviderImpl implements IMessageProvider {
@Resource
private MessageChannel output; //消息发送管道
public String send() {
String serial = UUID.randomUUID().toString();
output.send(MessageBuilder.withPayload(serial).build());
System.out.println("*********serial:"+serial);
return null;
}
}
- Controller(SendMessageController)
package com.jiao.springcloud.controller;
import com.jiao.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();
}
}
测试(启动7001、启动RabbitMQ、启动8801)
http://localhost:8801/sendMessage
消息驱动消费者:
pom
<!-- stream rabbit -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
yml
主启动
package com.jiao.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author jyl
* @create 2021-3-15
*/
@SpringBootApplication
public class StreamMQMain8802 {
public static void main(String[] args) {
SpringApplication.run(StreamMQMain8802.class,args);
}
}
业务类
package com.jiao.springcloud.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;
@Component
@EnableBinding(Sink.class)
public class ReceiveMessageListenerController {
@Value("${server.port}")
private String serverPort;
@StreamListener(Sink.INPUT)
public void input(Message<String> message){
System.out.println("消费者1号,----->接收到的消息:"+message.getPayload()+"\t port:"+serverPort);
}
}
测试(7001、RabbitMQ、8801、8802)
测试8801发送8802接收消息:http://localhost:8801/sendMessage
分组消费与持久化:
8803与8802一样,yml稍作修改。
启动(RabbitMQ、7001服务注册、8801消息生产、8802消息消费、8803消息消费)
运行后两个问题:(分组特性解决)
-
有重复消费问题
8801请求sendMessage,8802和8803都消费了。
解决:分组和持久化属性group(不同组可以全面消费,同一组会发生竞争关系,其中只有一个可以消费。)
8802/8803变成不同的组,group两个不同。group:(jiaoA,jiaoB)
8802修改yml:
8803修改yml:与上位置同
消费了两次。
8802/8803实现轮训分组,每次只有一个消费者,8801模块的发的消息只能被8802或者8803其中一个接收到,这样避免了重复消费。
8802/8803变成不同的组,group两个不同。group:(jiaoA,jiaoA)
结果:8801发送两次,8802消费一次,8803消费一次
- 消息持久化问题
- 停止8802/8803并且去掉8802分组的group:jiaoA,
- 8801先发送4条消息到RabbitMQ
- 8802启动,消息丢失。8803启动,消息消费,没有丢失。
二、Sleuth(分布式请求链路跟踪)
概述:
链路中任何一环出现延时或者错误都会引起整个请求最后失败。(提供一套完整的服务跟踪的解决方案,兼容支持了zipkin。)
搭建链路监控步骤:
- zipkin
H版后不用安装Zipkin Server,直接调用jar包。
下载地址:http://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/(zipkin-server-2.12.9-exec.jar)
在下载路径启动cmd窗口:输入 java -jar zipkin-server-2.12.9-exec.jar
后台启动:
运行控制台:http://localhost:9411/zipkin
Trace:类似于树结构的Span集合,表示一条调用链路,存在唯一标识。
span:表示调用链路来源,通俗讲span就是一次请求信息。
- 服务提供者(修改cloud-provider-payment8001)
pom
<!-- 包含了sleuth+zipkin -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
yml
...
spring:
application:
name: cloud-payment-service
zipkin:
base-url: http://localhost:9411
sleuth:
sampler:
probability: 1 #采样率值介于0-1之间,1表示全部采集
...
业务类PaymentController
...
@GetMapping("/payment/zipkin")
public String paymentZipkin(){
return "I'm PaymentZipkin server fall back.";
}
...
- 服务消费者(调用方)(修改cloud-consumer-order80)
pom、yml与上面一样
业务类OrderController
...
@GetMapping("/consumer/payment/zipkin")
public String paymentZipkin(){
String result = restTemplate.getForObject("http://localhost:8001"+"payment/zipkin",String.class);
return result;
}
...
- 依次启动eureka7001、8001、80(80调用8001几下测试:http://localhost/consumer/payment/zipkin)
- 打开浏览器访问: