Springboot 2.x 结合 activeMQ

ActiveMQ

消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题。实现高性能,高可用,可伸缩和最终一致性架构。是大型分布式系统不可缺少的中间件。消息形式支持点对点和订阅-发布

  1. ActiveMQ是消息队列技术,为解决高并发问题而生
  2. ActiveMQ生产者消费者模型(生产者和消费者可以跨平台、跨系统)
  3. ActiveMQ支持如下两种消息传输方式

什么是中间件(MOM)

由于业务、结构和技术是不断变化的,因此为其服务的软件系统就涉及到了整合。在整合、添加服务或扩展可用服务之后,就要考虑服务间消息传递的成本。所以需要增加一些组件去提供一个允许它们进行通信(不考虑它们之间的差异)的层。该层被称作中间件。
在这里插入图片描述

MOM的基本功能:

将信息以消息的形式,从一个应用程序传输到另一个或者多个应用程序。

mMOM主要特点:

消息异步接受:类似于手机短信的行为,消息发送者不需要等待消息接受者的响应,减少软件多系统集成的耦合度;

消息可靠接受:确保消息在消息中间件可靠保存,只要接受方接受到消息后才可删除,多个消息也可以组成原子事务

JMS

JMS即Java消息服务(Java Message
Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Java消息服务是一个与具体平台无关的API,绝大多数MOM提供商都对JMS提供支持。

消息传递模式
在这里插入图片描述
关键接口:

  1. ConnectionFactory:用于创建连接到消息中间件的连接工厂。
  2. Connection:代表了应用程序和服务之间的连接通路。
  3. Destination:指消息发布的地点,包括队列模式和主体模式。
  4. Session:表示一个单线程的上下文,用于发送和接受消息。
  5. MessageConsumer:由会话创建,用于接受发送到目的的消息。
  6. MessageProducer:由会话创建,用于发送消息。
  7. Message:是在消费者和生产者之间传递的对象,消息头,一组消息属性,和一个消息体。

引用

MQ(消息队列)

MQ全称为Message Queue,消息队列(MQ)是正确而又完整的 JMS 实现,消息队列(MQ)是一种应用程序对应用程序的通信方法。应用程序通过写和检索出入列队的针对应用程序的数据(消息)来通信,而无需专用连接来链接它们。消息传递指的是程序之间通过在消息中发送数据进行通信,而不是通过直接调用彼此来通信,直接调用通常是用于诸如远程过程调用的技术。

在这里插入图片描述

  1. Producer:消息生产者,负责产生和发送消息到 Broker;
  2. Broker:消息处理中心。负责消息存储、确认、重试等,一般其中会包含多个 queue;
  3. Consumer:消息消费者,负责从 Broker 中获取消息,并进行相应处理;
应用场景:

小明去书店买三本书,然后去吃饭
同步处理的情况下:小明要一本一本的买
消息队列:小明告诉店员,他要哪三本书,然后等他吃完饭在回来拿就好
则店员就是消息队列,只需要告诉他你要做的事情,放进队列,然后处理就好

解耦性场景:
看到公司游戏广告的用户从点击广告页面推送给我方,除了相对应的福利外,首先判断是否有注册过或者玩过本公司游戏,
没有给予注册,但是点击的消息也要传到对应的计算服务里面。
同步:如果点击信息的服务挂了,影响判断是否注册,过度依赖其余系统
消息队列:写入消息队列,不影响主体业务

秒杀:
**加粗样式**
为了更加直观的展示MQ的应用场景,这里我们就用一个常见的电商系统中的几个业务,来具体说明下MQ在实际开发中应用场景。
我们的实际场景大概是一个基于微服务架构的电商系统,分为用户微服务、商品微服务、订单微服务、促销微服务等。基于微服务模式开发的系统,MQ的使用场景更多,下面我们逐一说明:

  1. 注册后我们可能需要做很多初始化的操作,如:调用邮件服务器发送邮件、调用促销服务赠送优惠劵、下发用户数据到客户关系系统等。那么这时候我们将这些操作去监听MQ,当用户注册成功过后,通过MQ通知其他业务进行操作。确保注册用户的性能。
  2. 后台发布商品的时候,商品数据需要从数据库中转换成搜索引擎数据(基于elasticsearch),那么我们应该将商品写入数据库后,再写入到MQ,然后通过监听MQ来生成elasticsearch对应的数据。
  3. 用户下单后,24小时未支付,需要取消订单。以前我们可能是定时任务循环查询,然后取消订单。实际上,我更推荐类似延迟MQ的方式,避免了很多无效的数据库查询,将一个MQ设置为24小时后才让消费者消费掉,这样很大程度上能减轻服务器压力。
  4. 支付完成后,需要及时的通知子系统(进销存系统发货,用户服务积分,发送短信)进行下一步操作,但是,支付回调我们都是需要保证高性能的,所以,我应该直接修改数据库状态,存入MQ,让MQ通知子系统做其他非实时的业务操作。这样能保证核心业务的高效及时。

MQ框架比较

在这里插入图片描述

ActiveMQ的消息传递模式

在这里插入图片描述

P2P (点对点)消息域使用 queue 作为 Destination,消息可以被同步或异步的发送和接收,每个消息只会给一个 Consumer 传送一次。
Pub/Sub(发布/订阅,Publish/Subscribe)消息域使用 topic 作为 Destination,发布者向 topic 发送消息,订阅者注册接收来自 topic 的消息。发送到 topic 的任何消息都将自动传递给所有订阅者。接收方式(同步和异步)与 P2P 域相同。

安装与下载(windows)

  1. 官方网站下载:http://activemq.apache.org/activemq-5154-release.html
  2. 解压后,根据操作系统的位数选择,用管理员身份打开activemq.bat(直接启动)或者installService.bat(使用服务启动)
    在这里插入图片描述
  3. 登录http://localhost:8161/admin/,如果打开则为成功,账号密码都是admin

在这里插入图片描述
解释下上面图片中控制台这些按钮的基本信息:

  1. Home:查看 ActiveMQ 的常见信息
  2. Queues:查看 ActiveMQ 的队列信息
  3. Topics:查看 ActiveMQ 的主题信息
  4. Subscribers:查看主题的订阅者信息
  5. Connections:查看 ActiveMQ 客户端的连接信息
  6. Network:查看 ActiveMQ 的网络信息
  7. Scheduled:查看 ActiveMQ 的定时任务
  8. Send:用于通过表单方式向队列或者主题发送具体的消息

ActiveMQ的相关概念

1.Destination

目的地,JMS Provider(消息中间件)维护,用于对Message进行管理的对象。

MessageProducer需要指定Destination才能发送消息,MessageConsumer需要指定Destination才能接收消息。

2.Producer

消息生成者(客户端,生成消息),负责发送Message到目的地。应用接口为MessageProducer

3.Consumer【Receiver】

消息消费者(处理消息),负责从目的地中消费【处理|监听|订阅】Message。应用接口为MessageConsumer

4.Message

消息(Message),消息封装一次通信的内容。常见类型有:StreamMessage、BytesMessage、TextMessage、ObjectMessage、MapMessage。

5.ConnectionFactory
链接工厂, 用于创建链接的工厂类型。 注意,不能和 JDBC 中的 ConnectionFactory 混
淆。

6.Connection
链接. 用于建立访问 ActiveMQ 连接的类型, 由链接工厂创建. 注意,不能和 JDBC 中的
Connection 混淆。

7.Session
会话, 一次持久有效有状态的访问. 由链接创建. 是具体操作消息的基础支撑。

8.Queue&Topic

Queue是队列目的地,Topic是主题目的地。都是Destination的子接口。

Queue特点:队列中的消息,默认只能有唯一的一个消费者处理。

Topic特点:主题中的消息,会发送给所有的消费者同时处理。只有在消息可以重复处理的业务场景中可使用。

9.PTP

Point to Point。点对点消息模型,就是基于Queue实现的消息处理方式。

10.PUB&SUB

Publish&Subscribe。消息的发布/订阅模型。是基于Topic实现的消息处理方式。

ActiveMQ的异常处理

  1. 如何防止消息重复发送
    解决方法:增加消息状态表,通俗来说就是一个账本,用来记录消息的处理状态,每次处理消息之前,都去状态表中查询一次,如果已经有相同的消息存在,那么不处理,可以防止重复发送
  2. 丢消息怎么办
    解决方案:用持久化消息【可以使用对数据进行持久化JDBC,AMQ(日志文件),KahaDB和LevelDB】,或者非持久化消息及时处理不要堆积,或者启动事务,启动事务后,commit()方法会负责任的等待服务器的返回,也就不会关闭连接导致消息丢失了。
  3. 持久化消息非常慢
    默认的情况下,非持久化的消息是异步发送的,持久化的消息是同步发送的,遇到慢一点的硬盘,发送消息的速度是无法忍受的。但是在开启事务的情况下,消息都是异步发送的,效率会有2个数量级的提升。所以在发送持久化消息时,请务必开启事务模式。其实发送非持久化消息时也建议开启事务,因为根本不会影响性能
  4. 服务挂掉
    这得从ActiveMQ的储存机制说起。在通常的情况下,非持久化消息是存储在内存中的,持久化消息是存储在文件中的,它们的最大限制在配置文件的节点中配置。但是,在非持久化消息堆积到一定程度,内存告急的时候,ActiveMQ会将内存中的非持久化消息写入临时文件中,以腾出内存。虽然都保存到了文件里,但它和持久化消息的区别是,重启后持久化消息会从文件中恢复,非持久化的临时文件会直接删除

-------------------与springboot 2.0的整合------------------

pom.xml
<?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">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.alipay</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.2</version>
                <configuration>
                    <configurationFile>src/main/resources/mybatis-generator-config.xml</configurationFile>
                    <verbose>true</verbose>
                    <overwrite>true</overwrite>
                </configuration>
                <executions>
                    <execution>
                        <id>Generate MyBatis Artifacts</id>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>org.mybatis.generator</groupId>
                        <artifactId>mybatis-generator-core</artifactId>
                        <version>1.3.2</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>

    </build>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
<!-- 消息队列-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-activemq</artifactId>
        </dependency>
        <!--消息队列连接池-->
        <dependency>
            <groupId>org.apache.activemq</groupId>
            <artifactId>activemq-pool</artifactId>
            <version>5.15.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jms</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.activemq</groupId>
            <artifactId>activemq-client</artifactId>
        </dependency>
     </dependencies>
 </project>     
yml配置文件
#日志
logging:
  level:
    com:
      alipay:
        demo:
          mapper: debug
          quartz: debug
#  file: quartz-service.log


#端口
server:
  port: 8090
  tomcat:
    uri-encoding: utf-8


spring:
  activemq:
    user: admin
    password: admin
     #true 表示使用内置的MQ,false则连接服务器
    in-memory: true
      # 给java用的tcp端口是61616
    broker-url: tcp://127.0.0.1:61616
      #需要加入配置文件,支持发布订阅模型,默认只支持点对点
    jms:
      pub-sub-domain: true
      pool:
      #true表示使用连接池;false时,每发送一条数据创建一个连接
      enabled: true
      #最大连接数
      max-connections: 50
      packages:
        trust-all: true



生产者(producter)

BaseProducer.java

package com.hiqiblog.queue.producer;

import com.hiqiblog.queue.Producer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;

public abstract class BaseProducer implements Producer {

    @Autowired
    private JmsMessagingTemplate jmsTemplate;

    @Override
    public void enqueue(String message) {
        this.enqueue(this.getQueueName(), message);
    }

    protected void enqueue(String destinationName, String message) {
        jmsTemplate.convertAndSend(destinationName, message);
    }

    protected abstract String getQueueName();
}


Producer.java


package com.hiqiblog.queue;

/**
 * @Author helloc
 * @Date 2019/7/26 9:12
 * @Version 1.0
 */
public interface Producer {

    void enqueue(String message);

}

AppQueueProducer.java

/**
 * @Author helloc
 * @Date 2019/7/26 9:12
 * @Version 1.0
 */
@Component
public class AppQueueProducer extends BaseProducer {

    public final static String APP_QUEUE = "helloq";

    @Override
    protected String getQueueName() {
        return APP_QUEUE;
    }

}

controller

package com.hiqiblog.controller;

import com.hiqiblog.ViewModel.ResponseMessage;
import com.hiqiblog.queue.producer.BaseProducer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.jms.*;

/** 功能描述:模拟微信支付回调 @Author helloc @Date 2019/7/25 14:00 @Version 1.0 */
@RestController
@RequestMapping("/api/v1")
public class OrderController {

  @Autowired private BaseProducer baseProducer;
  /**
   * 功能描述:微信支付回调接口
   *
   * @param msg 支付信息
   * @return
   */
  @GetMapping("order")
  public Object order(String msg) {
    baseProducer.enqueue(msg);
    ResponseMessage responseMessage = new ResponseMessage();
    responseMessage.setCode("1");
    responseMessage.setMsg("Success");
    return responseMessage;
  }

  @GetMapping("common")
  public Object common(String msg) {
    baseProducer.enqueue(msg);
    ResponseMessage responseMessage = new ResponseMessage();
    responseMessage.setCode("1");
    responseMessage.setMsg("Success");
    return responseMessage;
  }
}


调用

在这里插入图片描述

结果:

在这里插入图片描述

消费者:
package com.hiqiblog.queue.consumer;

import com.hiqiblog.queue.Consumer;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;

/**
 * @Author helloc
 * @Date 2019/7/26 11:40
 * @Version 1.0
 */

@Component
public class AppQueueConsumer {
    @Override
    @JmsListener(destination = "helloq")
    public void dequeue(String message) {
         System.out.println("报文为:"+message);
    }

}

两种通讯模式

  1. point-to-point:点对点(queue)
  2. publish/subscribe发布/订阅模式(topic)
queue 特点

1)一个消息只能被一个服务接收

2)消息一旦被消费,就会消失

3)如果没有被消费,就会一直等待,直到被消费

4)多个服务监听同一个消费空间,先到先得

topic 特点

1)一个消息可以被多个服务接收

2)订阅一个主题的消费者,只能消费自它订阅之后发布的消息。

3)消费端如果在生产端发送消息之后启动,是接收不到消息的,除非生产端对消息进行了持久化(例如广播,只有当时听到的人能听到信息)

queue 模式

点对点通信,每个消息只有一个消费者,消息保证送达,离线消费者可以在下次上线后收到之前积压的消息

topic 模式

普通订阅

不区分消费者,当前有几个客户端在线,就发送几条广播给客户端。

持久订阅

区分消费者,消费者在线则直接发送消息广播给消费者,消费者离线,只要该消费者有topic登记,就会为其保留消息直至其再次连接后一次性推送,消息可以积压。

扩展

如何保证消息队列的高可用性?

  1. RabbitMQ有三种模式:单机模式,普通集群模式(启动多个实例,每个实例都同步queue元数据),镜像集群模式(你创建的queue,无论元数据还是queue里的消息都会存在于多个实例上,然后每次你写消息到queue的时候,都会自动把消息到多个实例的queue里进行消息同步)
  2. kafka
    Kafka天生就是一个分布式的消息队列,它可以由多个broker组成,每个broker是一个节点;你创建一个topic,这个topic可以划分为多个partition,每个partition可以存在于不同的broker上,每个partition就放一部分数据。
    kafka 0.8以后,提供了HA机制,就是replica副本机制。kafka会均匀的将一个partition的所有replica分布在不同的机器上,来提高容错性。每个partition的数据都会同步到吉他机器上,形成自己的多个replica副本。然后所有replica会选举一个leader出来,那么生产和消费都去leader,其他replica就是follower,leader会同步数据给follower。当leader挂了会自动去找replica,然后会再选举一个leader出来,这样就具有高可用性了。
    写数据的时候,生产者就写leader,然后leader将数据落地写本地磁盘,接着其他follower自己主动从leader来pull数据。一旦所有follower同步好数据了,就会发送ack给leader,leader收到所有follower的ack之后,就会返回写成功的消息给生产者。(当然,这只是其中一种模式,还可以适当调整这个行为)
    消费的时候,只会从leader去读,但是只有一个消息已经被所有follower都同步成功返回ack的时候,这个消息才会被消费者读到。

如何保证消息消费时的幂等性?
RabbitMQ是ack,Kafka是offset

如何保证消息的可靠性传输?

  1. RabbitMQ
    生产者弄丢了数据:
    ①事务机制:发送消息,如果消息没有成功被RabbitMQ接收到,那么生产者会收到异常报错,此时就可以回滚事务(channel.txRollback),然后重试发送消息
    ②confirm机制:开启confirm模式,在生产者那里设置开启confirm模式之后,你每次写的消息都会分配一个唯一的id,然后如果写入了RabbitMQ中,RabbitMQ会给你回传一个ack消息,告诉你说这个消息ok了。如果RabbitMQ没能处理这个消息,会回调你一个nack接口,告诉你这个消息接收失败,你可以重试
    RabbitMQ弄丢了数据:
    开启RabbitMQ的持久化,消息写入磁盘
    第一个是创建queue的时候将其设置为持久化的,这样就可以保证RabbitMQ持久化queue的元数据,但是不会持久化queue里的数据;第二个是发送消息的时候将消息的deliveryMode设置为2,就是将消息设置为持久化的,此时RabbitMQ就会将消息持久化到磁盘上去。必须要同时设置这两个持久化才行
    消费端弄丢了数据:
    RabbitMQ提供的ack机制,简单来说,就是你关闭RabbitMQ自动ack,可以通过一个api来调用就行,然后每次你自己代码里确保处理完的时候,再程序里ack一把。这样的话,如果你还没处理完,不就没有ack?那RabbitMQ就认为你还没处理完,这个时候RabbitMQ会把这个消费分配给别的consumer去处理,消息是不会丢的
  2. kafka
    生产者弄丢了数据:
    如果按照上述的思路设置了ack=all,一定不会丢,要求是,你的leader接收到消息,所有的follower都同步到了消息之后,才认为本次写成功了。如果没满足这个条件,生产者会自动不断的重试,重试无限次
    Kafka弄丢了数据:
    给这个topic设置replication.factor参数:这个值必须大于1,要求每个partition必须有至少2个副本。
    - 在kafka服务端设置min.insync.replicas参数:这个值必须大于1,这个是要求一个leader至少感知到有至少一个follower还跟自己保持联系,没掉队,这样才能确保leader挂了还有一个follower吧。
    - 在producer端设置acks=all:这个是要求每条数据,必须是写入所有replica之后,才能认为是写成功了。
    - 在producer端设置retries=MAX(很大很大很大的一个值,无限次重试的意思):这个是要求一旦写入失败,就无限重试,卡在这里了。
    消费端弄丢了数据:
    kafka会自动提交offset,那么只要关闭自动提交offset,在处理完之后自己手动提交offset,就可以保证数据不会丢
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值