工作台系列1-DIY工作站

​ 很小的时候就有了电脑,但是都没有成功拆掉重新安装过。那时候就羡慕能够拆掉并且自己DIY的年轻人(那个时候觉得这就是聪明,有天赋)。可能是我太笨,也可能是我三心二意,即使买了多年的“电脑爱好者”(我竟然记得这本书名,当时大多数都看不懂,但就是喜欢买),也没有学会自己DIY这件事情。已经到了这个年纪的自己(有老婆有娃),突然后那么种渴望:哪怕是被生活所迫(必须好好工作养家),也要抽空也要弥补遗憾;我有种感觉:一直渴望得到的东西,以后终归会以某种形式在生命中得到完结。于是借着自我提升学习,公司申请服务器困难为理由,开始了人生中第一次的DIY:

过程

  1. 看了很多人的DIY分享,自制渲染服务器,游戏多开工作室机器,深度学习机器等的文章,大致有了概念:

    • 洋垃圾:服务器拆出来的零部件性能比家用的全新零部件好,价格还便宜。一般着淘宝上面专业的卖家,基本质量没有问题。
    • 因为走的是“服务器”路线,所以主板也是ATX或者EX-ATX的,需要大极限。
    • 主板主要分为CPU计算密集型和GPU计算密集型:CPU计算密集型的可以是双路主板:X79,X99组装的人比较多;GPU密集型则要求主板有多个PCI-X接口。
  2. 确定了以上的信息开始通过淘宝找各种零部件了:

    • 主板:x79 双路主板,看了下价格,差距非常大,有650左右的,有2000左右的;毫无疑问,确认能用就上最便宜的。在看过几家的说明之后,确认了650左右的富士康,英业达c602主板也是可以用的,就是有短路问题,可能让主板报废。我想问题不大,到时候咨询下卖家怎么解决就好了,于是就大胆没了,最终证明我的判断没有错, 附主板链接(卖家很不错,一直耐心处理我的咨询)。 610
    • CPU: 主板敲定之后,确认合适的E5系列CPU,那必须是要上20核的,最终选择2660v2,附CPU链接。 1680
    • 内存:随意买了,先上32G。 563
    • 散热器:感觉水冷比较酷,查了一些资料说水冷散热效果不错,于是买了两个单风扇水冷。 388
    • 机箱:先马坦克,EX-ATX,ATX等都支持。 209
    • 硬盘:二手3T硬盘随便来一块。因为二手主板没有SATA线,补两根(一根备用)。 358 + 12.8
    • 电源:主板是24+8+8,所以只能买特定的电源,刚开始买了伯爵的,但是发现支持不了,差点烧坏主板;换了主板卖家推荐的电源 : 239
    • 网卡:因为是服务器主板,只有光线网卡,需要购买家用千兆网卡一枚。18
    • 显卡:一年前买的显卡GT730一直没用上。368; GTX750 259

    总价:4136

  3. 拿到这些零部件之后就要开始动手了。

    • 主板安装如果和机箱接触就会发生短路,然后就彻底完蛋了,毕竟不是ATX的主板,非常规,所以在淘宝卖家的指导下买了特定的主机箱加上特定位置的胶带纸解决问题。
    • 安装CPU,内存,显卡,硬盘,散热器和连接usb线。

软件篇

为了达到不带mac上班的目的(15 实在是重啊),考虑了可行性:

  1. 上班只要使用台式机,可以运行mac系统的话,编程画图等工作足够了。
  2. 如果是需要移动办公的话,利用ipad 进行远程连接自己的主机操作展示就行了。

于是有了两套方案:

  1. 安装centos为主系统,使用vnc来控制主机
  2. 安装win10为主系统,使用remote destop来控制主机

方案一

在尝试了方案一之后,怎么也解决不了vnc的显示问题,其实还是没有处理好显卡驱动。处理了整整一个礼拜,最终放弃。

方案二

使用 win10 做宿主机加鲁大师果然方便靠谱,remote desktop一点毛病没有。

接下来就是安装mac了,最终只有虚拟机这一条路了,考虑到自己的机器毕竟CPU能力还是强大的,只是安装了进行编程和omni相关软件的使用,想来应该问题不大,于是开始了。

问题1: Remote Desktop 内网和外网的访问方式
问题2: 如何使用mac的问题
  • mac因为是虚拟机中安装的,感觉还是有明显的卡顿的,先关闭各种特效解决看看。结果还是不理想的。
  • 于是找了淘宝远程支持安装黑苹果(就不折腾了,节省点时间,350RMB),在过程中还发现了之前的买的368的显卡是“马甲卡”,是被刷过的显卡,真是水好深啊。后来又买了个一块技嘉gtx750,等待搞定。
问题3: 分辨率问题
  • remote desktop相关的RDP协议本身也不是为了显示性能设计的, 默认最多2K,所以体验是差了点,和直接用机器去驱动显示还是不同的。
  • remote desktop的现实分辨率还收到client端的实际限制。

工作台制作系列3-X11

在自己装配完成主机之后,要开始安装主机和虚拟机了,这个时候主机并没有显示器,只有网线,所以考虑通过自己的笔记本连接主机进行安装,可是安装vmware需要界面如何处理?经过一段时间的了解,X11正好合适,顺道了解下。

什么是X11

神奇的X11,这个古老的Unix下的产物:一种C/S结构的协议。

适合这种场景:我有一个电脑A,希望使用服务器B来启动有界面的应用。通常通过如下方式:

  1. A ssh 到 B
  2. B 通过sshd设置ssh server配置来forward X11的协议数据到 A,这个时候 B是 X11的client, A是 X11的Server

背景

A : macOs 13.3

B : Centos7

  1. A 中安装xQuartz,自行搜索安装方式。
  2. 修改 B 的sshd配置:
    • 增加如下配置:
      X11Forwarding yes
      X11DisplayOffset 10
      X11UseLocalhost yes
    • 重启服务:service sshd restart。
  3. A 的 ~/.ssh/config 下面添加配置:XAuthLocation /opt/X11/bin/xauth。原因
  4. ssh -X B’s_username@B’s_host。(第一次要使用AX,用来生成授权文件)
  5. xclock 验证。

发散

现在的投屏软件有些也是用这个协议实现的。

VNC : RFB协议, vnc-server一般是和x11协议联合使用

X11是协议,一般情况下 A是我们自己机器,B是服务器端机器,A是X11的server,B是client;是通过ssh -X forwarding 连接到远程服务器,最后让B的client告诉了A来显示图形。

mac也是用X11协议来显示图画,不过将一些模块做到了kernal里面来提升性能。

mweb-hexo-git-pseudo-tty

很长时间没有再给增加新的博客内容了,机缘巧合,重新拾笔,发现了七牛的图床出现问题了,测试时使用的域名失效了,导致了图片全部失效。

经过了如下步骤:

  1. 找回图片:https://zhuanlan.zhihu.com/p/30513914
  2. 安装mweb,注册imgur作为远程图床,唯一一点麻烦的是,必须要开启shadowsocks的全局模式,然后才能上传图片。(按照:https://zhuanlan.zhihu.com/p/30513914 的教程不过其实并不需要远程图床,因为都是使用的相对路径读取的图片,图片已经传到github上面了;暂且拿这个图床做一个备份好了)
  3. 然后命令行的形式下上传内容的时候碰到了如下错误:
    error

ssh 通过 http-proxy代理(https://zhuanlan.zhihu.com/p/36102433)

通过这篇文章,看到了如下内容:
hexo-pseudo-tty
中文大多译为“伪终端”,勾起了我的好奇心,经过一番搜索和了解的到如下总结:
pseudo
参考文章:A, B

大致了解这个概念之后,继续进行,在

~/.ssh/config

中增加如下代码:
ssh-socket5
搞定了和github之间那堵墙的问题。


好了,搞定~~

LinkageError 排查过程

问题

在部署loan-lending的分支进行测试进件的时候,发现了如下的报错:
linkage-1

分析过程

  1. 看着就像是ClassLoader找不到对应的类;但是这个类就是此应用的class文件,还不是引入的二,三方包,奇怪。
  2. 在本地和k2上面都打印了对应的classLoader,看起来也都比较正常。3
  3. 在本地重启,关闭了一些干扰功能和日志(特别是consumer),好好观察了下日志,发现每次出现问题之前一定会有一次这个错误:
    linkage-2

  4. 这个问题看着就像是包冲突了,那么解决netty的包冲突,去掉所有其他的netty包,留下了一个版本,然后重启,再次测试没有问题了!!

推测

JVM在运行时加载类A的时候,如果类A的static 区域发送了LinkageError,那么这个A也会发生LinkageError。所以第一次报错是static区域中具体的LinkageError信息第二次以及以后的报错都是A相关的LinkageError;如果是在线上不断的有日志打印,还有很多其他的混淆信息,很难发现有且仅有一次的报错;(因为正常排查逻辑都会去关注报错量较大的信息)。

实验证明

  1. 自己起了项目,在HttpIo中增加如下代码:
    linkage-3

  2. testBean 嵌套 testBean2,
    linkage-4
    linkage-5

  3. 最外层的测试用例用反射调用testBean的test方法
    linkage-6
  4. 用这个模拟了真实应用中prophetclient相关的调用过程,最终调用看如下日志:
    linkage-7

    最终结论

    最终推测成立:
    >
    JVM在运行时加载类A的时候,如果类A的static 区域发送了LinkageError,那么这个A也会发生LinkageError。所以第一次报错是static区域中具体的LinkageError信息第二次以及以后的报错都是A相关的LinkageError

fastjson引发的oom事件

问题描述

12月1日的时候 风控系统 线上出现 metaspace OOM


排查过程

  • 查询了背景资料:hotspot 1.8 里面有了metaspace,且修改自定义classloader相关的class的回收机制。当自定义classloader被标记为可以回收的时候,相关的class才能被全部回收。

  • 由于 风控系统 使用了字节码生成动态class技术,所以初步判定为自定义classloader造成了内存泄漏。

  • 因为出现oom的情况下,java的vm参数设置的好,很容易就dump出heap,然后进行分析

  • 放到MAT使用 list object with incoming reference:
    pic1
    明确可以看到有很多的ClazzLoader对象。

  • 找到任意一个实例,去掉weak,soft,phantom类型的reference,留下的如下:
    pic2
    判断为fastjson 引起的内存泄漏。

  • 翻看代码查看哪里fastjson使用了func中的clazz对象:
    pic3
    pic4

  • 竟然是Func执行成功的情况下的 debug的日志 导致了这个问题,虽然这个日志并没有打印出来(debug级别),但是java执行的时候在这里没有lazy的处理。 很巧合的引起了这个问题。


验证复现问题:

使用fastjson的情况:

pic5

  • 通过分析heap可以发现相同的现象:
    pic7
    pic8
    pic9

不使用fastjson的情况:

pic10


结论

fastjson这个库对动态class文件的序列化是存在风险的。

hdfs-case-one

hdfs decommition 的时候同时开启balancer的问题

这次机房迁移发现 机器长久处于decommition in prograss 的状态:
hdfs-decommition-balancer-1.png

但是overview里面Number of Under-Replicated Blocks = 0。

老机房机器日志如下:
hdfs-decommition-balancer-2.png

新机房机器日志如下:
hdfs-decommition-balancer-3.png

搜了一些资料:
hdfs-decommition-balancer-4.jpg

结合balancer的日志:
hdfs-decommition-balancer-5.jps

判断是本机在decommissioning过程中只参考了本机的块是否复制成功,没有从全局去考虑(已经被balancer拷贝过了)。

估计也没人在decommisioning的过程中开启balancer的, 所以这些decommissioning in progress的机器上的datanode直接kill掉了,不影响使用,不丢失数据。

zeppelin-case-one

最近频繁发现 zeppelin刷新页面无法展示页面,从前端看是websocket一直hold。


现场

2016-12-09 下午2点半左右重现。

  • 重启zeppelin的时候发现 NIO Exception:too many file 问题提示,但是还是能够正常使用zeppelin。

  • 在系统中执行命令有如下提示:

    zeppelin-case-1-1.png


排查过程

  1. 猜测是zeppelin相关进程开启文件过多,zeppelin-server,zeppelin-interpriter对应的进程hold的文件数量如下:

    zeppelin-case-1-2.png

    zeppelin-case-1-3.png

  2. 猜测是操作系统层面问题,找到系统日志如下:

    zeppelin-case-1-4.png

  3. 经@万两 回忆 VFS 跟zeppelin的配置有关,zeppelin.notebook.storage 使用的类和VFS有关,默认类如下:

    zeppelin-case-1-5.png

    然后之前定制过一个这个repo类,自动进行commit notebook。

  4. 经@金砖猜测git文件太多。然后查看了.git,果然,objects文件夹有500+M。

  5. 定位完成,回滚到默认配置,删除.git文件夹。


总结,衍生

案例结束了。那么衍生一下:

查看本机当前用户的具柄数量用: ulimit -u 为:257395
查看本机最大文件数量:cat /proc/sys/fs/file-max 为:209708

具柄和 最大文件数量怎么不匹配呢?

据资料表明: file-max是系统所有进程总和能够有的最大的文件数量;而ulimit -u 是对单个用户限制的最大文件数量。

所以这个系统的这两个参数配置也是有问题的。

hacking phoenix

因为微服务使用了springboot,并且用使用了phoenix 来读写Hbase,应用经常超过1000的线程数量。


分析 stack:

自己管理了400个线程的线程池,phoenix-1-thread 有128个,HConnection的线程有256个,再加上一些tomcat的http线程,轻松就能到1000个线程。

略多,希望把phoenix和HConnection相关线程干掉。官方文档没有提到怎么设置,网上也没有资料。

那么,看源码吧。


解剖代码:

  1. JobManager 创建了 phoenix-{index}-thread ,JobManager 初始化是由QueryServicesImpl完成的(使用了QueryServicesOptions)

    图片

  2. JobManager.JobCallable 被 ServerCacheClient 使用。

  3. ServerCacheClient 被 MutationState的方法send(Iterator<TableRef> tableRefIterator) 使用。
  4. MutationState 被 PhoenixConnection的方法commit()使用。

恩,到这里,逻辑已经串联起来了。


分析问题:

PhoenixDriver 获取 connection 的时候可以使用方法 Connection connect(String url, Properties info), info 按理是我们的自定义配置,但是实际却不能生效。配置项参考

但是我们发现 PhoenixDriver.getQueryServices() 创建了 QueryServicesImpl

  1. PhoenixEmbeddedDriver 定义了函数 abstract public QueryServices getQueryServices() throws SQLException; 参数列表竟然是空的!
  2. QueryServicesImpl使用了一个默认的参数配置!

图片


第一次尝试:

所以修改 PhoenixEmbeddedDriver 定义 , PhoenixDriver 的实现:

  1. PhoenixEmbeddedDriver

    abstract public QueryServices getQueryServices(Properties properties) throws SQLException;
    
  2. PhoenixDriver

    @Override
    public QueryServices getQueryServices(Properties properties) throws SQLException {
        try {
            lockInterruptibly(LockMode.READ);
            checkClosed();
            // Lazy initialize QueryServices so that we only attempt to create an HBase Configuration
            // object upon the first attempt to connect to any cluster. Otherwise, an attempt will be
            // made at driver initialization time which is too early for some systems.
            QueryServices result = services;
            if (result == null) {
                synchronized(this) {
                       result = services;
                    if(result == null) {
                        services = result = new QueryServicesImpl( getDefaultProps().addAll(properties) );
                    }
                }
            }
            return result;
        } finally {
            unlock(LockMode.READ);
        }
    }
    

恩,到目前为止,自定义的参数能够传进去了。这个时候我实验了下,还是没有设置成功!


第二次尝试:

继续看QueryServicesImpl是怎么处理我们传进去的自定义的 配置的。竟然是这样的:

super(defaultProps, QueryServicesOptions.withDefaults())

QueryServiceOptions也是默认的。传入的defaultProps啥用都没有。ok,我们改造下:

super(defaultProps, QueryServicesOptions.withDefaults().setAll(defaultProps));

这下看起来对了,在实验下,还是没有成功!这个坑真深。再看下setAll方法,看到ReadOnlyProps对象。发现内部包了propsoverrideProps。 同时isEmptyasMapiterator实现的时候都只考虑到了props, 但是我们可以看到getRaw方法其实把两个map都考虑到了,我姑且认为实现这个类的人,忘记了修正asMapiteratorisEmpty,那么我们自己来修改吧,实现好的如下:

public Map<String,String> asMap() {
    Map<String, String> mergedMap = new HashMap<String, String>();
    mergedMap.putAll(props);
    mergedMap.putAll(overrideProps);
    return mergedMap;
}

@Override
public Iterator<Entry<String, String>> iterator() {
    return this.asMap().entrySet().iterator();
}

public boolean isEmpty() {
    return props.isEmpty() && overrideProps.isEmpty();
}

好的,再实验一下,终于好了~~~~~

可配置参数请参考QueryServices,附带使用方式:

Properties properties = new Properties();
properties.setProperty(QueryServices.THREAD_POOL_SIZE_ATTRIB, "32");
conn = DriverManager.getConnection(phoenixUrl, properties);

那么现在也修改下HConnection的连接数。

HConnection是由HConnectionFactory创建,实际也是用了HConnectionManager。

一路跟下去,可以看到ConnectionManager管理了一个pool,通过getBatchPool可以看到参数有:

hbase.hconnection.threads.max
hbase.hconnection.threads.core

默认都是256。

好,那我们修改下,都改成 128:

properties.setProperty(QueryServices.HCONNECTION_POOL_CORE_SIZE, "128");
properties.setProperty(QueryServices.HCONNECTION_POOL_MAX_SIZE, "128");

最后都搞定了!


回过头来,既然已经解决问题了,就了解了下这两个线程池都是干嘛用的。

  1. phoenix-1-thread 用来:

    缓存了HTable 和 HRegionLocation 的关系, Callable 任务是 thrift 的RPC调用。

  2. HConnection 线程池用来:

    缓存复用,减少创建开销。

hacking fastjson

使用 java agent 横向收集一些日志,在上线老应用servicemanager的时候,cpu和load飙升。

第一次尝试

先用了这个脚本跑了下获取cpu最高的几个线程对应的jstack(此脚本适用于由固定生命周期长的线程引起的cpu飙升):

#!/bin/bash

JAVA_HOME="/opt/taobao/java"
PID=$(${JAVA_HOME}/bin/jps | grep "Bootstrap" | cut -d " " -f1)
tmp_dir="/tmp/java_${PID}"
tmp_file="java_${PID}_trace.log"
threads_file="threads_${PID}_trace.log"
stack_file="stack_${PID}_trace.log"

if [ -d "${tmp_dir}" ]; then
        rm -r ${tmp_dir}
fi

mkdir ${tmp_dir}
cd ${tmp_dir}

if [ -f "${stack_file}" ]; then
        rm ${stack_file}
fi

touch ${stack_file}

${JAVA_HOME}/bin/jstack ${PID} > ${tmp_file}

ps H -eo user,pid,ppid,tid,time,%cpu --sort=%cpu --no-headers \
        | tail -10 \
        | awk -v "pid=${PID}" '$2==pid{print $4"\t"$6}' > ${threads_file}

for index in $( seq 1 10 )
do
        line=$(cat ${threads_file} | sed -n "${index}, ${index}p")
        nid=$(echo "${line}"|awk '{printf("0x%x",$1)}')
        cpu=$(echo "${line}"|awk '{print $2}')
        awk -v "cpu=${cpu}" '/nid='"${nid}"'/,/^$/{print $0"\t"(isF+    +?"":"cpu="cpu"%");}' ${tmp_file} >> ${stack_file}
done

发现从top 10的cpu占用的栈信息上面看不出来啥。应该不满足 固定生命周期长线程 前提。

第二次尝试

用 @沉思 所写的oncrash脚本收集所有的信息,脚本如下:

mkdir dumpdir
cd dumpdir
echo '
#!/bin/bash

if [ "x${JAVA_HOME}" == "x" ]
then
        JAVA_HOME="/opt/taobao/java"
fi

NOW=$(date '+%Y%m%d-%H%M%S')
PID=$(${JAVA_HOME}/bin/jps -l | grep 'org.apache.catalina.startup.Bootstrap' | cut -d " " -f1)

echo "JAVA_HOME=${JAVA_HOME}"
echo "PID=${PID}"
echo

echo "jstack"
${JAVA_HOME}/bin/jstack ${PID} > stack.${NOW}

echo "histo"
${JAVA_HOME}/bin/jmap -histo ${PID} > histo.${NOW}

echo "ps -Tel"
ps -Tel > ps.${NOW}

echo "top -c -M"
top -c -M -n3  > top.${NOW}

echo "top -H -p -c -M"
top -H -p ${PID} -c -M -n3 > top-java.${NOW}

echo "who"
w > w.${NOW}

echo "vmstat 1 10"
vmstat 1 10 > vmstat.${NOW}

echo "ps aux"
ps -aux > psaux.${NOW}

echo "free"
free -m > free.${NOW}

echo "netstat"
netstat -apn > netstat.${NOW}

echo "jstat -gcutil"
${JAVA_HOME}/bin/jstat -gcutil ${PID} > jstat-gcutil.${NOW}

echo "jstat -gc 1000 5"
${JAVA_HOME}/bin/jstat -gc ${PID} 1000 5 > jstat-gc.${NOW}

echo "lsof"
lsof -p ${PID} > lsof.${NOW}

echo "sar -n Sock"
sar -n SOCK > sarnsock.${NOW}

echo "sar -n DEV"
sar -n DEV >sarndev.${NOW}

echo "sar -b"
sar -b > sarb.${NOW}

echo "iostat 1 5"
iostat -k 1 5 > iostat.${NOW}

echo "jmapdump"
${JAVA_HOME}/bin/jmap -dump:format=b,file=jmapdump.${NOW}.bin ${PID}

' > ./call.sh
bash ./call.sh
rm ./call.sh
cd -
echo

获得栈信息如下:
fastjson

然后翻翻代码意识到了JDK6 ClassLoader里面使用了大量的synchronized

主要性能问题来源于两个原因:

  1. 代码中没有对Class和Method做cache,多个线程同时执行load某个类的时候就会有并发问题。

  2. json string的数据量大,所依赖的fastjson的方法 JSON.parseObject(String text, Class<?> class) 使用了asm修改了客户端自定义的pojo类,然后调用了defineClass来加载字节码,从stack中可以看到checkCerts也是有锁的。

最后cache所有使用到的Class和Method;去掉了fastjson的依赖,手动解析json;解决了问题。