解决问题的思路:排除法

我最近又一次刚刚解决了工作中的一个不大不小的技术问题,想分享下自己的这个简单的方法论:排除法。

先说说这个问题是什么,我又是怎么解决的。因为产品需要,购买了淘宝上的一种mini主机,决定采用Linux系统做产品,首先就要解决发行版的问题。经过比较选择,初步选用Manjaro Linux。但是上周突然发现一个很诡异的现象:启动时间太长,每次启动的时间从几秒钟到2分半种不等。我这么描述,实际上不够精确,那么启动时间是什么呢?从电源开启,到自动登录完成并且桌面显示出来了,总共的时间。而这里面最有意思的是会在桌面显示出来之前会有一个黑屏时间,这个时间是不太固定的。作为企业产品,启动时间慢和不固定都是不能容忍的。那么,怎么来解决这个问题呢?其实,我一直在使用排除法,缩小问题的范围,直到最终精准定位。

第一个范围排除:显示相关还是其他?为了搞清楚系统是不是其他部分都启动了,而显示部分可能会滞后,将系统的ssh服务开启,当发现“卡死”的时候,立刻从另一个机器去远程登录,发现每次都能登录,因此排除了其他问题,确认与显示相关。

第二个范围排除:是否与自动登录相关?把系统的自动登录关掉,让启动的时候必须输入用户名密码,结果发现:问题消失!这就证明了,问题与自动登录相关。

第三个范围排除:卡在自动登录之前还是自动登录之后?由于是黑屏,很不给力,找一下自动登录的机制,发现可以设置自动登录的延时,默认是0,改成3秒,发现登录界面闪过,出现了鼠标和背景,不动。原来黑屏的现象变成了固定背景的问题。初步判断是在自动登录之后。因此需要研究从登录到桌面出现,需要经过哪些步骤。

第四个范围排除:进程筛选。研究了X11的Display Manager(本例中lightdm)和桌面系统(本例中Xfce)的进程父子关系,通过ssh,找出卡死的进程,最后发现卡在 gnome_keyring_daemon程序上。

最后,综合判断,给出结论。gnome_keyring_daemon程序在初始化的时候会读 /dev/random获得随机数,而 /dev/radom 这个内核接口需要足够的熵来产生随机数,当采用自动登录的方案时,如果对系统没有任何刺激,内核获得熵的速度会比较慢,导致 gnome_keyring_daemon阻塞;此时如果动一动鼠标,则很快进入桌面系统;在手动登录的方案中,用户输入密码和回车就产生了足够的熵,进入系统也就顺利了。

当然,上面说到的是一个简化的排查模型,排除法只是一种方法,需要和其他的一些条件一起使用。

首先,解决问题的信念。没办法,作为产品,不解决这个问题没法用。当遇到岔路时,尽管问题没有解决,但也学到了更多的知识,不能气馁。我解决这个问题的过程中一直有一个支线问题在干扰我:系统会识别出一个没有硬件连接的笔记本显示屏。为了排除这种可能,我通过配置让系统忽略这个不存在的显示屏,发现问题依旧。这个做法有两个好处:排除了多屏问题,我顺便理解了X11的忽略显示屏配置方法。另外,当自动登录相关的结论出来之后,作为绕过问题的策略,可以设为手动登录而不去研究,但这样导致浅尝辄止,除非有更重要的事情,这种刨根问底的精神不能丢。

其次,对比尝试。第二个范围排除,我的灵感源于这样一个偶然事实:当黑屏的时候,我动动鼠标或者敲击键盘,总是能进入系统。没有这个尝试,我很难想到登录的问题。

第三,知识储备。我对X11其实理解得不算太全面,这次为了解决这个问题,不得不去更深的理解了不同部分的关系,尤其是启动顺序。

在OS X系统上挂载局域网上的NFS

我这个MacBookPro硬盘空间太小了,总是不够用,于是总想着各种折腾,如何更好的利用外部空间。方法有两种,外接USB硬盘和利用局域网中的其他机器硬盘。公司这边有一个centos 7的服务器常开着,硬盘又大,不用浪费了。 之前使用sshfs,但用起来感觉开销有些大。于是又折腾NFS。其实做完了很简单,不过要注意细节。本文记录下。

Server设定

centos7上需要安装nfs:

$ sudo yum install nfs-utils nfs4-acl-tools

然后配置 /etc/exports 文件:

/home/yourid       192.168.0.0/16(insecure,rw,all_squash,anonuid=1000,anongid=1000)

这里的几个选项比较关键,insecure允许客户端源端口>1024,这样在OS X上,普通用户就能挂载。另外把所有用户都映射到用户 yourid 的 User ID和 Group ID,这样就不会有权限问题。

打开防火墙的2049(NFS V4)端口:

$ sudo iptables -A IN_public_allow -s 192.168.1.0/24 -m state --state NEW -p tcp --dport 2049 -j ACCEPT

开启服务

$ sudo service nfs start

OS X客户端

命令行下直接挂载:

$ mount -t nfs -o vers=4 192.168.1.50:/home/yourid remote

angular-formly入门例子代码解析(一)

前文 简要介绍了angular-formly,本文对angular-formly的 入门例子 的代码做一个初步注解。在这个例子中,一共有六个表单项,展示了angular-formly的一些特点。

首先看如何引用这个库。HTML代码中的head部分,引用了一些必要的库,包括bootstrap(仅CSS部分), api-check, angular, angular-formly, angular-formly-templates-bootstrap, 如下:

<head>
<!-- Twitter bootstrap -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.css" rel="stylesheet">
 <!-- apiCheck is used by formly to validate its api -->
 <script src="//npmcdn.com/api-check@latest/dist/api-check.js"></script>
 <!-- This is the latest version of angular (at the time this template was created) -->
 <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.js"></script>
 <!-- This is the current state of master for formly core. -->
 <script src="//npmcdn.com/angular-formly@latest/dist/formly.js"></script>
 <!-- This is the current state of master for the bootstrap templates -->
 <script src="//npmcdn.com/angular-formly-templates-bootstrap@latest/dist/angular-formly-templates-bootstrap.js"></script>
 <title>Angular Formly Example</title>
</head>

JS代码中,顶层的Angular模块依赖:

var app = angular.module('formlyExample', ['formly', 'formlyBootstrap'], function config(formlyConfigProvider) {
 // set templates here
 formlyConfigProvider.setType({
   name: 'custom',
   templateUrl: 'custom.html'
 });
});

这个例子中顶层模块依赖了 formly和formlyBootstrap这两个module,formly是必须的,formlyBootstrap是Bootstrap特定的模板。config函数这里暂时忽略。

其次看看如何构造一个最小的formly应用。核心的HTML模版代码如下:

<form ng-submit="vm.onSubmit()" name="vm.form" novalidate>
   <formly-form model="vm.model" fields="vm.fields" options="vm.options" form="vm.form">
     <button type="submit" class="btn btn-primary submit-button" ng-disabled="vm.form.$invalid">Submit</button>
     <button type="button" class="btn btn-default" ng-click="vm.options.resetModel()">Reset</button>
   </formly-form>
 </form>

angular-formly最主要就是提供了 formlyForm这个directive,包含4个属性,其中,model和fields是必须的,model属性是指向controller里面所有表单项对应的变量(双向绑定变量)的父对象,fields属性对应controller里面的一个数组,该数组中每一个元素对应一个逻辑表单项。这里对照下controller中的关键代码:

vm.model = {
  awesome: true
};

vm.fields = [
  {
    key: 'text',
    type: 'input',
    templateOptions: {
      label: 'Text',
      placeholder: 'Formly is terrific!'
    }
  },
  /** 其他fields目前省略 */
];

换言之,根据目前摘出来的代码,我们可以期待目标程序会包含一个表单项,类型为 <input type="text"> ,与控制器中的 vm.model.text 变量绑定。这里可以把这个例子更加简化一些, html中的模板代码只需要写成:

<formly-form model="vm.model" fields="vm.fields" >
</formly-form>

在这个基础上,只要在controller代码中扩充vm.fields数组,就定义了更多的表单项,而这个特性,恰恰是angular-formly最让人喜欢使用的原因。本文到此结束,更多代码解析,等我再写。

视频转码利器handbrake

最近给小福星购买了巧虎会员,那些DVD不好总是拿电脑放,前一阵子我又自作主张的买了个外置光驱,被媳妇数落说:“你看看你总是不考虑我们(作者注:非IT技术人员)的感受,偏要买个光驱而不是DVD播放机,每次给孩子放巧虎DVD还得用电脑,麻烦,好好的电视不用!”弄得我怪不好意思的。因此我就必须想办法给DVD备份,让播放更简单。因为家里的电视支持外置U盘上MP4视频格式播放,因此把DVD转换成MP4就有作用了。

handbrake 是图形界面的视频转码工具,之前用过,这次再用来备份DVD,感觉很好用。所以写此文给看官推荐下。handbrake支持三大主流操作系统,在Mac OSX上安装和使用比较傻瓜,Windows上我估计也一样(毕竟很长时间不用Windows了),而在Linux上用来备份DVD的话,有一个小小的坑。这个坑与handbrake没有关系,与锁区加密有关。我的原则是这样的:handbrake使用之前,系统必须能够正常播放该DVD。而播放DVD的软件,我推荐 VLC

在Linux系统(我用的是Debian 8)上,巧虎DVD开始无法播放,Google了下发现DVD是锁区加密的,需要安装 libdvdcss 这个库才行。因此按说明安装好它,DVD就能正常播放了。DVD正常播放之后,handbrake也就能够使用了。目前的几期巧虎DVD,采用H.264编码,压缩完成的比特率约为1.2Mbps,图像大小720x576,30分钟的视频,300MB就压缩完成。还是非常节省空间的。关键是,在家里面把U盘插在电视机上就可以直接播放,媳妇乐了,小福星想看也就方便了。

angular-formly简介

angular-formly [1] 是 Angular (1.x)的表单构建库,因为工作我用到了这个库,觉得很有意思,写出来分享。本文首先尝试对这个库做简单的介绍。全文按三个问题组织。

问题一:为什么作者会写这个库?

其实,这个问题得问作者(请自行到github和项目主页去查)。但另一个类似的问题我就好回答了,为什么我找到并使用了这个库?最开始用angular构建表单的时候,我尝试了三种方法:

  • 手写每个表单域
  • ng-repeat
  • ngbs-forms

继续阅读...

可靠的ssh反向端口转发方法

基本问题:如何远程管理在NAT后面的Linux计算机?ssh反向端口转发比较不错。

digraph rportforward { "远程主机" -> "中转服务器" [ label="ssh反向端口转发" ] "客户端"  -> "中转服务器" [ label="ssh" ] "中转服务器" -> "远程主机" [ label="ssh" ] }

反向端口转发示意图

其中,中转服务器位于互联网,有独立的IP地址。简单的端口转发命令是

ssh -R <rPort>:127.0.0.1:22 <mServerHost>

但是简单使用这个命令是不可靠的,涉及到如下问题:

  • 需要远程主机的交互终端一直开着
  • 断线检测不可靠,会出现假连接
  • 无法自动重连

针对第一个问题,解决方案是采用 screen 程序,让screen 接管交互终端,这样比较不容易被误杀。

针对第三个问题,解决方案是采用 autossh程序,这是个能够自动重联ssh的程序,最简单的使用方法是直接替换 ssh

autossh -R <rPort>:127.0.0.1:22 <mServerHost>

但是,仅仅这样还是不够的,因为autossh自己并没有很好的解决断线检测的问题,但是提供一种机制:

  • monitor端口

简单的说,就是通过ssh做两个隧道,把数据发到远端再转发回来,如果没收到,就表明链路断了。这种方法使用 -M 参数:

autossh -M <mport> -R <rPort>:127.0.0.1:22 <mServerHost>

实验发现,这种方法也不是很靠谱。不用monitor端口,可以利用 SSHv2协议的KeepAlive机制,需要客户端和服务端配合,我发现这是最可靠的:

客户端,需要增加两个选项 ServerAliveInterval=15, ExitOnForwardFailure=yes, 服务端,需要增加两个选项: ClientAliveInterval=15 和ClientAliveCountMax=3 。

这样客户端和服务器端都会采用心跳来检查连接的健康状态,没有心跳就会断开连接。而客户端的 ExitOnForwardFailure=yes则确保新的连接必须成功,不成功则退出,然后 autossh接管异常退出,重连,客户端的命令行参数如下

autossh -R <rPort>:127.0.0.1:22 -o ServerAliveInterval=15 -o ExitOnForwardFailure=yes <mServerHost>

[2016.10.28更新] 在Centos 7系统上 autossh 的 -M 选项是必须的,因此改为

autossh -M 0 -R <rPort>:127.0.0.1:22 -o ServerAliveInterval=15 -o ExitOnForwardFailure=yes <mServerHost>

服务器端需要修改 /etc/ssh/sshd_config 文件,然后reload sshd服务。

[2017.05.22更新]

更多参数:

  • -f 表示autossh自己进入后台执行,
  • -qTN表示不分配终端,仅用于转发。因此改为
autossh -M 0  -f -qTN -R <rPort>:127.0.0.1:22-o ServerAliveInterval=15 -o ExitOnForwardFailure=yes <mServerHost>

另外,此方法对正向端口转发同样有效:

autossh -M 0  -f -qTN -L <localPort>:<remoteHost>:<remotePort>-o ServerAliveInterval=15 -o ExitOnForwardFailure=yes <mServerHost>

最佳的css最小化工具

看客:这是一个坑,我先站上,有时间再多补补内容!

今天解决一个很诡异的BUG。最后证明是CSS工具的BUG,因为 gulp-cssnano 压缩CSS后导致了浏览器兼容性问题(IOS8.1不兼容),将流程中cssnano这一步去掉了,问题解决。

想到如下问题:

  • 什么是最好的CSS最小化工具?

把这个问题的范围缩小下:有gulp插件的开源工具中,什么是最好的CSS最小化工具?看起来有两个选项

  • css-nano
  • clean-css

[9.20更新] 经过替换,看起来 clean-css 更优。

换个角度,想到另一个问题:

  • 如果主流浏览器都支持gzip作为content-encoding的话,部署服务器的时候也支持这个选项,css最小化是否就没啥意义了呢?

[9.20更新] 这两个不是一个层面的问题,CSS最小化会在CSS语义层面进行合并(例如去除相同的CSS规则),而gzip是一种无损压缩。

material design图标字体在UC浏览器(Android)的兼容性问题

调试这个兼容性(UC浏览器,Android版本11.0.4.846)问题发现两个坑:

第一,按官方文档 [1] 的第一种方法(Setup Method 1. Using via Google Web Fonts),UC浏览器并不工作,需要手工加上以下CSS代码:

 .material-icons {
/* Support for  UC ! */
text-rendering: optimizeLegibility;


}

第二,如果使用官方文档 [1] 的第二种方法或者使用 bootcdn [2] 的加速服务,也不工作,需要去掉两行(下面代码的6,7行)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url(https://example.com/MaterialIcons-Regular.eot); /* For IE6-8 */
src: local('Material Icons'),
     local('MaterialIcons-Regular'),
     url(https://example.com/MaterialIcons-Regular.woff2) format('woff2'),
     url(https://example.com/MaterialIcons-Regular.woff) format('woff'),
     url(https://example.com/MaterialIcons-Regular.ttf) format('truetype');
 }

悬而未决的疑惑:

Google的字体文件 [3] 到底对应的是github上发布的哪个版本?

参考

aria2

很久没有更新博客了,还是应该坚持写。推荐一个命令行版本的多线程下载工具 aria2 [1] ,可16倍加速下载,原理和网络蚂蚁一样。aria2 还支持断点续传。

我一般会使用 alias,把如下代码放到 ~/.bashrc 里面:

alias aria2c='aria2c --max-connection-per-server=16 -s 16 --min-split-size=1M'

这样,命令行使用时,不需要写更多的参数,默认就16倍加速了:

$ aria2c http://www.example.com/large/file
[1]https://aria2.github.io

angular中文件表单的模型和数据绑定、上传方法

angular (1.x)中,模板如下

<input type="file" ng-model="data.file" >

是无法绑定到Controller中的变量 ($scope.data.file) 的。如何解决?使用 angular-file-model [1] 之后,就可以在模板中用下面的形式

<input type="file" file-model="data.file" >

当选定文件后, $scope.data.file 就会成为一个 File object。

但是如何及时预览图像呢?

继续阅读...