1 在用户注册后发送消息到MQ,然后会员服务监听消息进行异步处理的场景下,有些时候我们会发现,虽然用户服务先保存数据再发送MQ,但会员服务收到消息后去查询数据库,却发现数据库中还没有新用户的信息。你觉得,这可能是什么问题呢,又该如何解决呢?
解决方案
-
当时倒不是因为主从的问题,而是因为业务代码把保存数据和发MQ消息放在了一个事务中,有概率收到消息的时候事务还没有提交完成,当时开发同学的处理方式是收MQ消息的时候sleep 1秒,或许应该是先提交事务,完成后再发MQ消息,但是这又出来一个问题MQ消息发送失败怎么办?所以后来演化为建立本地消息表来确保MQ消息可补偿,把业务处理和保存MQ消息到本地消息表操作在相同事务内处理,然后异步发送和补偿发送消息表中的消息到MQ
-
可能是数据写到了主库,然后查询了从库。但因为主从同步有延迟,导致没有查询到
-
也可能mq发信息写到了事务中,导致了mq的消费时,事务还没有提交
-
注册register的代码中把异常都吃掉了,没抛出来,注册又报错了,但还是继续执行并且发了消息
-
- 先保存用户注册的数据,同时记录下要发送mq的消息,入库在一个事务
- 通过异步任务定时拉取mq的消息表,发送到mq,进行处理
(其实这就是本地事务消息的实现)第二步不一定需要定时任务拉取。第一步完成后直接发mq即可 定时任务拉取只用来补偿
-
生产者发送给mq消息 即使异步发送也会有listener 来监听投递消息是否成功 如果失败 重试不就行了 ? 不是类似kafka 有100%投递 100%保证消费的配置吗?
补偿吗?我遇到过mq瘫痪的情况,没有补偿这个时候除了干着急我们还能做啥 -
如果有多个补偿实例,会不会造成消息重复?
补偿需要配合幂等,生产应用肯定用数据库做幂等。
2 除了使用Spring AMQP实现死信消息的重投递外,RabbitMQ 2.8.0 后支持的死信交换器DLX也可以实现类似功能。你能尝试用DLX实现吗,并比较下这两种处理机制?
自定义的私信队列,其实是发送失败,主要是生产者发送到mq的时候,发送失败,进了自定义的私信队列;
DLX的方式的方式其实解决已到了mq,但是因为各种原因,无法到达正常的队列中,大概分类下面几种吧:
消息消费时被拒绝(basic.reject / basic.nack),并且requeue = false
消息TTL过期
队列达到最大长度
- 公司内部分享的RabbitMQ的资料,欢迎大家交流
http://note.youdao.com/noteshare?id=e9f2f88c6c7fcb7ac690463eb230650a
业务需求里需要按某几个字段去重(acctId,billingCycleId,prodInstId,offerId)
我这里想到了遍历集合areaDatas 后用contains方法判断 重写AcctItemYzfBean实体类的equals方法实现,
请问有没有更好的方法? 代码如下
List<AcctItemYzfBean> newList = new CopyOnWriteArrayList<>();
//循环过滤、增强翼支付数据
Optional.ofNullable(areaDatas)//集合判空
.orElse(new ArrayList<>())
.stream()//转化为流 便于下面过滤和增强数据
.filter(Objects::nonNull)//元素判空
.filter(yzfBean -> this.judgeIfOfferId(yzfBean))//判断销售品ID是否相同
.filter(yzfBean -> this.enhanceYzfBean(yzfBean))//增强过滤accNbr和acctId
.filter(yzfBean -> this.judgeIfArrears(yzfBean))//判断是否不欠费
.filter(yzfBean -> this.judgeIfCancel(yzfBean))//判断是否销账金额大于0
.filter(yzfBean -> this.judgeIfReturn(yzfBean))//判断是否上月未返还
.forEach(yzfBean -> {
//去重 重写AcctItemYzfBean.equals方法
if(!newList.contains(yzfBean)) {
//增强latnName
yzfBean.setLatnName(commonRegionMap.get(yzfBean.getRegionId()));
//增强areaCode
yzfBean.setAreaCode(areaCode);
//数据封装
newList.add(yzfBean);
}
});
重写的equals方法
@Override
public boolean equals(Object yzfBeanObj) {
if(yzfBeanObj instanceof AcctItemYzfBean) {
AcctItemYzfBean yzfBean = (AcctItemYzfBean) yzfBeanObj;
if(Tools.isEmpty(yzfBean.getAcctId(), yzfBean.getBillingCycleId(), yzfBean.getProdInstId(), yzfBean.getOfferId())) {
return false;
}
if(yzfBean.getAcctId().equals(this.acctId) && yzfBean.getBillingCycleId().equals(this.billingCycleId)
&& yzfBean.getProdInstId().equals(this.prodInstId) && yzfBean.getOfferId().equals(this.offerId)) {
return true;
}
}
return super.equals(yzfBeanObj);
}
解:
比如下面的类,id1和id2重复认为是重复的,id3不需要考虑
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
static class Test {
private String id1;
private String id2;
@EqualsAndHashCode.Exclude
private String id3;
}
通过Set去重或者通过distinct去重即可:
List<Test> list = new ArrayList<>();
list.add(new Test("a","b","c"));
list.add(new Test("a","b","d"));
System.out.println(list.stream().collect(Collectors.toSet()));
System.out.println(list.stream().distinct().collect(Collectors.toList()));
















