1.RabbitMQ 虚拟机
1.1.先安装docker
1.2.安装rabbitmq
2.RabbitMQ 使用场景
3.rabbitmq 基本概念
3.1.rabbitmq六种工作模式
4.rabbitmq简单模式
4.1.新建一个空的工程:
4.2.新建一个maven模块:
4.2.1新建一个生产者发送消息队列的类:
4.2.2.新建一个消费者接收队列消息的类:
5.工作模式工程
6.发布订阅模式-交换机
7.路由模式
8.主题模式
9.RPC模式
10.订单的流量削峰案例
10.2为项目添加消息队列发送机制:
11.pd-web-consumer接收消息并存入数据库
12.动态配置刷新
12.1.添加依赖
12.2.yml配置rabbitmq连接
12.3.09项目中使用actuator暴露bus-refresh刷新端点
12.4.当前启动顺序:
13.服务不停机动态刷新配置
14.sleuth + zipkin 链路跟踪
14.1.添加Sleuth链路跟踪日志依赖到02,03,04,06
14.2.02,03,04,06 添加 zipkin 客户端依赖和rabbitmq配置
14.2.1. 2,3,4,6 添加 zipkin 客户端依赖
14.2.2. 06添加rabbitmq依赖和连接配置
14.2.3. 2,3,4,6 配置日志的发送方式: rabbit
15.选择正确的网卡,注册 IP 地址
1.RabbitMQ 虚拟机
1.1.先安装docker
安装docker环境
克隆 centos-8-2105: rabbitmq
设置ip:
./ip-static
ip: 192.168.64.140
ifconfig
mobaxterm 上传 docker 离线安装文件到 /root/ 目录
课前资料\devops课前资料\docker\docker-install 文件夹
按照笔记安装docker
https://wanght.blog.csdn.net/article/details/117327543
cat < /etc/docker/daemon.json
{
“registry-mirrors”: [
“https://docker.mirrors.ustc.edu.cn”,
“http://hub-mirror.c.163.com”
],
“max-concurrent-downloads”: 10,
“log-driver”: “json-file”,
“log-level”: “warn”,
“log-opts”: {
“max-size”: “10m”,
“max-file”: “3”
},
“data-root”: “/var/lib/docker”
}
EOF
cat命令: 向一个文件输出内容,进入后可以按ctrl+c退出,或者自己指定结束符(<<自己指定的结束符),最后输入自己指定的结束符,结束符以上的内容会被存储到文件中
重新加载docker配置
sudo systemctl daemon-reload
重启docker服务
sudo systemctl restart docker
第五步:查看镜像配置
docker info
检查docker下载下来的镜像
docker images
1.2.安装rabbitmq
启动 docker 容器
下载 docker 镜像
下载镜像位置:
https://hub.docker.com/_/rabbitmq?tab=tags&page=1&ordering=last_updated
带操作界面的rabbitmq
rabbitmq在线安装:
docker pull rabbitmq:management
启动运行容器:
docker run
-d
–name rabbit
-p 5672:5672
-p 15672:15672
-e RABBITMQ_DEFAULT_USER=admin
-e RABBITMQ_DEFAULT_PASS=admin
rabbitmq:management
docker ps
设置访问的用户名和密码:
-e RABBITMQ_DEFAULT_USER=admin
-e RABBITMQ_DEFAULT_PASS=admin \
设置开机启动:
docker update xxxx --restart=always
然后浏览器访问测试:
http://192.168.64.140:15672/
rabbitmq 容器不可访问:
重启网络服务
systemctl restart NetworkManager
重启 docker 系统服务
systemctl restart docker
重启容器
docker restart rabbit
2.RabbitMQ 使用场景
服务解耦
假设有这样一个场景, 服务A产生数据, 而服务B,C,D需要这些数据, 那么我们可以在A服务中直接调用B,C,D服务,把数据传递到下游服务即可
但是,随着我们的应用规模不断扩大,会有更多的服务需要A的数据,如果有几十甚至几百个下游服务,而且会不断变更,再加上还要考虑下游服务出错的情况,那么A服务中调用代码的维护会极为困难
这是由于服务之间耦合度过于紧密
再来考虑用RabbitMQ解耦的情况
A服务只需要向消息服务器发送消息,而不用考虑谁需要这些数据;下游服务如果需要数据,自行从消息服务器订阅消息,不再需要数据时则取消订阅即可
流量削峰
假设我们有一个应用,平时访问量是每秒300请求,我们用一台服务器即可轻松应对
而在高峰期,访问量瞬间翻了十倍,达到每秒3000次请求,那么单台服务器肯定无法应对,这时我们可以考虑增加到10台服务器,来分散访问压力
但如果这种瞬时高峰的情况每天只出现一次,每次只有半小时,那么我们10台服务器在多数时间都只分担每秒几十次请求,这样就有点浪费资源了
这种情况,我们就可以使用RabbitMQ来进行流量削峰,高峰情况下,瞬间出现的大量请求数据,先发送到消息队列服务器,排队等待被处理,而我们的应用,可以慢慢的从消息队列接收请求数据进行处理,这样把数据处理时间拉长,以减轻瞬时压力
这是消息队列服务器非常典型的应用场景
异步调用
考虑定外卖支付成功的情况
支付后要发送支付成功的通知,再寻找外卖小哥来进行配送,而寻找外卖小哥的过程非常耗时,尤其是高峰期,可能要等待几十秒甚至更长
这样就造成整条调用链路响应非常缓慢
而如果我们引入RabbitMQ消息队列,订单数据可以发送到消息队列服务器,那么调用链路也就可以到此结束,订单系统则可以立即得到响应,整条链路的响应时间只有200毫秒左右
寻找外卖小哥的应用可以以异步的方式从消息队列接收订单消息,再执行耗时的寻找操作
订单存储的解耦
…
3.rabbitmq 基本概念
RabbitMQ是一种消息中间件,用于处理来自客户端的异步消息。服务端将要发送的消息放入到队列池中。接收端可以根据RabbitMQ配置的转发机制接收服务端发来的消息。RabbitMQ依据指定的转发规则进行消息的转发、缓冲和持久化操作,主要用在多服务器间或单服务器的子系统间进行通信,是分布式系统标准的配置。
…
https://wanght.blog.csdn.net/article/details/102810522
3.1.rabbitmq六种工作模式
简单模式
工作模式
发布订阅模式
路由模式
主题模式
RPC模式
4.rabbitmq简单模式
4.1.新建一个空的工程:
4.2.新建一个maven模块:
添加rabbitmq依赖:
<?xml version="1.0" encoding="UTF-8"?>
4.0.0
<groupId>cn.tedu</groupId>
<artifactId>rabbitmq-api</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.4.3</version>
</dependency>
</dependencies>
4.2.1新建一个生产者发送消息队列的类:
package m1;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//1.连接服务器
ConnectionFactory f = new ConnectionFactory();
f.setHost(“192.168.64.140”);
f.setPort(5672);
f.setUsername(“admin”);
f.setPassword(“admin”);
//通信通道
Channel c = f.newConnection().createChannel();
/*2.在服务器上创建helloworld队列
如果队列在服务器上已经存在,不会重复创建
/
/
参数:
1.队列名
2.是否是持久队列,非持久队列,服务器重启或者断电,队列会消失.持久队列仍然存在
3.是否是排他队列/独占队列(如果这个队列是独占的,则只能被一个消费者占用接收
非独占可以由消费者共享,这个参数是针对消费者而言的)
4.是否是自动删除(自动删除的队列在没有消费者时,可以由服务器自动删除)
5.其他参数(键值对)
*/
c.queueDeclare(“helloworld”,false,false,false,null);
//3.发送消息 .getBytes():变数组
/*
参数:
1. 空串是默认的交换机
3. 对消息设置更多的属性参数
*/
c.basicPublish("","helloworld",null,"Hello world!".getBytes());
//4.断开连接(可选)
c.close();
System.out.println("消息已发送");
}
}
查看是否发送到RabbitMQ服务器:
点开队列消息,可以具体查看发送来了几条
4.2.2.新建一个消费者接收队列消息的类:
package m1;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//1. 连接
ConnectionFactory f = new ConnectionFactory();
f.setHost(“192.168.64.140”);
f.setPort(5672);
f.setUsername(“admin”);
f.setPassword(“admin”);
//通信通道
Channel c = f.newConnection().createChannel();
/*2.创建helloworld队列
消费者也创建同名队里的好处是:
生产者和消费者都创建同一个队列,可以不必关系他们的启动顺序,
谁先启动谁就负责创建队列
*/
c.queueDeclare(“helloworld”,false,false,false,null);
//2.5 创建 处理消息的回调对象
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
// message 是消息数据的封装对象
byte[] a = delivery.getBody();
String s1 = new String(a);
System.out.println(“接到消息:”+s1);
}
};
CancelCallback cancelCallback = new CancelCallback() {
@Override
public void handle(String s) throws IOException {
}
};
/*3.从helloworld 队列接收消息
收到消息后会把消息传递给一个回调对象进行处理
*/
// “helloworld”,true,处理消息的回调对象,取消消息的回调对象
c.basicConsume(“helloworld”,true,deliverCallback,cancelCallback);
}
}
第一次启动消费者后,生产者再发送消息,会被实时接收到
5.工作模式工程
Producer :
package m2;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost(“192.168.64.140”);
f.setPort(5672);
f.setUsername(“admin”);
f.setPassword(“admin”);
//通信通道
Channel c = f.newConnection().createChannel();
//创建队列
c.queueDeclare(“helloworld”,false,false,false,null);
//发送消息
while (true){
System.out.println(“输入消息:”);
String s = new Scanner(System.in).nextLine();
if (s.equals(“exit”)){
break;
}else {
c.basicPublish("",“helloworld”,null,s.getBytes());
}
}
System.out.println(“Byes”);
}
}
Consuner :
package m2;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consuner {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost(“192.168.64.140”);
f.setPort(5672);
f.setUsername(“admin”);
f.setPassword(“admin”);
//通信通道
Channel c = f.newConnection().createChannel();
//创建队列
c.queueDeclare(“helloworld”,false,false,false,null);
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery delivery) throws IOException {
String s1 = new String(delivery.getBody());
System.out.println("收到:"+s1);
/*模拟处理耗时信息
遍历字符串找'.'字符,没找到一个暂停一秒
注意:引用名别写错,是处理后的String参数,不是传入的String参数
*/
for (int i=0;i<s1.length();i++){
if(s1.charAt(i)=='.'){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
System.out.println("--------------消息处理结束");
}
};
CancelCallback cancelCallback = new CancelCallback() {
@Override
public void handle(String s) throws IOException {
}
};
// 从 helloworld 接收消息
/*
第二个参数 true: 自动确认、自动 ACK(Acknowledgement)
*/
c.basicConsume("helloworld", true, deliverCallback, cancelCallback);
}
}
开启两个消费者的并行运行,运行两个消费者,进行测试:
当服务器不知道消息该发给谁的时候,此时的规则,就是简单的轮询机制
修改后的Consuner :
package m2;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consuner {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost(“192.168.64.140”);
f.setPort(5672);
f.setUsername(“admin”);
f.setPassword(“admin”);
//通信通道
Channel c = f.newConnection().createChannel();
//创建队列
c.queueDeclare(“helloworld”,false,false,false,null);
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery delivery) throws IOException {
String s1 = new String(delivery.getBody());
System.out.println("收到:"+s1);
/*模拟处理耗时信息
遍历字符串找'.'字符,没找到一个暂停一秒
注意:引用名别写错,是处理后的String参数,不是传入的String参数
*/
for (int i=0;i<s1.length();i++){
if(s1.charAt(i)=='.'){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
//手动发送回执
// 参数: (发送消息的回执栏),是否确认之前收到的所有消息
c.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
System.out.println("--------------消息处理结束");
}
};
CancelCallback cancelCallback = new CancelCallback() {
@Override
public void handle(String s) throws IOException {
}
};
c.basicQos(1); //只接收1条消息,处理完之前不接收下一条
//只在手动ack模式下有效
// 从 helloworld 接收消息
/*
第二个参数 true: 自动确认、自动 ACK(Acknowledgement)
false:手动确认,处理消息后需要手动发送回执(所以上边要手动发送下回执)
*/
c.basicConsume("helloworld", false, deliverCallback, cancelCallback);
}
}
此时已经完成了消费者只会同时接收一条消息的操作,
但是仍存在问题,服务器发送不出去的消息,遇到服务器重启,消息就会消失,
所以这里要做到消息队列持久化,持久队列,队列信息存到磁盘,
对消息本身也要设置持久状态,是持久消息还是普通消息.
修改后的Consuner :
package m2;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consuner {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost(“192.168.64.140”);
f.setPort(5672);
f.setUsername(“admin”);
f.setPassword(“admin”);
//通信通道
Channel c = f.newConnection().createChannel();
//创建队列
//服务器端已经存在的队列,参数是不可以变的,要么换一个队列,要么删除原队列
c.queueDeclare(“task_queue”,true,false,false,null);
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery delivery) throws IOException {
String s1 = new String(delivery.getBody());
System.out.println("收到:"+s1);
/*模拟处理耗时信息
遍历字符串找'.'字符,没找到一个暂停一秒
注意:引用名别写错,是处理后的String参数,不是传入的String参数
*/
for (int i=0;i<s1.length();i++){
if(s1.charAt(i)=='.'){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
//手动发送回执
// 参数: (发送消息的回执栏),是否确认之前收到的所有消息
c.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
System.out.println("--------------消息处理结束");
}
};
CancelCallback cancelCallback = new CancelCallback() {
@Override
public void handle(String s) throws IOException {
}
};
c.basicQos(1); //只接收1条消息,处理完之前不接收下一条
//只在手动ack模式下有效
// 从 helloworld 接收消息
/*
第二个参数 true: 自动确认、自动 ACK(Acknowledgement)
false:手动确认,处理消息后需要手动发送回执(所以上边要手动发送下回执)
*/
c.basicConsume("task_queue", false, deliverCallback, cancelCallback);
}
}
修改后的Producer :
package m2;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost(“192.168.64.140”);
f.setPort(5672);
f.setUsername(“admin”);
f.setPassword(“admin”);
//通信通道
Channel c = f.newConnection().createChannel();
//创建队列
c.queueDeclare(“task_queue”,true,false,false,null);
//发送消息
while (true){
System.out.println(“输入消息:”);
String s = new Scanner(System.in).nextLine();
if (s.equals(“exit”)){
break;
}else {
//PERSISTENT 持久化的意思 MessageProperties.PERSISTENT_TEXT_PLAIN常量,意为持久化文本
c.basicPublish("",“task_queue”, MessageProperties.PERSISTENT_TEXT_PLAIN,s.getBytes());
}
}
System.out.println(“Byes”);
}
}
此时即使服务器重启后,滞留消息仍会保存,并继续发送
6.发布订阅模式-交换机
Producer:
package m3;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost(“192.168.64.140”);
f.setPort(5672);
f.setUsername(“admin”);
f.setPassword(“admin”);
//通信通道
Channel c = f.newConnection().createChannel();
//创建fanout交换机,命名为logs
c.exchangeDeclare(“logs”,“fanout”);
/*或者也可以用常量代替第二个参数
c.exchangeDeclare(“logs”, BuiltinExchangeType.FANOUT);
*/
//发送消息
while (true){
System.out.println("输入消息:");
String s = new Scanner(System.in).nextLine();
if (s.equals("exit")){
break;
}else {
/*第一个参数是交换机
第二个参数,对于fanout交换机无效,所以写不写都是无效的数据
*/
c.basicPublish("logs","",
null,s.getBytes());
}
}
System.out.println("Byes");
}
}
此时如果没有消费者接收消息,会直接被丢弃
ps:变量名提示不对应,是因为没有目标类的源码,ctrl点进去,右上角选择第一个选项,下载一下源码即可
Consuner:
package m3;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.TimeoutException;
public class Consuner {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost(“192.168.64.140”);
f.setPort(5672);
f.setUsername(“admin”);
f.setPassword(“admin”);
Channel c = f.newConnection().createChannel();
//创建一个随机命名的队列,创建交换机,做绑定
String queue = UUID.randomUUID().toString();
c.queueDeclare(queue,false,true,true,null);
c.exchangeDeclare(“logs”,BuiltinExchangeType.FANOUT);
// 第三个参数对fanout交换机是没有用的
c.queueBind(queue,“logs”,"");
//正常从队列接收消息
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
String s = new String(message.getBody());
System.out.println("收到:"+s);
}
};
CancelCallback cancelCallback = new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
//接收消息
/*
第二个参数 true: 自动确认、自动 ACK(Acknowledgement)
false:手动确认,处理消息后需要手动发送回执(所以上边要手动发送下回执)
*/
c.basicConsume(queue, true, deliverCallback, cancelCallback);
}
}
7.路由模式
Producer :
package m4;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost(“192.168.64.140”);
f.setPort(5672);
f.setUsername(“admin”);
f.setPassword(“admin”);
Channel c = f.newConnection().createChannel();
//创建direct交换机,命名为 direct_logs
c.exchangeDeclare("direct_logs", BuiltinExchangeType.DIRECT);
//发送消息
while (true){
System.out.print("输入消息:");
String s = new Scanner(System.in).nextLine();
System.out.print("输入路由键:");
String k = new Scanner(System.in).nextLine();
if (s.equals("exit")){
break;
}else {
/* 发送消息
第一个参数是交换机
第二个参数是路由键,默认交换机""的路由键就是队列名
*/
c.basicPublish("direct_logs",k,
null,s.getBytes());
}
}
System.out.println("Byes");
}
}
Consuner :
package m4;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.Scanner;
import java.util.UUID;
import java.util.concurrent.TimeoutException;
public class Consuner {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost(“192.168.64.140”);
f.setPort(5672);
f.setUsername(“admin”);
f.setPassword(“admin”);
Channel c = f.newConnection().createChannel();
//创建一个随机命名的队列,创建交换机,做绑定
String queue = UUID.randomUUID().toString();
c.queueDeclare(queue,false,true,true,null);
c.exchangeDeclare(“direct_logs”,BuiltinExchangeType.DIRECT);
System.out.print(“输入绑定建,用空格隔开:”);
String s = new Scanner(System.in).nextLine();//aa bb cc
String[] a = s.split("\s+"); // \s+正则表达式,表示1到多个空白字符
// 遍历绑定
for (String k : a) {
c.queueBind(queue,“direct_logs”,k);
}
//正常从队列接收消息
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
String s = new String(message.getBody());
// 取出消息上携带的路由键
String k = message.getEnvelope().getRoutingKey();
System.out.println("收到:"+k+","+s);
}
};
CancelCallback cancelCallback = new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
//接收消息
/*
第二个参数 true: 自动确认、自动 ACK(Acknowledgement)
false:手动确认,处理消息后需要手动发送回执(所以上边要手动发送下回执)
*/
c.basicConsume(queue, true, deliverCallback, cancelCallback);
}
}
8.主题模式
Producer :
package m5;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost(“192.168.64.140”);
f.setPort(5672);
f.setUsername(“admin”);
f.setPassword(“admin”);
Channel c = f.newConnection().createChannel();
//创建topic交换机,命名为 topic_logs
c.exchangeDeclare("topic_logs", BuiltinExchangeType.TOPIC);
//发送消息
while (true){
System.out.print("输入消息:");
String s = new Scanner(System.in).nextLine();
System.out.print("输入路由键:");
String k = new Scanner(System.in).nextLine();
if (s.equals("exit")){
break;
}else {
/* 发送消息
第一个参数是交换机
第二个参数是路由键,默认交换机""的路由键就是队列名
*/
c.basicPublish("topic_logs",k,
null,s.getBytes());
}
}
System.out.println("Byes");
}
}
Consuner :
package m5;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.Scanner;
import java.util.UUID;
import java.util.concurrent.TimeoutException;
public class Consuner {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost(“192.168.64.140”);
f.setPort(5672);
f.setUsername(“admin”);
f.setPassword(“admin”);
Channel c = f.newConnection().createChannel();
//创建一个随机命名的队列,创建交换机,做绑定
String queue = UUID.randomUUID().toString();
c.queueDeclare(queue,false,true,true,null);
c.exchangeDeclare(“topic_logs”,BuiltinExchangeType.TOPIC);
System.out.print(“输入绑定建,用空格隔开:”);
String s = new Scanner(System.in).nextLine();//aa bb cc
String[] a = s.split("\s+"); // \s+正则表达式,表示1到多个空白字符
// 遍历绑定
for (String k : a) {
c.queueBind(queue,“topic_logs”,k);
}
//正常从队列接收消息
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
String s = new String(message.getBody());
// 取出消息上携带的路由键
String k = message.getEnvelope().getRoutingKey();
System.out.println("收到:"+k+","+s);
}
};
CancelCallback cancelCallback = new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
//接收消息
/*
第二个参数 true: 自动确认、自动 ACK(Acknowledgement)
false:手动确认,处理消息后需要手动发送回执(所以上边要手动发送下回执)
*/
c.basicConsume(queue, true, deliverCallback, cancelCallback);
}
}
9.RPC模式
太复杂,没讲.
10.订单的流量削峰案例
导入项目:
1.将课前准备好的zip里边的 pd-web目录解压到rabbit工程目录
2.导入maven项目,选择pd-web下的pom.xml
3.file—project structures,配置jdk版本
4.执行/导入sql脚本,脚本文件在工程目录中有
5.如果用老师的数据库,还要改yml配置文件
6.点击RunPdAPP,启动项目
7.配置启动项,设置working directory,设置为pd-web目录,设置完后重启项目
1.
3.工程统一的jdk版本,也可以点击设置modules单独设置jdk版本
10.2为项目添加消息队列发送机制:
修改pd-web,发送订单到rabbitmq
8. 添加依赖: starter-rabbitmq
9. yml配置 rabbitmq 连接信息
10. 在启动类中(或自定义自动配置类)准备队列的参数
11. OrderServiceImpl
- 添加 AmqpTemplate 用来发送消息的封装工具
- 在 saveOrder() 方法中发送消息
- 把 saveorder() 中的数据库代码都注释掉
11.pd-web-consumer接收消息并存入数据库
1.复制pd-web,命名pd-web-consumer
2.用idea打开pom文件的名字,然后右键点击 Add as maven project 导入项目
3.添加OrderConsumer类
package com.pd;
import com.pd.pojo.PdOrder;
import com.pd.service.OrderService;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/*
基础配置: 1.依赖 2.连接 3.队列 均已完成
消费者从orderQueue接收订单,
调用业务代码,完成订单存储
*/
@RabbitListener(queues = “orderQueue”) //设置接收消息
@Component //设置spring接收实例
public class OrderConsumer {
@Autowired
private OrderService orderService;
@RabbitHandler //配合@RabbitListener,用来指定处理接收到的消息的方法,只能指定一个类方法
public void receive(PdOrder order) throws Exception {
orderService.saveOrder(order);
System.out.println("-----------------订单已存储,id="+order.getOrderId());
}
}
4.修改OrderServiceImpl类
加一行代码:
String orderId = pdOrder.getOrderId();
注释并解除注释一下代码:
12.动态配置刷新
2,3,4,9项目基础配置
12.1.添加依赖
- rabbitmq
- bus
再加一个binder-rabbit依赖,需要手动添加,插件里边没有
org.springframework.boot
spring-boot-starter-amqp
org.springframework.cloud
spring-cloud-bus
org.springframework.cloud
spring-cloud-stream-binder-rabbit
然后将三个依赖分别加入02,03,04,09
12.2.yml配置rabbitmq连接
注意缩进关系,rabbitmq是在spring下边
rabbitmq:
host: 192.168.64.140
port: 5672
username: admin
password: admin
然后将此条配置分别复制到02,03,04,09.
注意02,03,04使用了远程config中心,所以也要将改好的配置文件上传到git才生效
12.3.09项目中使用actuator暴露bus-refresh刷新端点
1.Actuator依赖添加到09项目中
添加09yml配置:
m.e.w.e.i
management:
endpoints:
web:
exposure:
include:
- bus-refresh
- env
12.4.当前启动顺序:
依次启动05 – 09 – 02…
02,03,04启动的时候要看控制台,确保连接了6001配置中心项目
访问配置中心暴露端点:
http://localhost:6001/actuator
post访问测试(无任何参数访问):
http://localhost:6001/actuator/bus-refresh
效果:
13.服务不停机动态刷新配置
14.sleuth + zipkin 链路跟踪
14.1.添加Sleuth链路跟踪日志依赖到02,03,04,06
添加完依赖后重启对应项目.
只需要添加 sleuth 依赖,0配置
访问订单方法进行测试日志打印:
http://localhost:3001/order-service/sadsasad
效果:
14.2.02,03,04,06 添加 zipkin 客户端依赖和rabbitmq配置
14.2.1. 2,3,4,6 添加 zipkin 客户端依赖
14.2.2. 06添加rabbitmq依赖和连接配置
org.springframework.boot
spring-boot-starter-amqp
14.2.3. 2,3,4,6 配置日志的发送方式: rabbit
06yml添加配置,在spring下
rabbitmq:
host: 192.168.64.140
port: 5672
username: admin
password: admin
zipkin:
sender:
type: rabbit
02,03,04也添加zipkin:配置
zipkin:
sender:
type: rabbit
启动zipkin:
java -jar zipkin-server-2.23.2-exec.jar --zipkin.collector.rabbitmq.uri=amqp://admin:admin@192.168.64.140:5672
启动之后,访问9411端口,打开zipkin
访问服务:
http://localhost:3001/order-service/asdasfasf
效果:
rabbitmq会有zipkin队列
15.选择正确的网卡,注册 IP 地址
选择网卡,配置网段:
用09尝试一下配置网段的应用;
在09项目中新建文件bootstrap.yml
bootstrap.yml
在引导配置中选择网卡
spring:
cloud:
inetutils:
# 忽略网卡
ignored-interfaces:
- VM.* # 正则表达式,VM开头,后面任意字符0到多个
preferred-networks: # 要是用的网卡的网段
# 指定物理网络的网段,
# 点.是正则表达式中的一个符号,这里需要转译为普通的字符,前边加
#- 192.168.89…+
- 172.88.13…+
需要注册IP地址,不注册主机名
application.yml
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka,http://eureka2:2002/eureka
instance:
prefer-ip-address: true
当前可能会出现注册的是ip地址,但是显示的不是ip地址,需要新加配置:(eureka:后)
instance:
prefer-ip-address: true
显示的注册信息:“主机:服务id:端口”
instance-id:
s
p
r
i
n
g
.
c
l
o
u
d
.
c
l
i
e
n
t
.
i
p
−
a
d
d
r
e
s
s
:
{spring.cloud.client.ip-address}:
spring.cloud.client.ip−address:{spring.application.name}😒{server.port} # 界面列表中显示的格式也显示ip
效果: