知识分享-消息中间件详解+rabbitMQ
知识分享-消息中间件详解+rabbitMQ
消息中间件
概述
消息中间件是基于队列与消息传递技术,在网络环境中为应用系统提供同步或异步、可靠的消息传输的支撑性软件系统。
应用场景
异步处理
对于电商app来说,顾客下单后要扣减商品库存,还要更新订单状态等一系列操作。当并发量大的时候,服务器磁盘、IO、CPU load 会很高,因此有的时候需要一个相对较长的时间间隔才能完成上述的一系列操作。如果用户提交完订单,让用户等待几秒才能成功,对于用户来说,会很不耐烦,甚至会流失大量用户。
如果有了 MQ ,可以让系统间的通信变为异步通信,系统A 发个消息到 MQ,系统 B什么时候获取消息进行处理,系统A不用管,系统A向MQ发送完消息只需要给用户及时反馈即可,剩下的操作系统B可能会在未来的几秒、几分钟甚至几小时后才进行处理。
应用解耦★★★★★
订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。
库存系统:订阅下单的消息,获取下单消息,进行库操作。
就算库存系统出现故障,消息队列也能保证消息的可靠投递,不会导致消息丢失。
流量削峰
流量削峰一般在秒杀活动中应用广泛
消息队列优缺点
关于消息队列的优点也就是上面列举的,就是在特殊场景下有其对应的好处,解耦、异步、削峰。
缺点有以下几个:
系统可用性降低
系统引入的外部依赖越多,越容易挂掉。本来你就是 A 系统调用 BCD 三个系统的接口就好了,人 ABCD 四个系统好好的,没啥问题,你偏加个 MQ 进来,万一 MQ 挂了咋整,MQ 一挂,整套系统崩溃的,你不就完了?如何保证消息队列的高可用,可以点击这里查看。
系统复杂度提高
硬生生加个 MQ 进来,你怎么[保证消息没有重复消费]?怎么[处理消息丢失的情况]?怎么保证消息传递的顺序性?头大头大,问题一大堆,痛苦不已。
一致性问题
A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,咋整?你这数据就不一致了。
所以消息队列实际是一种非常复杂的架构,你引入它有很多好处,但是也得针对它带来的坏处做各种额外的技术方案和架构来规避掉,做好之后,你会发现,妈呀,系统复杂度提升了一个数量级,也许是复杂了 10 倍。但是关键时刻,用,还是得用的。
常用消息中间件
AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。Erlang中的实现有RabbitMQ等。
JMS即Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Java消息服务是一个与具体平台无关的API,绝大多数MOM提供商都对JMS提供支持。
AMQP和JMS
MQ是消息通信的模型,并发具体实现。现在实现MQ的有两种主流方式:AMQP、JMS。
两者间的区别和联系:
- JMS是定义了统一的接口,来对消息操作进行统一;AMQP是通过规定协议来统一数据交互的格式
- JMS限定了必须使用Java语言;AMQP只是协议,不规定实现方式,因此是跨语言的。
- JMS规定了两种消息模型;而AMQP的消息模型更加丰富
常见MQ产品
ActiveMQ:基于JMS
RabbitMQ:基于AMQP协议,erlang语言开发,稳定性好
RocketMQ``:基于JMS,阿里巴巴产品,目前交由Apache基金会
Kafka:分布式消息系统,高吞吐量
其实现在主流的消息中间件就4种:kafka、ActiveMQ、RocketMQ、RabbitMQ
RabbitMQ详解
概述
RabbitMQ是由erlang语言开发,基于AMQP(Advanced Message Queue 高级消息队列协议)协议实现的消息队列,它是一种应用程序之间的通信方法,消息队列在分布式系统开发中应用非常广泛。
为什么选择RabbitMQ
1、ActiveMQ,性能不是很好,因此在高并发的场景下,直接被pass掉了。它的Api很完善,在中小型互联网公司可以去使用。
2、kafka,主要强调高性能,如果对业务需要可靠性消息的投递的时候。那么就不能够选择kafka了。但是如果做一些日志收集呢,kafka还是很好的。因为kafka的性能是十分好的。
3、RocketMQ,它的特点非常好。它高性能、满足可靠性、分布式事物、支持水平扩展、上亿级别的消息堆积、主从之间的切换等等。MQ的所有优点它基本都满足。但是它最大的缺点:商业版收费。因此它有许多功能是不对外提供的。
特点
RabbitMQ是使用Erlang语言开发的开源消息队列系统,基于AMQP协议来实现。
AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。
AMQP协议更多用在企业系统内,对数据一致性、稳定性和可靠性要求很高的场景,对性能和吞吐量的要求还在其次。
RabbitMQ的可靠性是非常好的,数据能够保证百分之百的不丢失。可以使用镜像队列,它的稳定性非常好。所以说在我们互联网的金融行业。对数据的稳定性和可靠性要求都非常高的情况下,我们都会选择RabbitMQ。当然没有kafka性能好,但是要比AvtiveMQ性能要好很多。也可以自己做一些性能的优化。
RabbitMQ可以构建异地双活架构,包括每一个节点存储方式可以采用磁盘或者内存的方式。
RabbitMQ的集群架构
非常经典的 mirror镜像模式,保证 100%数据不丢失。在实际工作中也是用得最多的,并且实现非常的简单,一般互联网大厂都会构建这种镜像集群模式。
mirror镜像队列,目的是为了保证 rabbitMQ数据的高可靠性解决方案,主要就是实现数据的同步,一般来讲是2 - 3个节点实现数据同步。对于100%数据可靠性解决方案,一般是采用 3个节点。
如上图所示,用 KeepAlived 做了 HA-Proxy 的高可用,然后有3 个节点的 MQ 服务,消息发送到主节点上,主节点通过 mirror队列把数据同步到其他的MQ节点,这样来实现其高可靠。
这就是RabbitMQ整个镜像模式的集群架构。
安装RabbitMQ
Linux安装
更新软件包和存储库
yum -y update
安装Erlang
yum -y install epel-release
yum -y update
yum -y install erlang socat
检查Erlang版本
erl -version
将得到如下输出
[root@liptan-pc ~]# erl -version Erlang (ASYNC_THREADS,HIPE) (BEAM) emulator version 5.10.4
下载RabbitMQ
Wget https://www.rabbitmq.com/releases/rabbitmq-server/v3.6.10/rabbitmq-server-3.6.10-1.el7.noarch.rpm
安装RabbitMQ
rpm –import https://www.rabbitmq.com/rabbitmq-release-signing-key.asc
运行RPM安装RPM包:
rpm -Uvh rabbitmq-server-3.6.10-1.el7.noarch.rpm
使用RabbitMQ
运行
systemctl start rabbitmq-server
开机自启
systemctl enable rabbitmq-server
检查状态
systemctl status rabbitmq-server
启动RabbitMQ Web管理控制台,方法是运行:
rabbitmq-plugins enable rabbitmq_management
通过运行以下命令,将RabbitMQ文件的所有权提供给RabbitMQ用户:
chown -R rabbitmq:rabbitmq /var/lib/rabbitmq/
将需要为RabbitMQ Web`管理控制台创建管理用户。 运行以下命令相同。
rabbitmqctl add_user admin StrongPassword
rabbitmqctl set_user_tags admin administrator
rabbitmqctl set_permissions -p / admin “.*” “.*” “.*”
Windows安装
省略
管理界面介绍
访问RabbitMQ的管理面板
第一次访问需要登录,默认的账号密码为:guest/guest
主页
connections:无论生产者还是消费者,都需要与RabbitMQ建立连接后才可以完成消息的生产和消费,在这里可以查看连接情况
channels:通道,建立连接后,会形成通道,消息的投递获取依赖通道。
Exchanges:交换机,用来实现消息的路由
Queues:队列,即消息队列,消息存放在队列中,等待消费,消费后被移除队列。
Queues 队列
该栏目展示的是消息队列的信息,里面有各个队列的概要信息,也可以在此栏目添加队列Queue。
Java代码集成
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
配置
配置类
@Configuration
public class RabbitMQConfig {
/**
* 发送消息JSON序列化
*/
/*@Override
public void afterPropertiesSet() {
//使用JSON序列化
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
}*/
//队列名称
public static final String LOCK_PRECORD = "lockRrecord";
@Bean("lockRrecord")
public Queue itemQueue() {
return new Queue(LOCK_PRECORD, true);
}
@Bean
public MessageConverter messageConverter() {
ContentTypeDelegatingMessageConverter messageConverter = new ContentTypeDelegatingMessageConverter();
messageConverter.addDelegate(MediaType.APPLICATION_JSON_VALUE, new Jackson2JsonMessageConverter());
messageConverter.addDelegate(MediaType.TEXT_PLAIN_VALUE, new SimpleMessageConverter());
return messageConverter;
}
}
监听
@Component
public class RabbitReceive {
private static final Logger LOGGER = LoggerFactory.getLogger(RabbitReceive.class);
@Resource
private XmSjDao xmSjDao;
@RabbitListener(queues = "xmEquipmentQueue")
public void xmCameraQueue(String data) {
String id = UUID.randomUUID().toString();
JSONObject parseObject = JSONObject.parseObject(data);
String baseValue = parseObject.get("base_value").toString();
String stid = parseObject.get("stid").toString();
baseValue = base64ImageUtils.uploadFile(baseValue);
//查看该图片是否已经存过
/*Integer count = xmSjDao.getCam(stid, baseValue);
if (count == null || count == 0) {
xmSjDao.insertCam(id, stid, baseValue);
} else {
xmSjDao.updateCam(stid, baseValue);
}*/
xmSjDao.insertCam(id, stid, baseValue);
}
}