一 、背景
众联项目 中批量发送邮件 使用到了 我新负责的基础服务 发邮件组件 ,2 个小时没有收到邮件
相关开发人员找我反馈
业务服务,调用基础服务 发邮件 一直没发出去,原因是 基础服务 发邮件逻辑是
业务服务调用一次,先写入数据库,然后定时任务 3s 查看 5 条,轮询发送 ,没发送 1 条,sleep 500ms,防止邮件服务器压力过大
事故原因是,发邮件服务器不知为什么阻塞了,定时任务一直结果不了,卡住了,导致 邮件发送积压
二 、排查过程
1、 不要着急,问清楚问题的原因,让提问者重新提问,明确具体问题 是
- 调用基础服务组件 ,发邮件没有响应, 下午 14:00 前有,目前 13:6 没有收到
2、先去基础服务页面查看邮件通知记录 - 通过页面查看发送 未完成的邮件通知,从 14:00 开始大概有几百条记录
3、梳理整个邮件发送代码逻辑
- 提供的邮件发送分为三种
- 直接发送,不报错记录到数据库
- 直接发送,发送成功保存数据库
- 先保存数据库,定时任务扫描 分配发送
三、修复方案
1、cs 服务,发送业务服务定时任务,每 3s 一次,先写入当前时间都 reids 中,24h 过期
/**
* 3秒1次,未完成的,才发送
*/
@LogTrace
@Scheduled(fixedRate = 1 * 3 * 1000,initialDelay = 1 * 10 * 1000)
public void mailTaskHigh() {
updateLastCheckTime();
//log.info("SendNotifyTask->mailTask task start-----");
//log.info("SendNotifyTask->mailTask,tryLock start");
MailSendPolicy mailSendPolicyHigh = configBizQueryService.mailSendPolicyHigh();
if(mailSendPolicyHigh.isSendClose()) {
log.info("mailTaskHigh closed,exit");
return;
}
//projectCode错误配置
if(ListKit.isEmpty(mailSendPolicyHigh.projectCodeList())) {
log.error("mailTaskHigh,projectCodeList,empty,exit");
return;
}
String lockKey = lockKey(mailSendPolicyHigh.getName());
// 任务最大占用时常
int lockTimeSecond = mailSendPolicyHigh.getLockTimeSecond();
boolean lockSucceed = lock.tryLock(lockKey, lockTimeSecond, TimeUnit.SECONDS);
if(mailSendPolicyHigh.isLogOpen()) {
log.info("SendNotifyTask->{},lockKey={},lockSucceed={}", mailSendPolicyHigh.getName(),lockKey,lockSucceed);
}
if (!lockSucceed) {
//log.info("SendNotifyTask->mailTask,tryLock false,task exit");
return;
}
//log.info("SendNotifyTask->mailTask,tryLock true,task ing");
try {
notifyTaskService.findMailThenSendNotify(mailSendPolicyHigh);
} catch (Exception e) {
log.error("SendNotifyTask mailTask task error", e);
} finally {
//log.info("SendNotifyTask->mailTask,unlock start");
lock.unlock(lockKey);
//log.info("SendNotifyTask->mailTask,unlock end");
}
//log.info("-----SendNotifyTask mailTask task end-----");
}
2、增加一个定时任务,每 min 查看这个 key ,是否 5min 没更新,发送超时邮件
@LogTrace
@Scheduled(fixedRate = 60 * 1000, initialDelay = 1 * 20 * 1000)
public void mailTaskHighTimeOutCheck() {
MailSendPolicy mailSendPolicyHigh = configBizQueryService.mailSendPolicyHigh();
Integer sendEmailTimeOut = mailSendPolicyHigh.getSendEmailTimeOut();
if (ObjectUtil.isEmpty(sendEmailTimeOut)) {
log.error("mailTaskHighTimeOutCheck,sendEmailTimeOut,empty,exit");
return;
}
String lastCheckTime = getLastCheckTime();
if (StringUtils.isNotEmpty(lastCheckTime)) {
Long currentTime = System.currentTimeMillis();
Long lastCheckTimeAsLong = Long.valueOf(lastCheckTime);
Long diff = (currentTime - lastCheckTimeAsLong) / 1000;
log.info("mailTaskHighTimeOutCheck is ok, diff :{},lastCheckTime:{}, sendEmailTimeOut:{}",diff,lastCheckTime,sendEmailTimeOut);
if (diff >= sendEmailTimeOut) {
sendNotification(mailSendPolicyHigh);
updateLastCheckTime();
}
}else {
log.info("mailTaskHighTimeOutCheck lastCheckTime is empty");
}
}
private String getLastCheckTime() {
String lastCheckTimeString = redisCacheClient.get(CommonConst.REDIS_KEY_TIMEOUT_CHECK);
return lastCheckTimeString;
}
private void updateLastCheckTime() {
redisCacheClient.setex(CommonConst.REDIS_KEY_TIMEOUT_CHECK, System.currentTimeMillis(), 24 * 60 * 60);
}
private void sendNotification(MailSendPolicy mailSendPolicyHigh) {
//发送钉钉通知
String sendDingPersons = mailSendPolicyHigh.getSendDingPersons();
List<String> recipients = JavaKit.splitAsStrList(sendDingPersons);
String message = LocalDateTime.now() + " mailTaskHighTimeOutCheck taskHigh 发送高频邮件任务超时,请尽快排查。";
String errorMessage = LogKit.format(message);
String alertMessage = AlertLogKit.buildErrorMessageDefault(errorMessage);
log.error(alertMessage);
csWechatSendService.sendWechat(WechatChannelTypeEnum.NOTIFY_PLATFORM.getCode(), recipients, message);
}
四、注意点
1.超时时间可以通过字典配置
2.reids key 设置 24h 过期,防止服务一直不启动,突然启动发送错误邮件
3.目前发送钉钉通知发送错误,后续可以增加发送邮件
4.先放到生产环境,跑一段数据,下次,发现阻塞,通过 arthas 定位具体卡主代码