今天咱们接着 上一篇第六章 Sleuth–链路追踪 继续写 SpringCloud Alibaba全家桶 , 第七章 Rocketmq--消息驱动,废话不多说,开始了

7.1 MQ简介

7.1.1 什么是MQ

MQ(Message Queue) 是一种跨进程的通信机制,用于传递消息。通俗点说,就是一个先进先出的数据结构。

7.1.2 MQ的应用场景

7.1.2.1 异步解耦

最常见的一个场景是用户注册后,需要发送注册邮件和短信通知,以告知用户注册成功。传统的做法如下:

此架构下注册、邮件、短信三个任务全部完成后,才返回注册结果到客户端,用户才能使用账号登录。但是对于用户来说,注册功能实际只需要注册系统存储用户的账户信息后,该用户便可以登录,而后续的注册短信和邮件不是即时需要关注的步骤。

所以实际当数据写入注册系统后,注册系统就可以把其他的操作放入对应的消息队列 MQ 中然后马上返回用户结果,由消息队列 MQ 异步地进行这些操作。架构图如下:

异步解耦是消息队列 MQ 的主要特点,主要目的是减少请求响应时间和解耦。主要的使用场景就是将比较耗时而且不需要即时(同步)返回结果的操作作为消息放入消息队列。同时,由于使用了消息队列MQ,只要保证消息格式不变,消息的发送方和接收方并不需要彼此联系,也不需要受对方的影响,即解耦合。

7.1.2.2 流量削峰

流量削峰也是消息队列 MQ 的常用场景,一般在秒杀或团队抢购(高并发)活动中使用广泛。

在秒杀或团队抢购活动中,由于用户请求量较大,导致流量暴增,秒杀的应用在处理如此大量的访问流量后,下游的通知系统无法承载海量的调用量,甚至会导致系统崩溃等问题而发生漏通知的情况。为解决这些问题,可在应用和下游通知系统之间加入消息队列 MQ。

秒杀处理流程如下所述:

  1. 用户发起海量秒杀请求到秒杀业务处理系统。
  2. 秒杀处理系统按照秒杀处理逻辑将满足秒杀条件的请求发送至消息队列 MQ。
  3. 下游的通知系统订阅消息队列 MQ 的秒杀相关消息,再将秒杀成功的消息发送到相应用户。
  4. 用户收到秒杀成功的通知。

7.1.3 常见的MQ产品*

目前业界有很多MQ产品,比较出名的有下面这些:

  • ZeroMQ

    号称最快的消息队列系统,尤其针对大吞吐量的需求场景。扩展性好,开发比较灵活,采用C语言

    实现,实际上只是一个socket库的重新封装,如果做为消息队列使用,需要开发大量的代码。

    ZeroMQ仅提供非持久性的队列,也就是说如果down机,数据将会丢失。

  • RabbitMQ

    使用erlang语言开发,性能较好,适合于企业级的开发。但是不利于做二次开发和维护。

  • ActiveMQ

    历史悠久的Apache开源项目。已经在很多产品中得到应用,实现了JMS1.1规范,可以和springjms

    轻松融合,实现了多种协议,支持持久化到数据库,对队列数较多的情况支持不好。

  • RocketMQ

    阿里巴巴的MQ中间件,由java语言开发,性能非常好,能够撑住双十一的大流量,而且使用起来

    很简单。

  • Kafka

    Kafka是Apache下的一个子项目,是一个高性能跨语言分布式Publish/Subscribe消息队列系统,

    相对于ActiveMQ是一个非常轻量级的消息系统,除了性能非常好之外,还是一个工作良好的分布

    式系统。

7.2 RocketMQ入门

RocketMQ是阿里巴巴开源的分布式消息中间件,现在是Apache的一个顶级项目。在阿里内部使用

非常广泛,已经经过了"双11"这种万亿级的消息流转。

7.2.1 RocketMQ环境搭建

接下来我们先在linux平台下安装一个RocketMQ的服务

7.2.1.1 环境准备

下载RocketMQ

http://rocketmq.apache.org/release_notes/release-notes-4.4.0/

环境要求

  • Linux 64位操作系统
  • 64bit JDK 1.8+
7.2.1.2 安装RocketMQ

1 上传文件到Linux系统

[root@spiritmark rocketmq]# ls /usr/local/src/
rocketmq-all-4.4.0-bin-release.zip

2 解压到安装目录

[root@spiritmark src]# unzip rocketmq-all-4.4.0-bin-release.zip
[root@spiritmark src]# mv rocketmq-all-4.4.0-bin-release ../rocketmq
7.2.1.3 启动RocketMQ

1. 切换到安装目录

[root@spiritmark rocketmq]# ls
benchmark bin conf lib LICENSE NOTICE README.md

2 启动NameServer

[root@spiritmark rocketmq]# nohup ./bin/mqnamesrv &
[1] 1467
# 只要进程不报错,就应该是启动成功了,可以查看一下日志
[root@spiritmark rocketmq]# tail -f /root/logs/rocketmqlogs/namesrv.log

3 启动Broker

# 编辑bin/runbroker.sh 和 bin/runserver.sh文件,修改里面的
# JAVA_OPT="${JAVA_OPT} -server -Xms8g -Xmx8g -Xmn4g"
# 为JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m"
[root@spiritmark rocketmq]# nohup bin/mqbroker -n localhost:9876 &
[root@spiritmark rocketmq]# tail -f /root/logs/rocketmqlogs/broker.log
7.2.1.4 测试RocketMQ

1 测试消息发送

[root@spiritmark rocketmq]# export NAMESRV_ADDR=localhost:9876
[root@spiritmark rocketmq]# bin/tools.sh
org.apache.rocketmq.example.quickstart.Producer

2 测试消息接收

[root@spiritmark rocketmq]# export NAMESRV_ADDR=localhost:9876
[root@spiritmark rocketmq]# bin/tools.sh
org.apache.rocketmq.example.quickstart.Consumer
7.2.1.5 关闭RocketMQ
[root@heima rocketmq]# bin/mqshutdown broker
[root@heima rocketmq]# bin/mqshutdown namesrv

7.2.2 RocketMQ的架构及概念



如上图所示,整体可以分成4个角色,分别是:NameServerBrokerProducerConsumer

  • Broker(邮递员)

    Broker是RocketMQ的核心,负责消息的接收,存储,投递等功能

  • NameServer(邮局)

    消息队列的协调者,Broker向它注册路由信息,同时Producer和Consumer向其获取路由信息

  • Producer(寄件人)

    消息的生产者,需要从NameServer获取Broker信息,然后与Broker建立连接,向Broker发送消

  • Consumer(收件人)

    消息的消费者,需要从NameServer获取Broker信息,然后与Broker建立连接,从Broker获取消

  • Topic(地区)

    用来区分不同类型的消息,发送和接收消息前都需要先创建Topic,针对Topic来发送和接收消息

  • Message Queue(邮件)

为了提高性能和吞吐量,引入了Message Queue,一个Topic可以设置一个或多个Message

Queue,这样消息就可以并行往各个Message Queue发送消息,消费者也可以并行的从多个

Message Queue读取消息

  • Message

    Message 是消息的载体。
  • Producer Group

    生产者组,简单来说就是多个发送同一类消息的生产者称之为一个生产者组。
  • Consumer Group

    消费者组,消费同一类消息的多个 consumer 实例组成一个消费者组。

7.2.3 RocketMQ控制台安装

1 下载

# 在git上下载下面的工程 rocketmq-console-1.0.0
https://github.com/apache/rocketmq-externals/releases

2 修改配置文件

# 修改配置文件 rocketmq-console\src\main\resources\application.properties
#项目启动后的端口号
server.port=7777
#nameserv的地址,注意防火墙要开启 9876端口
rocketmq.config.namesrvAddr=192.168.109.131:9876

3 打成jar包,并启动

# 进入控制台项目,将工程打成jar包
mvn clean package -Dmaven.test.skip=true
# 启动控制台
java -jar target/rocketmq-console-ng-1.0.0.jar

4 访问控制台

7.3 消息发送和接收演示


接下来我们使用Java代码来演示消息的发送和接收

   <dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.0.2</version>
</dependency>

7.3.1 发送消息

消息发送步骤:

  1. 创建消息生产者, 指定生产者所属的组名
  2. 指定Nameserver地址
  3. 启动生产者
  4. 创建消息对象,指定主题、标签和消息体
  5. 发送消息
  6. 关闭生产者
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.spring.core.RocketMQTemplate; public class RocketMQSendMessageTest { //发送消息
public static void main(String[] args) throws Exception {
//1.创建消息生产者,并且设置生产组名
DefaultMQProducer producer = new DefaultMQProducer("myproducer-group"); //2 为生产者设置NameServer的地址
producer.setNamesrvAddr("192.168.109.131:9876"); //3 启动生产者
producer.start(); //4 构建消息对象,主要是设置消息的主题 标签 内容
Message message = new Message("myTopic", "myTag", ("Test RocketMQ Message").getBytes()); //5 发送消息 第二个参数代表超时时间
SendResult result = producer.send(message, 10000);
System.out.println(result); //6 关闭生产者
producer.shutdown(); }
}

7.3.2 接收消息

消息接收步骤:

  1. 创建消息消费者, 指定消费者所属的组名
  2. 指定Nameserver地址
  3. 指定消费者订阅的主题和标签
  4. 设置回调函数,编写处理消息的方法
  5. 启动消息消费者

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt; import java.util.List; public class RocketMQReceiveMessageTest { //接收消息
public static void main(String[] args) throws Exception { //1 创建消费者,并且为其指定消费者组名
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("myconsumer-group"); //2 为消费者设置NameServer的地址
consumer.setNamesrvAddr("192.168.109.131:9876"); //3 指定消费者订阅的主题和标签
consumer.subscribe("myTopic", "*"); //4 设置一个回调函数,并在函数中编写接收到消息之后的处理方法
consumer.registerMessageListener(new MessageListenerConcurrently() {
//处理获取到的消息
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
//消费逻辑
System.out.println("Message===>" + list); //返回消费成功状态
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
}); //5 启动消费者
consumer.start();
System.out.println("启动消费者成功了");
}
}

7.4 案例

接下来我们模拟一种场景: 下单成功之后,向下单用户发送短信。设计图如下:

7.4.1 订单微服务发送消息

1 在shop-order 中添加rocketmq的依赖

    <dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.4.0</version>
</dependency>

2 添加配置

#rocketmq
rocketmq:
name-server: 192.168.109.131:9876 #rocketMQ服务的地址
producer:
group: shop-order # 生产者组

3 编写测试代码

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; @RestController
@Slf4j
public class OrderController2 { @Autowired
private OrderService orderService; @Autowired
private ProductService productService; //下单
@RequestMapping("/order/prod/{pid}")
public Order order(@PathVariable("pid") Integer pid) {
log.info("接收到{}号商品的下单请求,接下来调用商品微服务查询此商品信息", pid); //调用商品微服务,查询商品信息
Product product = productService.findByPid(pid); //模拟调用商品微服务需要2s的时间
try {
Thread.sleep(2000l);
} catch (InterruptedException e) {
e.printStackTrace();
} log.info("查询到{}号商品的信息,内容是:{}", pid, JSON.toJSONString(product)); //下单(创建订单)
Order order = new Order();
order.setUid(1);
order.setUsername("测试用户");
order.setPid(pid);
order.setPname(product.getPname());
order.setPprice(product.getPprice());
order.setNumber(1); //为了不产生大量的额垃圾数据,暂时不保存订单入库
//orderService.createOrder(order); log.info("创建订单成功,订单信息为{}", JSON.toJSONString(order));
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return order;
} //测试高并发
@RequestMapping("/order/message")
public String message() {
return "测试高并发";
}
}

7.4.2 用户微服务订阅消息

1 修改shop-user 模块配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud-alibaba</artifactId>
<groupId>com.spiritmark</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion> <artifactId>shop-user</artifactId> <dependencies>
<!--springboot-web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--shop-common-->
<dependency>
<groupId>com.spiritmark</groupId>
<artifactId>shop-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.4.0</version>
</dependency> </dependencies> </project>

2 修改主类

   @SpringBootApplication
@EnableDiscoveryClient
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}

3 修改配置文件

server:
port: 8071
spring:
application:
name: service-user
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql:///shop?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: root
jpa:
properties:
hibernate:
hbm2ddl:
auto: update
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
#rocketmq
rocketmq:
name-server: 192.168.109.131:9876

4 编写消息接收服务


@Slf4j
@Service("shopSmsService")
//consumerGroup-消费者组名 topic-要消费的主题
@RocketMQMessageListener(
consumerGroup = "shop-user", //消费者组名
topic = "order-topic",//消费主题
consumeMode = ConsumeMode.CONCURRENTLY,//消费模式,指定是否顺序消费 CONCURRENTLY(同步,默认) ORDERLY(顺序)
messageModel = MessageModel.CLUSTERING//消息模式 BROADCASTING(广播) CLUSTERING(集群,默认)
)
public class SmsService implements RocketMQListener<Order> { @Override
public void onMessage(Order order) {
log.info("收到一个订单信息{},接下来发送短信", JSON.toJSONString(order));
} } }

5 启动服务,执行下单操作,观看后台输出

7.5 发送不同类型的消息

7.5.1 普通消息

RocketMQ提供三种方式来发送普通消息:可靠同步发送、可靠异步发送和单向发送。

可靠同步发送

同步发送是指消息发送方发出数据后,会在收到接收方发回响应之后才发下一个数据包的通讯方

式。


此种方式应用场景非常广泛,例如重要通知邮件、报名短信通知、营销短信系统等。

可靠异步发送

异步发送是指发送方发出数据后,不等接收方发回响应,接着发送下个数据包的通讯方式。发送

方通过回调接口接收服务器响应,并对响应结果进行处理。

异步发送一般用于链路耗时较长,对 RT 响应时间较为敏感的业务场景,例如用户视频上传后通知

启动转码服务,转码完成后通知推送转码结果等。

单向发送

单向发送是指发送方只负责发送消息,不等待服务器回应且没有回调函数触发,即只发送请求不

等待应答。

适用于某些耗时非常短,但对可靠性要求并不高的场景,例如日志收集。

   <!--依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class)
@SpringBootTest(classes = OrderApplication.class)
public class MessageTypeTest { @Autowired
private RocketMQTemplate rocketMQTemplate; //同步消息
@Test
public void testSyncSend() {
//参数一: topic, 如果想添加tag 可以使用"topic:tag"的写法
//参数二: 消息内容
SendResult sendResult =
rocketMQTemplate.syncSend("test-topic-1", "这是一条同步消息");
System.out.println(sendResult);
} //异步消息
@Test
public void testAsyncSend() throws InterruptedException {
//参数一: topic:tag
//参数二: 消息体
//参数三: 回调
rocketMQTemplate.asyncSend("test-topic-1", "这是一条异步消息", new SendCallback() {
//成功响应的回调
@Override
public void onSuccess(SendResult result) {
System.out.println(result);
} //异常响应的回调
@Override
public void onException(Throwable throwable) {
System.out.println(throwable);
}
});
System.out.println("==================");
Thread.sleep(300000000);
} //单向消息
@Test
public void testOneWay() {
rocketMQTemplate.sendOneWay("test-topic-1", "这是一条单向消息");
}

三种发送方式的对比

发送方式 发送 TPS 发送结果反馈 可靠性
异步发送 不丢失
异步发送 不丢失
单向发送 最快 可能丢失

7.5.2 顺序消息

顺序消息是消息队列提供的一种严格按照顺序来发布和消费的消息类型。

  //同步顺序消息[异步顺序 单向顺序写法类似]
public void testSyncSendOrderly() {
//第三个参数用于队列的选择
rocketMQTemplate.syncSendOrderly("test-topic-1", "这是一条异步顺序消息",
"xxxx");
}

7.5.3 事务消息

RocketMQ提供了事务消息,通过事务消息就能达到分布式事务的最终一致。

事务消息交互流程:

两个概念:

  • 半事务消息:暂不能投递的消息,发送方已经成功地将消息发送到了RocketMQ服务端,但是服务

    端未收到生产者对该消息的二次确认,此时该消息被标记成“暂不能投递”状态,处于该种状态下的

    消息即半事务消息。

  • 消息回查:由于网络闪断、生产者应用重启等原因,导致某条事务消息的二次确认丢失,

    RocketMQ服务端通过扫描发现某条消息长期处于“半事务消息”时,需要主动向消息生产者询问该

    消息的最终状态(Commit 或是 Rollback),该询问过程即消息回查。

事务消息发送步骤:

  1. 发送方将半事务消息发送至RocketMQ服务端。
  2. RocketMQ服务端将消息持久化之后,向发送方返回Ack确认消息已经发送成功,此时消息为半事

    务消息。
  3. 发送方开始执行本地事务逻辑。
  4. 发送方根据本地事务执行结果向服务端提交二次确认(Commit 或是 Rollback),服务端收到

    Commit 状态则将半事务消息标记为可投递,订阅方最终将收到该消息;服务端收到 Rollback 状

    态则删除半事务消息,订阅方将不会接受该消息。

事务消息回查步骤:

  1. 在断网或者是应用重启的特殊情况下,上述步骤4提交的二次确认最终未到达服务端,经过固定时

    间后服务端将对该消息发起消息回查。
  2. 发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果。
  3. 发送方根据检查得到的本地事务的最终状态再次提交二次确认,服务端仍按照步骤4对半事务消息

    进行操作。
import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.Id;
import java.util.Date; //消息事物状态记录
@Entity(name = "shop_txlog")
@Data
public class TxLog {
@Id
private String txId;
private Date date;
}

//@RestController
@Slf4j
public class OrderController4 { @Autowired
private RestTemplate restTemplate; @Autowired
private OrderServiceImpl4 orderService; @Autowired
private ProductService productService; //下单--fegin
@RequestMapping("/order/prod/{pid}")
public Order order(@PathVariable("pid") Integer pid) {
log.info("接收到{}号商品的下单请求,接下来调用商品微服务查询此商品信息", pid); //调用商品微服务,查询商品信息
Product product = productService.findByPid(pid); if (product.getPid() == -100) {
Order order = new Order();
order.setOid(-100L);
order.setPname("下单失败");
return order;
} log.info("查询到{}号商品的信息,内容是:{}", pid, JSON.toJSONString(product)); //下单(创建订单)
Order order = new Order();
order.setUid(1);
order.setUsername("测试用户");
order.setPid(pid);
order.setPname(product.getPname());
order.setPprice(product.getPprice());
order.setNumber(1); orderService.createOrderBefore(order); log.info("创建订单成功,订单信息为{}", JSON.toJSONString(order)); return order;
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service; @Service
@RocketMQTransactionListener(txProducerGroup = "tx_producer_group")
public class OrderServiceImpl4Listener implements RocketMQLocalTransactionListener { @Autowired
private OrderServiceImpl4 orderServiceImpl4; @Autowired
private TxLogDao txLogDao; //执行本地事物
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) { String txId = (String) msg.getHeaders().get("txId"); try {
//本地事物
Order order = (Order) arg;
orderServiceImpl4.createOrder(txId,order); return RocketMQLocalTransactionState.COMMIT;
} catch (Exception e) {
return RocketMQLocalTransactionState.ROLLBACK;
}
} //消息回查
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
String txId = (String) msg.getHeaders().get("txId");
TxLog txLog = txLogDao.findById(txId).get(); if (txLog != null){
//本地事物(订单)成功了
return RocketMQLocalTransactionState.COMMIT;
}else {
return RocketMQLocalTransactionState.ROLLBACK;
}
}
}

7.6 消息消费要注意的细节

@Slf4j
@Service("shopSmsService")
//consumerGroup-消费者组名 topic-要消费的主题
@RocketMQMessageListener(
consumerGroup = "shop-user", //消费者组名
topic = "order-topic",//消费主题
consumeMode = ConsumeMode.CONCURRENTLY,//消费模式,指定是否顺序消费 CONCURRENTLY(同步,默认) ORDERLY(顺序)
messageModel = MessageModel.CLUSTERING//消息模式 BROADCASTING(广播) CLUSTERING(集群,默认)
)
public class SmsService implements RocketMQListener<Order> {}

RocketMQ支持两种消息模式:

  • 广播消费: 每个消费者实例都会收到消息,也就是一条消息可以被每个消费者实例处理;
  • 集群消费: 一条消息只能被一个消费者实例消费

第七章 Rocketmq--消息驱动的更多相关文章

  1. SpringCloud(七)Stream消息驱动

    Stream消息驱动 概述 屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型 官网:https://cloud.spring.io/spring-cloud-static/spring-cl ...

  2. Spring Cloud架构教程 (七)消息驱动的微服务(核心概念)【Dalston版】

    下图是官方文档中对于Spring Cloud Stream应用模型的结构图.从中我们可以看到,Spring Cloud Stream构建的应用程序与消息中间件之间是通过绑定器Binder相关联的,绑定 ...

  3. 第七章Bulk设备

    小川工作室编写,本书为LM3S的USB芯片编写,上传的均为草稿,还有没修改,可能还有很多地方不足,希望各位网友原谅! QQ:2609828265 TEL:15882446438 E-mail:paul ...

  4. 精通Web Analytics 2.0 (9) 第七章:失败更快:爆发测试与实验的能量

    精通Web Analytics 2.0 : 用户中心科学与在线统计艺术 第七章:失败更快:爆发测试与实验的能量 欢迎来到实验和测试这个棒极了的世界! 如果Web拥有一个超越所有其他渠道的巨大优势,它就 ...

  5. 第七章 LED将为我们闪烁:控制发光二极管

     第七章 LED将为我们闪烁:控制发光二极管 本章我们将会看到一个完整的linux驱动程序,通过linux驱动程序控制LED的四个小灯,通俗的说就是通过向linux驱动程序来控制LED小灯的开关.用到 ...

  6. 第七章 探秘Qt的核心机制-信号与槽

    第七章 探秘Qt的核心机制-信号与槽 注:要想使用Qt的核心机制信号与槽,就必须在类的私有数据区声明Q_OBJECT宏,然后会有moc编译器负责读取这个宏进行代码转化,从而使Qt这个特有的机制得到使用 ...

  7. Android群英传》读书笔记 (3) 第六章 Android绘图机制与处理技巧 + 第七章 Android动画机制与使用技巧

    第六章 Android绘图机制与处理技巧 1.屏幕尺寸信息屏幕大小:屏幕对角线长度,单位“寸”:分辨率:手机屏幕像素点个数,例如720x1280分辨率:PPI(Pixels Per Inch):即DP ...

  8. 第七章 DAO模式

    第七章 DAO模式 一.JDBC的封装 1.JDBC的封装: DAO位于业务逻辑和持久化数据之间,实现对持久化数据的访问.将数据库都封装起来,对外提供相应的接口 2.DAO模式的作用: 1.隔离业务逻 ...

  9. ROS机器人程序设计(原书第2版)补充资料 (柒) 第七章 3D建模与仿真 urdf Gazebo V-Rep Webots Morse

    ROS机器人程序设计(原书第2版)补充资料 (柒) 第七章 3D建模与仿真 urdf Gazebo V-Rep Webots Morse 书中,大部分出现hydro的地方,直接替换为indigo或ja ...

随机推荐

  1. 3种终极方法,彻底解决CDR不显示缩略图!

    站长所在的印刷出版行业,一般都是使用版本较低的CDR软件,以便更好的兼容出版厂,不然新版本的文件发厂出片时却打不开,而转低版本的话又容易出错.从最开始的 CorelDRAW 9 到现在的 CORELD ...

  2. jQuery 第五章 实例方法 事件

    .on() .one() .off() .trigger() .click / keydown / mouseenter ...    .hover() ----------------------- ...

  3. 【电子取证:FTK Imager篇】FTK Imager制作镜像详细介绍

    FTK Imager制作镜像详细介绍 以DD镜像制造为例,详细介绍了FTK Imager创建镜像的过程,记得大学的时候学习这些没什么教程,找到的资料也是语焉不详,故在此啰嗦一番---[suy] 一.磁 ...

  4. 【模板】【P1182】数列分段II——二分答案

    题意:给定一列数,分成m段,使每段和的最大值最小. 考虑二分最小段和size,答案显然满足单调性.可以在每次check中累加数列元素判断当前组的总和是否在size以内.由于序列元素均为非负整数,前缀和 ...

  5. Java基础教程——泛型

    泛型 Generics:泛型,愿意指"无商标的". 泛型,可以理解为"宽泛的数据类型",就是将类型由原来的具体的类型泛化. 泛型在建立对象时不指定类中属性的具体 ...

  6. Django之ModelForm实际操作使用

    1.定义model数据库字段如下: class User(models.Model): """ 员工信息表用户.密码.职位.公司名(子.总公司).手机.最后一次登录时间. ...

  7. 【GDKOI2014】JZOJ2020年8月13日提高组T2 石油储备计划

    [GDKOI2014]JZOJ2020年8月13日提高组T2 石油储备计划 题目 Description Input Output 对于每组数据,输出一个整数,表示达到"平衡"状态 ...

  8. 20201101_Python的虚拟环境问题

    虚拟环境使用总结: 1. 安装创建虚拟环境要使用的工具virtualenv  pip install virtualenv -i https://pypi.douban.com/simple/ #使用 ...

  9. 大数据-redis-redis启动出错

    redis启动出错Creating Server TCP listening socket 127.0.0.1:6379: bind: No error 解决方法(1) 首先如果你是从官方redis官 ...

  10. PyQt(Python+Qt)学习随笔:树型部件的QTreeWidgetItem项中列不同角色数据的有关访问方法

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 树型部件QTreeWidget中的QTreeWidgetItem项中可以有多列数据,每列数据可以根据 ...