rabbitmq使用笔记

场景

公司的数据,之前是采用定时任务进行同步,优点是技术比较成熟,没有学习成本。
但是也有不足:
数据无论如何会有延迟。
过多定时任务也会加大服务器压力。
定时任务是单线程的,如果其中某条记录报错,处理不好,会阻断整个定时任务。

用mq可以很好的解决上述问题。

集成过程

引入依赖

pom.xml中加入:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

application.properties中增加属性

注:以下属性不是自动注入的(不像datasource的属性),是需要手动引入的

spring.rabbitmq.host=192.168.10.11
spring.rabbitmq.port=5672
spring.rabbitmq.userName=root
spring.rabbitmq.passWord=1234
# virtualHost起到分隔的作用,因为一台机器可能供多个系统使用,难到要每个服务一台机器?
spring.rabbitmq.virtualHost=ticket  

RabbitConfig代码

这个类主要配置连接的作用,以及创建连接池:

/**
 *  rabbitmq的主配置类
 *  主要定义链接和虚拟机
 */
@Configuration
@EnableRabbit // @EnableRabbit 这个注解一定要加上,表示开启rabbitmq服务,不一定在application类上,任意configuration类即可
public class RabbitConfig {
    @Value("${spring.rabbitmq.exchangeName}")
    private String exchangeName;  // 交换机其实不属于这个connection级别
    @Value("${spring.rabbitmq.host}")
    private String host;
    @Value("${spring.rabbitmq.port}")
    private Integer port;
    @Value("${spring.rabbitmq.userName}")
    private String userName;
    @Value("${spring.rabbitmq.passWord}")
    private String passWord;
    @Value("${spring.rabbitmq.virtualHost}")
    private String virtualHost;

    @Bean
    public CachingConnectionFactory connectionFactory() {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory();// 这里也可以写,host,但是为了一致,在下面写了
        connectionFactory.setHost(host);
        connectionFactory.setUsername(userName);
        connectionFactory.setPassword(passWord);
        connectionFactory.setPort(port);
        connectionFactory.setVirtualHost(virtualHost);
        return connectionFactory;
    }

    @Bean
    public AmqpAdmin amqpAdmin() {
        return new RabbitAdmin(connectionFactory());
    }

    /* 重写rabbitTemplate模板 配置上必须的参数*/
    // 这个待考证,实测无用  不如配置Message里面的MessageProperties
    @Bean
    public RabbitTemplate rabbitTemplate() {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory());
        // 公司必须设置的参数-个性化参数
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setAppId("ali"); // 表示是从哪个系统发送的
        messageProperties.setContentType("text/plain");
        String messageId = UUID.randomUUID().toString();
        messageProperties.setMessageId(messageId);
        messageProperties.setContentEncoding("UTF-8");
        messageProperties.setType("uat.crm");
        messageProperties.setTimestamp(new Date());
        MessagePropertiesConverter messagePropertiesConverter = new DefaultMessagePropertiesConverter();
        messagePropertiesConverter.fromMessageProperties(messageProperties,"UTF-8");
        rabbitTemplate.setMessagePropertiesConverter(messagePropertiesConverter);
        return rabbitTemplate;
    }

    //配置消费者监听的容器
    @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory());
        factory.setConcurrentConsumers(3);
        factory.setMaxConcurrentConsumers(10);
        //factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);//设置确认模式手工确认
        return factory;
    }
}

DirectExchangeConfig代码

mq有多种发送规则。这里用direct模式。他和topic(订阅模式) 区别就是topic支持正则。

/**
 *  direct交换机的规则在这里配置
 */
@Configuration
public class DirectExchangeConfig {
    /**
     * direct和topic的区别就是topic支持正则,如果规则不很复杂,direct就足够
     * 需要多个交换器么,如果规则不复杂,不用多个交换器
     */
    @Bean
    public DirectExchange direct() {
        DirectExchange directExchange = new DirectExchange("direct");
        return directExchange;
    }

    @Bean
    public Queue mail() {
        Queue queue = new Queue("mail"); // 对列名称要和listener一致
        return queue;
    }


    @Bean
    public Queue short() {
        Queue queue = new Queue("short");
        return queue;
    }

    // bingding的作用是设置规则
    // queue和规则是多对多关系
    @Bean
    public Binding bindMailWithChecked(DirectExchange direct, Queue mail) {
        // 为什么要用with,因为推送方可能推送多种,如yanzhen,renzheng,yanjia等,不只可以推送一条消息
        return BindingBuilder.bind(mail).to(direct).with(RoutingKey.MAIL);
    }

    @Bean
    public Binding bindMailWithDeducted(DirectExchange direct, Queue mail) {
        return BindingBuilder.bind(mail).to(direct).with(RoutingKey.SHORT);
    }

    @Bean
    public Binding bindShortWithChecked(DirectExchange direct, Queue short) {
        return BindingBuilder.bind(short).to(direct).with(RoutingKey.MAIL);
    }

    @Bean
    public Binding bindShortWithDeducted(DirectExchange direct, Queue short) {
        return BindingBuilder.bind(short).to(direct).with(RoutingKey.SHORT);
    }
}

订阅模式代码

订阅模式其实也比较简单。可以通过路由实现分流。

@Configuration
public class SubscribeExchangeConfig {

    @Bean
    public AExchange aExchange() {
        FanoutExchange fanoutExchange = new FanoutExchange("aExchange");
        return fanoutExchange;
    }

    @Bean
    public Queue mail() {
        Queue queue = new Queue("mail"); // 对列名称要和listener一致
        return queue;
    }


    @Bean
    public Queue short() {
        Queue queue = new Queue("short");
        return queue;
    }

    // bingding的作用是设置规则
    // queue和规则是多对多关系
    @Bean
    public Binding bindMailWithAExchange(FanoutExchange aExchange, Queue mail) {
         return BindingBuilder.bind(mail).to(aExchange);
    }

    @Bean
    public Binding bindShortWithAExchange(FanoutExchange aExchange, Queue short) {
         return BindingBuilder.bind(short).to(aExchange);
    }
}

Service接口和实现类

DirectExchangeService接口:

public interface DirectExchangeService {
    public void sendMessage(String routingKey,String message);
}

DirectExchangeServiceImpl实现类:

@Service
public class DirectExchangeServiceImpl implements DirectExchangeService {
    private static Logger logger = LoggerFactory.getLogger(DirectExchangeServiceImpl.class);
    @Autowired
    RabbitTemplate rabbitTemplate;
    @Autowired
    private DirectExchange directExchange;
    @Override
    public void sendMessage(String routingKey,String message) {
        try {
            logger.info("mq准备推送数据,routingKey: {} ,message: {}",RoutingKey.MAIL, JSON.toJSONString(message));
            rabbitTemplate.convertAndSend(directExchange.getName(), RoutingKey.MAIL, message);
        }catch (Exception e){
            logger.error("mq推送异常,routingKey: {} ",RoutingKey.MAIL,e);
        }
    }
}

routingKey 路由字典表

/**
 *  routingkey建议使用字典表,作用至少有2点:
 *  1、后续可能有多个routingkey,便于维护
 *  2、send和binding中用的routingkey往往不在同一类中,用string容易错漏。
 */
public class RoutingKey {
    public static String MAIL="mail"; // 邮件
    public static String SHORT="short"; // 短信
}

SpringBootApplication启动类

注:@EnableRabbit注解可以加在这里,也可以加载configuration类上,但是一定要记得加。

@SpringBootApplication
public class OaSpringBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(OaSpringBootApplication.class, args);
    }
}

MqController代码

用来发送请求,惭愧一直都不太会mock的测试方法,就用请求把。

@Controller
@RequestMapping("mq")
class MqController {
    @Autowired
    private DirectExchangeService directExchangeService;
    @ResponseBody
    @RequestMapping("/send")
    public String send(){
        String message="1234已发邮件";
        directExchangeService.sendMessage(RoutingKey.MAIL,message);
        return "success";
    }
    @ResponseBody
    @RequestMapping("/send2")
    public String send2(){
        String message="666已发短信";
        directExchangeService.sendMessage(RoutingKey.SHORT,message);
        return "success";
    }
}

QueueListener类

listener监听,相当于消费者。
本例子集成在一个项目中了,实际listener往往单独作为一个项目。
消费者端只需要RabbitConfig和QueueListener即可。

/**
 *  监听queue队列并实现消费,推送端不需要配置listener,,因为它会消费掉队列
 *  推送端请记得注释掉这个类
 */
@Component
public class QueueListener {
    private static Logger logger = LoggerFactory.getLogger(QueueListener.class);
    @RabbitListener(queues = "mail")
    public void mailListener(String message) {
        logger.info("mail队列收到消息: {}", message);
        // TODO 更新数据库等操作
    }

    @RabbitListener(queues = "short")
    public void busindessListener(String message) {
        logger.info("short队列收到消息: {}", message);
        // TODO 更新数据库等操作

    }
}

验证方法

发送mqController的请求 localhost:8080/mq/send
打印listener监听到的日志即表示成功。

拓展问题:是否可以通过配置的形式,不消费消息

@RabbitListener 里面是有这个方法的,exclusive(排除)方法。
true 排除消费
false 不排除消费(默认)
boolean exclusive() default false;

问题是如何通过配置的形式注入呢?例如@Value。
注解里面不能使用@Value。
如果是在这里面写true或false,不能适配各个环境。和直接注释掉代码的方式是一个意思。

单交换机和多交换机

单交换机的确定是,如果交换机相同,routing_key也相同。 那么所有符合该规则的都可以接受到请求。细粒度不够。

多交换机配合多routing_key,组合方式可以可以有 m*n种,细粒度完全足够。

如果情况不是很多,很有一种比较无脑的方式就是不用routing_key,直接交换机对应各种情况,也是可以满足需求的。

如何设计

3个主要元素为(交换机*key一般是总数):
交换机
routingKey
queue

例如有2个业务,3个用户
对比以下几种实现方法。

1交换机,6key,6queue

功能可以实现,不过要在key上花功夫

2交换机,3key,6queue或者3queue

较为合理,是2*3的数量

6交换机,不用key,6queue,或者3queue

心有点大额,如果有10个业务,1000个用户,难道要10000个交换机。

6个queue和3个queue的区别就是,3个queue需要传递业务类型,否则message不知道如何处理,如果是6个queue那就是单业务了。

topic模式的优点

建议使用topic模式,因为兼容性强。
他比driect模式,支持正则,使用更灵活。
比fanout细粒度更高,而且也可以当fanout模式来用,方法就是用* 绑定,那么无论什么key,都会推送到queue。就相当于fanout模式。

Message里面配置MessageProperties的代码

public MessageProperties generateMessageProperties(){
    MessageProperties messageProperties = new MessageProperties();
    messageProperties.setAppId("crm");
    messageProperties.setContentType("text/plain");
    String messageId = UUID.randomUUID().toString();
    messageProperties.setMessageId(messageId);
    messageProperties.setContentEncoding("UTF-8");
    messageProperties.setType("uat.crm");
    messageProperties.setTimestamp(new Date());
    return messageProperties;
}

public void test(){
	// 新建Message对象的时候,加入属性
	Message message=new Message("".getBytes(), generateMessageProperties());
}

listener可以直接接受Message对象么

可以,以下2种写法都可以:

@RabbitListener(queues = "mail")
public void aListener(Message message) {
    logger.info("收到消息: {}", message);
    // TODO 更新数据库等操作
}

@RabbitListener(queues = "shortMessage")
public void bListener(String message) {
    logger.info("收到消息: {}", message);
    // TODO 更新数据库等操作
}

channel和queue的区别

其实这2个不是一个维度。channel 由 connection 创建,channel可以发送queue。
他的主要作用相当于提高性能,因为如果每发送一个queue就创建一个connection,太浪费资源,但是我用channel,就可以保持连接不变的情况下,发送多个queue。

概念

基本元素

虚拟机(以及用户)
通道(channel)
路由(交换机)(exchange)
队列(queue)

从高到低基本是这么个层级。

mq自动重连

其实就是几行配置,也记录下吧。

connectionFactory.getRabbitConnectionFactory().setAutomaticRecoveryEnabled(true);
connectionFactory.getRabbitConnectionFactory().setRequestedHeartbeat(60);//60秒
connectionFactory.getRabbitConnectionFactory().setConnectionTimeout(60000);//60秒
connectionFactory.getRabbitConnectionFactory().setHandshakeTimeout(10000);//10秒
connectionFactory.getRabbitConnectionFactory().setShutdownTimeout(10000);//10秒
connectionFactory.getRabbitConnectionFactory().setRequestedChannelMax(512);//最大设置512,rabbitmq的server端也会做限制最大
connectionFactory.getRabbitConnectionFactory().setNetworkRecoveryInterval(10000);//10秒
mq是http请求吗

不是,是基于长连接的,所以不是http请求。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值