解决问题的思路:排除法
我最近又一次刚刚解决了工作中的一个不大不小的技术问题,想分享下自己的这个简单的方法论:排除法。
先说说这个问题是什么,我又是怎么解决的。因为产品需要,购买了淘宝上的一种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反向端口转发比较不错。
其中,中转服务器位于互联网,有独立的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。
但是如何及时预览图像呢?