阿里云AMQP与SpringBoot配置实践

规范要求

  • 命名规范:生产者、消费者、事件、消息 定义

  • 生产和非生产 账户隔离;   
  • virtual-host区分开发dev、生产prod、测试test (virtual-host支持最多10个,项目多了支持不了)
  • 针对某些场景发送量较大,如日志、短信、邮件等非核心业务服务消息,生产可独立账户或virtual-host(避免影响业务数据)
  • exchange  路由命名: 路由类型.生产者名( amq.topic.oa )  ,阿里云支持3中类型(direct【 Binding Key = Routing Key ,适用于区分发往同一个 Exchange】,topic【 Binding Key 和 Routing Key 的通配符匹配路由消息至 Queue】,fanout【将消息路由至所有绑定的 Queue】)
  • routing key 命名:    生产者名.事件名; 
  • queue 队列命名:消费者名.模型名.生产者名; 
  • bindingkey:  生产者名.事件名(=routingkey)
  • 发送端注意发送内容相同,但事件、routing key不一样;消费者消费一样的内容,但有多个事件需要进行绑定;

 

Spring Boot 配置

 

pom.xml中引入如下依赖内容

 

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-amqp</artifactId>

</dependency>

<dependency>

    <groupId>com.alibaba.mq-amqp</groupId>

    <artifactId>mq-amqp-client</artifactId>

    <version>1.0.4</version>

</dependency>

 

spring.rabbitmq.host=1618060924840827.mq-amqp.cn-shenzhen-a.aliyuncs.com #地址

spring.rabbitmq.port=5672 #端口

spring.rabbitmq.username=LTAI4FchjfDJ5L54MRr6DngE 用户名

spring.rabbitmq.password=****** 密码

spring.rabbitmq.virtual-host=hscf-oa 连接到AMQP的那个vhost 

spring.rabbitmq.exchange-name=amq.topic Exchang名称 会有多种Exchang.

#-----发送方的确认

spring.rabbitmq.publisher-confirms=true   # 是否启用【发布确认】

spring.rabbitmq.publisher-returns=true    # 是否启用【发布返回】

spring.rabbitmq.template.mandatory=true   # 触发returnedMessage回调必须设置mandatory=true, 否则Exchange没有找到Queue就会丢弃掉消息, 而不会触发回调

#-------------消费端配置

spring.rabbitmq.listener.simple.acknowledge-mode=manual # 设置手动确认(ack) Queue -> C 消费端配置

spring.rabbitmq.listener.simple.concurrency=1 #最小的消费者数量

spring.rabbitmq.listener.simple.max-concurrency=5 #最大的消费者数量

# spring.rabbitmq.listener.simple.default-requeue-rejected=true #消费者消费失败,自动重新入队

配置权限认证相关类,创建一个 AliyunCredentialsProvider类实现CredentialsProvider类

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

/**

 * @author wangmingsen

 * @create 2019-09-21-10:17

 **/

 

public class AliyunCredentialsProvider implements CredentialsProvider {

    /**

     * Access Key ID.

     */

    private final String accessKeyId;

    /**

     * Access Key Secret.

     */

    private final String accessKeySecret;

    /**

     * security temp token. (optional)

     */

    private final String securityToken;

    /**

     * 资源owner账号(主账号)

     */

    private final long resourceOwnerId;

 

    public AliyunCredentialsProvider(final String accessKeyId, final String accessKeySecret,

                                     final long resourceOwnerId) {

        this(accessKeyId, accessKeySecret, null, resourceOwnerId);

    }

 

    public AliyunCredentialsProvider(final String accessKeyId, final String accessKeySecret,

                                     final String securityToken, final long resourceOwnerId) {

        this.accessKeyId = accessKeyId;

        this.accessKeySecret = accessKeySecret;

        this.securityToken = securityToken;

        this.resourceOwnerId = resourceOwnerId;

    }

 

    @Override

    public String getUsername() {

        if(StringUtils.isNotEmpty(securityToken)) {

            return UserUtils.getUserName(accessKeyId, resourceOwnerId, securityToken);

        else {

            return UserUtils.getUserName(accessKeyId, resourceOwnerId);

        }

    }

 

    @Override

    public String getPassword() {

        try {

            return UserUtils.getPassord(accessKeySecret);

        catch (InvalidKeyException e) {

            //todo

        catch (NoSuchAlgorithmException e) {

            //todo

        }

        return null;

    }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

/**

 * @author wangmingsen

 * @create 2019-09-21-10:15

 **/

 

@Configuration

public class RabbitConfig {

 /**

 * 资源owner账户 ID 信息

 */

 private static final long RESOURCE_OWNER_ID =1618060924840827L;

 

 @Autowired

 private RabbitProperties rabbitProperties;

 

 @Bean

 public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {

 RabbitTemplate template = new RabbitTemplate(connectionFactory);

 template.setMessageConverter(new Jackson2JsonMessageConverter());

 return template;

 }

 

 @Bean

 public ConnectionFactory connectionFactory() {

 com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory =

 new com.rabbitmq.client.ConnectionFactory();

 rabbitConnectionFactory.setHost(rabbitProperties.getHost());

 rabbitConnectionFactory.setPort(rabbitProperties.getPort());

 rabbitConnectionFactory.setVirtualHost(rabbitProperties.getVirtualHost());

 

 AliyunCredentialsProvider credentialsProvider = new AliyunCredentialsProvider(

 rabbitProperties.getUsername(), rabbitProperties.getPassword(), RESOURCE_OWNER_ID);

 rabbitConnectionFactory.setCredentialsProvider(credentialsProvider);

 rabbitConnectionFactory.setNetworkRecoveryInterval(5000);

 rabbitConnectionFactory.setConnectionTimeout(300000);

 rabbitConnectionFactory.setHandshakeTimeout(300000);

 rabbitConnectionFactory.setShutdownTimeout(0);

 CachingConnectionFactory connectionFactory = new CachingConnectionFactory(rabbitConnectionFactory);

 connectionFactory.setPublisherConfirms(rabbitProperties.isPublisherConfirms());

 connectionFactory.setPublisherReturns(rabbitProperties.isPublisherReturns());

 return connectionFactory;

 }

}

创建消息生产者,通过注入RabbitTemplate实例来实现消息的发送

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

@Autowired

RabbitTemplate rabbitTemplate;

@PostMapping("/test")

public void test(){

 

    this.rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {

        if (!ack) {

            System.out.println("HelloSender消息发送失败" + cause + correlationData.toString());

        else {

            System.out.println("HelloSender 消息发送成功 " );

        }

    });

    Car car = new Car();

    //方式一发送

    Message message = MessageBuilder.withBody(JSON.toJSONString(car).getBytes()).build();

    message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT); //持久化

    rabbitTemplate.send(exchange,routingkey ,message);

    //方式二发送

    rabbitTemplate.convertAndSend(exchange,routingkey ,car,mes -> {

        mes.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);

        return mes;

    });

     

}

创建消费者来消费消息.。通过@RabbitListener注解定义该类对Queue队列的监听,通过@RabbitHandler注解来指定对消息的处理方法

1

2

3

4

5

6

7

8

9

10

11

12

13

RabbitHandler

@RabbitListener(queues = {"${spring.rabbitmq.queue.after-loan}"})

public void process(Message message, Channel channel) throws IOException {

    try{

        String carInfo = new String(message.getBody(), StandardCharsets.UTF_8);

        log.info("【MQ消息接收】xxx信息: {}", carInfo);

        Car car = JSON.parseObject(carInfo, Car.class);

        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); //确认消费

    catch (Exception e) {

        log.error("【MQ消息接收】xxxx处理异常", e);

        //channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);  //丢弃这条消息

     }

}

下面是几种Exchange类型,我们目前使用的是Topic Exchange.

Exchange 类型

本文介绍消息队列 AMQP 中不同类型的 Exchange 的路由规则及其使用场景,帮助您更好地根据使用场景选择合适的 Exchange 类型。

背景信息

Producer 将消息发送至 Exchange。Exchange 根据 Routing Key 和 Binding Key 路由消息。Routing Key 是 Producer 为消息添加的属性。Binding Key 是 Exchange 和 Queue 之间的绑定关系。不同类型的 Exchange 在路由消息时的规则不同,适用于不同的使用场景。

Direct Exchange

路由规则

Direct Exchange 将消息路由至 Binding Key 与消息的 Routing Key 完全匹配的 Queue。

使用场景

Direct Exchange 根据 Binding Key = Routing Key 的规则路由消息,适用于区分发往同一个 Exchange 的消息。

示例

图 1. Direct Exchange

图 1 所示,Direct Exchange 分别与 Queue A 和 Queue B 绑定。Consumer 可以从 Queue A 中拉取所有图片(img)创建(create)消息,从 Queue B 中拉取所有图片(img)日志(log)消息。
Producer 向 Direct Exchange 发送消息:

  • 消息 A 的 Routing Key 为 img.create。
  • 消息 B 的 Routing Key 为 img.log。


Direct Exchange 绑定 Queue:

  • Direct Exchange 和 Queue A 的 Binding Key 为 img.create。
  • Direct Exchange 和 Queue B 的 Binding Key 为 img.log。


Direct Exchange 根据 Binding Key = Routing Key 的规则路由消息:

  • Routing Key 为 img.create 消息 A 路由至 Binding Key 为 img.create 的 Queue A。
  • Routing Key 为 img.log 消息 B 路由至 Binding Key 为 img.log 的 Queue B。

 

Topic Exchange

路由规则

Topic Exchange 根据 Binding Key 和 Routing Key 的通配符匹配路由消息至 Queue。

说明

  • 星号(*)代表一个单词(例如,cn)。
  • 井号(#)代表零个或多个单词(例如,cn.zj.hz)。

 

使用场景

Topic Exchange 使用通配符匹配的路由模式,适用于通过 Topic 对消息进行区分的场景。

示例

图 2. Topic Exchange

图 2 所示,Topic Exchange 分别与 Queue A 和 Queue B 绑定。Consumer 可以从 Queue A 中拉取所有中国(cn)杭州(hz)的文件(files),从 Queue B 中拉取所有中国(cn)商店(store)的文件(files)。
Producer 向 Topic Exchange 发送消息:

  • 消息 A 的 Routing Key 为 files.cn.hz。
  • 消息 B 的 Routing Key 为 files.cn.hz.store。
  • 消息 C 的 Routing Key 为 files.cn.sz.store。


Topic Exchange 绑定 Queue:

  • Topic Exchange 和 Queue A 的 Binding Key 为 files.cn.hz.#。
  • Topic Exchange 和 Queue B 的 Binding Key 为 files.cn.*.store。


Topic Exchange 根据通配符匹配的规则路由消息:

  • Routing Key 为 files.cn.hz 消息 A 路由至 Binding Key 为 files.cn.hz.# 的 Queue A。
  • Routing Key 为 files.cn.hz.store 消息 B 路由至 Binding Key 为 files.cn.hz.# 的 Queue A 和 Binding Key 为 files.cn.*.store 的 Queue B。
  • Routing Key 为 files.cn.sz.store 消息 C 路由至 Binding Key 为 files.cn.*.store 的Queue B。

 

Fanout Exchange

路由规则

Fanout Exchange 将消息路由至所有绑定的 Queue。

使用场景

Fanout Exchange 无视 Routing Key 和 Binding Key 的匹配规则,将消息投递至所有绑定的 Queue,适用于广播消息。

示例

图 3. Fanout Exchange

图 3 所示,Fanout Exchange 将消息投递至所有绑定的 Queue,Consumer 可以从所有 Queue 中拉取同一消息。

Producer 向 Fanout Exchange 发送消息:

消息 A 的 Routing Key 可以为任意值。

Fanout Exchange 绑定 Queue:

Fanout Exchange 和 Queue A、Queue B、Queue C 的 Binding Key 都可以为任意值。

Fanout Exchange 将消息路由至所有绑定的 Queue:

Routing Key 为任意值的消息 A 分别路由至 Binding Key 为任意值的 Queue A、Queue B、Queue C。

 

 

参考文献

阿里云AMQP官网

 

 

 

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A-ezra

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值