一、简介
1、是什么
在这之前,我们先看下机构图
一般就是发送消息,然后给exchange,如果发生阻塞了,先丢给队列,继续把下一个消息发给exchange。然后队列就一个一个地给对应的消费者接受消息。这样就是异步处理,加快了流程,提高效率。而RabbitMQ就是干这个的。
消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量 削锋等问题实现高性能,高可用,可伸缩和最终一致性
2、AMQP
RabbitMQ 是一个由 Erlang 语言开发的 AMQP 的开源实现。
AMQP :Advanced Message Queue,高级消息队列协议。它是应用层协议的一个开放 标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不 受产品、开发语言等条件的限制。
3、特性
- 可靠性
- 灵活的路由
- 消息集群
- 高可用
- 多种协议
- 多语言客户端
- 管理界面
- 跟踪机制
- 插件机制
二、RabbitMQ初识
1、docker下载与安装
下载
docker pull rabbitmq:management
安装
docker run -di --name=tensquare_rabbitmq -p 5671:5671 -p 5672:5672 -p 4369:4369 -p 15671:15671 -p 15672:15672 -p 25672:25672 rabbitmq:management
6个端口,都要给它暴露,但我们用也只是用15672
访问http://192.168.12.128:15672/
,账号密码都是guest
2、直接模式(Direct)
将消息直接发送给唯一一个节点使用,直接到queue。其中,其经过一个rabbitMQ自带的exchange:""
,该exchange的名字为空字符串,一般称为default exchange。
- 这种情况不需要将exchange 进行任何绑定操作
- 传送消息只需要一个
RouteKey
,就是发送到的队列名字 - 如果
vhost
中部存在RouteKey
中指定的队列名,则该消息会被抛弃
1)创建队列
2)写代码,创建生产者
生产者,就是产生一个消息,给队列,等有接收者出现了,队列就把消息给接收者
主要就是这个maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
yml
spring:
rabbitmq:
host: 192.168.12.128
编写生产者测试类
@SpringBootTest
class TensquareRabbitmqDemoApplicationTests {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
void contextLoads() {
rabbitTemplate.convertAndSend("MyQueue","生产者发送的一条消息");
}
}
- 其中
convertAndSend()
是一个发送消息的函数,其参数如下,直接模式采用convertAndSend(String routingKey, Object object)
就是(队列名,消息对象)
。但是学到后面routingKey
是一个规则而不是队列名了,这里为什么可以这样用我也不是很懂,但直接模式把routingKey
当作队列名就很好理解了
运行,查看queue
有了一条消息,这时候就要有一个消费者对其进行接受
编写消费者
@Component
//接受哪个队列的消息
@RabbitListener(queues = "MyQueue")
public class Customer1 {
@RabbitHandler
public void showMsg(String msg){
System.out.println("消费者接受到消息:"+msg);
}
}
@RabbitListener(queues = "MyQueue")
接受哪个队列的消息@RabbitHandler
方法前加就对了,然后其发送的是什么类型,这个参数就要写什么类型
3、分列模式(Fanout)
将一个消息发给一个或者几个交换器,然后交换器和某些队列绑定。绑定后,该交换器收到消息,将该消息都发给队列,然后对应的消费者和队列绑定接收到消息。这样,可以实现群发的效果
- 这种模式不需要RouteKey
- 这种模式需要提前将Exchange与Queue进行绑定,一个Exchange可以绑定多个 Queue,一个Queue可以同多个Exchange进行绑定。
- 如果接受到消息的Exchange没有与任何Queue绑定,则消息会被抛弃
1)交换器与队列绑定
多创建几个队列
创建交换器
绑定
to queue写绑定的队列,这样就完成交换器和队列的绑定
2)消息生产者
测试类
@Test
void test2(){
rabbitTemplate.convertAndSend("ex_fanout","","分列消息");
}
- 使用
convertAndSend(String exchange, String routingKey, Object object)
函数,第一个是交换器名字,第二个是规则(主题模式会说到的),第三个就是发送消息的对象
3)消息消费者
分别编写3个类,每个类接受不同的队列
@Component
//接受哪个队列的消息
@RabbitListener(queues = "MyQueue")
public class Customer1 {
@RabbitHandler
public void showMsg(String msg){
System.out.println("队列1接受到消息:"+msg);
}
}
@Component
//接受哪个队列的消息
@RabbitListener(queues = "MyQueue2")
public class Customer2 {
@RabbitHandler
public void showMsg(String msg){
System.out.println("队列2接收到消息:"+msg);
}
}
@Component
//接受哪个队列的消息
@RabbitListener(queues = "MyQueue3")
public class Customer3 {
@RabbitHandler
public void showMsg(String msg){
System.out.println("队列3接收到消息:"+msg);
}
}
启动application
队列2接收到消息:分列消息
队列3接收到消息:分列消息
队列1接受到消息:分列消息
4、主题模式(Topic)
主题模式说白就是分列模式的高级版。分列模式发消息的convertAndSend(String exchange, String routingKey, Object object)
中的routingKey
是空,而主题模式就是对这个进行编写
1、概述
routingKey,翻译过来就是路由键,有点类是一个下标之类的东西,有了routingkey。在GUI界面中exchange和queue绑定对应的routingkey,在Java中创建消息的时候,添加不同的routingkey,可以对应地接受到消息
之前也学过一个,根据发送不同的消息,交换器发送给不同的队列。比如这个
一个生产者,一个交换器,3个队列。
其中,队列A和交换器绑定但是routingkey为good.#
(#代表是匹配多个),其他同理。这个意思就是说,如果发送的消息时写的routingkey是good.abc、good.asdfasdf
,队列A都能接受,而BC不能接受。如果发送消息时的routingkey是asd.log、zxcvz.log
,B能接受而AB不能接受。如果是good.log
,则ABC都能接受。
看能接受什么,还是得看设置的routingkey,然后消息和routingkey进来,匹配,发送消息
- 这种模式需要RouteKey,也许要提前绑定Exchange与Queue
- “#”表示0个或若干个关键字,“”表示一个关键字。如“log.”能与“log.warn”匹配,无法 与“log.warn.timeout”匹配;但是“log.#”能与上述两者匹配。
- 同样,如果Exchange没有发现能够与RouteKey匹配的Queue,则会抛弃此消息
- 在进行绑定时,要提供一个该队列关心的主题,如“#.log.#”表示该队列关心所有涉及 log的消息(一个RouteKey为”MQ.log.error”的消息会被转发到该队列)。
2、创建交换器与绑定
创建交换器
创建交换器
绑定
绑定完成
3、消费者
good.ass:
@Test
void test3(){
rabbitTemplate.convertAndSend("ex_topic","good.ass","主题模式1");
}
队列1接受到消息:主题模式1
ass.log:
@Test
void test4(){
rabbitTemplate.convertAndSend("ex_topic","ass.log","主题模式2");
}
队列2接收到消息:主题模式2
good.log:
@Test
void test5(){
rabbitTemplate.convertAndSend("ex_topic","good.log","主题模式3");
}
队列1接受到消息:主题模式3
队列2接收到消息:主题模式3
队列3接收到消息:主题模式3
5、代码
三、用户微服务
1、准备工作
代码生成,端口9008
导包
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-amqp -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
yml:
spring:
redis:
host: 192.168.12.128
rabbitmq:
host: 192.168.12.128
2、发送短信
发送短信,生成随机数,给缓冲一份,给用户(用RabbitMQ发送)一份,然后现在测试给控制台显示一份
在此之前先导入一个随机数的包,这个包还有很多功能
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
controller
@PostMapping(value = "sendsms/{mobile}")
public Result sendSms(@PathVariable String mobile){
userService.sendSms(mobile);
return new Result(true,StatusCode.OK,"短信发送成功");
}
在rabbitmq创建一个sms
名的queue
service
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendSms(String mobile) {
//生成6位数字验证码
String checkcode = RandomStringUtils.randomNumeric(6);
//向缓存保留一份,在缓存保留2消失
redisTemplate.opsForValue().set("checkcode_"+mobile,checkcode,2, TimeUnit.HOURS);
//给用户发一份(rabbitmq)
//队列为sms,发送Map类型
Map<String,Object> map = new HashMap<>();
map.put("mobile",mobile);
map.put("checkcode",checkcode);
rabbitTemplate.convertAndSend("sms",map);
//控制台输出
System.out.println("验证码为"+checkcode);
}
}
验证码为250061
3、用户注册
controller
@PostMapping("register/{code}")
public Result register(@PathVariable String code,@RequestBody User user){
userService.register(user,code);
return new Result(true,StatusCode.OK,"注册成功");
}
public void register(User user, String code) {
String checkcodeRedis = (String) redisTemplate.opsForValue().get("checkcode_" + user.getMobile());
if (checkcodeRedis==null){
throw new RuntimeException("请先获取手机验证码");
}
if (!checkcodeRedis.equals(code)){
throw new RuntimeException("验证码错误");
}
user.setId(idWorker.nextId()+"");
user.setRegdate(new Date());
user.setUpdatedate(new Date());
user.setLastdate(new Date());
user.setOnline(0l);
user.setFanscount(0);
user.setFollowcount(0);
userDao.save(user);
}
- 这个注册没用到rabbitmq
四、短信微服务
用来消费rabbitmq,上面用户微服务是生成一个消息,这里消费一个消息,并且把消息发送给用户,然后利用上面的用户注册完成注册
创建模块,导入amqp包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
yml
server:
port: 9009
spring:
application:
name: tensquare-sms
rabbitmq:
host: 192.168.12.128
1、监听类
@Component
@RabbitListener(queues = "sms")
public class SmsListener {
@RabbitHandler
public void sendSms(Map<String,Object> msg){
System.out.println("手机号:"+msg.get("mobile"));
System.out.println("验证码:"+msg.get("checkcode"));
}
}
手机号:18200000000
验证码:250061
2、发送短信(阿里云通信)
4分5一条短信,就是说1块可以发22次。
进入阿里云,登陆注册,产品选择短信服务
1)申请签名与模板
- 申请签名
- 申请模板
这里写好点,不然不给过的。
2)创建AccessKey
创建一个AccessKey,然后里面有账号和密码的
这个很重要的啊,不要给别人看了
3)充钱
就充1.2块OK了
4)写代码
导包
<!-- https://mvnrepository.com/artifact/com.aliyun/aliyun-java-sdk-dysmsapi -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
<version>1.1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.aliyun/aliyun-java-sdk-core -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.5.16</version>
</dependency>
点进API查看,有这么个东西
但是,大佬已经帮我们打包好成一个类了,我们就感谢地拿来用吧(其实就是把官方的代码,弄成一个类封装好)
①工具类
package com.tensquare.sms.utils;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsResponse;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 短信工具类
* @author Administrator
*
*/
@Component
public class SmsUtil {
//产品名称:云通信短信API产品,开发者无需替换
static final String product = "Dysmsapi";
//产品域名,开发者无需替换
static final String domain = "dysmsapi.aliyuncs.com";
@Autowired
private Environment env;
// TODO 此处需要替换成开发者自己的AK(在阿里云访问控制台寻找)
/**
* 发送短信
* @param mobile 手机号
* @param template_code 模板号
* @param sign_name 签名
* @param param 参数
* @return
* @throws ClientException
*/
public SendSmsResponse sendSms(String mobile,String template_code,String sign_name,String param) throws ClientException {
String accessKeyId =env.getProperty("aliyun.sms.accessKeyId");
String accessKeySecret = env.getProperty("aliyun.sms.accessKeySecret");
//可自助调整超时时间
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
//初始化acsClient,暂不支持region化
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
IAcsClient acsClient = new DefaultAcsClient(profile);
//组装请求对象-具体描述见控制台-文档部分内容
SendSmsRequest request = new SendSmsRequest();
//必填:待发送手机号
request.setPhoneNumbers(mobile);
//必填:短信签名-可在短信控制台中找到
request.setSignName(sign_name);
//必填:短信模板-可在短信控制台中找到
request.setTemplateCode(template_code);
//可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
request.setTemplateParam(param);
//选填-上行短信扩展码(无特殊需求用户请忽略此字段)
//request.setSmsUpExtendCode("90997");
//可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
request.setOutId("yourOutId");
//hint 此处可能会抛出异常,注意catch
SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
return sendSmsResponse;
}
public QuerySendDetailsResponse querySendDetails(String mobile,String bizId) throws ClientException {
String accessKeyId =env.getProperty("accessKeyId");
String accessKeySecret = env.getProperty("accessKeySecret");
//可自助调整超时时间
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
//初始化acsClient,暂不支持region化
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
IAcsClient acsClient = new DefaultAcsClient(profile);
//组装请求对象
QuerySendDetailsRequest request = new QuerySendDetailsRequest();
//必填-号码
request.setPhoneNumber(mobile);
//可选-流水号
request.setBizId(bizId);
//必填-发送日期 支持30天内记录查询,格式yyyyMMdd
SimpleDateFormat ft = new SimpleDateFormat("yyyyMMdd");
request.setSendDate(ft.format(new Date()));
//必填-页大小
request.setPageSize(10L);
//必填-当前页码从1开始计数
request.setCurrentPage(1L);
//hint 此处可能会抛出异常,注意catch
QuerySendDetailsResponse querySendDetailsResponse = acsClient.getAcsResponse(request);
return querySendDetailsResponse;
}
}
private Environment env;
:自动拿到yml中的配置public SendSmsResponse sendSms(String mobile,String template_code,String sign_name,String param)
翻译为人话:sendSms(电话号码,签名,模板,一个字符串的key=>value东西),其中,key为:
yml添加
aliyun:
sms:
accessKeyId: 你猜猜
accessKeySecret: 你猜猜
sign_name: 十次方1024博客
template_code: SMS_205826458
- accessKeyId和accessKeySecret就是申请的AccessKey 里面的东西
- sign_name
- template_code
②修改SmsListener
@Component
@RabbitListener(queues = "sms")
public class SmsListener {
//导入工具类
@Autowired
private SmsUtil smsUtil;
//从yml自动拿去数据
@Value("${aliyun.sms.sign_name}")
private String sign_name;//签名
@Value(("${aliyun.sms.template_code}"))
private String template_code;//模板编号
@RabbitHandler
public void sendSms(Map<String,Object> map){
String mobile = (String) map.get("mobile");
String checkcode = (String) map.get("checkcode");
try {
//{"code":"验证码"}
smsUtil.sendSms(mobile,template_code,sign_name,"{\"code\":\""+checkcode+"\"}");
} catch (ClientException e) {
e.printStackTrace();
}
System.out.println("手机号:"+mobile);
System.out.println("验证码:"+checkcode);
}
}
5)测试
启动UserApplication和SmsApplication
发短信
sms类控制台:
手机号:18200778967
验证码:858345
user控制台
验证码为858345
手机短信
哦我的天啊,感动哭了