记一次unable to create new native thread错误处理过程

早上运维说线上出错了,发了如下截图:

错误截图

unable to create new native thread,看到这里,首先想到的是让运维搞一份线上的线程堆栈(可能通过jstack命令搞定的)。发现线上的堆栈竟然有5M多大,打开文件后线程数量居然达到了8000多个。有大量的线程堆栈如下图所示:

线程堆栈

。看到有大量的线程是以scheduler-开头的,可以确定的是程序里有使用spring的定时任务功能,定时的处理一些任务。会不会是有长时间的定时任务造成线程没有关闭。于是翻看了下spring 的配置文件。代码如下:

    <task:executor id="executor" pool-size="5" />
    <task:scheduler id="scheduler" pool-size="10" />
    <task:annotation-driven executor="executor" scheduler="scheduler" />

可以肯定的是这里有配置线程池,但是线程池的大小也就10个啊,怎么会有8000多个线程。看到这里,我差点去翻spring的源码,想看看是不是spring的bug。检查看发现通过task:scheduler启动的线程是以scheduler-命名的,并不会有后面的SendThread与EventThread。上面的堆栈还能看出线程与zookeeper有关。于是还是回到自已的代码中,(必竟自已的代码出问题的可能性还是比框架代码出问题的概率要高出一个量级的)。   服务是分布式部署的,各部署的机器能够提供对等的服务。对于有需要一台机器执行的任务便会用到分布式锁,而zookeeper是能够用来实现分布式锁的,代码如下:

/**
 * 基于zookeep实现的分布式锁
 */
public class ZookeeperLock implements DistributedLock,Watcher{
    private String basePath;
    private ZooKeeper zk ;
    private String host;
    
    Logger logger = LoggerFactory.getLogger(ZookeeperLock.class);
    private final static String realIP =  IpUtils.getRealIp();
    
    public ZookeeperLock(String host,String basePath) {
        this.basePath = basePath;
        this.host = host;
    }
    
    @Override
    public boolean getDistributedLock(String businessname) {
        if(StringUtils.isBlank(businessname)){
            throw new IllegalArgumentException("businessname can not be empty!!!");
        }
        try {
            zk = new ZooKeeper(host, 3000, this);
            logger.info("success get zk:"+zk +"and host is:" + host);
        } catch (IOException e) {
            logger.error("error is IOException:" + e.getMessage());
        }
        int retries=0;
        while(retries++ < 5){
            if(zk.getState() == States.CONNECTED ){
                String key =basePath + "/" + businessname + "_" +DateFormatUtils.format(new Date(), "yyMMdd") ;
                try {
                    if(zk.exists(key, false) == null){
                        zk.create(key, realIP.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
                        logger.info("get distributed lock success! ip is " + realIP);
                        return true;
                    }
                    
                } catch (KeeperException e) {
                    logger.error("error is KeeperException: " + e.getMessage());
                    releaseLock();
                } catch (InterruptedException e) {
                    logger.error("error is InterruptedException 1: " + e.getMessage());
                }
                logger.debug("fail to get lock , interrupt the loop!");
                break;
            }
            //仅在State状态不少Connected时才会循环多次等待zookeeper连接成功
            try {
                Thread.sleep(1000);
                logger.info("zookeeper client has not connected." + zk.getState());
            } catch (InterruptedException e) {
                logger.error("error is InterruptedException 2: " + e.getMessage());
            }
        }
        return false;
    }

    @Override
    public void releaseLock() {
        if(zk != null){
            try {
                zk.close();
                zk = null;
            } catch (InterruptedException e) {
            }
        }
    }

    public void process(WatchedEvent event) {
        logger.debug( " Zookeeper client connect success! The clent ip is " + realIP);
    }

}

分布式锁原理: 1):往指定的目录创建临时节点,创建成功的成获取锁。 2):业务执行完成后,需要删除相应的临时节点,相当于释放锁。 代码提供了加锁与解锁的逻辑,通过ZookeeperLock 对象,便能够实现分布式锁了。ZookeeperLock通过xml文件的配置来进行初使化。 为了方便对ZookeeperLock 的使用,提供了相应的模版类,代码如下:

@Component 
public class ZookeeperLockTemplate {
    private static final Logger logger = LoggerFactory.getLogger(ZookeeperLockTemplate.class);
    
    @Autowired  //容器会自动注入
    private ZookeeperLock distributedLock;
    
    public void execute(String businessname, ZookeeperLockCallBack callBack){
        try {
            if (distributedLock.getDistributedLock(businessname)) {
                callBack.call();
            }
        } catch (Exception e) {
            logger.error("ZookeeperLockTemplate error! ", e);
        } finally {
            distributedLock.releaseLock();
        }
    }
    
}

代码看上去并没有什么问题,但是问题就出在ZookeeperLockTemplate这个类上。通过componet注解注释的类在Spring 容器里是单例的,里面的属性即使scope类型是prototype,每次使用的时候也是用的同一个实例。上面的ZookeeperLockTemplate如果在多线程的情况下使用,便会有大的问题,一个线程可能关闭另一个线程开启的锁,从而造成另一个线程的锁无法关闭,导制zk = new ZooKeeper(host, 3000, this)这个对象的线程一直在内存中修改的方法也很简单,在execute方法里,每次都从spring IOC的容器里取一次就好了。   上面的代码如果有问题,那在线上应该能够频繁的重现,而不至于上线好久了才发现这样的问题。这里主要是因为线上就三个定时任务,而且定时任务执行的并不频繁,但是他们的执行时间是有重叠的(也就是有可能有多线程的情况)。从日子累计到8000多条线程也可以看出只有运行了一定时间后,才可能出现问题。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏芋道源码1024

2018 年你不能错过的 Java 类库

因为内容非常好,我便将它整理成参考列表分享给大家, 同时附上各个库的特性简介和示例。

1442
来自专栏java思维导图

给你一份SpringBoot知识清单

在过去两三年的Spring生态圈,最让人兴奋的莫过于Spring Boot框架。或许从命名上就能看出这个框架的设计初衷:快速的启动Spring应用。因而Spri...

1594
来自专栏Java技术栈

2018年不能错过的 14 个 Java 库!

3631
来自专栏编舟记

R3 Corda 和 springboot 集成

因为Corda内置的Corda Webserver已经被标记成弃用了,一般不再提供支持;再者,springboot的生态明显占优。

2512
来自专栏竹清助手

浅谈Linux磁盘修复e2fsck命令

检查 /dev/mapper/VolGroup00-LogVol02 是否有问题,如发现问题便自动修复:

3142
来自专栏MelonTeam专栏

浅析Binder机制

一个老生常谈的话题,但也是在Android学习过程中一定会遇到的一个主题。结合自己的学习历程分享一下我对Binder架构的基本理解吧。 刚开始学习的时候,并没...

26110
来自专栏大魏分享(微信公众号:david-share)

JavaEE中资源注入松耦合的实现 | 从开发角度看应用架构13

上下文和依赖注入(CDI)规范是Java EE规范中的许多从属规范之一。虽然CDI是在Java EE 6中引入的,但CDI背后的概念已经出现在各种框架中,包括S...

1052
来自专栏转载gongluck的CSDN博客

_CrtSetDbgFlag

_CrtSetDbgFlag 若要了解有关 Visual Studio 2017 RC 的最新文档,请参阅 Visual Studio 2017 RC 文档。...

4639
来自专栏技术博文

Memcache

Memcached概念:     Memcached是一个免费开源的,高性能的,具有分布式对象的缓存系统,它可以用来保存一些经常存取的对象或数据,保存的数据像一...

4234
来自专栏皮皮之路

【Spring】浅谈spring为什么推荐使用构造器注入

60514

扫码关注云+社区

领取腾讯云代金券