1、问题描述
现有类GroupExtTimeOutQueueRunner implements CommandLineRunner,
run方法如下:
public void run(String... args) {
RPriorityBlockingQueue<CallCdr> blockingQueue = redissonClient.getPriorityBlockingQueue(RedisKeyPrefix.GROUP_EXTENSION_TIMEOUT_QUEUE);
while (true) {
CallCdr callCdr = null;
//从redis的阻塞队列中获取元素,若无法获取到元素 则线程挂起
// 一直阻塞在这里 下面的业务方法不运行
callCdr = blockingQueue.take();
//执行业务方法
dosomething(callCdr );
}
}
使用springboot2在开发环境上是完全ok的,但是部署到外部tomcat后,出现以下问题:
1、程序正常启动,无任何报错信息
2、该开机启动可以正常运行,但是当无法从队列中获取到元素的时候,线程挂起
3、线程挂起导致程序中的正常的url无法访问,列如访问程序中的url http://127.0.0.1:8080/ctilink/test 完全无法访问
原因分析:可能是由于获取不到元素,一直阻塞着 导致的
解决方案:使用线程池
伪代码如下
线程类,run方法真正执行业务逻辑
package com.ps.uzkefu.apps.ctilink.handler;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.ps.uzkefu.apps.callcenter.entity.Router;
import com.ps.uzkefu.apps.callcenter.service.ExtensionStatusService;
import com.ps.uzkefu.apps.ctilink.ommodel.CallCdr;
import com.ps.uzkefu.apps.ctilink.ommodel.CallCdrComparator;
import com.ps.uzkefu.apps.ctilink.rediskey.CdrType;
import com.ps.uzkefu.apps.ctilink.rediskey.EventType;
import com.ps.uzkefu.apps.ctilink.rediskey.RedisKeyPrefix;
import com.ps.uzkefu.apps.ctilink.routerHandler.BaseRouterHandler;
import com.ps.uzkefu.apps.ctilink.routerHandler.ExtensionGroupRouterHandler;
import com.ps.uzkefu.apps.ctilink.service.CallInQueueService;
import com.ps.uzkefu.apps.oms.account.entity.User;
import com.ps.uzkefu.apps.oms.account.service.UserService;
import com.ps.uzkefu.common.ExecutionContext;
import com.ps.uzkefu.common.UkConstant;
import com.ps.uzkefu.utils.ExecuteOMAPI;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RBucket;
import org.redisson.api.RPriorityBlockingQueue;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
/**
* Author:ZhuShangJin
* Date:2018/6/26
*
*/
@Getter
@Setter
public class QueueHandler implements Runnable{
public Logger logger = LoggerFactory.getLogger(this.getClass());
RedissonClient redissonClient;
ExtensionStatusService extensionStatusService;
UserService userService;
CallInQueueService callInQueueService;
@Override
public synchronized void run() {
logger.error("2222222222222222222222222222222");
try {
RPriorityBlockingQueue<CallCdr> blockingQueue = redissonClient.getPriorityBlockingQueue(RedisKeyPrefix.GROUP_EXTENSION_TIMEOUT_QUEUE);
blockingQueue.trySetComparator(new CallCdrComparator());
boolean jj = (blockingQueue==null);
while (true) {
CallCdr callCdr = null;
try {
callCdr = blockingQueue.take();
String to = callCdr.getToNum();
String from = callCdr.getFromNum();
String visitorId = callCdr.getVistorId();
String key = from + "_" + to + "_" + visitorId;
RBucket<CallCdr> bucket = redissonClient.getBucket(RedisKeyPrefix.CALL_VISITOR_CDR + key);
CallCdr getByKeyCallCdr = bucket.get();
RBucket<Router> routerRBucket = redissonClient.getBucket(RedisKeyPrefix.ROUTER + to);
Router router = routerRBucket.get();
// 正常进入队列 可以接听的
if (getByKeyCallCdr != null && !getByKeyCallCdr.isHangup() && Objects.equals(CdrType.NO_TIME_OUT, callCdr.getTimeOutType())) {
//查找空闲坐席
String extNum = extensionStatusService.getFreeExtensionNum(getByKeyCallCdr.getGroupNum()+"", getByKeyCallCdr.getStrategy());
// extNum = null;
// extNum = getByKeyCallCdr.getExtensionNum();
// if (Objects.equals(getByKeyCallCdr.getLastTimeOutExt(), "2004")) {
// extNum = "2003";
// } else {
// extNum = "2004";
// }
//无空闲坐席
logger.debug("是否正在播放排队===ivr:"+getByKeyCallCdr.isHasPlayQueueVoice());
if (StringUtils.isBlank(extNum)) {
if (!getByKeyCallCdr.isHasPlayQueueVoice()) {
getByKeyCallCdr.setHasPlayQueueVoice(true);
getByKeyCallCdr.setMenuId(UkConstant.EXTENSION_GROUP_TIP_MENU_ID);
String voiceId = router.getExtensionGroups().get(0).getVoices().get(0).getVoiceName();
logger.debug("播放排队音乐"+voiceId);
// 播放排队音乐
ExecuteOMAPI.turnToIvr(getByKeyCallCdr.getPbxUrl(), getByKeyCallCdr.getVistorId(), UkConstant.EXTENSION_GROUP_TIP_MENU_ID, voiceId);
// todo 真正的排队 corpcode groupnum 在排队超时出队的时候删除相应的元素 在来挂机的时候删除相应元素
RPriorityBlockingQueue<CallCdr> blockingQueueByCorp = redissonClient.getPriorityBlockingQueue(RedisKeyPrefix.CALL_VISITOR_QUEUE+getByKeyCallCdr.getCorpCode()+":"+getByKeyCallCdr.getGroupNum());
CallCdr inQueueCall = new CallCdr();
inQueueCall.setCorpCode(getByKeyCallCdr.getCorpCode());
inQueueCall.setGroupNum(getByKeyCallCdr.getGroupNum());
inQueueCall.setRedisKey(getByKeyCallCdr.getRedisKey());
inQueueCall.setFromNum(getByKeyCallCdr.getFromNum());
inQueueCall.setToNum(getByKeyCallCdr.getToNum());
inQueueCall.setVistorId(getByKeyCallCdr.getVistorId());
blockingQueueByCorp.put(inQueueCall);
}
// todo 统计排队信息 入队 组内分机全忙碌 真正的排队 重回队列 不做任何改变
logger.debug("是否正在播放排队ivr:"+getByKeyCallCdr.isHasPlayQueueVoice());
bucket.set(getByKeyCallCdr);
blockingQueue.put(getByKeyCallCdr);
if (getByKeyCallCdr.getGroupNum() > 0) {
callInQueueService.inQueue(getByKeyCallCdr.getCorpCode(), getByKeyCallCdr.getGroupNum() + "", from, getByKeyCallCdr.getQueueTime());
}
} else {
//查找出最先进入队列中的呼入 和getByKeyCallCdr 进行对比 是否为同一个呼入 若是 则接听getByKeyCallCdr
//若不是 则把getByKeyCallCdr 放回队列 直到 找到最先进入队列中的呼入 todo
RPriorityBlockingQueue<CallCdr> blockingQueueByCorp = redissonClient.getPriorityBlockingQueue(RedisKeyPrefix.CALL_VISITOR_QUEUE+getByKeyCallCdr.getCorpCode()+":"+getByKeyCallCdr.getGroupNum());
CallCdr realToAnswer = blockingQueueByCorp.take();
if (!Objects.equals(getByKeyCallCdr.getRedisKey(),realToAnswer.getRedisKey())){
blockingQueue.put(getByKeyCallCdr);
// 不是同一个呼入
String realTo = realToAnswer.getToNum();
String realFrom = realToAnswer.getFromNum();
String realVisitorId = realToAnswer.getVistorId();
String realKey = realFrom + "_" + realTo + "_" + realVisitorId;
RBucket<CallCdr> realBucket = redissonClient.getBucket(RedisKeyPrefix.CALL_VISITOR_CDR + realKey);
getByKeyCallCdr = realBucket.get();
if (getByKeyCallCdr == null){
continue;
}
}
//有空闲坐席 拨打电话到分机
//查找真正应该接的 corpcode groupnum todo 注意出队
User user = userService.selectOne(new EntityWrapper<User>().eq("extension", extNum).eq(ExecutionContext.CORP_CODE, getByKeyCallCdr.getCorpCode()));
// 设置下一个振铃的坐席的姓名
getByKeyCallCdr.setUserName(user.getUserName());
// 设置下一个振铃的坐席id
getByKeyCallCdr.setCreater(user.getId());
//如果已有 分机振铃超时未接 设置 下一个要振铃的分机
if (getByKeyCallCdr.isHasRingExt()) {
getByKeyCallCdr.setNextExt(extNum);
}
// 设置要在分机振铃时 加入到振铃超时队列
getByKeyCallCdr.setNeedPutToExtTimeOutQueue(true);
//向分机呼叫
String result = ExecuteOMAPI.turnToExt(getByKeyCallCdr.getPbxUrl(), visitorId, extNum);
logger.debug("呼叫分机========" + extNum);
//修改状态 为正在呼叫 分机中
getByKeyCallCdr.setCalling(true);
// 出队 计算排队的时间
int queueLength =(int) ((new Date().getTime() - getByKeyCallCdr.getManyInQueueTime().getTime())/1000);
int alredayQueueLength = getByKeyCallCdr.getQueueTimeLength();
getByKeyCallCdr.setQueueTimeLength(queueLength+alredayQueueLength);
// 设置出队列的时间 也就是分机振铃的时间
getByKeyCallCdr.setManyOutQueueTime(new Date());
bucket.set(getByKeyCallCdr);
}
}
// 分机振铃超时
if (Objects.equals(CdrType.EXT_TIME_OUT, callCdr.getTimeOutType())
&& getByKeyCallCdr != null
&& !getByKeyCallCdr.isHangup()
&& Objects.equals(EventType.RING, getByKeyCallCdr.getLastEvent())) {
//分机 振铃超时 重新进入队列
BaseRouterHandler routerHandler = new ExtensionGroupRouterHandler(router, getByKeyCallCdr);
routerHandler.extRingTimeOut();
}
//排队超时
if (Objects.equals(CdrType.GROUP_TIME_OUT, callCdr.getTimeOutType()) && getByKeyCallCdr != null && (callCdr.getInQueueCnt() == getByKeyCallCdr.getInQueueCnt()) && !Objects.equals(EventType.ANSWER, getByKeyCallCdr.getLastEvent())) {
//排队超时 执行挂机 todo 超时的菜单id 和语音文件名
// todo 统计排队信息 出队
logger.debug("from:"+from+"to:"+to+"第"+getByKeyCallCdr.getInQueueCnt()+"次排队超时,挂机");
String result = ExecuteOMAPI.turnToIvr(getByKeyCallCdr.getPbxUrl(), visitorId, UkConstant.HANG_MENU_ID, getByKeyCallCdr.getQueueTimeOutVoiceName());
getByKeyCallCdr.setHangup(true);
getByKeyCallCdr.setNeedSaveCdr(true);
getByKeyCallCdr.setMenuId(UkConstant.HANG_MENU_ID);
bucket.set(getByKeyCallCdr);
//排队超时 移除 corpcode+groupNum队列中真正排队的来电
RPriorityBlockingQueue<CallCdr> blockingQueueByCorp = redissonClient.getPriorityBlockingQueue(RedisKeyPrefix.CALL_VISITOR_QUEUE+getByKeyCallCdr.getCorpCode()+":"+getByKeyCallCdr.getGroupNum());
CallCdr inQueueCall = new CallCdr();
inQueueCall.setCorpCode(getByKeyCallCdr.getCorpCode());
inQueueCall.setGroupNum(getByKeyCallCdr.getGroupNum());
inQueueCall.setRedisKey(getByKeyCallCdr.getRedisKey());
inQueueCall.setFromNum(getByKeyCallCdr.getFromNum());
inQueueCall.setToNum(getByKeyCallCdr.getToNum());
inQueueCall.setVistorId(getByKeyCallCdr.getVistorId());
if (blockingQueueByCorp.contains(inQueueCall)){
blockingQueueByCorp.remove(inQueueCall);
}
callInQueueService.outQueue(getByKeyCallCdr.getCorpCode(), getByKeyCallCdr.getGroupNum() + "", from);
}
if (Objects.equals(CdrType.IVR_KEY_TIME_OUT, callCdr.getTimeOutType()) && getByKeyCallCdr != null && !getByKeyCallCdr.isPressKey()) {
// 按键超时
Map<String, String> keyResourceTypeIdMap = getByKeyCallCdr.getKeyResourceTypeIdMap();
String jumpType = null;
String jumpResourceId = null;
if (MapUtils.isEmpty(keyResourceTypeIdMap)) {
//如果IRV没有设置按键菜单,直接走无按键菜单超时跳转
jumpType = getByKeyCallCdr.getKeyTimeoutJumpType();
jumpResourceId = getByKeyCallCdr.getKeyTimeoutJumpId();
getByKeyCallCdr.setIvrPlayCnt(0);
} else {
//按键超时,播放当前ivr
jumpType = getByKeyCallCdr.getJumpType();
jumpResourceId = getByKeyCallCdr.getVoiceId();
}
getByKeyCallCdr.setJumpType(jumpType);
getByKeyCallCdr.setJumpResourceId(jumpResourceId);
BaseRouterHandler routerHandler = null;
routerHandler = new EventHandler().getHandler(getByKeyCallCdr, jumpType, jumpResourceId, router, routerHandler);
getByKeyCallCdr = routerHandler.routerHandler();
bucket.set(getByKeyCallCdr);
routerRBucket.set(router);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
开机启动类,通过线程池来执行业务逻辑
package com.ps.uzkefu.apps.ctilink.init;
/**
* Author:ZhuShangJin
* Date:2018/9/13
*/
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.ps.uzkefu.apps.callcenter.entity.Router;
import com.ps.uzkefu.apps.callcenter.service.ExtensionStatusService;
import com.ps.uzkefu.apps.ctilink.handler.EventHandler;
import com.ps.uzkefu.apps.ctilink.handler.QueueHandler;
import com.ps.uzkefu.apps.ctilink.ommodel.CallCdr;
import com.ps.uzkefu.apps.ctilink.ommodel.CallCdrComparator;
import com.ps.uzkefu.apps.ctilink.rediskey.CdrType;
import com.ps.uzkefu.apps.ctilink.rediskey.EventType;
import com.ps.uzkefu.apps.ctilink.rediskey.RedisKeyPrefix;
import com.ps.uzkefu.apps.ctilink.routerHandler.BaseRouterHandler;
import com.ps.uzkefu.apps.ctilink.routerHandler.ExtensionGroupRouterHandler;
import com.ps.uzkefu.apps.ctilink.service.CallInQueueService;
import com.ps.uzkefu.apps.oms.account.entity.User;
import com.ps.uzkefu.apps.oms.account.service.UserService;
import com.ps.uzkefu.common.ExecutionContext;
import com.ps.uzkefu.common.UkConstant;
import com.ps.uzkefu.utils.ExecuteOMAPI;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by pangkunkun on 2017/9/3.
* 超时 振铃超时 排队超时 按键超时
*/
@Component
@Order(2)
public class GroupExtTimeOutQueueRunner implements CommandLineRunner {
@Autowired
RedissonClient redissonClient;
@Autowired
ExtensionStatusService extensionStatusService;
@Autowired
UserService userService;
@Autowired
CallInQueueService callInQueueService;
private static ExecutorService queueThreadPool = Executors.newFixedThreadPool(50);
@Override
public void run(String... args) {
QueueHandler queueHandler = new QueueHandler();
queueHandler.setCallInQueueService(callInQueueService);
queueHandler.setRedissonClient(redissonClient);
queueHandler.setExtensionStatusService(extensionStatusService);
queueHandler.setUserService(userService);
queueThreadPool.execute(queueHandler);
}
}
然后部署到外部tomcat,访问http://127.0.0.1:8080/ctilink/test 正常