一次线上线程dead问题排查

问题描述

记录一下这次线上问题排查的思路及收获,以及仍没有解决的点。
服务背景:这属于一个控量的服务,我们会对需要平均投放的数据进行更新分时处理。我们开启了两个单线程,并且部署在不同的机器上,但同时只会有一个线程在处理(通过在redis里设置一个key作为互斥锁)。问题出现在其中一台机上的线程死了,但其RedisLock这个线程并没有关闭掉(???这是一个疑问点,按正常流程,应该执行finally方法,然后该线程被interrupt掉),导致这个key一直被持有,从而另一个线程执行不了更新操作。

问题总结

针对这个问题,总结出了几个知识点:

线程不执行finally方法

  • 线程try里面死锁
  • 线程catch里面System.exit()
  • 出现Error级别的异常,但我们只捕捉到了Exception异常级别

反思

  • 日志的error级别一定要和info日志打印到不同的文件里。这个问题为什么排查不出来,因为线上日志被冲掉了。而这是一个偶发性问题,很难复现。目前现有的结论都是推测的。
  • 对于程序中一定要用线程池取代单线程,防止线程死掉,造成难以察觉错误。
// 一个class类的部分代码,其作用是取redis中的当天数据,然后进行分时操作;
    private Thread thread;
    private boolean enable = true;

	//启动线程
    public void start() {
        if(null == thread || !thread.isAlive()){
            Worker worker = new Worker();
            thread = new Thread(worker);
            thread.start();
        }
    }

    //关闭该线程
    public void stop(){
        if(null != thread){
            thread.interrupt();
        }
        enable = false;
    }
    class Worker implements Runnable{

        @Override
        public void run() {
            RedisLock rl = null;
            try {
                rl = new RedisLock("job_name", 30);
            } catch (URISyntaxException e) {
                LOGGER.error("", e);
            }

            while (enable){
                RedisConnection jedis = null;
                Pipeline pipeline = null;
                try {
                    Thread.sleep(1000 * 60 * 1);

                    // redis锁, 每日job只能有一个执行
                    boolean lock = rl.lock();
                    if(lock){
                        // 获得锁
                        jedis = new RedisConnection(...);
                        pipeline = jedis.pipelined();
                        // 针对redis的一些操作
                        ......
                    }
                }
                catch (InterruptedException interrupted){
                    break;
                }
                catch (Exception e) {
                    LOGGER.error("",e);
                }
                finally {
                    if(null != pipeline){
                        try {
                            pipeline.close();
                        } catch (IOException e) {
                        }
                    }
                    if(null != jedis){
                        jedis.close();
                    }
                    if(null != rl){
                        rl.unlock();
                    }

                }
            }
            if(null != rl){
                rl.close();
            }
        }
}
//redis 竞争锁
public class RedisLock {
    private static final Logger LOGGER = LoggerFactory.getLogger(RedisLock.class);

    private String lockKey;
    private long keyValue;
    private int expire;

    private volatile boolean isLocked; // 现有锁状态

    private Thread thread;

    /**
     *
     * @param lockKey 锁名
     * @param expire 锁过期时间
     * @throws URISyntaxException
     */
    public RedisLock(String lockKey, int expire) throws URISyntaxException {
        this.lockKey = lockKey;
        this.expire = expire;

        this.isLocked = false;      // 未锁
    }

    public void close(){
        unlock();
    }

    /**
     * 请求锁
     * @return
     */
    public boolean lock() {
        if(isLocked){
            return isLocked;
        }
        synchronized (this){
            isLocked = lock(lockKey, expire);
            if(isLocked){
                if(null == thread || !thread.isAlive()){
                    thread = new Thread(new Worker());
                    thread.start();
                }
            }
            LOGGER.debug(String.format("%s lock %s!", lockKey, isLocked));
            return isLocked;
        }
    }

    /**
     * 释放锁
     */
    public void unlock() {
        if (isLocked) {
            synchronized (this) {
                // 锁
                if (null != thread) {
                    thread.interrupt();
                    try {
                        thread.join();
                    } catch (InterruptedException e) {
                    }
                    thread = null;
                    LOGGER.debug(String.format("%s unlock!", lockKey));
                }
            }
        }
    }

    private boolean lock(String lockKey, int expireSec){

        Jedis jedis = null;
        try{
            jedis = Environment.getRedisPool().getResource();//new RedisConnection(RedisInfo.CAP);
            // 获得key的值
            String value = jedis.get(lockKey);
            if (null == value) {
                // 如果key没有值,则添加key同时累加1
                keyValue = jedis.incrBy(lockKey, 1);
                if (1 == keyValue) {
                    // 如果累加值等于1,则说明是第一个使用者,则获得逻辑锁,并添加过期时间
                    jedis.expire(lockKey, expireSec);
                    return true;
                }
                else {
                    // 如果累加值不等于1,则说明不是第一个使用者,则放弃处理
                }
            }
            else {
                // 如果key有值,说明已经被占用,则放弃处理
            }
        }
        catch(Exception ex){
        }
        finally {
            if(null != jedis){
                jedis.close();
            }
        }
        return false;
    }

    class Worker implements Runnable {

        @Override
        public void run() {

            while(true){
                Jedis jedis = null;
                try {
                    Thread.sleep(1000);
                    if (isLocked) {
                        // 锁且未释放
                        jedis = Environment.getRedisPool().getResource();//new RedisConnection(RedisInfo.CAP);
                        expire(jedis, lockKey, expire);
                    }
                }
                catch (InterruptedException e) {
                    break;
                }
                catch (Exception e){
                    LOGGER.error("", e);
                }
                finally {
                    if(null != jedis){
                        jedis.close();
                    }
                }
            }

            Jedis jedis = null;
            try{
                jedis = Environment.getRedisPool().getResource();//new RedisConnection(RedisInfo.CAP);
                releaseSource(jedis);
            }
            catch (Exception e){
                LOGGER.error("", e);
            }
            finally {
                if(null != jedis){
                    jedis.close();
                }
            }
            LOGGER.debug(String.format("%s release lock thread!", lockKey));
        }

        private void releaseSource(Jedis jedis){
            del(jedis, lockKey);
            isLocked = false;   // 未锁
        }

        private void del(Jedis jedis, String key){
            if(null != jedis) {
                jedis.del(key);
            }
        }
        private void expire( Jedis jedis, String key, int expire){
            if(null != jedis) {
                jedis.expire(key, expire);
            }
        }
    }

猜你喜欢

转载自blog.csdn.net/qq_28376741/article/details/89022507