使用rabbitmq和异步线程的方式进行同步数据

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

场景:在添加数据到mysql后,需要同步到ck中


提示:以下是本篇文章正文内容,下面案例可供参考

一、使用异步线程同步

写一个线程池

@Bean("insertClickhouseDataExecutor")
ThreadFactory threadFactory = new ThreadFactoryBuilder.setNameFormate("insertClickHouseDataExecutor-%s").build();
return new ThreadPoolExecutor(NUMBER_OF_CORES+1,NUMBER_OF_CORES<<1,KEEP_ALIVE_TIME,TimeUtil.SECONDS,new LinkedBlockingQueue<>(CAPACITY),threadFactory,new ThreadPoolExecutor.CallerRunsPolicy());

public void asyncInsertDataToClickhouse(List<> 插入的数据集合){
	insertClickhouseDataExecutor.submit(()->{
		syncDataToClickhouse(插入的数据集合)
	})
}


syncDataToClickhouse(){
//使用xml手写批量插入即可(foreach)
	Mapper.batchSave(插入的集合)
}


二、使用rabbitmq进行同步

MySQLTableDataDTO mySQLTableDataDTO  = new MySQLTableDataDTO();
//把信息封装进一个实体类中,为了让生成的id作为redis的key,解决幂等性问题,也就是下面的第四步
msgSender(JSONObjet.toJSONString(mySQLTableDataDTO),rabbitProperties.getExchage(),rabbitProperties.getDataRouting());


public void megSender(String content,String exchange,String routing){
//开启confirm机制,里面是函数式接口,提供性接口
	rabbitTemplate.setConfirmCallback((correlationData,ack,cause)->{
		log.info("confimCallback触发回调");
		if(!ack){
			log.info("消息没有到达exchange!")
		}
	})
	
	//开启returns机制
	rabbitTemplate.setReturnsCallback(returned -> {
		String msg = returned.getMessage().getBody();
		log.info("消息入路由失败,内容:{}",msg);
	})
	
	//发送消息
	//下拉姆达表达式 就是MessagePostProcessor 这个函数式接口  返回的类。 他是返回型接口
	rabbitTemplate.convertAndSend(exchange,routing,content,message -> {
		//消息持久化,确保rabbitmq宕机重启之后消息丢失
		message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
		return message;
	})
}

三、考虑confirm回调中消息不丢失

消息的持久化是基于队列的持久化,如果消息在发送给消费者之前宕机了,那么消息还存在,假如队列不是持久化,那么消息的持久化毫无意义。还有缺点就是严重影响到了RabbitMq的性能,写入硬盘的速度比写入内存的速度慢的不只一点点。对于可靠性不是那么高的消息可以不采用这个方式,需要在可靠性和吞吐量之间做一个全权衡。

//如果想在confirm回调中进行重发,需要一个类 继承CorreLationData ,
/**
 * CorrelationData的自定义实现,用于拿到消息内容
 */
public class CorrelationDataExt extends CorrelationData {
    //数据
    private volatile Object data;
    //队列
    //交换机
    //routingkey
}

public void megSender(String content,String exchange,String routing){
	   //使用自定义的数据对象
        CorrelationDataExt correlationData = new CorrelationDataExt();
        correlationData.setData(json);
        //开启confirm机制,里面是函数式接口,提供性接口
rabbitTemplate.setConfirmCallback((correlationData,ack,cause)->{
	log.info("confimCallback触发回调");
	if(!ack){
		log.info("消息没有到达exchange!")
		if(correlationData instanceof CorrelationDataExt ){
			 CorrelationDataExt correlationDataExt = (CorrelationDataExt) correlationData;
			 //从correlationDataExt中获取各种数据,然后重新发送
		}
	}
})
//下拉姆达表达式 就是MessagePostProcessor 这个函数式接口  返回的类。 他是返回型接口
rabbitTemplate.convertAndSend(exchange,routing,content,message -> {
	//消息持久化
	message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
	return message;
},correlationData)
}

四、幂等性问题

场景:当消费者返回ack时,由于网络断开或者其他原因导致RabbitMQ并没有接收到这个ack,那么rabbitmq不会把这个消息给删除,当重新建立连接后,会重新发送消息给消费者,导致重复消费。
解决:

	1、消费者监听到消息后获取消息的MsgId(这个MsgId是我们自定义消息的字段,是主键),我们利用第二步的JSONObject.parseObject(msg,MySQLTableDTO.class) 转为DTO类,再getId即可
	先去Redis中查询这个MsgId是否存在。也可以生产者发送消息时指给消息对象设置唯一的 MessageID,只有该 MessageID 没有被消费者存入到Redis中即该消息未被消费,这样重发的消息才能在重试机制中再次被消费。 可以使用redis的setnx
	2、如果不存在,则正常消费消息,并把消息的id存入Redis中。
	3、如果存在则丢弃或者拒绝此消息并不返回队列。(在消费者中判断)(channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);true代表丢弃,或给死信队列。
	

五、重试机制(retry)

1:xml中进行重试

//重试次数超过设定的多少次之后,就会进入死信队列中(需要自己写死信交换机和死信队列)
default-requeue-rejected: false

2:定时任务重试

1:在第三步添加的消息实体类中,在发消息的实体类中添加一个参数:count,代表重试几次,count
2:设置定时任务(@Scheduled),判断每次count为多少,如果大于3次(自己设定的)就丢弃或给到死信队列中,如果小于3次就修改数据库,把count+1即可。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值