关于springboot2开机启动CommandLineRunner阻塞运行导致无法正常访问url

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 正常

猜你喜欢

转载自blog.csdn.net/zsj777/article/details/82846350
今日推荐