为了同窗们看起来一目了,特按以下思路进行讲解。java
1.出现的场景缓存
2.分析及解决的过程安全
3.总结session
最近公司要使用zookeeper作配置管理(后面简称ZK),而后本身就提早用虚拟机进行了ZK三台集群的搭建。以后开始选择使用zookeeper的java client工具,google了半天,发现了一个很名强大的Apache的Curator工具,不少底层的东西都已经给你封装好了,因此用起来很方便,由于我使用的场景是作配置管理,因此使用Curator的Framework就够了。Curator相对于zookeeper,就至关于Guava之于Java.ide
由于天天的访问量上亿级的,因此考虑的因素仍是不少,所以从网上找了一些demo,而后本身就开始写一些测试的类,下边的这个方法是用于获取客户端,而且加入了一些监听和输出:工具
private static CuratorFramework getClient(String namespace) throws Exception{ ACLProvider aclProvider = new ACLProvider() { private List<ACL> acl ; @Override public List<ACL> getDefaultAcl() { if(acl ==null){ ArrayList<ACL> acl = ZooDefs.Ids.CREATOR_ALL_ACL; acl.clear(); acl.add(new ACL(Perms.ALL, new Id("auth", "admin:admin") )); this.acl = acl; } return acl; } @Override public List<ACL> getAclForPath(String path) { return acl; } }; String scheme = "digest"; byte[] auth = "admin:admin".getBytes(); int connectionTimeoutMs = 1000; String connectString = "127.0.0.1:2181"; CuratorFramework client = CuratorFrameworkFactory.builder(). aclProvider(aclProvider). authorization(scheme, auth). connectionTimeoutMs(1). connectString(connectString).sessionTimeoutMs(50). namespace(namespace). retryPolicy(new RetryOneTime(1)).build(); client.getConnectionStateListenable().addListener(new ConnectionStateListener() { @Override public void stateChanged(CuratorFramework client, ConnectionState newState) { System.out.println("** STATE CHANGED TO : " + newState); } }); client.start(); client.getZookeeperClient().internalBlockUntilConnectedOrTimedOut(); // client.getZookeeperClient().blockUntilConnectedOrTimedOut(); System.out.println(client.getZookeeperClient().isConnected()); System.out.println(client.getState()); return client; }
获取客户端以后就能够启动(client.start())客户端而且建立至关的Node以及它的payload. 感受写的已经能够了,并且通过简单的测试,以为能够了,而后就上到测试环境上了,测试环境的访问量并非很大,因此也没有什么特别异常,以后就放到线上了。性能
当把程序放到线上去以后,系统的JVM监控系统就开始报警,线程数由几百迅速增长到了三、4千个,直接超过了咱们设置的报警阈值,因此感受使用jstack命令 jstack -l pid > threadDump,找一个 stack analyzer online的一个网站 fastthread.io, upload作好的threadDump文件,上边有不少汇总,而后基本上一目了然:学习
1700多个TIMED_WATING,还有1700多个TIMED_WATING,这里边确定有问题,而后继续往下拉,会按线程分组进行展现:测试
会发现有大概有77%的线程和Curator有关系,这个应该就是它的问题了,那么点开里边的内容,就能看到线程的明细了,继续:优化
里边有Curator Framework的代码了,找到至关的行907,发现只要Client一启动的话就会使得BlockingQueue会有一个take()的动做,这个take的含义是将head取到,若是没有的话就等待,这就是线程WAITING的状态,而后继续看是在什么地方调用的它。
找到了,原来是客户端启动(client.start())的时候进行的调用,由于我在网上看到不少地方说build模式拿到的Client是线程安全的,因此我就每次拿一次client,而后调用其start()。这样每一个不一样的线程就会都等待在那个位置上。我没有在Finally调用 CloseableUtils.closeQuietly(client); 由于请求量太大,若是频繁的调用关闭客户端会形成性能降低,必须保持一个长链接。
打开Curator的官网上,里边也进行了说明,建立采用build的方式是线程安全的,可是要保持单例。
这样问题找到了,下边开始想着若是修复和优化,首先让它实现单例,同时还不能用完以后就直接关闭。同时要保持长链接,在特定状况下进行链接关闭,那就若是出现异常为
KeeperException.ConnectionLossException时须要捕获而且进行计数和关闭。同时也为了效率考虑,再获取Node的payload时将payload进行缓存,这样再次减小了对zk的大量访问。同时能够根据本身的实际状况去考虑缓存的时间。
if(client == null || client.getState().equals(CuratorFrameworkState.STOPPED) || !client.getNamespace().equals(namespace)) { synchronized (ZookeeperUtil.class) { if(client == null || client.getState().equals(CuratorFrameworkState.STOPPED) || !client.getNamespace().equals(namespace)) {
CloseableUtils.closeQuietly(client); client = getClient(namespace); } } }
同时在网上找到zookeeper集群上从3.4开始,从客户端链接数maxClientCnxns(配置在zoo.cfg)默认链接数为60,改成0时不限制。
总结:
1. 当遇到线程数增长或CPU太高时须要使用jstack将JVM的线程数据导出到文件,而后经过在线工具或本身下载的工具进行分析,我仍是比较喜欢这个在线的分析工具,它能分析出总的线程数中按状态进行分析,还能够按线程类型进行分组,很强大。
2. 遇到问题要冷静思考,而后多写几个小的demo进行测试。我其实在写这个问题的过程当中我是写了测试类进行模拟的,而后经过本机的jvisualvm查看栈的状况,根我推断的一致的,因此就会找到解决的方法。
3.有些技术知识仍是从官方网站学习,并且若是看书的话,须要从头看到尾,这样的话基本上能了解事务的所有内容,不然只看到部份内容。
若是有写的不对的地方,欢迎同窗们来拍砖~