这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战
需求
商家有多个门店,每天的固定时间点需要给门店的会员发送固定格式的短信。
又不是不能用版
LV1看到需求后:这还不简单,最多半天时间,来咱根据需求先画个流程图(内心OS:我是不是很专业):
流程图是不是很清晰,来,咱马上开始 复制粘贴 艺术创作:
public void sendSmsMessage() {
shopDao.findAll().forEach(shop -> {
String telNums = memberDao
.findByShopId(shop.getId())
.stream()
.map(Member::getTelNum)
.collect(Collectors.joining(","));
httpSendSmsMessage.batchSendMessage("祝你生日快乐!", telNums);
});
}
LV1实现完成后,提交PR
等待大佬的夸奖,没想到,仅仅过了几分钟,大佬就拒绝了LV1的PR
并细心的附上了方案的优缺点:
优点
- 能用
- 开发周期端
缺点
- 一个门店发生错误,会影响所有门店的短信发送。
- 仅可以正常正确运行,一旦发生错误,只能手工介入。
互相不打扰版
LV1盯着大佬发过来的优缺点,仔细思索了下,意识到自己将需求想的太简单了,经过一番搜索,找到了可以通过异步的方式来解决门店间互相影响的问题,既然找到解决方案了,咱就说干就干,还是先来一张流程图:
有了流程图和异步知识的积累,代码也很快写了出来:
public void eachOtherSendSmsMessage() {
CountDownLatch latch = new CountDownLatch((int) shopDao.count());
shopDao.findAll().forEach(shop -> {
asyncBetterThinkService.sendSmsMessage(shop.getId(), latch);
});
try {
latch.await();
} catch (InterruptedException e) {
log.error("线程在等待时被中断", e);
}
}
完成代码后,LV1也升级到了LV3,虽然这样第一个缺点解决了,第二个该怎么解决呢,LV3再次陷入了沉思。
错误重试版
又是一番搜索和讨论后,LV3将错误分成了两类,一类是第三方短信接口的错误,一类是自己程序内部的错误,当是第一类错误发生时,程序应当自动进行重试且超过设定的重试次数后,判定为错误,保存日志,另一类错误发生时,不进行自动重试,直接保存日志。有了解决方案后,咱还是先来一张流程图:
实现代码如下:
-- 主方法
public void errorSendSmsMessage() {
CountDownLatch latch = new CountDownLatch((int) shopDao.count());
shopDao.findAll().forEach(shop -> {
try {
asyncBetterThinkService.errorRetrySendSmsMessage(shop.getId(), latch);
} catch (Exception e) {
// 发生非第三方错误是保存错误日志
SendSmsMsgError error = new SendSmsMsgError();
error.setReason("xxxx");
error.setShopId(shop.getId());
}
});
try {
latch.await();
} catch (InterruptedException e) {
log.error("线程在等待时被中断", e);
}
}
-- 异步方法
@Async
public void errorRetrySendSmsMessage(String shopId, CountDownLatch latch) {
String telNums = memberDao
.findByShopId(shopId)
.stream()
.map(Member::getTelNum)
.collect(Collectors.joining(","));
// 每个线程独立重试次数
int selfRetry = retry;
boolean successRetry = false;
while (selfRetry > 0) {
try {
httpSendSmsMessage.batchSendMessage("祝你生日快乐!", telNums);
// 发送成功后,直接终止重试
selfRetry = 0;
successRetry = true;
} catch (Exception e) {
log.error("发送短信错误", e);
}
if (selfRetry > 0) {
try {
// 一般发生的错误都会是网络波动,所以先等待两秒后再重试
Thread.sleep(TimeUnit.SECONDS.toMillis(2));
} catch (InterruptedException e) {
e.printStackTrace();
}
selfRetry--;
}
}
if (!successRetry) {
// 保存错误信息
SendSmsMsgError error = new SendSmsMsgError();
error.setReason("xxxx");
error.setShopId(shopId);
}
latch.countDown();
}
-- 手工重试方法
public void handleRetry(String msgErrorId) {
sendSmsMsgErrorDao.findById(msgErrorId).ifPresent(sendSmsMsgError -> {
String telNums = memberDao
.findByShopId(sendSmsMsgError.getShopId())
.stream()
.map(Member::getTelNum)
.collect(Collectors.joining(","));
httpSendSmsMessage.batchSendMessage("祝你生日快乐!", telNums);
});
}
这次完成后,LV3信心满满的提交了PR
,大佬也很快通过了LV3的请求。
尾声
实际上,即使到最后,我们这个方案也未达到最优,比如:未考虑门店或会员过多时查询的耗时及发送的耗时等等,但笔者这个项目的实际情况时门店和会员数量都不会太多,所以方案的选择也要结合实际项目的需求和开发的周期,如果开发周期很紧张,笔者认为因为数据量过多引发的查询耗时问题是可以往后延期的。
好了,最后,再说几句题外话,就是切勿花费大量的时间去比较开发语言的好坏,说到底,开发语言只是一种工具,每种工具有各自擅长的领域罢了,并没有优劣高低之分,而真正能桎梏你成长的是你解决问题的思路和深度。