IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> 个人分析理解,项目到底需不需要用到MQ技术 -> 正文阅读

[大数据]个人分析理解,项目到底需不需要用到MQ技术

作者:token annotation punctuation

个人分析理解,项目到底需不需要用到MQ技术

订单项 -> 订单规格-> 履行集 -> 履行流程-> OPU

上面是公司根据订单业务抽象成的订单框架流程结构。

这里的OPU是最小执行单元,它可以是同步也可以是异步。

由于该项目没有秒杀方面的需求,MQ就是用来跨系统的异步通信。

这里的异步用到过两种技术,一开始是多线程(线程池) + 定时任务(Quartz),后来改为了 RabbitMQ。

由于涉及保密条例,这里用伪代码来表示下不用MQ的情况下,是如何实现与外围系统的异步通信

有几点要先注意下。

  1. 使用线程池需要注意线程池的数量设置,套用公式 U * U *(1 + w/c):CPU内核数 * 期待CPU利用率 * (1+ 单条线程等待时间/单条线程计算时间)
  2. 定时任务的循环周期需要确定好,曾经项目中设置为1分钟,后来根据每天的订单数量和订单里异步类型的通信数量,改周期为5分钟
  3. 消息发送失败的重试机制要想好,项目中一开始为了配套这块业务,主要是准备了两个重要的表:(1)订单号、存储报文、接口信息和重试次数等字段的quartz_info_request表 (2) 记录所有内调外或者外调内接口的日志信息表quartz_info_log ,里面含有订单号、接口信息等字段
/**
 * @Description:线程池配置
 * @author: 老街俗人
 */
@Configuration
public class ThreadPoolConfig {
@Bean
public ThreadPoolExecutor threadPoolExecutor{
    //核心线程数
    int corePoolSize = 50;
    //最大线程数
    int maximumPoolSize = 100;
    //超过 corePoolSize 线程数量的线程最大空闲时间
    long keepAliveTime =2;
    //以秒为时间单位
    TimeUnit unit = TimeUnit.SECONDS;
    //用于存放提交的等待执行任务
    BlokckingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(40);
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
    corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,
        //默认策略,在拒绝任务时抛出RejectedExecutionException
        new ThreadPoolExecutor.AbortPolicy());
    );
    return threadPoolExecutor;
}
}
/**
 * @Description:异步请求
 * @author: 老街俗人
 */
public class SendService{
    @Autowired
    ThreadPoolExecutor threadPoolExecutor;
    
    public void asyncSend(){
        threadPoolExecutor.submit(()->{
            //1.调用对应的接口,发送请求
            //2.把相应信息记录到quartz_info_request表里,重试次数设置为0,是否成功设置为false
        })
    }
}
/**
 * @Description:定时任务里负责扫描quartz_info_request和quartz_info_log表,并比对哪些接口需要重试
 * @author: 老街俗人
 */
public class Sendjob{
    public void retry(){
        //扫描quartz_info_request和quartz_info_log表
        //根据订单号匹配
        //quartz_info_log没有匹配到到第三方响应接口返回来的信息,且已经超时,则重试接口;
        //如果明确响应接口已经报错,则把quartz_info_request对应的数据的重试次数置为最大次数,把当前订单信息录入异常表,用于对账和人工修单
        //......
        //实际上还有很多的步骤需要考虑,这里为了简洁明了,就写了几个主要步骤
    }
    
}

这样线程池 + 定时任务,也可以做到异步通信的结果,至于订单的对账和修复,全在于设计消息发送失败机制的多方面考虑。

明显不那么容易把控

接下来再看看用了RabbitMQ后,带来的影响。

首先是代码:

/**
 * @Description:MQ配置,这里使用的是直联交换机
 * @author: 老街俗人
 */
@Configuration
public class RabbitConfig {
//队列的名字是DirectQueue
@Bean
public Queue DirectQueue(){
    //durable 设置是否持久化,默认为false,true会存储在硬盘
    return new Queue("DirectQueue",true);
}
//交换机的名字是MyDirectExchange
@Bean
public DirectExchange MyDirectExchange(){
    //durable 设置是否持久化,默认为false,true会存储在硬盘
    return new DirectExchange("MyDirectExchange",true,false);
}
//绑定,将队列和交换机绑定
@Bean
public Binding bindingDirect(){
    //durable 设置是否持久化,默认为false,true会存储在硬盘
    return BindingBuilder.bind(DirectQueue()).to(MyDirectExchange()).with("MyDirectRouting");
}
/**
 * @Description:消息确认机制的配置
 * @author: 老街俗人
 */    
@Bean
public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory){
    RabbitTemplate rabbitTemplate = new RabbitTemplate();
    rabbitTemplate.setConnectionFactory(connectionFactory);
    //开启Mandatory,触发回调函数
    rabbitTemplate.setMandatory(true);
    //这里针对的是生产者->broker(主机)消息传递失败的措施
    rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback(){
        @Override
        public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        	System.out.println("ConfirmCallback:     "+"correlationData:"+correlationData);
            System.out.println("ConfirmCallback:     "+"确认情况:"+correlationData);
            System.out.println("ConfirmCallback:     "+"cause:"+correlationData);
            //把correlationData记录到异常信息表,方便修单(确认模式下的回调函数,一般可能是网络问题或者磁盘满了,这些情况出现较少,配合日志监控系统,运维人员能即时修复)
        }
    });
    //这里针对的是交换机->队列消息传递失败的措施
    rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback(){
        @Override
        public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey){
            	System.out.println("ReturnCallback:     "+"消息:"+message);
				System.out.println("ReturnCallback:     "+"回应码:"+replyCode);
                System.out.println("ReturnCallback:     "+"回应信息:"+replyText);
                System.out.println("ReturnCallback:     "+"交换机:"+exchange);
                System.out.println("ReturnCallback:     "+"路由键:"+routingKey);
			//把message记录到异常信息表,方便修单(这种情况下的回调函数,一般可能是routingkey出错或者找不到队列,这些情况出现较少,生产环境基本不可能有)
        }
    });
    return rabbitTemplate;
}    
    
    
}

接下来是消费者手动确认的消息确认机制代码

@Configuration
public class RabbitConfig2 {
@Autowired
private CachingConnectionFactory connectionFactory;
@Autowired
private AckReceiver ackReceiver;//消息接收处理类
@Bean
public SimpleMessageListenerContainer simpleMessageListenerContainer(){
    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
    container.setConcurrentConsumers(1);
    container.setMaxConcurrentConsumers(1);
    container.setAcknowledgeMode(AcknowledgeMode.MANUAL); // RabbitMQ默认是自动确认,这里改为手动确认消息
    //设置一个队列
    container.setQueueNames("DirectQueue");
    container.setMessageListener(ackReceiver);
    return container;
} 
}

消费者代码

/**
 * @Description:消费者根据Message发送请求,如果出现异常失败,根据具体情况选择重发还是放弃
 * @author: 老街俗人
 */
@Component
public class AckReceiver implements ChannelAwareMessageListener {
    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            //msg就是传递的消息
            String msg = message.toString();
            //从msg里取出对应信息,调用对应的接口,发送请求
            //多条信息可以配合for循环发送
            channel.basicAck(deliveryTag, true); //当该参数为 true 时,则可以一次性确认 delivery_tag 小于等于传入值的所有消息
        } catch (Exception e) {
            //可以更详细的划分Exception,根据不同异常
            //可以选择重新放回队列,如超时或者网络问题
            //失败不重发的要记录到异常订单表,方便对账和修单
			//channel.basicReject(deliveryTag, true);//当该参数为 true 时,该消息会重新放回队列,所以需要自己根据业务逻辑判断什么时候使用拒绝
            channel.basicReject(deliveryTag, false);
            e.printStackTrace();
        }
    }
}

生产者代码

/**
 * @Description:异步请求
 * @author: 老街俗人
 */
public class SendService{
    @Autowired
    RabbitTemplate rabbitTemplate;
    
    public String asyncSend(){
    	Map<String,Object> map=new HashMap<>();
    	//加入多条键值对,存入相关信息,发送到broker
        //例如订单号、请求报文、接口名称等
        rabbitTemplate.convertAndSend("TestDirectExchange", "TestDirectRouting", map);
        return "ok";
    }
}

总结一下

使用MQ后,明显看到对于消息确认机制更加完善了,相当于用MQ代替了基于数据库的定时任务,效率更高了不说,对于开发人员,一旦出现消息异常或丢失,更容易排查。

只看分布式环境下的异步通信功能,MQ的引入还是很有必要的。

个人基于公司项目的理解,如有更好的看法,欢迎沟通~

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2021-07-22 14:16:32  更:2021-07-22 14:16:57 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/6 16:16:45-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码