(1)场景,短信回执场景,会有二次回执的情况,但是我们只处理一次回执的消息体,如何不处理二次回执呢?
// 队列中有数据且容量未达到100,可继续放入队列数据
if (viberReceiptQueue.size() > 0 && (dlvViberReceiptMap.size() + fldViberReceiptMap.size()) <= 100) {
while (viberReceiptQueue.size() > 0
&& (dlvViberReceiptMap.size() + fldViberReceiptMap.size()) <= 100) {
ViberReceipt viberReceipt = viberReceiptQueue.take();
logger.info("HandlerRunnable viberReceipt take:{}", JSONObject.toJSONString(viberReceipt));
if (Objects.isNull(viberReceipt.getPartId())) {
MccContact mccContact = mccContactMapper.selectByPrimaryKey(viberReceipt.getMessageId());
viberReceipt.setPartId(Long.parseLong(mccContact.getPartId().toString()));
// 这里做一个二次回执业务过滤,判断contact表的状态是否已经被修改,如果已经被修改,则不处理
if (!CmsStateDef.CONTACT_STATE_CSD.equals(mccContact.getContactState())) {
continue;
}
}
//failed 发送失败 pending mmgw发送成功 success 发送成功 ceg一次成功回执pending 但一次失败回执failed 去重
if ("failed".equals(viberReceipt.getResult())) {
String key = viberReceipt.getMessageId() + "-" + viberReceipt.getResult();
String value = RedisUtil.getValue(RedisUtil.FUNTYPE1, key);
logger.info("qry FLD cache key:{}, is repeat flag:{}", key, value);
if (StringUtils.isEmpty(value)) {
fldViberReceiptMap.put(viberReceipt.getMessageId(), viberReceipt);
RedisUtil.setValue(RedisUtil.FUNTYPE1, key, "2", 3600);
}
else {
RedisUtil.delKey(RedisUtil.FUNTYPE1, key);
}
}
else if ("success".equals(viberReceipt.getResult())) {
dlvViberReceiptMap.put(viberReceipt.getMessageId(), viberReceipt);
}
}
这里我们通过RedisUtil.setValue(RedisUtil.FUNTYPE1, key, “2”, 3600);方法。将一次回执之后的key设置到缓存中去,这样二次回执过来的时候,会去查看缓存中是否有值,有值,则一次回执已经处理,该消息体为二次回执。不处理。
(2)上面这种解决方法看起来好像没有什么问题,但是Redis一般都是分布在多容器的,如果两个容器的两个消息同时getValue,这样既取不到数据,并且两条消息都进行了处理,显然与我们要求的不符合。
解决方法:使用RedisUtil的单线程处理方法incr
// 队列中有数据且容量未达到100,可继续放入队列数据
if (viberReceiptQueue.size() > 0 && (dlvViberReceiptMap.size() + fldViberReceiptMap.size()) <= 100) {
while (viberReceiptQueue.size() > 0
&& (dlvViberReceiptMap.size() + fldViberReceiptMap.size()) <= 100) {
ViberReceipt viberReceipt = viberReceiptQueue.take();
logger.info("HandlerRunnable viberReceipt take:{}", JSONObject.toJSONString(viberReceipt));
MccContact mccContact = mccContactMapper.selectByPrimaryKey(viberReceipt.getMessageId());
viberReceipt.setPartId(Long.parseLong(mccContact.getPartId().toString()));
// 这里做一个二次回执业务过滤,判断contact表的状态是否已经被修改,如果已经被修改,则不处理
if (!CmsStateDef.CONTACT_STATE_CSD.equals(mccContact.getContactState())) {
continue;
}
//failed 发送失败 pending mmgw发送成功 success 发送成功 ceg一次成功回执pending 但一次失败回执failed 去重
if ("failed".equals(viberReceipt.getResult())) {
String key = viberReceipt.getMessageId() + "-" + viberReceipt.getResult();
int incr = RedisUtil.incr(RedisUtil.FUNTYPE1, key, 1, 3600l);
logger.info("qry FLD cache key:{}, is repeat flag:{}", key, incr);
if (incrValue != incr) {
fldViberReceiptMap.put(viberReceipt.getMessageId(), viberReceipt);
}
}
else if ("success".equals(viberReceipt.getResult())) {
dlvViberReceiptMap.put(viberReceipt.getMessageId(), viberReceipt);
}
}
1.Redis自增key 的好处
限流可以使用redis原子计数器incr.但是除了限流以外,很多系统会在一些节日的时候搞一些活动,当然,这些活动是有奖品的,并且奖品的数量也是有限的。为了防止在高并发的时候,出现多个人中奖的情况,那么可以使用分布式锁,比如redis的分布式锁,zookeeper的分布式锁。当然,我们也可以采用简单一点的方案,就是使用redis原子计数器incr来统计。