异步
对于一些比较耗时的操作.可以将他们 以 异步的方式实现. 完成后在通知调用者
Spring实现的异步方法
除了这三种.还有一种基于MQ消息队列的方式实现异步
这里的异步.可以使Service层实现异步. 也可以是Controller层实现异步
MQ架构
MQ适用场景
1. 异步处理
2. 流量消峰填谷
3. 解耦微服务
MQ的选择
搭建MQ
模式
1. 单Master模式
2. 多Master模式
3. ... ...
RocketMQ 包含NameServer和Broker模式
搭建RocketMQ控制台
基于SpringBoot的控制台
Rocket术语概念
Rocket进阶
适用Spring编程模型进行消息驱动的微服务.可以使用各种MQ.比如:kafka,ActiveMQ
Spring 消息编程模型-生产者
1. ➕依赖
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
</dependency>
2. 写配置
rocketmq:
name-server: 127.0.0.1:9876
producer:
# 小坑,必须指定group
group: test-group
3. 适用RocketMQ
@Autowrited
private RocketMQTemplate rocketMQTemplate;
4. 发送消息到RocketMQ
rocketMQTemplate.convertAndSend(
UserAddBoundMsgDTO.builder()
.userId(share.getUserId())
.bonus(50)
.build(0);
)
总结:
想要使用 各种MQ在Spring中进行编程的话. 可以这么使用:SpringBoot整合了各种各样的MQ:
编写RocketMQ消费者
1. 实现RocketMQListener<T>:T为消息体. 生产者传递的消息体
2. 实现 onMessage(T t):
当有消息来临的时候 进行处理的逻辑
3. 在实现类上,添加@Service: 将此类交给Spring管理
添加@RocketMessageListener(consumerGroup = "test-group", topic = "add-bonus")
add-bonus: 此处的add-bonus需要和生产者的数据一致
cnsumerGroup: 消费者的Group.
生产者Group配置在yml文件. 消费者Group配置在注解中
分布式事务01
问题:
在Service层添加 @Transaction(rollbackFor = Exception.class)
如果发生Exception异常.数据库操作就回滚.
但是,在第四步,将数据写入缓存中发生了异常. 这时,数据库操作会回滚. 而MQ消息已经发送了. 导致用户积分
还是会增加.
导致审核没有成功,用户却增加了积分的情况.
事务消息
RocketMQ提供事务消息.
1. RocketMQ发送半消息
2. 消息存储到MQServer内. 但是不能投递. 消费者不会接触阵容调休息.
3. 生产者执行本地事务.
4. 生产者执行成果. 发送二次确认请求. 如果该请求将该请求标记为Commit. 消费者会接触到该请求
5. 如果接收到的是rollback,就将这条消息删除掉.
6. 如果长时间,半消息 为 未消费状态. 就去查询本地事务状态.
7. 生产者根据本地事务执行结果.
简单来说,就是一条消息由生产者发送.等待生产者执行本地事务. 如果本地事务执行成功的话.再消费这条消息
分布式消息状态
分布式事务编码
1. 发送半消息
新增表:
调用事务消息方法
rocketMQTemplate.sendMessageTransaction()
... ...
sendMssageTransaction:源码:
public TransactionSendResult sendMessageInTransaction(String txProducerGroup, String destination, Message<?> message, Object arg) throws MessagingException {
try {
TransactionMQProducer txProducer = this.stageMQProducer(txProducerGroup);
org.apache.rocketmq.common.message.Message rocketMsg = RocketMQUtil.convertToRocketMessage(this.objectMapper, this.charset, destination, message);
return txProducer.sendMessageInTransaction(rocketMsg, arg);
} catch (MQClientException var7) {
throw RocketMQUtil.convert(var7);
}
}
消息体: 还可以设置头部信息.例如 Header中存储TransactionID ==> UUID.randomUUID().toString();
实现RocketMQLocalTransactionListener
@Slf4j
@RocketMQTransactionListener(txProducerGroup = "tx-add-bonus-group")
public class MyRocketTransaction implements RocketMQLocalTransactionListener {
/**
* 执行本地事务
* @param message
* @param o
* @return
*/
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
return null;
}
/**
* 本地事务检查方法
* 检查本地事务是否执行成功
* @param message
* @return
*/
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
return null;
}
}
注解上的 txProducerGroup 的值,需要和 调用发送 事务型消息方法 中参数一致的值.
实现逻辑:
这里的逻辑大部分是 自己业务的逻辑. 需要调用的 RocketMQ 的API 的是,自己业务验证完毕.需要对事务型消息进行COMMIT或ROLLBACK.
这里的 RocketMQLocalTransactionState.COMMIT. 便是对 事务型消息提交. 确认,更改标记. 可被消费者消费
在catch中还有 ROLLBACK
SpringCloudStream
提供了更为通用操作MQ的方法. 用于构建消息驱动的微服务框架.
框架,Binder整合了Rocket,Rabbit各种MQ操作.
Stream编程模型
1. Destination Binder: 目标绑定器.: 与消息中间件通信的组件
2. Destination Bindings: 目标绑定
2.1 Binding是连接应用程序和消息中间件的桥梁.用于消息的消费和生产. 由binder创建
3. Message: 消息
上图:
微服务集成了 Stream. Stream的Application创建了俩个Binging. 左边的Binding连接了
RabbitMQ. 右边的Binding连接了Kafka.
左边的RabbitMQ是Input. 右边的Kafka是Output
Input:微服务接收消息.
Output:微服务发送消息
图中的细致代码:
Stream:生产者
1. ➕依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
</dependency>
2. 写注解:
@EnableBinding(Source.class)
@SpringBootApplication
@EnableFeignClients(defaultConfiguration = UserCenterFeignConfiguration.class)
public class UserCenterApplication {
public static void main(String[] args) {
SpringApplication.run(UserCenterApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
@EnableBinding(Source.class) :在启动类上添加此注解
3. 写配置:
spring:
cloud:
stream:
rocketmq:
binder:
name-server: 127.0.0.1:9876
bindings:
output:
# 用来指定topic
destination: stream-test-topic
Stream发送消息
@Autowired
private Source source;
@GetMapping("/test-stream")
public String sendStream(){
source.output().send(
MessageBuilder.
withPayload("消息体").
build()
);
return "success";
}
Stream:消费者
为用户中心整合Stream
1. ➕依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
</dependency>
2. 写注解
@EnableBinding(Sink.class)
@SpringBootApplication
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
3. 写配置
spring:
cloud:
stream:
rocketmq:
binder:
name-server: 127.0.0.1:9876
bindings:
output:
# 用来指定topic
destination: stream-test-topic
# 一定要设置,要不应用不能启动.
# 使用RocketMQ必须要设置.其他MQ可留空
group: binder-group
4. 写代码
@Slf4j
@Service
public class TestStreamConsumer {
@StreamListener(Sink.INPUT)
public void receive(String messageBody){
log.info("通过Stream收到消息了,收到的messageBody = 为:{}",messageBody);
}
}
Stream:自定义接口-生产消息
public interface MySource {
String MY_OUTPUT = "my-out-put";
@Output(MY_OUTPUT)
MessageChannel output();
}
使用:
@Autowired
private MySource mySource;
@GetMapping("/test-customer-interface")
public void testCustomerInterface(){
mySource.output().send(
MessageBuilder
.withPayload("自定义接口信息")
.build()
);
}
使用起来跟之前的Source的使用方法一致
这里在学习时,mybatis报了一个错.原因是因为: mybatis将这个接口也扫描到了.但是这个接口并不是Mybatis的配置类
修改 mybatis扫描包即可
Stream:自定义接口-消费消息
1. 创建接口
public interface MySink {
String MY_SINK = "my_input";
@Input(MY_SINK)
SubscribableChannel input();
}
自定义servier
@Slf4j
@Service
public class MyTestCustomerStream {
@StreamListener(MySink.MY_SINK)
public void receive(String messageBody){
log.info("自定义接口消费:通过stream收到了消息:messageBody = {}",messageBody);
}
}
透过现象看本质
Source,Sink Spring提供的接口.和我们写的自定义接口,MySource和MySink. 本质是一样的. 而 Processor接口是
能生产,能够消费的接口.
而yml文件的配置:其中有俩个属性和我们自定义的属性是一致的
cloud:
stream:
rocketmq:
binder:
name-server: 127.0.0.1:9876
bindings:
input:
destination: stream-test-topic
group: binder-group
my-input:
destination: stream-my-topic
group: my-group
配置中的my-input和代码中的
public interface MySink {
String MY_SINK = "my-input";
@Input(MY_SINK)
SubscribableChannel input();
}
是一致的.这是因为 Spring 使用IOC技术. 在启动类上书写 MySource.class 和 MySink.class .
会根据IOC创建 名为: my-input 和 my-output 的代理对象
消息过滤
消费者消费消息 某种条件消费.
CloudStream监控
SpringBoo-actuator
以actuator进行监控.
CloudStream异常处理
错误处理:
应用处理 系统处理
CloudStream+RocketMQ 重构生产者
// TODO 未学习
CloudStream+RocketMQ 重构消费者
// TODO 未学习