Recent Posts

Phoenix Nemo's avatar

新轮子:Planet.js

一开始的选择(需要登陆)中纠结使用 Planet 还是博客的时候就已经在关注 Planet 这个东西。

非常奇特,本身并不存储什么数据,但是用这样一个简单的方式在极低的成本下将大量的用户和内容结合到了一起。

终于决定要建立一个社区星球的时候,我才发现它连主题模板都不支持响应式才不是我想写个好看点的主题结果折腾一天无果(ノ=Д=)ノ┻━┻

总之,使用的是 Ubuntu 仓库内的 planet-venus 包,遇到了各种问题…

  • 莫名其妙无法获取 feed,浏览器访问正常,用 curl 正常
  • 莫名其妙无法读取 feed 内容,其他 feedparser 均正常
  • 对文章内使用相对链接的内容无能为力
  • 模板语言落后,不要跟我说 old fashion
  • 输出路径一直是 cwd,不知道是不是 feature。但是这导致我的 home 下面到处都是 output 目录

强迫症犯了。遂决定自己实现一个因为逻辑很简单啊不过就是把 RSS 抓下来排个序再丢出去么

但是到具体的细节,还是有不少需要考量的东西。

压缩和编码

有些网站打开了 gzip 压缩,有些网站使用了非 UTF-8 编码…只是暴力读取的结果就是页面上一半正常一半乱码。

好在 feedparser 给出了使用 zlibiconv代码样例,这个问题就迎刃而解啦。

相对路径和内容安全

Planet Venus 似乎是会解析处理文章内不带有协议的链接和图片,以使这些资源能够在 planet 的页面中直接通过原地址访问。但是问题在于,planet venus 似乎只是暴力给所有非完整 URL 形式的资源地址都强行加上协议和主机名。于是就出现了这种情况——

  • 原文地址 http://www.example.com/2017/01/21/example.html
  • 原文中引用的资源 images/1234.jpg
  • 看起来没毛病,但是 Planet Venus 解析过来就是 http://www.example.com/images/1234.jpg
  • 这似乎看起来也没毛病… 然!而!正确的地址是 http://www.example.com/2017/01/21/images/1234.jpg

…呵呵哒。

先不管为什么会有这么奇怪的资源路径,但是 Feedparser 却可以正确解析到带有跟路径的地址。也就是上面的资源地址,Feedparser 解析完之后就是正确的 /2017/01/21/images/1234.jpg

但是只有正确的相对路径还不够,因为 planet 是单独的站点,直接把 HTML 往页面中插,结果就是浏览器会去请求 planet/2017/01/21/images/1234.jpg 然而 planet 的服务器上并不会有这个资源。

于是这个问题先放着,来看另一个问题。

聚合的一个特点是,几乎无法控制来源内容的安全性——因为其他的网站服务器都不是自己维护的,其他人能否保证自己的网站不被入侵、被跨站…都是未知数。如果有订阅的网站被入侵挂了马,阅读/订阅聚合 planet 的用户也会中招。

解决这个问题,常见的办法就是过滤掉不安全的 HTML tag。于是这里引入了 sanitize-html

其实我一开始打算用 regex 直接切掉不过还是不够 robust 所以乖乖去找包了… 但是惊喜的是这个包居然可以实现按规则替换!这就顺利解决了之前的问题,可以将 Feedparser 解析得到的路径加上正确的协议和主机地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
sanitizeHtml(html, {
transformTags: {
// restore origin links
'img': function (tagName, attribs) {
if (attribs && attribs.src) {
if (attribs.src.startsWith('/') && attribs.src.charAt(1) !== '/') {
// root-relative path, add corresponding host before root
return {
tagName: tagName,
attribs: {
src: host + attribs.src
}
};
} else if (attribs.src.startsWith('//') ||
attribs.src.startsWith('http://') ||
attribs.src.startsWith('https://')) {
// absolute path, do nothing
return {
tagName: tagName,
attribs: attribs
};
} else {
// Feedparser may not correctly recognize this path, try with host/path
return {
tagName: tagName,
attribs: {
src: host + '/' + attribs.src
}
};
}
}
// don't miss the default action or it throws exception.
return {
tagName: tagName,
attribs: attribs
};
}
}
});

解决了文章内容相对路径的问题,要过滤特定的标签或者标签属性则是这个包本来就要做的事情了,小菜。

处理完文章对象之后,用 Array.prototype.sort 带一个 compare function 通过更新日期排序,接下来就可以简单渲染页面和 RSS 文件啦。

其他

一些不太常见的功能,例如 http proxy 的支持(在特殊的网络环境下可能用到),长文章仅展示 summary 并提示继续阅读,avatar 的支持,模板和输出目录保持和配置文件的相对路径,等。

然后作为深有体会——

1
2
3
CSS
IS
AWESOME

因此打死不写前端的家伙选择直接套用了 YUI Library 的 purecss 框架做一个还算看得过去的模板。至少… 几番折腾之后把响应式和 pre 等难缠的宽度搞定了顺便玩了下 media query

于是代码 -> GitHub

以及已经在使用的 Planet NyaaCat

从开坑到基本完善大概花了 15 个小时… 果然长时间不写代码手生才不是冻得手打字都变慢了呢

Phoenix Nemo's avatar

玩了一下 NGINX RealIP 模块

最近要给网站上 CDN 于是折腾了下在 NGINX 部分获取客户端真实 IP 的方案。

嘛… 意想不到的简单就是…

安装 realip 模块

如果是 Debian/Ubuntu 系统,直接安装 nginx-extras 这个包即可。包含了很多有用的模块,不需要再自己编译。

如果是其他发行版,且没有提供额外模块的包的话,需要自己编译 NGINX。编译参数加 --with-http_realip_module 即可。

获得前端服务器地址

常见的 CDN 前端 IP 都可以从 CDN 提供商处获得,例如 CloudFlare 的 IP 地址段在这里

如果需要找到 Google Cloud Platform 的 IP 地址段,可以使用 Google 提供的 TXT 记录查询。

1
$ dig @8.8.8.8 _cloud-netblocks.googleusercontent.com TXT

获得记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
; <<>> DiG 9.11.0-P2 <<>> @8.8.8.8 _cloud-netblocks.googleusercontent.com TXT
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 42732
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;_cloud-netblocks.googleusercontent.com. IN TXT

;; ANSWER SECTION:
_cloud-netblocks.googleusercontent.com. 3599 IN TXT "v=spf1 include:_cloud-netblocks1.googleusercontent.com include:_cloud-netblocks2.googleusercontent.com include:_cloud-netblocks3.googleusercontent.com include:_cloud-netblocks4.googleusercontent.com include:_cloud-netblocks5.googleusercontent.com ?all"

;; Query time: 51 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Wed Feb 08 20:31:29 JST 2017
;; MSG SIZE rcvd: 331

这里面的 _cloud-netblocks1.googleusercontent.com 等地址即是用于保存 GCP IP 段的地址。继续查询:

1
dig @8.8.8.8 _cloud-netblocks1.googleusercontent.com TXT

获得记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
; <<>> DiG 9.11.0-P2 <<>> @8.8.8.8 _cloud-netblocks1.googleusercontent.com TXT
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 22867
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;_cloud-netblocks1.googleusercontent.com. IN TXT

;; ANSWER SECTION:
_cloud-netblocks1.googleusercontent.com. 3599 IN TXT "v=spf1 ip4:8.34.208.0/20 ip4:8.35.192.0/21 ip4:8.35.200.0/23 ip4:108.59.80.0/20 ip4:108.170.192.0/20 ip4:108.170.208.0/21 ip4:108.170.216.0/22 ip4:108.170.220.0/23 ip4:108.170.222.0/24 ?all"

;; Query time: 55 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Wed Feb 08 20:32:39 JST 2017
;; MSG SIZE rcvd: 270

于是得到了一堆 IP 地址。

设置 RealIP 模块

文件 /etc/nginx/snippets/realip.conf,请注意这个位置在其他发行版未必存在,放在 NGINX 配置目录下即可。

1
2
3
4
set_real_ip_from  192.168.1.0/24;
set_real_ip_from 192.168.2.1;
set_real_ip_from 2001:0db8::/32;
real_ip_header X-Forwarded-For;

然后在 vhost 配置文件中引用这个配置。

1
include snippets/realip.conf;

搞定,重启 NGINX 即可获得客户端真实 IP。

Note:

GCP 的 X-Forwarded-For 的客户端 IP 在第一个 , 的前面,所以一般需要 split(',')[0]

jaseywang's avatar

树莓派(raspberrypi)、saltstack 在线下自助机运维上的应用

目前每家院区都分布了从数台到近百台规模不等的自助机,覆盖了北京市属 22 家医院的三十多个院区,一千多台的日常变更、升级管理、甚至常人看来很简单的开关机成了摆在眼前的一大问题。下面这篇博客会抛出 4 个问题并且分享下我们线上的实战经验。

1. 开关机,一个看似很幼稚的问题
你去市面上问一个的有点经验的运维,「服务器怎么开关机」,他可能第一反应是,「服务器要关机吗?」如果你接着问,「如果现在就有这么一个需要,需要先关机过段时间再开机,怎么办?」,他很可能会告诉你,「远程卡啊,这不是标配吗,再不行,电话个驻场的帮忙开下呗」。 以上的情况对于机房的服务器是适用的,但是对于自助机,放在院内各个大厅角落,物理环境恶劣,随时断网断电,尘埃飞扬,更别谈恒温空调 UPS 之类的,要实现开关机,真不是一件容易的事情。我们在院内用的自助机,说白了就是一台组装的 PC,仅仅是通过串口并口连接了一些读卡器扫码器等外置设备。既然是消费级别的 PC,就不会有远程卡这么「高端」设备,当然,对于 PC 级别来说,市面上是有跟企业级别 iDRAC/iLO/iMana 类似技术的设备的:Intel 的 AMT,最终的实现效果跟远程卡几乎一样,另外一种能实现开机的方式就是通过 Wake-on-LAN 这种古老的协议。

有开就有关,AMT 能开能关,Wake-on-LAN 就没办法了。但是基本思想很简单,需要在每台机器上安装一个 agent,通过此 agent 来下发关机以及后续的变更操作。

有了上面的思路,我们来对比下 AMT 以及 Wake-on-LAN 这两种方案的优劣,如下图:

对于目前的业务来说,我们最看重的是经济成本以及可扩展性,很明显,Wake-on-LAN 完爆 AMT。

有了大方向,下面是具体的实现,首先是至少有一台常年开机的机器用来唤醒其他的机器,选自助机肯定不行,这些机器本身就会出现白天开机晚上关机的状态,后来想到了树莓派,设备小,可以完全放进一台自助机里面,只要该自助机的电源线不拔,就能保证树莓派的常年开机状态;管理简单,默认使用的 Raspbian 系统,源的同步,账户的管理,监控、变更等等跟我们线上完全打通;经济成本低,一台树莓派配上 SD 卡电源线插座传感器(这个后面会单独提)不到 400。有点需要注意的是,很多情况下院内会有多个 vlan,正常情况下 Wake-on-LAN 不支持跨网段的唤醒,一般来说,放在不同位置的自助机网段会不大一样,加上我们需要监控不同位置的温湿度烟雾的状态,所以最终决定出单家院区 4 台树莓派的标准配置。下图是一套树莓派的配置图:

搞明白了上面的核心问题,接下来要解决的是流程类的问题,每上一个新院区,我们会直接格式化 SD 卡灌装定制的 raspbian 系统,salt 初始化,zabbix/freeipa 注册,实施人员到现场之后插电接网即可。

2. 如何优雅的开关机
上面的话题仅仅覆盖了基本的原理,在我们上线的早期,每个院区仅仅上线了个位数的机器,同时运行的院区在三四个左右。这里需要提到一个背景,每个院区的部分自助机,同一个院区的不同自助机,开关机时间都不同,比如友谊位于门诊大厅的自助机是每天早上 7:00 准时开机对外使用,但是二三楼的自助机则是每天早上 07:10 启用,还有一台位于急诊大厅的机器则是全年 24 小时开机,而对于同仁医院来说,所有的机器都是早上 06:45 开机。

早期,我们通过 saltstack/jenkins/git 在树莓派上维护对应的院区的 crontab 列表来实现自助机的开关机。

另外一个频繁出现的问题是,院方会经常性的提出修改自助机开关机服务时间的需求,每次运营转达给我们再经过 git/jenkins 提交的效率异常的低效。为了彻底的将这部分重复低效的工作从运维手里解放出来,我们为院方开发了一套开放平台(open),除了日常的开发者文档更新、开发接口 API 调用,接口测试用例等之外,一块重要的功能就是为院方开放了自助机服务器时间的自主修改查看权限,这样将日常繁杂的自助机时间管理下移到了院方手里。

目前该平台支持全局、个别自助机的开关机时间:

bangkok 上面的 KioskScheduler 进程定期会向 open 平台同步各个院区所有自助机的开关机时间,将其写入到 MySQL,底层通过 APScheduler 这个任务框架来调度,使用 BackgroundScheduler 调度器,默认的 MemoryJobStore 作业存储。每家院区的规则都分为全局跟单独两种,全局规则针对该院区的所有自助机有效,优先级低;单台自助机规则仅针对单台自助机规则生效,优先级高。KioskScheduler 根据以上优先级的不同,优先执行高优先级的规则。这样「积水潭 2017 年春节期间,除了 10、11 号自助机在每天的 08:00 ~ 16:00 开机之外,其余所有的自助机均关机」的规则就能顺利实现了。

同时,为了确保 KioskScheduler 运行正常,应用层面通过 monit 实现进程的监控,业务层面的规则执行与否以及是否达到预计,我们通过 python-nmap 实现了一个批量扫描的脚本,每次开关机时间点触发后的 5min/10min/15min 三个阶段,对命中规则的自助机进行批量的存活性扫描,对于未达到期望的自助机会触发报警到我们的自助机运维同事那边进行人工处理。

对于开机的方式,部署在院内的 4 台树莓派通过 syndic 机制收到请求后同时向目标机器发起唤醒的请求,根据之前的经验,单台树莓派的唤醒偶尔会出现唤醒失败的情况,另外一点 4 台里面只有保证至少有 1 台存活,那么开机就能成功触发。

3. 如何低成本实现 OOB 的核心功能?
先来看下行业标杆的 Dell 远程卡(OOB)提供的一些核心功能,如下图:

根据我们日常使用频率的统计,80% 以上的操作花在了如下的操作上:

1. 服务器的开关机,尤其是机器 hang 住了/kernel panic 之后的开关机
2. 由于各种原因无法 ssh 之后需要通过虚拟终端进入查看当前机器屏幕的状态
3. 机器主要零部件的硬件状态,健康状况

对于一台自助机来说,其核心是简化后的服务器,没有 raid 等企业功能,除此之外,提供的服务跟服务器并无二异,仅仅是加上了 GUI 而已。对于第一、二两点,上面的话题已经阐述过,比较遗憾的是目前并不能对机器的死机、蓝屏实现脱离系统的开关机重启以及实时屏幕的查看,但是对于日常的开关机,VNC 远程操控,绰绰有余;对于第三点,目前诸如磁盘等底层硬件的监控是放在应用层来监控,对于我们来说,个别自助机硬件层面的宕机并不会影响整个院区的使用,我们及时报修返厂更换即可解决此类问题,众所周知的是,对于绝大多数的医院,其院内的物理环境,比如尘埃浓度(PM10)、温湿度等跟正规的机房比相距甚远,早期,我们发现某院区的部分的自助机经常性的宕机蓝屏,经过若干天系统级别应用级别的排查,都无所发现,后来现场的志愿者提醒我们是不是温度太高导致的,现场勘查后发现,西区的部分自助机直接放在了仅有一顶透明帐篷遮蔽的半室外空间内,6 月市内温度 30 度的情况下,这部分自助机箱体的温度已经接近 40 度了,蓝屏在所难免了。

为了及时发现这类问题,我们后续在所有树莓派上全部引进了温度、湿度以及烟雾的传感器,成本非常低,一个 DHT11 的温湿度传感器加上一个 MQ-2 的烟雾传感器成本在 20RMB 以内,加上现成的 Adafruit_DHT/RPi.GPIO 的库直接调用,完美解决此类问题。通过分散在不同地点的四台树莓派,我们能够推断出当前院内的物理环境,这对改进目前自助机的物理位置有非常重要的意义。

下图是某院区蓝屏故障增大期间周边温度的变化,观察可以发现具备一致性关系:

类似的,我们在院方机房放置的连接专线的路由设备,同样发现多次类似的问题,下图是某院区机房空调设备故障温度升高导致设备丢包增大中断服务的监控:

很多看似简单的问题,一旦场景转变了,需要应对的措施也就随之改变了。

4. 如何有效实现自助机的变更操作
自助机上的变更,主要分为两类,一种是最为频繁的版本升级,包括自助机上挂号缴费程序的版本、第三方依赖的程序升级,这类变更的特点是,每次的程序升级,其升级包都相对较大,小的几兆,大的有上百兆;第二种变更更多的集中在日常的系统变更,比如修改 hosts 以对应不同的 nat 影射、增删改查注册表等系,对于这类变更,最大的特点是占用的带宽小,但是操作频繁。

针对这两种变更,分别设计了两种不完全一样的变更方式,但是底层都是通过 saltstack master/syndic/minion 的架构实现文件的传输。中心端一台 master(存在单点的问题),每家院区 4 台部署了 syndic 的树莓派,同时跑 master/syndic,向中心端注册,底层的自助机部署有 minion 向其中一台 syndic 注册,目前 minion 到 syndic 同样存在单点问题,后续考虑将 syndic 升级成 MultiSyndic。

对于第一种变更方式,我们将要升级的版本库文件打包上传至 git,通过 jenkins 将数据从打包机 git pull 下来之后,在打包机通过 salt 将升级的文件分发到 salt syndic 上,syndic 上会起 http 服务,自助机每次开机的时候会自动的向 http 服务检查是否有最新的版本,如果有的话则会升级,对于回滚,我们将版本号递增内容回滚至上一版本重启机器即可。
对于第二种变更方式,如果是修改注册表之类的变更,我们使用 cmd.run 直接将需要跑的 key 执行结束,类似下面这样:
cmd.run 'reg add "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel" /v "{5399-XXX-CBA0}" /t reg_dword /d 1  /f'

如果是传输文件的变更,跟第一种方式类似,不同的是,文件落地到 syndic 之后,我们会直接通过 master/minion 的方式 push 到每台自助机,而不是主动 pull 的方式。同时为了确认文件的完整,每次从 master 到 syndic,从 syndic 到 minion 的两个关键步骤都会做一次 md5 校验,最终实现的效果可以参考早期的 saltpad 版本,下图是 bangkok 中变更的页面展示:

除了最基本的单次变更外,增加了多次执行的执行步骤以及执行步骤的模版功能,前者对于多合一的操作步骤很适用,后者对于经常使用的诸如修改 hosts 文件等很适用。

在这之前,我们尝试直接通过 salt master/minion 的方式进行日常的变更管理,这种方式对于线下的实施同事来说非常的不友好,其灵活性以及方便性远低于目前的方案,遂未在团队内推广使用。上面介绍的系统是目前在线的稳定系统,使用了一段时间整体反馈还不错,后续会优化版本控制以及更加自动的回滚等操作。

alswl's avatar

👁️ 预测未来?

拉普拉斯之妖

未来是可以被预测的么?

专家在预测股票趋势变化,天气预报员可以预测未来一周甚至更长时间的天气。 如果给他们更多的信息和参数,是否可以将未来预测的更准确? 如果精确的粒度可以达到基本粒子级别,同时给一个计算力超群的计算器,能否精确的推衍未来变化?

这些想法在我刚接触经典力学时浮现,学习了牛顿三定律之后,异常激动。 感觉人类可以凭借技术的进步,逐步对未来精确预测。彼时可以解决人类即将遇到的任何问题了,化问题于无形。

这想法其实在 200 年前就出现了。法国伟大的数学家拉普拉斯(Laplace)在他的著作「概率论」里面, 提出了这样的观点:

我们可以把宇宙现在的状态视为其过去的果以及未来的因。假若一位智者会知道在某一时刻所有促使自然运动的力和所有组构自然的物体的位置,假若他也能够对这些数据进行分析,则在宇宙里,从最大的物体到最小的粒子,它们的运动都包含在一条简单公式里。对于这位智者来说,没有任何事物会是含糊的,并且未来只会像过去般出现在他眼前。

「根据当前已知即可预测未来」,这种决定论,就是拉普拉斯之妖。

打破

除了拉普拉斯的决定论,这个世纪末还发生了另外一件事情。 开尔文男爵在 19 世纪的最后一天,发表了「物理大厦已经落成,所剩只是一些装饰性工作」的言论。 然而,在随后的岁月里,经典物理大厦却开始被量子力学逐步击破,轰然倒地。

在决定论这个方面,根据海森堡的不确定性理论,粒子的位置与动量不可同时被确定。 这个结论说明目前技术下面,无法精准测量粒子的状态。 既然无法准确测量粒子的状态,就失去了演算未来可能性的基础,更无法预测未来了。

那既然是无法预测,是否表示,预测未来这件事情是无稽之谈,未来是不成规律的?

混沌:被理解错误的「蝴蝶效应」

针对未来的预测,动力系统中,有相当多的研究和思考,其中比较注明的一项是:蝴蝶效应。

关于「蝴蝶效应」,不用过多解释。命名的来源据说是发现者 Edward Lorenz 觉得图形像是蝴蝶。 而有另一种另一说法是,Edward 的一个比喻「巴西蝴蝶煽动,引起德克萨斯的龙卷风」。

事实上这种说法是不精准的。Edward 在预测天气模型中,发现起始数据的微小差异, 会导致数日之后计算结果的巨大差异。 从点在于在预测模型上,忽略了一个蝴蝶煽动引起的力量,即起始条件的设定,会导致结果的巨大差异。 而不是表名龙卷风是蝴蝶煽动产生的。

如果蝴蝶煽动能够产生龙卷风。那么一个普通人的呼吸,也可以造成同样的效果。😂

分形和混沌

和混沌相关的,还有一个重要概念「分形」:

一个粗糙或零碎的几何形状,可以分成数个部分,且每一部分都(至少近似地)是整体缩小后的形状”[2],即具有自相似的性质。分形的核心是自相似。

随机从这种自相似的图形中提取样本,样本集表现出不可预测的特性。

混沌的定义:

非线性系统在一定参数条件下展现分岔(bifurcation)、周期运动与非周期运动相互纠缠,以至于通向某种非周期有序运动。这种运动是不可预测,呈现出失序的状态。三体问题,即是一例混沌的场景。

为什么混沌和分形是具有相关性的呢?这里有一个重要概念 IFS。 IFS(Iterated Function System)是迭代函数系统,即在数学上被认为一个完全向量空间上收缩映射的有限集。 分形的图案,部分可以抽象出 IFS 公式。

这个公式的奇妙之处是在于,在公式渐进推算过程之中,初始看到的结果是混沌无序的,而在逐步运算之后,可以看出分形的特征。

下图是根据一个 IFS 公式,逐步构造出一个分形图片的过程:

图片来自 Wikipedia https://en.wikipedia.org/wiki/Iterated_function_system

这种由初期混沌,逐渐表现为分形的情况,被学者总结为:「混沌在(生成)时间上是分形的;而分形的在空间(展示)上是混沌的。」 混沌中可以找到有序,在非线性中找到理性,混沌中可以演化出非规则,也可以演化出在规则中混沌。

哲学上的象征

感谢你迷迷糊糊的读到这里,混沌不仅仅在数学和物理上面,吸引着一代代人的探索。 在哲学生活指导方面,也有重大的意义。早在古人说「分久必合,合久必分」, 就是一种稳定线性的表现,具有预测性的表现。 而「一生二、二生三,三生万物」则体现了古人对分形的理解。 经典的「马蹄毁了一场战争」故事,讲的是混沌理论。

这些对未来预测能力的渴望,表现了对生活的掌控欲望,对未来可行性的探索, 在低谷期可以带来希望,大跃进时期可以带来警醒。


对于我个人而言,虽然失去了能够预测未来的理论基础而略感遗憾。 但是反过来想想,如果真的发明了一台机器,能够精确预言到未来, 那么在发明实现的当天,就能够推衍出未来所有出现的科学理论、文学、艺术创作, 那给后人创造了一个多么再无新事物的未来,这该是一个多么无趣的世界啊。


原文链接: https://blog.alswl.com/2017/01/prophecy/
欢迎关注我的微信公众号:窥豹

3a1ff193cee606bd1e2ea554a16353ee
jaseywang's avatar

通过 noVNC 实现数千台自助机的实时可视化

背景很简单,目前我们运营维护着北京市属三甲医院数千台的自助机,这里面包含挂号取号机、检查报告机以及其他若干衍生出来的自助机种类,数量已经超过一千多台,不同的院区通过密密麻麻的专线跟我们的机房源源不断的进行着各种协议的数据传输与交互。

由于各方面技术以及非技术层面的限制,只能使用微软的系统,为了管理这部分的机器,一方面我们通过 saltstack 来进行日常的产品升级以及变更,对于桌面的可视,比如我们需要知道当前这台自助机前患者的操作,就需要 VNC 来帮助了。

一年前自助机刚上线时候,没有很详细的考虑过自助机的大规模运维问题,当时每台自助机都在 Clonezilla 打包阶段打包进了 realVNC,通过一台 Win 跳板机的 realVNC 客户端去连接,realVNC 有书签的功能,所以当时需要查看每台自助机界面的运行情况相对比较方便,随着数量的增加,这种先登录一台 win 的跳板机,在使用 realVNC 书签查看的传统 CS 架构就捉襟见肘了。迁移到 web 上势在必行,本着可小动不大动的原则,调研了 realVNC 的各种 SDK,遗憾的是都不支持,加上 realVNC
有版权问题,放弃。相比之下,TightVNC 则不存在这类问题。

后来我们组的同学调研了几个基于 web 的 VNC 客户端,peer-vncnoVNCguacamole ,peer-vnc 活跃量太低,底层用的是 noVNC,guacamole 还处在孵化期(功能相当强大)并且比较重,放弃。最终选择在 noVNC 的基础上做一些工作。

由于 TightVNC 默认不支持 WebSockets, noVNC 提供了 websockify 这个工具来做 TCP socket 的代理,接受 WebSockets 的握手,转化解析成 TCP socket 流量,然后在 CS 两端传递。由于我们涉及到较多的机器信息需要维护管理,我们将所有的机器信息,比如自助机的编码,机器名,IP/MAC 相关的信息预先存储到 MySQL 里面,接着按照 token: host:port 既定格式生成每家院区的连接信息配置文件,这一步可以在 jenkins/rundeck 上建个 job 方便每次增删改查。接着就可以启动了转发了。

在浏览器中打开本地的 6080 端口即可。下图是集成到我们内部管理平台上的截图,可以快速定位到自助机的健康状况并通过 VNC 进入确诊当前的状态:

noVNC 默认情况下会以交互式的方式连接,在这个过程中会做身份权限校验(账号连接、读写控制),是否是 true color 等,这个对于生产不是很适用,我们后来将授权这块做在 Django 上,结合 LDAP 做登录认证。考虑到专线带宽的限制,默认关闭了 true color 开启了压缩。VNC 对带宽的消耗还是比较厉害的,平均下来,每开一个新链接,会消耗 1Mbps 左右的带宽,所以如果需要做实时的展示大屏,需要考虑这块的瓶颈。

下图是 noVNC 实时图像,由此我们可以看到当前这台自助机的实时状态并对问题的自助机采取一定的操作:

最后将其挂在 haproxy/Nginx 后面,Nginx 1.3 之后支持 websocket,一个快速搭建并可投入使用的自助机可视化平台就算完成了。该平台我们内部命名为 bangkok。

以上思想可以移植到 *nix 平台上,目前不少主流的云 OpenStack/OpenNebula/Digital Ocean 也集成进了 noVNC。

alswl's avatar

👷如何做年前大扫除

今年过年特别早,离春节只剩下二十多天了。 为期 7 天的春节里,工程师们不上班,那万一线上业务出现了故障怎么办? 大公司的朋友们会安排专门的人进行值班(此处心疼一下那些需要大年三十还要值班保证高峰的工程师们), 而作为创业团队人少,难做到在线值守,就需要对线上进行一些整理盘点,找出潜在问题,为春节长假做一些准备。

我们称之为年前大扫除。

大扫除需要做些什么呢,且听我一一道来。

PS: 冷知识,大扫除英文是 spring cleaning,所以春节大扫除是 Spring Festival spring cleaning。

大扫除的内容

大扫除其实是一个查漏补缺+囤积粮草的事情。

查漏补缺,即找出潜在的问题。这些问题平时可能不会特意去查看, 借助大扫除这个运动,恰好进行盘点。 计算机的世界里,有一个方法论非常好使,在极多场景可以见到其身影:分层。 TCP 的七层模型,架构设计的 N 层 模型,都是对分层思想的使用。 查漏补缺也不例外,我们可以按照业务访问流程,将需要排查的问题拆分为:业务、应用、中间件、网络、物理、存储 etc。

通过分层,不仅仅完成了自上而下地遍历整个技术栈,也同时将不同模块的内容交给不同的责任方, 确保任务的分割。

分完模块,还要告知大家如何具体查找问题。 这里我介绍一个通用的方法:USE1

For every resource, check Utilization, Saturation, and Errors.

USE 方法是从 Brend Gregg 那里学来的。 在技术设施的领域里,Resource 即是指各种类型的资源,比如 CPU、磁盘、网络、内存, Utilization 指的是使用率,可以简单分为百分制和非百分制。 Saturation 是指饱和率,支持 queue 的资源,就会有这个指标。 Error 即错误,可以从错误统计和日志得知。2

业务领域里面,USE 也有相对应的含义。以审核系统举例, 对应的 USE 可以理解为「审核应用实例跑的 CPU 占用如何,任务队列是否塞满,业务日志是否有异常」。

除了 USE 里面提到的指标,还有几个指标特别重要: TPS 、Latency 和 Capacity。 这几个指标对性能敏感的尤为重要。 检查 USE 的同时,我们必须关注一下这三个指标, 确保 TPS / Latency 是否满足我们预期的 SLA。 哦?压根没有制定 SLA,不要慌,和历史数据对比,先制定一个粗糙的 SLA。 哦?连历史数据都没有?那只能找你 Leader 让他考量一下了。

负责每个子系统的同学,记得检查时候将这些收集到的数据列下来。 在 Metric 做的还不够完善时候,这些数据也是很宝贵的。

在我看来,检查 USE / TPS / Latency ,最大的作用是将抽象的可用性指标描述为几个易于理解的数值进行量化。 一旦能够量化,就可以对比、观测、监控,并且 Review 起来也异常轻松

应对方案

检查出问题之后,就要考虑应对了。时间急任务多,我们的应对方案是是囤积粮草 / 写救命笔记。

囤积粮草比较好理解,基于已有的容量预估,为容易出问题的系统提供一份冗余。 有些团队平时做基础设施就比较,做 Scale 就是小轻松。 那平时 Scalable 做的不好的朋友,就只能将应用实例多开一些,以避免临时出现的流量波动。

无状态的服务好搞,有状态的 DB 就很难在短时间内做 Scale。 检查这些服务的容量,如果重点资源临近阈值,比如 DB 的硬盘资源,缓存的内存容量。 核心服务的余量在检查中真的发现问题的话,那也只能短期内做扩容了。

对于小团队来说,春节长假的特殊性在于响应会变慢甚至是联系不上。 一旦线上有异常,可能找不到合适的人员来进行处理。 所以第二条写救命笔记则更为重要。 「Google SRE」里面有个小段子,一个绝对不能被按的按钮, 这个按钮会清空内存数据,在飞行过程中被宇航员按了。幸亏美女工程师(下图)写了相关的救命手册, 专门写了针对这种情况的操作,救了这些宇航员的命。

图片来自 「Google SRE」

从这个故事里面可以看到,一个紧急操作手册是多么重要。 所以在大扫除期间,我们还要补一补平时的文档,将一些常见问题 / 常规操作记录下来。 步骤需要细致到能让让每个远程值班的同学做到 step by step 操作。


啰嗦了这么多,相信大家对大扫除要做些什么已经有所印象了,祝大家过个好年,流量涨涨涨,还能平平安安的。


原文链接: https://blog.alswl.com/2017/01/spring-cleaning/
欢迎关注我的微信公众号:窥豹

3a1ff193cee606bd1e2ea554a16353ee
Phoenix Nemo's avatar

重新迁移回 GCP

忍得住打 Call,忍不住折腾。看到黑科技就手痒。

虽然 AWS 用得挺安心不过越来越多的人向我抱怨博客打开速度很慢,想看文章都要等好久什么的。于是纠结了下,还是迁回了最贵但是最快的 Google Cloud Platform。这里记录一下调(zhe)教(teng)过程,毕竟 GCP 的 Cloud CDN 依然在 Alpha/Beta 阶段,可配置的选项实在太少而且很多 caveats。

前期准备

  • 域名
  • SSL 证书
  • 网站程序或内容

创建实例

我这样没什么访问量的静态网站用 f1.micro 就好啦~ 不过流量比较大的网站的话,还是建议选择高一些的配置和 SSD。

然后创建一个实例组(instance group),确保该实例组包含刚才创建的实例。

NGINX 配置

网站等会说。总之先配置好 SSL 和 Health Check。

1
2
# mkdir -p /var/www/hc
# touch /var/www/hc/index.html

默认站点配置 default.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server {
listen 80;
listen [::]:80;
server_name hc; # health check
root /var/www/hc;
index index.html;
}

server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 301 https://$host$request_uri;
}

网站配置 example.com.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com;

ssl on;
ssl_certificate /etc/ssl/private/example_com.pem;
ssl_certificate_key /etc/ssl/private/example_com.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4";
keepalive_timeout 70;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_dhparam /etc/ssl/certs/dhparam.pem;

add_header Strict-Transport-Security max-age=63072000;
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;

root /var/www/example.com;
location / {
# ...
}
}

(SSL 相关的很多配置步骤省略,请自行查阅其他文档)

创建负载均衡器

戳到 Networking 标签,选择 Load balancing 然后创建一个 HTTP(S) Load Balancing。首先为负载均衡器命名,因为要分别创建 2 个负载均衡所以这里可以写例如 website-http 之类作为区分。配置页面的三个标签:

  • backend
    • 创建一个新的 backend
    • backend service 选择 http
    • instance group 选择刚才创建的实例组
    • port numbers 80
    • health check 创建新的 health check 并设置 custom header,和之前 NGINX 里的配置一致,这里是 hc
    • 勾选 Enable Cloud CDN
  • Host and path rules
    • 添加域名对应的 //* 两个路径规则。如果有特殊需求则另外添加。
  • Frontend configuration
    • Protocol 选择 HTTP
    • Create IP Address 然后 reserve 一个 IP

第二个负载均衡器用于 HTTPS,名称可以是 website-https 这样。配置:

  • backend
    • 因为配置不一样,所以要再创建一个新的 backend
    • backend service 选择 https (要点 Edit)
    • instance group 选择刚才创建的同一个实例组
    • port numbers 443
    • health check 创建新的 health check 并设置协议为 https,custom header 为网站域名(因为要和 SSL 证书相符,示例里是 example.com
    • 勾选 Enable Cloud CDN
  • Host and path rules
    • 同上
  • Frontend configration
    • Protocol 选择 HTTPS
    • IP 选择刚才 reserve 的 IP
    • 创建证书,分别上传签发的证书、CA 证书链和私钥

更新解析&收尾

将域名的 DNS 解析到刚才 reserve 的 IP 即可。

至此就是基本的(当前版本的)Google Cloud CDN 配置步骤。还有很多可以自定义和扩展、优化的空间,但是这些需要根据特定的需求变化因此不再详细记录。

以及,不要忘记在虚拟机里把网站跑起来~

最后,南酱的 live 超棒(๑•̀ㅂ•́)و✧

alswl's avatar

🔑 也谈 HTTPS - 如何内测

(图片来自 茶杯中的可爱小白鼠 壁纸 - 2560x1920-堆糖,美好生活研究所)

在上篇文章 🔒 也谈 HTTPS - HTTPDNS + HTTPS 中, 我们谈了如何基于 HTTPDNS 来部署无坚不摧的 HTTPS 通信环境, 这次我们讨论另外一个比较头疼的问题:部署。

小站点部署 HTTPS 相对成本低,改改前端代码,就可以上线了。 但作为业务有一定复杂度的大网站,就没办法这么暴力上线了。

前端在基础库中调整 Scheme 之后,仍然可能存在很多边边角角没有覆盖到。 比如 JS 里面写死了 HTTP,那在 HTTPS 下请求 HTTP XHR 的话, 浏览器会将请求拦截掉。 一旦出现这种故障,用户就无法正常使用业务,小白用户往往也不懂得自己将 https:// 换成 http:// 使用。

解决的思路是足够的内测,找一群人帮我在 HTTPS 环境下使用足够长时间。 让他们当小白鼠,提前发现问题并解决。 于是,我把目光转向了身边的一大大群小白鼠,整个办公室的同事~😄

没错,我要强制所有同事使用 HTTPS 的公司网站,从而靠他们帮我发现问题。

靠发邮件、QQ 广播呼吁大家使用 HTTPS 站点的方法,估计是不行的。 没有利益驱动,推动力是不足的,我必须想点强制的手段让他们使用 HTTPS。

有三种方法来达到这个效果:

  1. 业务系统内入口判断用户身份,是雇员的话,切换到 HTTPS
  2. Nginx 入口系统判断 IP 来源,办公室 IP 则切换到 HTTPS
  3. 改造办公室网络,访问站点时候,自动切换到 HTTPS

为了避免对线上业务系统、基础设施造成影响,我采用了第三条方案。

说干就干,直接对公司网络出口设备是 ROSvia 动起刀子。

实现的原理如下:

  • A:办公室网络的 🐁 们请求站点 http://www.duitang.com
  • B:操作 RouterOS 的防火墙,将 dst 为 www.duitang.com IP 的 TCP 请求都 dst-nat 到新的一台 Nginx 服务器 proxy.duitang.com
  • C:这台 proxy.duitang.com 做过特别定制,将所有针对 *.duitang.com 请做一次 302 请求,将 http://www.duitang.com 请求都转发到 https://www.duitang.com
  • D:Client 收到 302 请求,重新请求 https://www.duitang.com
  • E:同 B
  • F:proxy.duitang.com 将请求转发到真正的 www.duitang.com 服务器

PS:这里要小心的是,需要配置 proxy.duitang.com 的 resolver 避免 Nginx 内部请求。

流程图:

这样操作之后,在办公室网络下,所有访问公司网站的 HTTP 流量都会跳转到 HTTPS。

PS:我原始方案想使用 ROS 的 L7 防火墙 直接抓 HTTP 包,match HTTP 头数据, 再修改返回的 TCP 包。 但测试下来发现 ROS L7 Firewall 不支持写 TCP 数据。 所以我最后只能使用中间跳转的方案。

如果不是使用 ROS 的朋友也不用担心,原理和流程已经讲清楚了, 无非是使用 Cisco / Huawei 网络设备的防火墙命令实现需要的功能。


上篇文章发完之后,好几个朋友问我 IP 证书供应商的事情。我就简单说一下我了解的情况。

国内 SSL 证书供应商们会给他们兜售的产品起各种各样花里胡哨的名字, 什么超真、超强、超安、超快,国外有些企业也会搞什么 Pro / Super / Premium / Essential, 其实 SSL 证书的区分,笼统来说就三种类型:DV / OV / EV, Domain Validation / Orgnization Validation / Extented Validation。 他们区别除了字面意思,就是所有权审核流程一个比一个麻烦。

想基于 IP 直接搞所有权审核,要看对应供应商的证书是否支持。 去年年底我做了一个调查,支持 IP 证书的厂家如下:

现在 Wosign 爆了丑闻,于是支持 IP SSL 又少了一家。 只剩下 GlobalSign 了,但是 GlobalSign OV 又贵审核又麻烦, 不知道看到此文的大神们有没有更好的推荐。


参考链接:


原文链接: https://blog.alswl.com/2016/12/https-2/
欢迎关注我的微信公众号:窥豹

3a1ff193cee606bd1e2ea554a16353ee
alswl's avatar

教你在上海挑房子

点开的同学别失望,这并不是教你如何快速致富的 😂 ……


上海政府在 11-28 出了房屋新政 via,对房价进行进一步调控。 其核心思想是「认房又认贷」。3 月份的政策 via 是限制购房资格。 这次 11 月份调整则是提高二套房首付比例。 双管齐下,进一步给上海房市进行降温。

在这种严苛的政策下面,如何挑选一套自己满意,家人住得安心的房子就尤为关键了。 我对房市一直比较关注,也曾有几位朋友咨询我的一些经验。之前我是将 Evernote 笔记链接贴给别人,这次我就详细讲讲,如何在上海挑房子。

第一步,是建立有价值的 Indicator,并对具体房源进行计算。 Indicator 的含义是建立一系列评价指标,比如价格、户型、位置, 再根据这些 Indicator 给房源进行打分。 如果我是一个购房者,我挺希望我的置业顾问告诉我这套房有哪些优点、缺点,综合评价得几份。 (当然实际情况下这些置业顾问都是吹得天花乱坠,尽睁眼说瞎话。) 看房的过程可能长达数月,而人的记忆会随着时间逐步失真,只对最近看到的事物有深刻印象。 使用客观的数字评价房源,则可以进行精准的评价,避免产生主观臆断。

我使用的 Indicator 和评价细项罗列如下,供参考:

  • 价格
  • 面积
  • 地理位置:内环、中环、外环
  • 交通:离地铁距离,换乘便利性;多地铁线换乘;
  • 户型:南北通透;客厅朝南;全明;户型方正;无暗室
  • 装修:毛胚;简装;普通;精装;豪装
  • 商圈:购物中心;超市;菜场
  • 学区房
  • 产权:满五唯一
  • 楼层
  • 房龄:5 / 10 / 15 / 20
  • 政策发展前景
  • 停车

第二步,就是和家人达成一致的期望,避免潜在的纠纷。 每个人对未来的住房有自己的期望,即便是一家人, 各自看中的点也不会太一样。不管平时家庭决策是什么流程,我都建议坐下来一起讨论讨论。 将 Indicator 依次列出,大家讨论各自心中的优先级。

除了优先级的讨论,还要考虑一下各自对 Indicator 划分等级的理解。 比如对房型、装修这两项,评价标准就会很模糊,这一切都需要讨论清楚。

我个人最为看中价格、位置、交通,可以放弃的有学区房、停车、户型。

第三步,划定圈子,判断趋势。 众多 Indicator 里面,价格是固定的,户型、楼层等是和具体房源相关的, 我们能够撬动的最重要因素,其实就是位置。

作为上海这个拥有 2500w 人口的超大型城市,是不可能把各个位置的房源都扫一遍的, 所以必须重点规划自己准备实地考察的区域。 那么我们就在上海地图上面划几个圈了,数据可以通过链家、中原地产等多网站收集。 安居客有一个大颗粒度的圈子,展示了社区集中点 via, 下图是我筛选条件之后形成的几个簇:

(数据爬取分析这一块,我只服一个人,他专门写了一堆爬虫捞数据然后分析, 如果对杭州的房地产投资感兴趣,可以私信我,我可以帮忙问问。)

从城市级别来看,其实不同区域的变化趋势基本是一致的,但是局部地区可能受外部因素而出现较大波动。 比如上海闸北并入静安,价格就大涨;大宁板块产生一个地王,也会让这片区域价格大幅波动; 甚至新开业一个 Mall,都会让周边价格波动。 这些外部因素,必须提前关注政府市政规划方案、房产网站、公众号、房产微博。 政策相关推荐阅读:「上海市城市总体规划(2015-2040)纲要概要」 via 规划了整体发展格调。 「上海市商业网点布局规划(2013-2020)」via 规划了 14 个市级商圈,之前只有 10 个,还有若干区级商圈。

第四步,快速行动,快速决策

还记得捡西瓜的故事么,小猴子一直想找个大西瓜,但一路看过去最后什么都没捡到。 看房不仅仅是一个体力活,而且非常讲究时效性的,必须能够快速响应,快速决策。 在上海这中刚需强需求旺盛的城市,一套评价都不错的房源,半天内就会被别人拿下。 所以前期观察一段时间之后,必须摸清楚自己的需求和承受能力,遇到合适的房源, 就速战速决。

速战速决还依赖实地看房源的效率。 从我的经验来看,一天至多可以看 10 套左右的房源。 出发前一天约好中介、房东,列一个清单,时间、地点、联系方式全部罗列下来,第二天依照清单行动。 另外为了提高速度,全程应该打车,对于上海这样的一线城市,出租车费用和房产价格相比,真的是小钱。

在看中合适的房源之后,有些人可能面对这笔巨额交易摇摆不定,而错失了良机。 收益于 Indicator + 前期数据准备,我们在看房时候应该具有了客观的评价能力。 一旦各方面满意,不要犹豫,快速决策。 但也不要因为几次擦肩而过就冲动决策,要相信客观的 Indicator 数据。

我推荐上班族看房时间安排是,频繁看房两个月,每周末两天出动。 前期一个月看房,不管看中多中意的,都不做决策, 但要计算出一个自己心理预期。一个月后,看到合适的立刻上手。


OK 写完了。回顾其实这四个步骤,其实就是选一个开源软件的流程嘛。 设定特性需求 -> 设定期望 -> 调研候选者 -> 快速测试上线。 嚯嚯嚯。

最后,如果你恰好在看房,希望你看完本文有些收获,收获自己中意的住房。


原文链接: https://blog.alswl.com/2016/12/house/
欢迎关注我的微信公众号:窥豹

3a1ff193cee606bd1e2ea554a16353ee
alswl's avatar

🔒 也谈 HTTPS - HTTPDNS + HTTPS

最近谈论 HTTPS 的文章很多,其原因之一是运营商作恶底线越来越低,动不动就插播广告, 前两天小米还联合几家公司发文 关于抵制流量劫持等违法行为的联合声明 痛斥某些运营商。 另一方面也是苹果 ATS 政策的大力推动,逼迫大家在 APP 中全部使用 HTTPS 通信。 上 HTTPS 的好处很多:保护用户的数据不外泄,避免中间人篡改数据, 对企业信息进行鉴权。

关于 HTTPS 如何购买证书,如何部署,网上的教程已经太多了,实践起来没有太大的难处。 我们在部署 HTTPS 的时候,遇到了一些新问题,首当其冲的就是 HTTPS 部分网络不可访问的问题:

尽管使用了 HTTPS 技术,部分邪恶的运营商,仍然使用 DNS 污染技术,让域名指向的他们自己服务器 而这些服务器并没有部署 SSL 服务(就算部署了,也会触发 SSL 证书 Common name 不一致报警), 导致 443 端口直接被拒绝。

这个问题不解决,强行上 HTTPS 的话,会导致一部分用户出现无法访问网站 一旦用户不爽了,轻则对产品不信任,重则直接导致用户流失。

运营商为了赚广告钱、省网间结算是不择手段的。 他们普遍使用的劫持手段是通过 ISP提供的 DNS 伪造域名。 那有没有什么方法可以解决 DNS 劫持呢? 业界有一套解决这类场景的方案,即 HTTPDNS。

HTTPDNS 的原理很简单,将 DNS 这种容易被劫持的协议,转为使用 HTTP 协议请求 Domain <-> IP 映射。 获得正确 IP 之后,Client 自己组装 HTTP 协议,从而避免 ISP 篡改数据。

有两篇文章很清晰的讲解了 HTTPDNS 的细节:

点击 https://dns.google.com/resolve?name=www.duitang.com / http://119.29.29.29/d?dn=www.duitang.com 感受一下 DNS-over-HTTPS / HTTPDNS。

单 IP 多域名支持

这个方案看似完美,但是在实际生产中,会遇到一个问题。

Android / iOS 在操作系统级别对 HTTPS 通信是提供了封装。 APP 无法在发起连接时候,也没有权限直接操作 socket。 所以尽管 APP 拿到了域名对应的 IP,却没有办法让这个 IP 在 HTTPS 里生效。

解决的思路很暴力:彻底放弃域名系统,完全使用基于 IP 系统的通讯。

原本请求 https://www.duitang.com 的 request, 调整为请求 https://221.228.82.181

OK,做到这一步,我们就可以跟运营商劫持说拜拜了。

不,还没结束。

完全搞定运营商之后,这 IP 方案给我们自己带来一个困扰: Nginx 服务器无法通过 Host 来识别不同域名下面的请求了!!! 在由于使用一个独立 IP,会导致所有域名请求混在一起,无法分别。 大公司可以 dedicated IP,小公司就玩不起了。

为了解决同一个 IP 下面多个域名的问题,我们引入了一个URL参数: __domain。 当请求 IP 域名时候,必须带着这个参数,服务器会将请求域名解析出来,再分发到对应的域名。

实现这个逻辑的 Nginx 核心代码:

set $query_domain $arg___domain;
if ($query_domain !~ '(www|a|b)\.example\.com') {
    rewrite ^ http://www.example.com/404/ redirect;
}
set $my_host $query_domain;
location / {
    proxy_set_header Host $my_host;
    proxy_set_header X-REAL-IP $remote_addr;
    proxy_pass $scheme://127.0.0.1;
}

最后一个注意事项是,记得调整 Nginx 配置的 remote_addr,否则都变成了 127.0.0.1, 也许会导致其他一些策略失效。

完美收工,效果如下:https://221.228.82.181/?__domain=www.duitang.com

恭喜你,已经掌握核心科技了,再也不怕运营商瞎折腾了,从此走上了业务蓬勃发展的金光大道……☀️

下一篇文章,我会再谈谈如何做 HTTPS 的「内测」,避免将线上业务一次性切到 HTTPS 导致不少边边角角业务无法正常使用。



原文链接: https://blog.alswl.com/2016/11/https-1/
欢迎关注我的微信公众号:窥豹

3a1ff193cee606bd1e2ea554a16353ee
Phoenix Nemo's avatar

使用 Unbound 搭建更好用的 DNS 服务器

用了好久的 DNSMasq 方案终于在大半年前彻底炸掉了。

原因不光是 DNSMasq 性能和安全性完全不足以撑起公网缓存/递归 DNS 的任务,也有想要做反污染和加速的时候确实太蛋疼的问题。

现在使用的方案是 Unbound+DNSCrypt,外带一份加速列表。这段时间看来,不管在我本机还是在公网服务的两台,效果和反馈都很不错。

准备工作

需要的程序:

  • unbound
  • dnscrypt-proxy
  • makefile
  • git

makefilegit 用于处理 dnsmasq-china-list

Unbound 配置

修改文件 /etc/unbound/unbound.conf。没有这个文件的话,一般需要找一下软件包里提供的配置 example 文件复制过去。这里列出的仅包含需要修改的部分,其他的按照默认配置一般没有问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
num-threads: 2 # 线程数可以修改为物理核心数
interface: 0.0.0.0 # 侦听所有 IPv4 地址
interface: ::0 # 侦听所有 IPv6 地址
# 如果只需要本机使用,则一个 interface: 127.0.0.1 即可
so-rcvbuf: 4m
so-sndbuf: 4m # 本机使用的话,这俩 buf 可以取消注释
so-reuseport: yes # 如果开了多线程,就写 yes
msg-cache-size: 64m # 本机可以设置 4m 或者更小
rrset-cache-size: 128m # 本机可以设置 4m 或者更小
cache-max-ttl: 3600 # 建议设置一个不太大的值...专治各种运营商 DNS 缓存不服
outgoing-num-tcp: 256 # 限制每个线程向上级查询的 TCP 并发数
incoming-num-tcp: 1024 # 限制每个线程接受查询的 TCP 并发数
# 下面这四个不需要解释了吧,不想用那个就写 no
do-ip4: yes
do-ip6: yes
do-udp: yes
do-tcp: yes
tcp-upstream: no # 默认是 no,隧道状态比较稳的话也不需要写 yes。一些情况下强制使用 tcp 连上游的话写 yes
access-control: 0.0.0.0/0 allow # 本机用的话建议设置 127.0.0.0/8 allow,局域网用适当调整
root-hints: "/etc/unbound/root.hints" # 没有的话在 ftp://FTP.INTERNIC.NET/domain/named.cache 下载一份
hide-identity: yes # 不返回对 id.server 和 hostname.bind 的查询。
hide-version: yes # 不返回对 version.server 和 version.bind 的查询。
# 不过下面有 identity 和 version 的自定义选项,不隐藏这些的话,修改下选项还可以卖个萌(´・ω・`)
harden-glue: yes # 建议打开
module-config: "iterator" # 禁用 DNSSEC 检查,如果上游不支持 DNSSEC 就关掉。注意这个选项有可能在其他 include 的文件里
unwanted-reply-threshold: 10000000 # 针对各种网络不服,数值为建议值,具体可以自己修改看看效果
do-not-query-localhost: no # 一般是为了防止扯皮丢包开着,不过等下要用 DNSCrypt 所以关掉
prefetch: yes # 蛮好用的,开着吧
minimal-responses: yes # 省带宽,开着吧。本机用可以关掉
# 关键部分来了,把默认查询全部丢给 DNSCrypt。使用 [地址]@[端口] 指定查询地址和端口,默认端口 53。
# 然后把国内的地址丢给国内的缓存服务器。这两个选项的顺序不能错哟。
# 如果使用隧道查询,把这个地址改为隧道对端的地址,或者一个国外的 DNS 服务器都可以,例如 8.8.8.8。
# 具体看是在对端开 DNS 还是直接用国外的服务器。后者的话,前面 outgoing-interface 可以直接设置隧道本地端的地址,不过要配合 dnsmasq-china-list 的话,还是写路由表比较合适,否则不够灵活。
include: "/etc/unbound/accelerated-domains.china.unbound.conf"
forward-zone:
name: "."
forward-addr: 127.0.0.1@5353

DNSCrypt 配置

修改文件 /etc/default/dnscrypt-proxy

1
2
DNSCRYPT_PROXY_LOCAL_ADDRESS=127.0.0.1:5353 # 设置侦听在 127.0.0.1 端口 5353
DNSCRYPT_PROXY_RESOLVER_NAME=cisco # cisco 其实蛮快的,但是慢的话就去用个别的吧。d0wn 的那堆服务器真的不稳定,和名字一个样...

如果使用 systemd 的话这个文件就没用辣,看这里看这里

修改文件 /usr/lib/systemd/system/dnscrypt-proxy.socket

要修改的部分:

1
2
ListenStream=127.0.0.1:5353
ListenDatagram=127.0.0.1:5353

127.0.0.1:5353 就是上面 unbound 配置里 DNSCrypt 的监听地址。

dnsmasq-china-list 来加速

首先 clone 这个仓库到本地。

执行 make unbound 来生成一份 unbound 配置,然后放在上面 unbound 配置里写的地址,也就是 /etc/unbound/accelerated-domains.china.unbound.conf。默认的 DNS 是 114DNS,最不太近的一段时间都挺残的所以不建议用。如果要改为其他的,例如 DNSPod PublicDNS 的话使用 make SERVER=119.29.29.29 unbound 即可。

如果还需要一些特定的缓存上游设置,要放在 include: "/etc/unbound/accelerated-domains.china.unbound.conf" 这句前面。来举一只栗子。

举一只果子的栗子。

AppStore 的加载和下载速度一直在国内饱受一些极客们的诟病,然而同时饱受诟病的运营商 DNS 解析 AppStore 的下载地址反而是国内的 CDN 地址,速度会非常快。

需要使用 dig 工具,并且要在国内的网络环境下测试。Linux 发行版里都有包可以装,OS X 则自带。

第一条命令:dig +trace a100.phobos.apple.com.

递归追踪解析结果,这个记录被 CNAME 到了 a100.phobos-apple.com.akadns.net.

第二条命令:dig +trace a100.phobos-apple.com.akadns.net.,得到这条记录被 CNAME 到了 a1-a200.itunes-apple.com.akadns.net.

第三条命令:dig +trace a1-a200.itunes-apple.com.akadns.net.,看到了 CDN 的地址,a1-a200.phobos.apple.chinacache.net.

所以如果有国内的地址直接去跟踪解析 akadns.net 的 DNS 的话,一般是可以正确解析到国内 CDN 的。先找一个 akadns.net 的权威 NS 地址,比如 a1-128.akadns.net 对应的 IP 是 193.108.88.128。同时还得有国内的 CDN 地址也从国内地址去解析,那么配置里这样写:

1
2
3
4
5
6
forward-zone:
name: "akadns.net."
forward-addr: 193.108.88.128
forward-zone:
name: "chinacache.net."
forward-addr: 119.29.29.29 # DNSPod PublicDNS 的服务器地址

多次尝试之后,发现还有一个 CDN 地址 0gq2p7eckbs26f.mwcname.com,那么也把这个地址交给 unbound 通过国内网络解析。

1
2
3
forward-zone:
name: "mwcname.com."
forward-addr: 119.29.29.29

一并加到上面的配置里,然后来测试下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Phoenix-X1-Carbon :: ~ » dig @127.0.0.1 a100.phobos.apple.com

; <<>> DiG 9.10.3-P4 <<>> @127.0.0.1 a100.phobos.apple.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 24454
;; flags: qr rd ra; QUERY: 1, ANSWER: 14, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;a100.phobos.apple.com. IN A

;; ANSWER SECTION:
a100.phobos.apple.com. 3596 IN CNAME a100.phobos-apple.com.akadns.net.
a100.phobos-apple.com.akadns.net. 117 IN CNAME a1-a200.itunes-apple.com.akadns.net.
a1-a200.itunes-apple.com.akadns.net. 297 IN CNAME a1-a200.phobos.apple.chinacache.net.
a1-a200.phobos.apple.chinacache.net. 1799 IN CNAME a1-a200.phobos.apple.cncssr.chinacache.net.
a1-a200.phobos.apple.cncssr.chinacache.net. 1799 IN CNAME cc00109.h.cncssr.chinacache.net.
cc00109.h.cncssr.chinacache.net. 120 IN A 223.99.228.87
cc00109.h.cncssr.chinacache.net. 120 IN A 113.207.33.15
cc00109.h.cncssr.chinacache.net. 120 IN A 113.207.33.12
cc00109.h.cncssr.chinacache.net. 120 IN A 202.110.80.14
cc00109.h.cncssr.chinacache.net. 120 IN A 61.179.105.154
cc00109.h.cncssr.chinacache.net. 120 IN A 61.179.105.7
cc00109.h.cncssr.chinacache.net. 120 IN A 119.188.138.172
cc00109.h.cncssr.chinacache.net. 120 IN A 120.192.248.195
cc00109.h.cncssr.chinacache.net. 120 IN A 221.181.39.76

;; Query time: 233 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Thu Apr 28 00:34:02 CST 2016
;; MSG SIZE rcvd: 387

走国内 CDN 了吧~

重启相关服务,更改自己所用的 DNS 地址,完事儿收工(「・ω・)「

=== 2016-04-30 14:00 更新 ===

如果需要 edns-client-subnet 支持的话,需要手动编译源码安装。命令

1
2
3
4
# 克隆源码
svn co http://unbound.nlnetlabs.nl/svn/branches/edns-subnet/
# 编译安装
./configure --enable-subnet --with-libevent && make && sudo make install

配置文件的格式

1
2
# 默认向所有服务器发送 edns-client-subnet
send-client-subnet: 0.0.0.0/0

如果只对特定权威 DNS 发送 edns-client-subnet 请求,则按照此格式写多行 IP。

Phoenix Nemo's avatar

关于 SSL 和 IPKVM/VNC Applet 的笔记两则

虽然是没啥关系的两个东西不过最近没啥可写的所以放在一块做个笔记。

COMODO SSL CA 正确的串联姿势

有客户买了 COMODO Positive SSL 之后问我证书链变了要怎么串联。嘛确实文件变多了不过不至于这就不会了吧…

SSL 证书串联用 cat 命令的话顺序是自下而上,也就是 自己的证书 -> 二级 Intermediate CA -> 一级 Intermediate CA -> Root CA

所以不管文件有多少这个顺序总是有的。以 COMODO Positive SSL 为例,假设域名为 example.comcat 的串联命令:

1
cat example_com.crt COMODORSADomainValidationSecureServerCA.crt COMODORSAAddTrustCA.crt AddTrustExternalCARoot.crt > example_com.signed.crt

于是就可以拿着 example_com.signed.crt 和生成 CSR 时一并生成的 example_com.key 去给 NGINX 咯。

附一份 NGINX 配置样例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
server {
listen 80;
listen [::]:80 ipv6only=on;
server_name example.com;
## redirect http to https ##
rewrite ^ https://example.com$request_uri? permanent;
}

server {
listen 443 ssl;
listen [::]:443 ssl ipv6only=on;
server_name example.com;

ssl on;
ssl_certificate /etc/nginx/ssl/example_com.signed.crt;
ssl_certificate_key /etc/nginx/ssl/example_com.key;
ssl_protocols SSLv3 TLSv1;
ssl_prefer_server_ciphers on;
ssl_ciphers ALL:!aNULL:!ADH:!eNULL:!LOW:!EXP:RC4+RSA:+HIGH:+MEDIUM;
keepalive_timeout 70;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;

location / {
# ...
}
}

最后不忘打广告: [https://c.phoenixlzx.com]

=== 2014-10-15 更新 ===

SSLv3 再次爆出漏洞,在 Google 的新草案 TLS_FALLBACK_SCSV 还未明朗的情况下,目前禁用 SSLv3 是一个 不考虑 IE6 的 workaround。

1
2
3
4
5
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ECDHE-RSA-AES256-SHA384:AES256-SHA256:HIGH:!RC4:!MD5:!aNULL:!eNULL:!NULL:!DH:!EDH:!AESGCM;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;

IPKVM/VNC Applet 的使用

从 7u45 版本之后的 Java Webstart 就不能再直接执行 Ubersmith/SolusVM/Proxmox 等等的 IPKVM/VNC Applet 了。虽然实际上是这些商业软件的错不过还是很想让人骂 Java。

解决方案:下载一个旧版的 Java,比如 jdk6。

但是直接安装也是不能运行的,因为实际上调用的 java executable 是最新版 Java 的,所以一样报错。

如果是在 Linux 下,jre6 的目录在 $HOME/jre6/jre1.6.0_38,那么下载到 Applet 之后用下面的命令启动:

1
2
applet_path=/path/to/your/applet.jnlp
~/jre6/jre1.6.0_38/bin/java -verbose -Xbootclasspath/a:~/jre6/jre1.6.0_38/lib/javaws.jar:~/jre6/jre1.6.0_38/lib/deploy.jar:~/jre6/jre1.6.0_38/lib/plugin.jar -classpath ~/jre6/jre1.6.0_38/lib/deploy.jar -Djava.security.policy=file:~/jre6/jre1.6.0_38/lib/security/javaws.policy -DtrustProxy=true -Xverify:remote -Djnlpx.home=~/jre6/jre1.6.0_38/bin -Dsun.awt.warmup=true -Djnlpx.origFilenameArg=$applet_path -Djnlpx.remove=false -esa -Xnoclassgc -Djnlpx.splashport=59999 -Djnlpx.jvm=~/jre6/jre1.6.0_38/bin/java -Djnlpx.vmargs="-esa -Xnoclassgc" com.sun.javaws.Main $applet_path
Phoenix Nemo's avatar

2014·夏

2014年的夏天应该是我一直以来度过的最繁忙和劳累的一段时光。多方面的压力、打击、困扰,加上自身各种情况条件不利,这个暑假真的让人感觉非常憋屈窝囊。

实习

最值得一提的当然是我的第一份实习。大三下学期那会也就是想找个实习锻炼下自己,也可以给家里减轻一些经济负担。在 Twitter / V2EX 发布求职贴之后陆续有不少公司找上门来,大部分公司都给出了非常不错的条件,有些甚至在我明确答应之前就问我什么时候过去…虽然简历写得比较糟糕,不过仅仅通过和各种公司打交道还是见识到了一点互联网行业的竞争状态。也算是头一回知道原来北京的创业公司工资可以给这么高

最终选择爱奇艺的原因大概就是 iven 前辈所说可以玩集群和虚拟化。虽然公司给的待遇实在让我不知道说啥比较好(除去房租几乎不够生活费,回学校一趟花掉一半工资在路费上…),但是在经济状况还没有糟糕到不得不去待遇最高的公司的时候,我还是想多见识点东西学学经验。说实话爱奇艺没怎么让我失望,工位上就有质量非常好的千兆公网带宽,配备23寸 Dell 显示器(虽然主机配置不怎么样…),弹性工作制,且几乎没有看到员工加班(虽然听说很多人回家后还是会挂上 VPN 连回公司继续工作),办公环境有物业定时打扫,茶水间的阿姨遇到自动售货机有优惠活动就会通知大家,看我打了几个喷嚏还专门去帮我拿了药,公司对于新员工和实习生的成长也算是费了脑筋…

感受颇深的是我所在的团队。在校期间经常和已经参加工作的前辈们闲聊,前辈们吐槽和抱怨比较多的基本上就是一些比较奇葩的同事,其次是公司的种种不合理。我一直认为在公司遇到奇葩同事属于及其正常甚至一定会发生的现象,在决定动身去帝都的时候也做了很多心理准备和各种应对处理办法。不过来到之后发现我想太多… 团队成员友好和睦程度完全超出我的想像,完全没有遇到之前朋友们吐槽的种种情况。每天中午都是一起出去吃饭,路上一边挑餐厅挑半天一遍各种调侃…互相尊重、理解、关心、帮助几乎是整个团队互动的原则。

说到这里其实我有些愧疚。因为自身的很多原因,每天工作的效率都非常低,心情也相当不好,写代码自然也极其浮躁。iven 前辈每次 review 代码的时候都会提出一大堆完美主义的问题,要我一遍一遍修改重构,甚至有时候让我感觉「为什么要把一大堆坑死人不偿命的框架揉在一起,为什么我有一只处女座的 mentor」… 不过事实是我发现自己在写代码的时候越来越想着更优化的解决方案,框架内提供了解决方案的尽量不去用 hack 的方法。在面对一堆完全陌生的框架揉在一起的坑,且自己整个人都很不好的情况下,还是发挥出了平日 1/3 的开发效率。韧性应该有不少提高吧…

跑路

曾经津津乐道卖 VPS 的某某某又跑路了~ (为何是「又」…) 可是现在我也要干这种事。想想真是可笑。

其实跑路并不是一方原因直接决定,也算是很多方面的共同作用结果。一直以来 UltraKVM 项目都处于亏损状态且耗费了我大量的精力,但是由于整个虚拟化平台上还有很多朋友在跑的应用,我也还是一直坚持着做下去。8月份的攻击事件之后我把机房寄发的账单转给了吸引攻击流量的客户,却被喷小公司服务不靠谱。客户修改了自己的联系信息之后不见踪影(其实修改客户信息,财务面板是会发邮件的… 所有历史信息都会留底,所以不要觉得自己改了信息就找不到了)。无奈之中找朋友诉苦,意想不到地得到了帮助。于是向机房发邮件请求下架机器,并向他们一直以来的高质量服务表示感谢(MultaCOM 机房是我目前见过的最好的机房没有之一,不管是网络还是服务)。机房帮我取消了攻击流量的账单,在我指定的日期帮我下架机器并寄送到另外的机房。因此对 MC 机房也抱有很深的歉意。

为了这件事情,我三天没吃下饭,睡不着觉,通知客户备份数据的邮件发了一遍又一遍,担心用 sendmail 发会被丢到垃圾箱还用自己的个人邮箱发了两遍,承诺会在新机房上线并为所有有效客户提供为期至少一年的免费 VPS 服务。(吐槽:就这样还有人在下架之后找我说 VPS 为啥不能访问了云云让我感觉非常不爽,同时感谢朋友的帮助才让我有偿还客户的机会)。一直到给机房发送了最后要付的所有费用之后才算是舒坦了一些。

权限狗

暑假里另外一件闹得比较欢乐的事情是喵窝服务器。应该是敖厂长的原因(?) 整个姬家都可以看到很多人在玩 Minecraft。一边在姬家宣传,管理组也是费尽了心思来准备向墙内玩家开放申请。

其实向墙内玩家开放也是我主要在推,因为墙内确实很多高素质的玩家(先不说游戏技术如何),我很希望能吸引到这些玩家来玩。17精心策划了宣传帖,二小姐把之前用的服务器寄到了帝都,我和壕桥手忙脚乱地把服务器送到机房调试了一个晚上,我还花了两个小时写了申请表单的程序,都是在为了迎接新玩家所做的准备。

不过事实上情况并没有我们想像得那么好。前三天来申请的玩家里,过审率只有不到1/12,接下来的两天里更是掉到了 1/20。从开放申请到开学之前,几乎每天都是好几十封白名单申请邮件。前几天去大概看了下,过审率依旧是 1/15 的样子。不过就算如此严格的审核,我们依然遇到了漏网之鱼…

事件经过详细说的话估计今晚我就不用睡觉了。大致就是先后有三个熊孩子在服里发飙被封,一个是要在毫无干系的百度贴吧到处发帖把我和其他几个管理骂了一通,要和我「正面刚」(…错字?),另一个则恐吓我说要去 mcbbs 「好好说说这件事」。再后面则是一个熊孩子根本没过审核,在群里刷屏伸手被拒绝后骂我们对新手不友好,是「虐待他」…..于是目前还记得的就这仨。

熊孩子被封么无非就是到处发帖子骂,是不是相关论坛也不管(素质…),然后拼命要求加群加好友然后在附言里破口大骂权限狗一类的词,或者是各种无理取闹要求入群入服或者挑衅之类。这么看咱这开源社区的管理员都算是非常好当的了233,对付熊孩子咱也只有放置 play 咯。”Argue with idiots, and you become an idiot.” – Hackers and Painters

客户中心

从大一开始我就在做各种域名/主机/数字证书的代理商,来找我买的都是姬家或者其他 SNS 的朋友,我也没怎么去宣传,权当是自己用也方便下朋友用吧。不过麻烦在于一个域名后缀在几家注册商都可以注册这样的问题,客户为了拿到更低的价格光在我这里就要注册好几个帐户。因此整合自己的业务,做统一的客户中心算是早就有的想法。在返校前两天的时候终于抽时间搭了起来,顺便用掉了两张阿里云的抵用券。广告:地址在 [https://c.phoenixlzx.com]

于是麻烦又来了——

坑:换了域名,支付宝的接口要重新审核。一连三次审核失败:

  • 第一次:网站无法访问——泥煤啊我是 https 好吗!
  • 第二次:网站非中文——泥煤啊我有多语言支持的好吗!另外狗爹那样的专业坑爹英文站怎么过审的?!
  • 第三次:网站未备案——泥煤啊我之前一样不备案怎么过审的?!一个二级域名你给我备案试试看啊!

于是干脆不提交审核了,我想其他办法用。

坑:Hostbill 默认货币选择的 USD,于是支付宝接口愣是不显示。不要那么智能好吗?敢不敢在结帐的时候自动转换?!

然后我批量更新了下产品价格,然后手残误操作把价格提太高了,想再降回去,于是玩完:所有产品的价格配置坏掉了。200多个产品啊!我一个个改得心脏都早搏了………

坑:Hostbill 自己的 whois 文件有问题,很多域名后缀无法正确查询可用性。Google 了半天找到一个论坛的帖子有解决方案,却没有提供 patch 过的文件。没办法自己一点点按照修改步骤打了 patch。由于在公司被 mentor 带出来的习惯,每一处独立的变更都会 commit 一次,然后创了一晚上80个 commit 的记录…..壕桥我超过你的一天50个记录了!

剩下没有的域名自己按照类似的方法也修了,文件丢在 Github

返校

学校的事情一直想等毕业以后再写,写本文的时候也纠结了很久,最后还是稍微写一点点。本来公司的项目第一个版本就要上线,还差最后的几个功能,突然接到母上大人的电话要求我返校报到。直接结果是——我下个月生活费不够了。

然后在学校便是各种跑宿舍分配、联系任课老师等等等…

比较让我提起精神的事情是发现社团办公室整洁了不少,书架上也放满了各种著名的计算机类书籍。新任主席也不像以前那么傲娇,而且一个月写出了一套很不错的前端页面(blueed 老伙计你可以安心的去了…

归京

明天就回帝都了。今晚好好休息… 虽然还有很多憋屈的事情根本都没提,不过也算是吐出来不少。希望回去公司可以有更好的状态吧。

晚安,好梦。

Phoenix Nemo's avatar

Sierra 4G LTE EM7345 with Arch Linux

X1 Carbon 2015 内置 Sierra EM7345 4G LTE 适配器,系统里看到是这样:

1
2
$ lsusb
Bus 001 Device 002: ID 1199:a001 Sierra Wireless, Inc.

根据 ThinkPad EM7345 4G LTE Mobile Broadband - Overview and Service Parts 这块 EM7345 基本完美支持联通的 3G/4G 制式。

插入 microSIM 卡,然后安装需要的包:

1
# pacman -S usbutils usb_modeswitch modemmanager mobile-broadband-provider-info

启用 modemmanager 服务

1
systemctl enable modemmanager.service

然后加载内核模块 cdc_mbim,文件 /etc/mkinitcpio.conf

1
MODULES="... cdc_mbim"

重新生成内核镜像

1
# mkinitcpio -p linux # or change to your kernel preset here

重启即可在 NetworkManager 中新建 GSM 设备的 broadband (移动宽带) 连接。

顺便上一张图,老家小城市还是郊区所以 4G 信号几乎没有,还是达到了这个速度,点个赞(๑•̀ㅂ•́)و✧

参考文档:

Phoenix Nemo's avatar

公共场合请使用指纹识别器

前几天带本本去导师办公室修改论文,虽然开机启动速度快,但是还是在老师的注视之下紧张敲错了三遍密码…得,本来没在意的也记住密码了(ry

所以为啥有指纹识别器的本子偏偏要输密码(:з」∠)

检查设备

总之先来看下指纹识别器是哪家生产的好了。

1
2
Phoenix-X1-Carbon :: ~ » lsusb | grep -i 'finger'
Bus 001 Device 003: ID 138a:0017 Validity Sensors, Inc. Fingerprint Reader

搜索一下就发现 X1 Carbon Gen3 这么受欢迎的本子早已上了 ArchWiki https://wiki.archlinux.org/index.php/Lenovo_ThinkPad_X1_Carbon_(Gen_3)

安装驱动

安装 fprintd 包,这个包会依赖 libfprint,但是官方仓库中的 libfprint 目前的版本在此指纹识别器上无法正常工作,表现为 enroll 只扫描一次,并且无法验证指纹。

Note that recent versions of fprint are broken for this model : One is able to enroll a finger but recognition always fails.

GitHub 上已经有了 vfs5011 相关的 issue 和 fix,但是还没有发布新的稳定版。所以暂时安装 aur/libfprint-git 包。

配置指纹验证

Fprint - ArchWiki 上已经给出了配置方法。将 auth sufficient pam_fprintd.so 加入 /etc/pam.d/system-local-login 中的第一位置即可。同样方法可以用于 /etc/pam.d/ 下的其他文件。例如 KDE 登陆使用的 sddm

扫描指纹,以普通用户身份命令:

1
$ fprint-enroll

默认是右手食指。可以使用 -f 选项指定手指。正常的表现为成功扫描五次即可保存指纹,并且在需要验证的时候自动启动指纹识别器。不过还有个小问题,如果保存多个指纹,似乎只能识别保存指纹列表中的第一个。

然而我懒得折腾了就这样吧

Phoenix Nemo's avatar

使用 IPSec 连接带有 chnroutes 的隧道网络配置小记

标题似乎不是很易懂,于是解释下先。

位于大陆的服务器 A 和位于海外的服务器 B,A 与 B 之间使用隧道互联成内网,服务器 A 配置 chnroutes 以在必要的时候通过海外服务器访问网络,并在服务器 A 上配置 IPSec 服务器,从而使终端用户能够在任何网络环境下安全接入内网。大致描述如下:

需要加速海外访问时:终端用户 <- IPSec VPN -> 服务器 A <- 隧道 -> 服务器 B <-> 互联网

需要访问大陆网域时:终端用户 <- IPSec VPN -> 服务器 A <-> 互联网

此场景适用于互联网公司为员工提供快速、安全、便捷的工作网络环境,对于个人用户来说负担较大,不建议使用。

配置隧道

最简单的方案在服务器 A 和 B 上配置 GRE 隧道即可,步骤简单不再赘述。需要注意的是为了能让客户端的内网地址能够访问到隧道的对端,也就是服务器 B 端,服务器 B 上配置隧道时 peer 的参数应当是包含服务器 A 和 VPN 客户端 IP 的 IP 段,例如 ip addr add 10.7.0.1 peer 10.7.0.2/24 dev gre0

隧道打通后,服务器 A 的路由配置为:

  • 到服务器 B 的公网 IP 路由经服务器 A 的公网网关出站
  • 默认出口路由为隧道对端
  • 注意设置内网之间的路由
  • chnroutes 配置到大陆的流量经由服务器 A 的公网网关出站

配置 IPSec

安装 strongswan

1
# apt-get install strongswan

修改 /etc/ipsec.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
config setup
charonstart=yes
nat_traversal=yes
uniqueids=never # 修改为 yes 可以限制为单个设备连接

conn ios
keyexchange=ikev1
authby=xauthpsk
xauth=server
left=%defaultroute
leftsubnet=0.0.0.0/0
leftfirewall=yes
right=%any
rightsubnet=10.7.0.64/26 # 在配置的隧道网段中选择一个小段以避免地址冲突
rightsourceip=10.7.0.65/26 # rightsubnet 和 rightsourceip 按情况
pfs=no
auto=add

修改 /etc/ipsec.secrets

1
2
3
: PSK "Your pre-shared key" # 预共享密钥,注意修改引号内部分

username : XAUTH "userP@ss" # 用户名和密码,按格式每行一个

配置 iptables 转发

1
# iptables -t nat -A POSTROUTING -o <公网网卡> -s <VPN 网段> -j SNAT --to-source <公网地址>

在本例中,iptables 的命令为

1
iptables -t nat -A POSTROUTING -o eth0 -s 10.7.0.64/26 -j SNAT --to-source xxx.xxx.xxx.xxx

DNS 加速优化

既然是服务器中转分流,那么需要在服务器上进行 DNS 解析方可获得最佳效果。

安装 dnsmasq 和 git

1
# apt-get install dnsmasq git

修改 /etc/dnsmasq.conf 部分:

1
2
server=8.8.8.8 # 默认上游服务器通过隧道交由 Google DNS 解析以获得最佳海外站点效果
conf-dir=/etc/dnsmasq.d # 启用配置文件目录

获取 dnsmasq-china-list,将其中的配置文件软连接到 /etc/dnsmasq.d 下即可使用 114DNS 直接解析大部分需要加速的国内站点。

屏蔽公网对本机 DNS 服务的直接访问:

1
2
# iptables -A INPUT -p udp -d <服务器 A 公网 IP> --dport 53 -j DROP
# iptables -A INPUT -p tcp -d <服务器 A 公网 IP> --dport 53 -j DROP

修改 /etc/strongswan.confcharon { ... } 块内添加:

1
dns1 = 10.7.0.2  # 此处可填服务器 A 上除公网 IP 和 localhost 之外的任意可绑定 IP 地址,例如隧道的本地端地址

测试

一切就绪后重启 strongswan 服务:

1
# service strongswan restart

客户端的 IPSec VPN 配置:

  • 服务器地址为服务器 A 的公网地址
  • 用户名、密码、预共享密钥为服务器 A 中配置文件中的值

连接上之后在客户端 ping 任意公网 IP、服务器 A 和服务器 B 的隧道端 IP 应该都是通的。至此,客户端连接 IPSec VPN 后所有流量都会加密经由服务器 A 中转,在必要时会经由海外服务器 B 转发,保证了访问工作必需站点的速度和在任何公共网络环境下的数据安全。

Phoenix Nemo's avatar

使用 iptables 过滤 DNS 放大攻击

昨晚开始我们的 DNS 日志中出现大量的针对wradish.comANY记录请求,导致大量正常解析请求超时。今天上午到中午时分再次出现流量更大、针对ping.zong.co.uaANY记录请求,导致整个 DNS 服务瘫痪,完全无法响应正常解析请求。

检查后发现 wradish.com 早已因多次 DNS 放大攻击而被列入开放 DNS 服务器黑名单,网络上也有大量的针对该域名攻击请求包的过滤方法。ping.zong.co.ua 却完全没有任何搜索记录。

DNS 放大攻击 (DNS Amplification Attack) 即通过向多台 DNS 开放服务器发送无意义的 recursive 解析请求,从而使得根域名服务器遭到超大流量的解析请求包,无暇顾及正常的解析请求,由此实现分布式拒绝服务攻击 (Distributed Denial of Service)。我们的 DNS 服务器在这里既是攻击者,也是受害者。

DNSMasq 默认允许 recursive 请求,且似乎本身就没有设计为开放 DNS 服务,所以貌似也没有办法禁用掉 recursive 请求。因此我们使用 iptables 规则来过滤掉这些请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Create a chain to store block rules in
iptables -N BADDNS

# Match all "IN ANY" DNS Queries, and run them past the BADDNS chain.
iptables -A INPUT -p udp --dport 53 -m string --hex-string "|00 00 ff 00 01|" --to 255 --algo bm -m comment --comment "IN ANY?" -j BADDNS
iptables -A FORWARD -p udp --dport 53 -m string --hex-string "|00 00 ff 00 01|" --to 255 --algo bm -m comment --comment "IN ANY?" -j BADDNS

# Block domains that are being used for DNS Amplification...
iptables -A BADDNS -m string --hex-string "|04 72 69 70 65 03 6e 65 74 00|" --algo bm -j DROP --to 255 -m comment --comment "ripe.net"
iptables -A BADDNS -m string --hex-string "|03 69 73 63 03 6f 72 67 00|" --algo bm -j DROP --to 255 -m comment --comment "isc.org"
iptables -A BADDNS -m string --hex-string "|04 73 65 6d 61 02 63 7a 00|" --algo bm -j DROP --to 255 -m comment --comment "sema.cz"
iptables -A BADDNS -m string --hex-string "|09 68 69 7a 62 75 6c 6c 61 68 02 6d 65 00|" --algo bm -j DROP --to 255 -m comment --comment "hizbullah.me"

# Rate limit the rest.
iptables -A BADDNS -m recent --set --name DNSQF --rsource
iptables -A BADDNS -m recent --update --seconds 10 --hitcount 1 --name DNSQF --rsource -j DROP

添加以上规则后执行 iptables-save 来使更改生效。

目前为止似乎没有报告出现了明显规模的 DNS 放大攻击征兆,攻击者针对我们防火墙封锁的反应速度、攻击所用的记录以及从日志中发现的大量不同请求源也暗示着我们的服务器可能是最终攻击目标,用攻击流量消耗尽服务器带宽而导致服务瘫痪。至于原因,我也没有想明白为何会有攻击者对一个小型私用服务器感兴趣。

PS. 到本文发表时,攻击依旧在继续,且流量越来越大。目前阿里云的云盾设置可以说无力,只能依靠 iptables 暂时缓解。后续的应对计划仍在制定中。

=== 2014-05-24 更新 ===

Aveline 菊苣给出了一个DNS Amplification IPTABLES block lists 给点128个赞!

Phoenix Nemo's avatar

Kancolle Broker - 舰娘直连游戏!

废话:啊……我终于治好了自己的拖延症。

事情的起因,无非就是 2015 年的第一场大戏——舰娘国服。嘛。不多评论,大家心里都明白。之后用 nginx + SNI Proxy 配置了一个代理服务器方便大家在日本地区外玩舰娘。但是这种代理服务有一个很大的问题就是会引起恶意攻击,被用作 DDoS 攻击的肉鸡使用,因此日本法律也是禁止 VPN 服务器的。

这个 idea 出现的时候还在帝都,房间里没开空调没有暖气,4M带宽20人用还有人开迅雷,无奈只好烧朋友送的 4G 流量卡来维持网络需求。完全没有力气做事情的时候偏偏看到 DMM 那个变态到极点的登录验证……

于是一直拖到回家之后碰上一大堆喵窝的活动… bangumi.moe 那边也在一直催着干活,直到今天我才良心发现用力让自己头脑清醒一点,认真分析了下 DMM 的登录验证。

整个过程大概是这样:客户端请求登录页面,页面中包含两个 token,加载完毕后一个 token 作为 header 数据,一个作为 XHR POST 请求数据发送给服务器,获得另外两个 token。这两个 token 将作为用户登录 email 和 password 的 key。用户登录时 post 给服务器的数据大概是下面这样:

1
2
3
4
5
6
7
8
9
{
"token": "11dde99d39af2287fc6eed02632ccbee",
"login_id": "user@example.com",
"save_login_id": 0,
"password": "P@ssw0rd",
"use_auto_login": 0,
"dfdfa8466f24710893d99529acaaeef0": "user@example.com",
"2760cb37702b03d10d92caf9daaaf675": "P@ssw0rd"
}

… 好了,看晕了吧。咱就不吐槽日本人脑子里到底在想啥了。总之来看代码 -> Github

原理就是登录后用 cookie 拿到游戏 link 的 apptoken,而这个游戏的 link 是不会验证日本 IP 的。

想测试效果的话,那么先关闭本地的各种舰娘代理,移除所有相关的 hosts,然后访问这里。使用 DMM 帐号登录,成功的话会跳到舰娘的游戏页面。适用于所有在日本境外没有日本代理的情况下使用。

问:会不会有安全问题?

必须的。而且比 SNI 代理更不安全。但是反过来说,也是比 SNI 更安全的。因为 DMM 登录发送密码完全没有加密,只是靠 HTTPS。所以用这个程序的话:

  1. 密码都是可以在服务端记录的。虽然程序里完全没有记录密码相关的代码,但是要加也不是难事。
  2. 没有 HTTPS 的情况下,你发送的所有数据都是明文的。所以我做的这个 demo 用了 HTTPS 加密链接。

所以如果是有 HTTPS 加密且 deploy 这个程序的人比较靠谱的话,安全性还是有保障的。

至于大家是不是相信我……嗯这个看各位如何考量啦。

最后:我真的没玩过舰娘,我也不会去玩。

Phoenix Nemo's avatar

懒(烂)办法制作 Telegram Sticker Pack

Telegram 支持表情包 (Stickers Pack) 之后各个群里就开始出现各种奇怪的聊天表情。嘛,虽然有些做得不错但是终究是别人分享的,自己有很多好看的表情图片就不便使用了。

所以今天折腾了下,用现成的图片集来制作 stickers pack。本文使用的 sips 命令内置于 OS X 操作系统,Linux 则可以更方便地使用 ImageMagick 来操作。

Telegram 要求 Sticker 图片为 PNG 格式,并且要有透明层,至少一边为 512 像素,另一边则不超过 512 像素。最大文件大小为 350KB。透明层就算了,我不会玩 PS。那么直接偷懒(死)来批量把当前目录下 JPG 和 PNG 混杂的图片们统一转换为 PNG 好了。

命令:

1
2
3
4
5
6
# 创建 output 目录以存放输出图片...
mkdir -p output
# 把当前目录下的所有 JPG 文件转换为 PNG 丢进 output 目录里。
for i in *.jpg; do sips -s format png $i --out output/$i.png;done
# 把剩下的 PNG 图片丢进 output 准备下一步的批量操作以及简单清理... 没有的话可以跳过。
mv *.png output/ && rm *.jpg

接下来把所有图片缩放为宽或高最大为 512 像素,并且保持比例。

1
2
cd output
sips -Z 512 *.png

搞定。最后一步就是添加 Stickers Pack。需要敲这位机器人

几条命令发给 stickers bot:

  1. /newstickerpack
  2. 发送表情包的名字…
  3. 在内置的 emoji 中发送一个最符合你要发送图片的表情…
  4. 然后把对应的图片作为文件发送
  5. 如果还有其他图片的话重复 3-4
  6. 全部表情图片设置完毕,发送 /publish 命令
  7. 为你的 stickers pack 取一个短名字 (用于 URL)

KABOOM! 新的 Stickers Pack 制作完成~ 快拿链接去和大家分享吧~

最后贴我的芙兰酱的表情包
这些图片木有高清的所以如果谁能做一份高清的表情来请务必告诉我_(:3」

=== 2015.06.07 更新 ===

Mika 菊苣制作了一份芙兰酱的高清表情包

Phoenix Nemo's avatar

迁移到了 uWSGI

虽然 PHP FPM 一直用得还行… 唔,大概已经不能叫做「还行」了,因为它总是莫名其妙地一个请求不响应然后阻塞掉了其他所有的请求。然后就是无止境的 504 Gateway Timeout… 唯一的解决方案是重启 FPM。

然后负载一高就%@T$#$#%@#$%什么的不说了。

无缝切换 PHP FPM 到 uWSGI

安装必要的包

1
apt-get install uwsgi uwsgi-plugin-php supervisor

如果是全新安装的话,php 的各种库也要一起装上

1
apt-get install php5-cgi php5-mysql php5-curl php5-gd php5-idn php-pear php5-imap php5-mcrypt php5-mhash php5-pspell php5-recode php5-sqlite php5-tidy php5-xmlrpc php5-xsl

创建文件 /etc/uwsgi/apps-available/uwsgi-php.ini 并软连接到 /etc/uwsgi/apps-enabled/uwsgi-php.ini

1
ln -s /etc/uwsgi/apps-available/uwsgi-php.ini /etc/uwsgi/apps-enabled/uwsgi-php.ini

文件内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
[uwsgi]
plugins = php

; leave the master running as root (to allows bind on unix socket or low ports)
master = true
master-as-root = true

; listening on unix socket
socket = /var/run/uwsgi-php.sock
chown-socket = www-data:www-data

; drop privileges
uid = www-data
gid = www-data

; our working dir
project_dir = /var/www

; load additional PHP module ini files
env = PHP_INI_SCAN_DIR=/etc/php5/embed/conf.d

; ; or if you need to specify another PHP.ini
; php-ini = /etc/php5/embed/php.ini
; ; then load additional php module ini files manually
; for-glob = /etc/php5/embed/conf.d/*.ini
; php-ini-append = %(_)
; endfor =

; allow .php and .inc extensions
php-allowed-ext = .php
php-allowed-ext = .inc

; set php timezone
php-set = date.timezone=UTC

; disable uWSGI request logging
disable-logging = true
; use a max of 10 processes
processes = 10
; ...but start with only 2 and spawn the others on demand
cheaper = 2

如果要指定 php.ini 的话,env 会失效所以单独用一个循环来读入所有的额外模块配置。envphp-ini/for-glob 二选一。不知道有没有更优雅一些的方法呢…

创建 Supervisor 配置文件 /etc/supervisor/conf.d/uwsgi-php.conf

1
2
3
4
5
6
7
[program:uwsgi-php]
command = uwsgi --ini /etc/uwsgi/apps-enabled/uwsgi-php.ini
autorestart = true
user = root
stderr_logfile = /var/log/supervisor/uwsgi-php.log
logfile_maxbytes = 1048576
logfile_backups = 10

user 一定是 root,因为需要权限创建 unix socket。之后 uWSGI 会自动降权(参见前面的配置文件)。

然后在 NGINX PHP 的部分修改为

1
2
3
4
5
location ~ \.php$ {
include uwsgi_params;
uwsgi_modifier1 14;
uwsgi_pass unix:/var/run/uwsgi-php.sock;
}

最后重启所有服务并清理——

1
2
3
service supervisor restart
service nginx reload
apt-get purge php5-fpm

搞定(๑•̀ㅂ•́)و✧

Phoenix Nemo's avatar

想要导出 Telegram 贴图

Telegram 上出现了越来越多的优质贴纸,想要把这些贴纸用到其他 IM 平台上的时候就会比较麻烦,所以一直想要一键导出一个贴纸包的功能。

可惜的是,Telegram bot API 的限制,并没有任何简单的办法通过贴纸消息获得贴纸包的信息。寻找另外的途径,例如 telegram.me 的贴纸链接会定向到 tg://addstickers?set=[StickerSet]。搜索了一下现成客户端的源码,都是交给 MTProto 的 API 处理,也没有明确的解析过程。而这些客户端所调用的 messages.getStickerSet 也没有在官方的文档中列出。(吐槽:Telegram 的协议、文档和代码真是糟糕,查阅的时候我的表情一直是 黑人问号.gif

由于最近状况不是很好,所以只好暂时放弃继续读 webogram 的源码。因为读 Angular 的东西实在是折磨…

所以依然是选择直接发 sticker 再转为图片发给用户的模式。这样的已经有了相关的 bot,于是改为多个 sticker 打包、支持多语言、支持 jpg 和 png 以及批量缩放功能的 bot。需要安装 Node.js v4.0 及以上版本和支持 webp 的 ImageMagick。

虽然实现效果看起来还可以,但是并未实现最初希望的功能,所以只能是练手用的轮子而已。不过,这个轮子稍微尝试了一些新的东西。例如超简陋的内存数据库,而且很多细节考量更加周到,例如任务锁虽然不是写过最麻烦的,不过应该算是相对完善的。当然也考虑了内存数据库的手动释放以防内存爆炸为此还特地在群里讨论了 object children 被 undefine 而 object 其他 children 还在被引用的状态下是否可以回收部分内存的问题

源码的实现非常简单,但是好久不写代码还是手生,折腾了一下午写功能加一晚上和朋友们 debug。读源码戳 GitHub

这里有一只 bot 跑在测试环境,所以可以尝试一下。如果没理你说明沙盒没开,那么就请自己去跑源码来使用辣ᕕ(ᐛ)ᕗ

有几点坑,比如这个 node-telegram-bot-apionText 方法无法正确匹配 Negative Lookahead 的正则表达式(不应该啊…然而没深究),adm-zip 非常非常不好用,jszip 文档表述不清 API 调用复杂然而用起来了就还不错。

但是最坑的是,只为实现这么一个简单功能的 bot,我的 node_modules 目录下居然有

1
2
Phoenix-X1-Carbon :: js/telegram-stickerimagebot/node_modules ‹master› » ll | wc -l                                                                                               1
104

WHAT??? 104 个依赖包!!!

真是可怕…明明我已经尽可能减少不必要的依赖了…

Phoenix Nemo's avatar

搭建一套权威 DNS 服务架构

萌 DNS 已经年久失修。尽管一直有计划完全重写出一套应用目前各种 DNS 特性和优化的完整平台,但是目前的精力不允许。所以为了先让萌 DNS 的用户们至少先有一个能支持 Let’s Encrypt 的 DNS 服务,决定暂时舍弃 GeoDNS 功能,使用一套更加成熟的解决方案提供服务。

搭配方案如下:

服务器部署:

  • 管理服务器 x1
    • MySQL Master
    • PowerDNS
    • PowerDNS-Admin, supervisor, virtualenv, gunicorn…
    • NGINX, Let’s Encrypt
  • DNS 服务器 x4
    • MySQL Slave
    • PowerDNS

在管理服务器上安装 PowerDNS 和 MySQL Master 的考量是由于 PowerDNS-Admin 使用 PowerDNS HTTP API,在管理服务器(或管理私网中)启动一个仅用于提供 API 和操作主数据库的 PowerDNS 实例能够减轻 Primary NS Server 的压力并提升安全性。整套架构使用 Ansible 进行自动化部署,不过好久没用了各种生疏,照着文档折腾好久的配置…

于是这里暂且记录下整个过程。有些坑只是作者一时疏忽或者有别的考量但没有明确记录,也许在未来的版本中会修复。

安装 PowerDNS

所有服务器均使用 Ubuntu 16.04,需要 PowerDNS 4.0 以上的版本。按照此页面的说明添加 PowerDNS 官方的仓库即可。

1
# apt install pdns-server pdns-backend-mysql mysql-server

由 dpkg 自动配置 PowerDNS 的数据库,然后删除 /etc/powerdns/pdns.d无关的配置文件。

1
2
# rm /etc/powerdns/pdns.d/pdns.local.conf
# rm /etc/powerdns/pdns.d/pdns.simplebind.conf

配置 MySQL Replication,管理服务器作为 Master,其他 DNS 服务器作为 Slave。细节不多讲,官方文档 或者 DigitalOcean Tutorial

管理服务器 (MySQL Master) PowerDNS 配置文件 /etc/powerdns/pdns.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
api=yes
api-key=yourapisecretkey
api-logfile=/var/log/pdns-api.log
config-dir=/etc/powerdns
guardian=yes
include-dir=/etc/powerdns/pdns.d
launch=
local-address=127.0.0.1 # 不对外提供服务
local-ipv6=::1
security-poll-suffix=
setgid=pdns
setuid=pdns
webserver=yes
webserver-address=127.0.0.1 # 仅向本机的 PowerDNS-Admin 调用。如果配置在内网,请使用内网 IP
webserver-allow-from=127.0.0.1/32 # 同上,如果使用内网则写 PowerDNS-Admin 在内网的 IP
webserver-port=8081
default-soa-name=ns1.example.com # 改为 Primary NS 的地址
default-soa-edit=INCEPTION-INCREMENT
default-soa-mail=hostmaster.example.com # 改为默认服务器管理员的邮箱地址,并将 '@' 替换为 '.'
default-ttl=3600

DNS 服务器 (MySQL Slaves) PowerDNS 配置文件 /etc/powerdns/pdns.conf

1
2
3
4
5
6
7
8
9
10
11
config-dir=/etc/powerdns
daemon=yes
disable-axfr=yes
guardian=yes
include-dir=/etc/powerdns/pdns.d
launch=
security-poll-suffix=
server-id=ns1.example.com # 改为当前服务器的 ID,ns1/ns2/ns3/etc...
setgid=pdns
setuid=pdns
version-string=anonymous # 可以写任意字符串恶搞_(:з」∠)_

安装 PowerDNS-Admin

作者有提供详细的教程但是还是有坑

安装依赖:

1
# apt install git python-pip supervisor virtualenv python-dev libmysqlclient-dev libsasl2-dev libldap2-dev libssl-dev letsencrypt

创建数据库,切换到普通用户权限,clone 仓库到本地,然后一步一步操作即可。

1
2
3
4
5
6
7
8
$ git clone https://github.com/ngoduykhanh/PowerDNS-Admin.git
$ cd PowerDNS-Admin
$ virtualenv flask
$ source ./flask/bin/activate
$ pip install -r requirements.txt
$ pip install mysql gunicorn
$ cp config_template.py config.py
$ vim config.py

配置文件 config.py 中需要更改的地方:

1
2
3
4
5
6
7
8
9
SECRET_KEY = 'yoursessionencryptkey'
SQLA_DB_USER = 'yourdbusername'
SQLA_DB_PASSWORD = 'yourdbpassword'
SQLA_DB_HOST = 'localhost'
SQLA_DB_NAME = 'yourdbname'
PDNS_STATS_URL = 'http://localhost:8081/'
PDNS_API_KEY = 'yourapisecretkey'
PDNS_VERSION = '4.0.0'
RECORDS_ALLOW_EDIT = ['A', 'AAAA', 'CNAME', 'SPF', 'PTR', 'MX', 'TXT', 'SRV', 'NS', 'SOA']

然后执行 ./create_db.py。如果没有报错说明数据库安装成功,执行 ./run.py 即可访问 http://127.0.0.1:9393 看到登陆页面了。

部署 Web 服务

直接跑 run.py 当然不科学。Supervisor 配置文件 /etc/supervisor/conf.d/pdnsadmin.conf

1
2
3
4
5
6
7
8
9
10
11
[program:pdnsadmin]
command=/home/pdns/PowerDNS-Admin/flask/bin/gunicorn run:app
directory=/home/pdns/PowerDNS-Admin/
user=pdns
autostart=true
stdout_logfile=/var/log/supervisor/pdns-stdout.log
stdout_logfile_maxbytes=1MB
stdout_logfile_backups=2
stderr_logfile=/var/log/supervisor/pdns-stderr.log
stderr_logfile_maxbytes=1MB
stderr_logfile_backups=2

创建 DHParam

1
2
# cd /etc/ssl/certs
# openssl dhparam -out dhparam.pem 4096 # 如果性能不够请使用 2048

NGINX 配置文件 /etc/nginx/site-enabled/pdnsadmin.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
server {
listen 80;
server_name dns.example.com;

location /.well-known {
default_type "text/plain";
root /var/www/html;
}

location / {
return 301 https://dns.example.com$request_uri;
}
}

server {
listen 443 ssl;
listen [::]:443 ssl;
server_name dns.example.com;

ssl on;
ssl_certificate /etc/letsencrypt/live/dns.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/dns.example.com/privkey.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4";
keepalive_timeout 70;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;

ssl_dhparam /etc/ssl/certs/dhparam.pem;

add_header Strict-Transport-Security max-age=63072000;
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;


access_log /var/log/nginx/dns.example.com.access.log;
error_log /var/log/nginx/dns.example.com.error.log;

location /.well-known {
default_type "text/plain";
root /var/www/html;
}

location /static {
alias /home/pdns/PowerDNS-Admin/app/static;
}

location / {
proxy_pass http://127.0.0.1:8000;
proxy_redirect default;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forward-IP $remote_addr;
port_in_redirect on;
server_name_in_redirect off;
proxy_connect_timeout 300;
}
}

记得把 dns.example.com 换成自己的域名。

签发 Let’s Encrypt。也不多讲。NGINX 配置中已经有了针对 Let’s Encrypt 的续期设置。

然后重启各项服务

1
2
# systemctl restart supervisor
# systemctl restart nginx

查看 PowerDNS-Admin 的运行状态,使用 supervisorctl status

添加 GLUE 记录

要使自己的 NS 生效,必须有保存在上级 NS 中的记录。很多域名注册商都提供了配置 GLUE 记录的功能,例如 Hexonet (1API):

简言之,需要把自己的 NS 服务器及对应的 IP 记录到上级 NS。完成之后,通过 PowerDNS-Admin 添加自己的域名,zone 类型为 NATIVE。然后添加所有 NS 服务器的 A/AAAA 以及所有的 NS 记录——你没听错,要自己写 NS 记录。其他域名也需要添加这些 NS 记录,否则不会托管。

收尾

全部完成之后就是一个完整功能的 DNS 服务了。如果希望启用 DNSSEC,需要在管理服务器中通过 pdnsutil 来添加 key。

由于目前 PowerDNS-Admin 没有限制不能添加提供的 NS 之外的名称服务器,所以其他域名按照添加 GLUE 记录的方法,也可以将这些 NS 服务器「变成」自己的 NS。

好了,不会说话了。讲效果——

一般来说,DNS 服务都会提供多台 NS 服务器域名,将域名的 DNS 改为这些 NS 服务器才能托管到该 DNS 服务上。但是现在只需要知道这套 DNS 的服务器 IP 地址,即可给自己的域名添加 GLUE 记录、NS 记录和 NS 对应的 A/AAAA 记录进而使用自己的域名作为 NS,而不需要用 DNS 服务的 NS 域名。当然一般就是看起来会比较厉害而已…

Phoenix Nemo's avatar

ThinkPad X1 Carbon 2015 与久违的 Arch Linux

对 X1 Carbon 2015 垂涎已久。然而中国市场不提供定制配置、没有 16G 内存的版本、512G PCIe SSD 的型号配置触控屏。这些都不能忍…于是折腾了好久,打扰了至少三位在美国的朋友,最终在黑色星期五当天下单,前天顺丰到手。

先晒一下单。这个配置在联想美国官网购买的原价是 2622.60 美元,黑色星期五的各种优惠后价格是 1630 美元。嘛… 多出来的预算败了一块 1TB SSD 当移动硬盘,真是麻烦帮忙带回国的朋友了(っ‘ω’c)

X1 Carbon 2015 20BSCTO1WW

  • Intel Core i7 5600U 2.6GHz / 3.6GHz
  • 16G (2x8G) DDR3 1600MHz On-board RAM
  • 512G PCIe SSD
  • 2560x1440 QWHD IPS
  • Fingerprint Reader
  • Sierra LTE EM7345

其中 SSD 是 SAMSUNG SM951,测试读写速度分别为 1546MB/s 和 1260MB/sdmidecode --type memory 显示有两条镁光制造的内存,这个倒是和网络上流传的单根内存不太一样。不过两根都是 on-board,所以是无法更换的。

屏幕颜色偏暖,大概是为了长时间工作不会太劳累设定的。ThinkScope 提供了一份 ICC 文件,效果有点偏蓝(紫红?)。不过屏幕型号并不是页面上写的 LG LP140GH1-SPA2,而是 LG LP140QH1-SPB1。总之雾面屏加 86% sRGB coverage 导致它的显示效果比旁边的 MacBook Pro Retina 差了一大截,而且 HiDPI 带来软件上的问题… 嘛这个后面再说。

X1 Carbon 2015 整个机身做工很好,拿着很有份量(不是沉!)给人一种很结实的感觉,完全没有其他 ThinkPad 的塑料感。唯一略心颤的是屏幕没办法像 MacBook Pro 一样稳,和其他笔记本一样调整屏幕角度还是会晃动几下,大概是铰链设计的差异导致。键盘仍然是那个熟悉的手感——哪怕是悬浮式键帽,仍然和老式 ThinkPad 键盘手感不相上下。换到这么舒服的键盘之后打字速度已经快到让 fcitx 分不清键序啦( ´∀ )σ)Д`)其实是 GTK 程序的 bug。ThinkLight 改到键盘下方,可以更好地看清键帽更高大上了,但是没办法当作光源看其他东西… 不过本来也不怎么亮还是爱护下眼睛吧。触控板很舒服,虽然大概还是不够和 MacBook Pro 相比,毕竟人家有底层驱动优化,系统层还支持很多手势。单从手感上来讲还是很不错的。

新本本送到后开机检查硬件,然后当然是要删掉预装的 Windows 10 然后换上 Arch Linux 啦。之前 T420 的 UEFI 安装 Arch Linux 总是启动不能,然而 X1 Carbon 就很顺利。当然是关掉了 Secure Boot 的。

续航感觉还不错,中午送到开箱检查的时候就还有 60% 的电量,然后从下午开始到晚上一直在安装配置系统,到半夜的时候报告还剩 15% 电量。中间有几次编译,所以顺便看了下散热。室内温度大概 10 摄氏度,sensors 检测高负载时最高温度 57 摄氏度,一般工作温度 35-40 摄氏度。这是有点出乎意料的,本来以为超极本的散热都会很悲剧。不过据说其他人也有到过 80 度的,所以要再用用看。

Sierra LTE EM7345 是 microSIM 槽,然而手头只有 nanoSIM 所以还没用上。回去学校把流量卡补成 microSIM 再玩玩看。

安装过程完全不折腾。硬件兼容性报告只有一句话:Everything works out of box.

如果一定要在兼容性上挑刺儿的话,大概是 GPS 只有在 Windows 下才有办法打开,打开之后就可以直接在 Linux 下用了。

回到 Arch Linux 上。一年没用,KDE 已经快不认识了。通过更改 DPI、字体大小和图标大小,Qt5 的程序算是很好的支持了 HiDPI。然而一些 GTK / Qt4 程序就没那么好说话了… 总之用各种奇怪的办法让这些跟不上时代的程序变得勉强能看。

刚开始用 MacBook Pro 不久的时候,除去再也无法接受 retina 之下的显示效果(retina 有毒啊)之外,由于键盘、输入法(!!!)、快捷键、工具链、UNIX 生态等等问题上,OS X 上的工作效率只有 Linux 的 1/3 左右(是真的,很早的时候解决一个问题大概花了一个多小时,后来在 OS X 上再次遇到却花了一个下午)。

总之用来这几天,可以说是非常满意的。没有任何生产商设置的门槛,OS-Specific 的东西通过各种 折腾 Google、折腾 IRC 频道里的各位、折腾 ArchWiki… 等基本都解决了。现在看到它就有想摸一摸、想用它工作的冲动(๑•̀ㅂ•́)و✧然后上个图~

最后的一点牢骚。

一年多没怎么碰 Linux 桌面,虽然看起来变化很大,但是实际上的提升基本没有感觉到。倒不如说,由于硬件技术一直在革新,Linux 桌面却没有了当年的势头。前段时间去 KDE 主页看的时候发现他们把捐赠放到了首页上,而且赞助页看起来好粗糙,完全不像是 KDE 一贯精致的设计风格。从对 HiDPI 的支持来看更是如此。KDE/High-dpi issues 页面从半年前到现在基本没有太大的变化…

嘛。当然了就算是钱多得没处花的某有点软的公司也没让自己的操作系统好好的支持 HiDPI ( ´∀ )σ)Д`)

开源界仍然有大量被广泛使用的应用保持着粗制滥造、works for me 等等的心态。虽然绝大多数不会有人为之付费,但是这并不是不认真的理由。而且这样下去也并不会有多少杀手级的应用出现。

虽然作为 DevOps 的话只要有好用的命令行工具链就万事大吉了。 更何况现在厨子整天瞎搞,果子就算还能继续赚无脑追捧的大众的钱,它还能制作多少 developer-friendly 的产品呢…
(´・ω・`)

Phoenix Nemo's avatar

用优雅的方式在 OS X 中为单个应用设置语言

买到 CLIP STUDIO PAINT Pro,激活之后发现不给启动,显示 Unsupported OS 并退出。搜索了下发现是因为语言设置的问题导致,需要将系统环境设置为应用所支持的语言才能运行。

嘛。日本人做事情也是让人无话可说,那么多应用都没有多语言支持的… 用能支持的语言来显示就好了嘛。

话说回来,我一开始设置的系统语言是简体中文,虽然后备语言加了 English 和日本語,不过有些不能自动变更语言的应用在更换系统语言为 English 之后变得很别扭。于是寻找可以单独设置应用语言的方法。

用惯了 Linux 再用 OS X 其实并没有那么容易的改变习惯… 当我准备尝试单独 export 一份 locale 再运行 app 的时候 OS X 直接告诉我不适用我的表情简直和伊莉雅一样。

睡了一觉起来继续 Google。找到了可以单独为应用设置语言并且 launch 的 app Language Switcher,看起来不错,但是每次启动 CLIP STUDIO PAINT 都要先打开这货,这不是我想要的效果。

于是最终找到一个合适的解决方案:用 defaults 命令。

原帖在这里

设置 CLIP STUDIO PAINT Pro 语言环境命令:

1
defaults write jp.co.celsys.CLIPSTUDIOPAINT.lip AppleLanguages '("en-US")'

其中 jp.co.celsys.CLIPSTUDIOPAINT.lip 可以在应用的显示包信息 -> Contents -> Info.plist 中找到。

执行后就可以直接双击启动啦~

最后献丑一张w

Phoenix Nemo's avatar

在 Ubuntu 服务器上搭建 OpenConnect 服务器小记

最近猫给推荐的 Cisco AnyConnect,面基的时候也看到 Aveline 菊苣在用 AnyConnect(插嘴:正太菊苣果然好可爱!!!) 于是自己折腾了下。作为少有的能让我折腾起来的 VPN,暂且把搭建步骤简单记录下。

AnyConnect 的好处是基于 HTTPS,证书可以申请 StartSSL 的,而且配置也不很复杂。另外配置文件里发现了很多专门为商业化/企业服务定制的选项,例如最大同时在线客户端数量,同账户最大在线设备数量等等。

OpenConnect 是 AnyConnect 的开源实现。目前 0.8.0 版本需要 GnuTLS 3.1 以上版本,所以我们就直接在 Ubuntu 14.04 中搭建。另外 就算是基于 HTTPS,这货也是要用 TUN 设备的,所以 OpenVZ 用户们请注意。

准备

命令

1
2
3
apt-get install build-essential libwrap0-dev libpam0g-dev libdbus-1-dev \
libreadline-dev libnl-route-3-dev libprotobuf-c0-dev libpcl1-dev libopts25-dev \
autogen libgnutls28 libgnutls28-dev libseccomp-dev libhttp-parser-dev

安装

下载源码

1
2
3
wget -c ftp://ftp.infradead.org/pub/ocserv/ocserv-0.8.0.tar.xz
tar xvf ocserv-0.8.0.tar.xz
cd ocserv-0.8.0

编译安装

1
2
3
4
5
./configure --prefix=/opt/ocserv 
make
make install
mkdir /opt/ocserv/etc/
cp doc/sample.config /opt/ocserv/etc/config

配置

这里只记录需要修改的重点配置,其他配置请参照样例配置文件的注释按需修改。

文件 /opt/ocserv/etc/config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
auth = "plain[/opt/ocserv/etc/passwd]"
listen-host = connect.your.domain
max-clients = 128
max-same-clients = 4
server-cert = /opt/ocserv/etc/ssl/server-cert.pem
server-key = /opt/ocserv/etc/ssl/server-key.pem
ca-cert = /opt/ocserv/etc/ssl/ca.pem
mobile-idle-timeout = 2400
ipv4-network = 192.168.1.0
ipv4-netmask = 255.255.255.0
dns = 8.8.8.8
dns = 8.8.4.4
route = 192.168.1.0/255.255.255.0
route-add-cmd = "ip route add 192.168.1.0 dev tun0"
route-del-cmd = "ip route delete 192.168.1.0 dev tun0"

创建用户,命令

1
/opt/ocserv/bin/ocpasswd -c /opt/ocserv/etc/passwd username

按提示输入两次密码。

iptables 规则

1
iptables -t nat -A POSTROUTING -j SNAT --to-source <server ip> -o <nic>

记得把 <server ip><nic> 改为服务器公网 IP 和对应网卡的名称。

搞定

折腾完毕,AnyConnect 客户端可以成功使用了。

不过呢折腾完之后发现这货其实被干扰得很厉害啊…

Phoenix Nemo's avatar

末字幕组时代,我们何去何从

日本のアニメやドラマを勝手に翻訳して字幕を付け、違法配信する中国のアマチュア集団「字幕組」。北京在住のメンバーを取材してその実態や動機、違法性を伝えたところ、中国のネットメディアが本紙記事を翻訳、「字幕組がなくなれば抗日ドラマに洗脳される」などと擁護の声が殺到した。

这是好久之前出现在日本各大新闻站上的文章。今天偶然和朋友闲聊时再次聊到了版权问题。

前些时间开始,射手网、人人影视、极影等字幕发布站相继被 takedown,相关律师函甚至恐吓信开始出现在站长们的邮箱中。在中国,「版权」第一次闹得这么凶。然而可悲的是,这并不是因为中国人开始有了版权意识,而仅仅是影响了一些商业集团的利益。

在近期某字幕组发布的作品中,发布者这样写到:

現今,版權的世代到來
觀眾也都紛紛的看版權去了
這對我們來說不是什麼問題,因為我們本來就不在意什麼載量
做字幕本來就是自爽的,與其說是做給別人看,不如說是做給自己看
對字幕界比較大的問題其實是新人減少這件事

從這幾天MANGLOBE(動畫公司)倒閉事件來看
看似盛況的MAG(漫画、アニメ、ゲーム)也明確的顯示出,其實是正在走下坡的
但是國外購買版權這情況,是有助於動畫公司轉虧為盈,不再只是依靠BD賺錢
我們理所當然的是要感到高興
這絕對絕對不是壞事,有愛支持正版是對的

但同時我也希望各位,如果真的很喜歡某片的話
在經濟允許的情況下,買個BD原盤支持一下吧,因為並不是每個片子都有版權購買(AMAZON用郵局的VISA卡就能買囉)
請把口中的支持化作實際的支持吧,良好的作品不該讓它沈入水中

另一方面
由於版權大量崛起,似乎可以望見字幕組的數量會越來越少
有所謂的版權可以看後,也會大幅降低新人願意加入字幕組的意願,因此製作字幕的人會越來越少
最後,支撐著字幕組的人只剩下老人
工作,結婚,後宮,家庭,最後連老人們都各奔東西(現在越來越坑就是因為現實的干擾越來越多
接著就化作了時代的眼淚

曾经在早些年,在大陆的视频站大部分仍然只是互联网上用于大家互相上传视频自娱自乐的时候,并没有某相关部门令人无力吐槽的审查和胡搅蛮缠,日本动画也并没有那么大规模的影响力和市场。字幕组的相继出现,让国内的观众们能够看到制作精良的日本动画,也促进了国内 ACG 圈子的发展。更重要的,字幕组将日本的二次元文化、包含其中的人的素养,人性的光辉和思考,带给了越来越多的人。

在这个时候应运而生的发布站,整合了多个字幕组的作品给观众挑选,也直接加剧了字幕组之间的竞争。经过一段时间的发展,字幕组的工作流程越来越优化,翻译和字幕的质量越来越高,甚至做到了日本电视台首播后两三个小时就可以制作出精美字幕的程度。

字幕组的百家争鸣慢慢引起了商界的注意。好像就在一夜之间,各大视频站都开始争相购买日本动画的版权。这本来是一件好事,但是相关部门的审查、过长的广告等等问题引发了观众们的不满,他们开始将发布站和视频站推向一个对立面,微博上甚至出现了「视频站不给看,我们去xxx下载!」类似激进的言论。然而与此同时,某圈内朋友表示:

国内所有视频网站我都是VIP
我觉得体验还不错
就翻译有待加强

字幕组的建立,有些是因为兴趣爱好,也有些只是随口说出的玩笑。但是共有的一点,都对二次元文化充满了热爱。很多组员看到了喜欢的作品会攒下生活费购买蓝光盘用于收藏,根本不会拆封,只是为了支持一下制作公司而已。但是从法律上来讲,字幕组的行为依然不能被接受。

我们推动了动画在国内的推广,但是并没有实力给制作公司分钱。
所以是我们退场的时候了,让专业的来吧。

所有人都已经看到了残酷的现实。曾经为了兴趣抑或只是一句玩笑建立起的小组,一路走来承载着众多组员们的羁绊,转瞬间已经到了并不华丽的谢幕。

也许会有字幕组和发布站继续存在下去,但是更多人会选择在线观看视频站购买的正版动画。虽然并不是中国人的版权意识提高了,但是至少,视频公司不管是通过广告还是销售会员获得的利润,可以用来继续购买更多的版权动画给大家看。对动画制作公司,对观众们,都不是坏事。

说到底,大部分字幕组,只是为了大家能在一起玩得开心罢了。字幕只是让大家聚到一起的载体,就算没有再继续做字幕,这些「字幕组」之名也可以让组员们继续走在一起吧。想必,这段可能仅仅存在于近几代人中的记忆,会被小心翼翼地保存一生。

R.I.P

Phoenix Nemo's avatar

尝试迁移到 AWS Cloud

发了个帖子抱怨服务器一大堆却没个放自己个人站的地儿。其实是服务器都是生产环境要跑各种业务,不能放自己的东西。其实个人站就一个博客一个知识库,还都是静态的,连买个 Linode 都觉得资源浪费。放在 GCE f1.micro 上吧,是便宜了不过网络抽死。如果不是因为要用 SSL 防运营商劫持和中间人攻击,真的直接扔 GitHub 了。

感谢 @sparanoid 的建议,花了一下午尝试将自己的静态网站部署在 AWS 云上。

AWS 准备工作

S3 文件桶

创建一个 S3 文件桶,并在「属性」 -> 「静态网站托管」页选择「启用网站托管」,然后索引文件填写 index.html

在此页面会得到一个网站终端节点,例如 example.com.s3-website-ap-northeast-1.amazonaws.com 这样。先复制备用。

上传 SSL 证书

其实之前签发了一张三年的 ShinoSaki ECC 证书,然而 AWS 不认(ノ=Д=)ノ┻━┻ 只好去签了 AlphaSSL 的泛域名。

需要安装 awscli,可以直接 sudo pip install awscli

上传证书命令如下(示例名称记得改成自己的哦)

1
aws iam upload-server-certificate --server-certificate-name cert-name --certificate-body file://example_com.pem --private-key file://example_com.key --certificate-chain file://ca.pem --path /cloudfront/yoursite/

前面的 file:// 也不能移去,否则会报错 A client error (MalformedCertificate) occurred when calling the UploadServerCertificate operation: Unable to parse certificate. Please ensure the certificate is in PEM format.

创建 CloudFront Distribution

如果是 Jekyll 一类会创建「文件名.html」的程序,origin 直接下拉菜单中选择 S3 的文件桶地址即可。但是 Hexo 和 MinoriWiki 都是目录名下的 index.html,所以只能使用之前得到的网站终端节点。填入地址后,下面的配置随自己的想法选择即可。Alternate Domain Names (CNAMEs) 填写自己的域名,然后下面的 SSL 选择 Custom SSL Certificate (example.com) 和一个对应的证书。Default Root Object 填写 index.html

为什么只能使用网站终端节点而不是 S3 地址的原因是 CloudFront 和 S3 的 Default Root Object 工作原理不同,如果是请求子目录,那么 CloudFront 不会给默认加上 index.html 导致 403。

创建之后 CloudFront 需要(很长一段)时间来部署。于是先把分配的 CDN 地址 xxxxxxxxxxxxx.cloudfront.net. 复制备用。

设置 Route 53

建立一个 Route53 Hosted Zone,然后导入以前的 zonefile 或者什么的。网站的地址 Type 选择 A - IPv4 address,然后 Alias 选择 Yes。Alias Target 是刚才复制的 CloudFront 的 CDN 地址。Routing Policy 选择 Simple——GeoDNS 将交由 CloudFront 完成。

网站程序准备

Hexo

Hexo 已经有了现成的 S3 deployer,不过主页上的那个不工作也不管别人给提交的 PR。所以推荐使用 hexo-deployer-aws-s3

不过貌似是会强制覆盖每个文件的,上传花了好久啊比 Git 慢多了… S3 啥时候支持 Git (ノ=Д=)ノ┻━┻

MinoriWiki

MinoriWiki 从一开始就是面向 UNIX 系使用者的一套超简单个人知识库,所以压根没考虑用 S3 啊(ノ=Д=)ノ┻━┻

不过自己写的东西好处就是可以各种改(死)所以半个下午发布了一个带有 S3 支持的新版。使用了 node-s3-client 库,可以只上传修改了的文件。

搞定

一路搞下来,CloudFront 也就差不多了,域名解析应该也更新了。试试看效果吧~

没啥访问量的个人站,用 Route53 + S3 + CloudFront 的开支一个月可以控制在 2 美元左右,但是性能、网络和可用性远远比一个月 2 美元的 VPS 好得多。

然而在国内… AWS 被滥用还是挺凶的。所以各种服务被干扰,于是成了现在这惨样。

其实还不如直接托管在 GitHub 然后上个支持自定义 SSL 和 PAYG 的 CDN

jaseywang's avatar

Big transaction and MySQL replication lag

This saturday afternoon I was on call, and got a alert, one of our production MySQL slave begin to have lag like this:

After some time reviewing the monitoring metrics, I got the big breakthrough, there existed large spike of lock structs and delete operations during the lag period:

Usually, this was caused by big transaction on master node. The verfication was quite straightfoward, just analyze the binlog, the script was copied from percona.

After running the script, I do really found something astonishing, during that time the master had one transaction per minute, and each was comparatively big, nearly 10k delete operations for one transaction:
Timestamp : #160904 13:26:54 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 7559 row(s) affected
Timestamp : #160904 13:27:56 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 7726 row(s) affected
Timestamp : #160904 13:28:57 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 7864 row(s) affected
Timestamp : #160904 13:29:59 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 8039 row(s) affected
Timestamp : #160904 13:31:01 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 7961 row(s) affected
….
Timestamp : #160904 13:33:05 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 7985 row(s) affected
Timestamp : #160904 13:34:06 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 7849 row(s) affected
Timestamp : #160904 13:35:08 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 7927 row(s) affected
Timestamp : #160904 13:36:10 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 8014 row(s) affected
Timestamp : #160904 13:37:11 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 8031 row(s) affected
Timestamp : #160904 13:38:13 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 8104 row(s) affected
Timestamp : #160904 13:39:15 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 7730 row(s) affected
Timestamp : #160904 13:40:16 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 7668 row(s) affected
Timestamp : #160904 13:41:18 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 7836 row(s) affected
Timestamp : #160904 13:42:20 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 7757 row(s) affected

Timestamp : #160904 13:48:29 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 7835 row(s) affected
Timestamp : #160904 13:49:31 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 7839 row(s) affected
Timestamp : #160904 13:50:32 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 8028 row(s) affected
Timestamp : #160904 13:51:34 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 8223 row(s) affected
Timestamp : #160904 13:52:35 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 8119 row(s) affected
Timestamp : #160904 13:53:37 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 7924 row(s) affected
Timestamp : #160904 13:54:38 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 2139 row(s) affected
Timestamp : #160904 14:17:10 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 5592 row(s) affected
Timestamp : #160904 14:18:12 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 8260 row(s) affected
Timestamp : #160904 14:19:13 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 7876 row(s) affected
Timestamp : #160904 14:20:14 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 7994 row(s) affected
Timestamp : #160904 14:21:16 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 7994 row(s) affected
Timestamp : #160904 14:22:17 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 7813 row(s) affected

Timestamp : #160904 18:15:27 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 8191 row(s) affected
Timestamp : #160904 18:16:27 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 8180 row(s) affected
Timestamp : #160904 18:17:27 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 8304 row(s) affected
Timestamp : #160904 18:18:28 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 8128 row(s) affected

If we compare the transaction of other time period, we can get the result, below is the top 10 transaction of Sep 2 which is far less than 10000:
Timestamp : #160902 18:00:31 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 850 row(s) affected
Timestamp : #160902 10:00:33 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 813 row(s) affected
Timestamp : #160902 8:00:10 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 786 row(s) affected
Timestamp : #160902 10:55:42 Table : `qmq_backup`.`qmq_log_records` Query Type : DELETE 646 row(s) affected
Timestamp : #160902 12:30:01 Table : `qmq_backup`.`qmq_log_records` Query Type : INSERT 608 row(s) affected
Timestamp : #160902 12:30:01 Table : `qmq_backup`.`qmq_log_records` Query Type : INSERT 606 row(s) affected
Timestamp : #160902 19:07:37 Table : `qmq_backup`.`qmq_log_records` Query Type : INSERT 599 row(s) affected
Timestamp : #160902 19:10:09 Table : `qmq_backup`.`qmq_log_records` Query Type : INSERT 582 row(s) affected
Timestamp : #160902 19:07:23 Table : `qmq_backup`.`qmq_log_records` Query Type : INSERT 575 row(s) affected

That explains why io thread works well but still have big lag, since by default slave has only single thread to process all including big transaction from master, it can't handle that and break.

What's next? find which service cause that big transaction, back-flush from the DB log, there do really have a job that purge the old data every minute like this:
[2016-09-04 14:21:53  INFO com.benmu.rapidq.backup.backup.task.HistoryCleaner:?] clean history qmq_backup_messages data by condition create_time < '2016-08-20 14:21:53', total clean: 892, elapse: 60 ms

After the discussion with developer, we decided to stop the job. Besides that, we consider to increase the thread(slave_parallel_worker) of slave since we currently use MySQL 5.7 which is its big selling point.

jaseywang's avatar

Nginx 200 response with empty body by double slash

We setup a new cluster of Nginx to send requests to HIS using curl with domain name as the Host header, unfortunately, we get 200 return code but empty body like this:

When we use IP as Host, it works well, and return the expected result:

After some debug, nothing found and tcpdump comes:

From the tcpdump, the above red arrow uses domain name as Host header and the below one uses IP as Host header, we see there exists a double slash when using domain name.

After some review of Nginx config file, we found the misconfiguration location:

alswl's avatar

API 集成测试实践

为了提高测试,工程师需要对自己提交的产物进行测试,一般是单元测试、集成测试。 之后提交物流转到 QA 团队,QA 团队根据需求描述对提交物进行测试, 这个测试过程非常耗费人力。 尤其是当开发交付的质量不高时候,很可能自身没有经过测试,会遇到主干流程都无法进行的状况。

如果在 QA 人工介入测试之前,就进行一轮黑盒自动化集成测试,可以大大地提高 QA 团队的工作效率。 基于这样的判断,我们团队花了一些时间,将基于 API 的自动化测试系统搭建起来。 现在将这个系统的选型和运行状况拎出来,和大家分享。

确认测试范围、目标和意义

  • 范围
    • 后台输出的 API 级别 URL
    • 使用场景
      • 打包时候的冒烟
      • Dev / QA 手工添加添加新特性用例
  • 目标
    • 覆盖大部分的 URL,当期设计为 top 10 URL,仅包含 GET 接口
    • 选型时,需要考虑非幂等(POST / DELETE / PUT)等接口
  • 意义
    • 提高开发效率,一种自动化的 IT 测试方案
    • 提高测试效率,减少人工集成测试成本
    • 提高工程质量,通过覆盖率提升,保证工程质量逐步提升,放心开发新功能

特性需求

选型一个系统,不是看市面上有哪些可以供选择,而是看我需要什么样特性的一款产品。 如果自己的需求和市面上的现成产品差异过大,也可以考虑自己定制。

  • Required
    • 开源
    • 免费
    • 使用 DSL 或者简单代码描述测试用例
    • 支持细粒度的单 API 测试和构建带过程的测试用例
    • HTTP API
  • Optional
    • CI 集成
    • UI

挑选出来的选型和评价

这部分工作,是和团队的其他成员一起去看的,大家各自分头寻找一些产品,然后进行评测,给出结论。

经过讨论,我们将重点关注放在这么几款下面:

搭建 demo,进行试用

在确定选用那几款产品之后,就可以集中精力在几款候选者里面。搭建相应的环境,对他们进行实际测试。

supertest:

  • 功能太简单了,简单到几乎可以自己写掉,不算一个 test framework

pyresettest:

  • 哈哈哈,YAML based,dreamed feature
  • 支持 YAML / extractor / validator
  • 天生支持 host 为参数
  • create for me!!!

hippie-swagger:

  • 在使用上,和 supertest 差异不大
  • 仍然需要自己定义,在 swagger 描述文件不存在时候会抛错,描述文件不符合时会抛错

robotframework:

  • 较为复杂
  • 有 YAML 了,不用试了

使用感觉

经过一个季度的试用,我们基于 pyresttest 的项目 abao 运行较稳定。 尽量在工程师提交代码之后,运行一次,从而可以在早期发现问题。

由于是基于 Python 的源代码,我们还给 pyresttest 开发了几款插件:

  • cookie_extractor:用来解析特定的 cookie
  • file_choice_generator:从文件随机选择预设数据
  • file_seq_generator:从文件顺序选择预设数据

在和 CI 的配合方面,我们在 Jinkins 搭建了 abao / abao-master 项目, 前者响应每次 Push 请求,都会自动构建一遍,后者每天凌晨会将 master 运行一遍。

感谢项目贡献者:

project  : abao
 repo age : 5 months
 active   : 32 days
 commits  : 109
 authors  :
    39  Chery.Peng  35.8%
    33  3D          30.3%
    17  yanqi.chen  15.6%
    11  橙子        10.1%
     7  fiona66     6.4%
     2  雪糕        1.8%

参考文档


原文链接: https://blog.alswl.com/2016/08/api-integration-test/
欢迎关注我的微信公众号:窥豹

3a1ff193cee606bd1e2ea554a16353ee
alswl's avatar

搞定暴涨的流量

2013 年左右,我司业务发展迅速,每天晚上都会面临服务器濒临崩溃情况。 我相信每个高速发展的互联网企业在某个阶段都会面临这样的情形,比如去年爆红的「足迹」。 过程往往是:线上出现故障,手机会收到报警,然后登录到服务器上去解决问题。 处理这种问题工种现在有一个时髦的名称,叫做「SRE(Site Reliability Engineer)」系统可用性工程师。

虽然我常常救火,但是我还是想尽可能避免线上发生故障。「最好的消息,就是没有消息。」 减少故障出现概率,增强系统可用性,降低故障处理时间是 SRE 的最大课题。 在这里有最常用的两个手段,一个是优化性能,一个是做好容量规划和扩展。 这里我着重讨论后者「容量规划」。

^ 看我的一堆报警消息

面临的问题

面对暴涨流量,一边是业务方的满心欢喜,一边就是工程师的烦恼和压力了。 也许是一个受欢迎的功能上线了,或者是某个社会活动导致流量爆发,系统开始出现高延迟,磁盘 IO 不够用了。 也许是 DB 第一个倒下,也许是 RPC 系统第一个倒下…… 呃,大神可能会说,我艹,RPC 系统第一个倒下还搞个屁啊,赶紧倒闭算了。

核心的问题就是,在现有性能下面,在面临可能的大流量冲击时候,如何做到不慌不忙,能够 handle 住突如其来的流量?

设定容量目标

在解决这个问题之前,我们得先考虑清楚,我们到底要多强的流量处理能力。 如果今天我们只是一个两三台服务器的小团队,却企图设计一个能够抗住 1 亿 pv 访问的系统, 显然是不现实的,至少是不经济的。

衡量系统容量的指标可以简化为在什么流量下面,提供什么样的可用性保证。 一个实际的样例是,在 1 亿 pv 下面,提供 99.99% 的可用性, 其可用性的评判标准是「服务器在 200ms 内返回正确的数据」。

这里有一个重要的概念,可用性保证,术语是服务等级协议(SLA)。 这个指标可以从大部分标准云供应商的标准条款里看到,比如我司机房供应商提供的可用性保证是 99.9%。 阿里云 ECS 的 SLA 是「99.95%」,统计周期是 1 个月 (如果故障时间低于 5 min,不计入故障时间,云供应商都这样,特别霸权)。

一个对 SLA 的直观认识是(具体数据来自 High availability - Wikipedia, the free encyclopedia):

  • 99.0% 意味着一年有 87 天不可用
  • 99.5% 意味着一年 1.83 天不可用
  • 99.9% 意味着一年 8.76 小时不可用
  • 99.99% 意味着一年 52.56 分钟不可用
  • 99.999% 意味着一年 5 分 15 秒不可用,这是高可用的一般标准

设定越高的 SLA 的成本越高,具体 SLA 的设定是成本、收益、期望的平衡。 不同的业务需要的 SLA 也不一样,一般认为 99.9% 基本可用,99.99% 可用性较高, 99.999% 为高可用。

有些云供应商号称 8 个 9,9 个 9,那往往都是对于存储服务里面的数据不丢失这个指标。 除了忽悠忽悠人,这个 SLA 没什么用的。

测量

做一件伟大事情时候,先有目标,下一步如果是迈出脚步出去闯荡,那么往往换来的是一个身心疲惫的自己。 更稳当的做法是,先摸摸清楚,自己有几把刷子,是不是还要再练练,有没有资格上战场。 没有 Profiling,就是瞎子,根本不用谈优化和容量规划。

对于一般的业务场景而言,常见的测量指标分为三类:

  • 服务器的硬件指标(CPU、内存、硬盘 IO、硬盘容量、网络)
  • 服务的软件指标(QPS / latency / pool)
  • 业务的数据指标(核心业务指标,比如注册数,核心动作次数)

我司的实践情况是这样的,我们使用 Zabbix 测量服务器,用自己设计的系统收集服务数据,使用 Grafana 呈现。 后者被设计到 RPC 系统内部,数据是全量收集。 我司在业务层面的数据监控做的还不足,这种不足不仅仅体现在数据的全面性上面,还体现在相关成员(比如产品汪)对数据的利用率上面。

除了测量线上的实施数据,了解某个设施的性能极限也是很重要,目前常见的测量方式是:

  • 模拟流量进行测试
  • 在线上进行测试,并实时跟踪进展情况,出现异常时候,停止流量切入
  • 从线上引入流量到测试环境进行测试

我发现,第一种方法往往不准,第三种方法对于小团队来说,成本太高。第二种方法是最粗暴和有效的。

预警和提醒

仅仅知道当前系统的性能表现是不足的,重要的如何将这些数据利用起来,对未来系统增长进行预估。 流量增长 vs 资源消耗,这个曲线大部分情况是线性的,有些情况确实指数增长的。

常见的做法是,给核心指标设置一个阈值(比如 80% 磁盘使用率,40% 磁盘 IO 利用率),当监控的数据到达这个阈值时候。 就必须进行容量扩充,进行负载均衡。

一个从运维同学身上学到的是,提前采购一些设备放到机房里面,比如硬盘、内存,别到时候供应商来不及供货。 必要库存可以降低 MTBF。

除了设定阈值报警,应当定期跑一些脚本获得数据。定期检查报警系统,避免报警系统失效。

必选项 - Scalable

上文写到,「必要时候进行容量扩充,进行负载均衡」。 这点的提出,意味这需要保证基础设施是可扩展的,支持负载均衡,支持硬件扩容

Web 系统比较容易做到横向扩容,使用 Nginx / LVS 等负载均衡即可。 中间件服务一般也是在设计时候就考虑了扩展。(什么?你们家 RPC 系统设计调用不支持扩展?什么脑残设计?!)

DB 级别的服务,往往就要花一些心思了,一些技术(比如 MySQL)想要做到横向扩展, 需要进行提前设计。而一些设施虽然容易进行扩展,比如 ES / Kafka 等现代化设施, 但在部署的时候仍然要进行一些提前准备。

除了提前做好 Scalable,还有几个和部署相关的 tips 可以供参考:

  • 使用工具:自动化部署,现在有太多工具可以供选择,比如 ansible 就是一个很好的工具
  • automatic everything:避免登录服务器操作才能保证未来自动化
  • 工程化:用最佳实践去维护部署系统,用工程化的态度去写部署代码
  • 保持同质,避免花样:避免使用 shell 级别的操作原语操作部署系统,使用预设的 module 去操作

End

好了,现在去预测一下当大流量来临之际,你的服务会在哪些环节失败。 想不出来的话,就一点点去测量各个环节性能,然后做一把容量规划吧。

调优和增加容量,这是两个手段,这两个手段互相作用,互相影响。使用时候需要根据成本和收益进行选择。

关于容量规划的更多细节,可以看看 Web容量规划的艺术 (豆瓣) 这里看看。只是这本书写在 2010 年,并且作者介绍的过于传统运维视角一些。


原文链接: https://blog.alswl.com/2016/06/capacity-planning/
欢迎关注我的微信公众号:窥豹

3a1ff193cee606bd1e2ea554a16353ee
alswl's avatar

一次「分答」记录

尝试在团队内部发起一次类似「分答」的沟通方式,一对一面聊。 回答其他工程师的问题,这种沟通的方式暨在提供一个特定的场合,帮助加强双方了解, 解决团队中其他工程师的一些实际的问题。

在征得对方同意之后,我将他的问题和我的回答 PO 出来。

  1. 喜欢什么游戏,玩游戏的问题
    • 喜欢和人玩游戏,不喜欢和不认识网友玩游戏
    • 小学从红月开始玩,传奇、CS、魔兽、真三、DOTA,大学时候和舍友玩游戏,毕业后不玩
    • minecraft 尝试着玩过,没有玩下去,找不到人
    • 日活的桌面游戏,三国杀等蛮喜欢
    • 不喜欢浪费时间,怕没控制
    • 从玩游戏里面学习什么么?不,就是纯粹享受游戏,不会想这么多
  2. 喜欢什么语言
    • 看场合,小东西,小场景用 Python,生产环境用 Java,生产环境又有时间,考虑 Scala
    • 只允许选一门语言,就用 Python
    • 最吸引的特点,熟悉程度高,生产效率高,第三方库丰富,粘合性强
    • 为 Python 做一些功能扩展?不同时期答案会不一样,目前来说,希望有一个开发效率更高的 framework;有时间的话,会考虑如何绕过底层 GIL 问题
  3. 有什么事情投入精力,铩羽而归
    • 工具向重的人,推广 git 花了两年,推广 Restful 不利
    • 做业务开发 leader,和 tsu、小管一起做,项目管理、团队管理没做好,没做好,有挫败感。
  4. 职业迷茫期
    • 我作为一个学渣,并且长期以来没有人给予职业生涯规划,一直对自己未来发展有困惑
    • 如何在无领导的情况之下,探索未来道路,规划自己工作内容?跟优秀的人聊,跟外部的人聊,看博客,看书
    • 迷茫是客观存在的,并且可能持续存在很久,正视它
    • 现实中一定会遇到各种挑战和困难的,用挑战和困难填充自己,丰富自己的生活
    • 迷茫是未知,有恐惧,有兴奋。对当前的我来说,恐惧可能更多,但是要面对
    • 职业生涯里面,前期的同质度更高,资深工程师可以会给初级工程师规划清晰一些,但是越往后期,越难规划,需要自己探索
  5. 推荐书
  6. 学习来源
    • 博客,infoq 等专业信息来源
    • 书籍
    • 周围的人
    • 尽可能从每天时间挤出时间阅读
  7. 生活和工作平衡点
    • 不同的人,同一个人的不同时期所需要的平衡不一样
    • 对自己人生期望不一样的人,平衡点不一样
    • 对我来说,目前阶段不存在明显边界,尽可能投入工作
  8. 如何保持激情
    • 人总是有低谷期的,不可能一直保持亢奋,除非是甲亢
    • 短期情绪会有起伏,长期来看,保持梦想和对自己高要求,可以帮助保持激情
    • 我有低谷期,低谷期适合反思
  9. 如何擦屁股
    • 现实是残酷的,总有屁股要擦,自己也有脏屁股,今天要给昨天的自己擦屁股
    • 现实中存在脏屁股,尽可能了解它,看代码、文档,设计方案,了解的人,让自己变舒服
    • 责任心的体现,做好当下的事情;解决这样的问题,解决当前的问题;避免留下脏屁股,设计好,规划好
    • 责任心的话很虚。一个人做擦屁股、低产值的事情,从长远来看,是错的;短期需要撑下来;并且帮助团队避免这样的事情发生
  10. 最有成就感的事情
    • 顶着家庭压力,到上海来
    • 在堆糖改造环境,提升自己心智水平,不害怕问题,自信,自省

原文链接: https://blog.alswl.com/2016/06/q-and-a/
欢迎关注我的微信公众号:窥豹

3a1ff193cee606bd1e2ea554a16353ee
alswl's avatar

海贼王和创业团队

一个同事在知乎提了一个问题 如果把草帽海贼团比作一个创业团队,这个Team的组织架构是怎样的?有哪些优势,又有哪些不足?

这个话题很有趣,作为追了多年的 fans ,又是身处互联网创业团队的我要来强答一记。

海贼王的世界很大,富有个性的角色和团队也很多,恰好可以和显示世界中的互联网创业团队进行对比。 柳传志讲的好,做大事,要「建班子,定战略,带队伍」,我们就着金句来看看草帽这个团队怎么样。

产品和战略

我先讲产品和战略。

由于海贼王世界的设定,以及作为一个全年段漫画动画的原因。 海贼王世界的整体战略是较为简单,海贼团的目标都较为单一,即「找到 哥尔 D 罗杰 留下的宝藏」。 海贼团们实现目标的路径也较为单一,打斗增强战斗力,不断寻找线索,去伟大航道寻找宝藏。

所以故事的开展反而是围绕另外一条隐秘的线索「历史的真相」,尾田大神埋坑很深。 「历史的真相」并不是作为大部分人努力和前进的目标,所以「海贼王」世界的故事虽然曲折跌宕, 但是产品模型极为单一,不具备太多可以讨论的点。

团队

接着讲团队,一个团队第一重要的是创始人,这必须要是一位领袖人物。

评价领袖优秀程度,从这么几个角度评价:

  • 意志力:打不死的小强,不达目标誓不罢休
  • 专业技能:能打、脑子灵光、心灵手巧、一技之长
  • 规划能力:除了体力值和专业智慧,也要讲讲战略和谋略
  • 管理能力:沟通协调、团队管理促进能力,能够带领团队实现目标,促进他人成长

以这个标准来看几个具有领袖气质的角色:

  • 路飞
    • 意志力:5
    • 专业技能:5
    • 规划能力:1(率性而为的大爷)
    • 管理能力:1(团队自由生长)
  • 白胡子
    • 意志力:5
    • 专业技能:5
    • 规划能力:4(顶上之战的过程证明)
    • 管理能力:5(队长们的成长和忠诚度证明)
  • 多弗朗明哥
    • 意志力:5
    • 专业技能:5
    • 规划能力:5
    • 管理能力:5
  • 艾斯
    • 意志力:5
    • 专业技能:4(以牺牲的时间点战斗力打分)
    • 规划能力:2(追踪黑胡子,一个人冒进,被黑胡子摆了一道)
    • 管理能力:2(也就混到一个队长,能够鼓励其他人,但是没发现黑胡子成长的问题)
  • 唐僧(乱入一个对比)
    • 意志力:5+
    • 专业技能:4(熟读经书算不算专业能力?)
    • 规划能力:0
    • 管理能力:0

从上面的判断可以看出,对于一个领袖而言,路飞仅仅是不错,但是谈不上多么优秀, 倒是白胡子,典范啊典范。

根据上面提到的能力模型,草帽海贼团的其他角色大家也可以心里评估出来了: 大部分人都是意志力、专业技能强悍(即便是乌索普,射击能力也是可以评上 3),但是同时也在规划能力和管理能力上面较弱。

唉,这么弱,很难继续支撑草帽海贼团继续走下去啊,怎么办呢?

带队伍

草帽海贼团,一个极为漂亮的「自组织团队」。

什么是「自组织团队」?来自 InfoQ 的一篇文章 什么是自组织团队? 里面讲到团队的特性:

  • 分散式的控制,也就是说与集中式的控制截然相反,
  • 不断适应改变的环境,
  • 在局部相互作用下自然浮现出来的结构,
  • 反馈,包括肯定的和否定的
  • 弹性,归结于系统修复和调整的能力。

用简单的关键词概括,其实是这么几个关键词:

  • 自行决策
  • 自己成长
  • 适应环境变化
  • 团队内部信任并且沟通顺畅

从这几个关键词来看,草帽海贼团和这种自组织模式极为匹配。路飞从来不会发号施令,统一调度,顶多喊几句口号振奋一下大家。 路飞自身的规划能力和帮助他人成长的能力也远不及格。团队里面其他的成员,索隆、香吉士等的成长,完全是靠自己, 甚至在「两年」跨度这么长的时间,尾田也是借外部力量帮助团队这些成员成长,而不是依靠领袖来培养成员。

路飞选择这种自组织模式,不仅仅是一种巧合,更多的是一种逼不得已。尾田大神写的就是青春热血漫画,要是换成一个有勇有谋, 那就成主旋律电视剧了,大家可能也是因为路飞这种优点和缺点喜欢他。

Anti-自组织模式

和「自组织模式」相反的情况,可以参照一下多弗朗明哥的团队。我们可以看到,在多弗朗明哥团里里面,有严密的等级关系(四大干部,家族干部,普通小兵), 有帮助人才成长的流程(培训罗的一系列流程)。 这种设定也是多弗朗明哥的战略能力和管理能力的体现。

还有一种更极致的组织模式「集权模式」,由核心层发号施令,协同作战。 海贼王的世界里,较少发现这样的模式,反派角色海军有一点点像。 倒是另外一部作品「星球大战」,其中的帝国、第一秩序,都是典型的 Manager 发号施令,底层士兵只要好好作战即可。 统一培养成行的克隆人,就可以支撑这样的团队发挥十足战力。

这种集权模式,缺少向上反馈能力,缺少自发的创新能力。在业务发展顺利规模扩张阶段,也许可以发展不错,但是长久以往, 核心层必然会发觉创新力和驱动力不足,花费大量的精力在管理、制度建设、奖惩机制上面。

路飞的缺点和优点一样鲜明,他很幸运的(可能也完全不知道)使用了「自组织团队」这种模式,完成了创业的起步阶段。 但是这种模式有也有自带的缺陷,需要目标一致性很高,团队成员自身有极高成长度。一旦遇到业务爆炸(顶上之战这种规模的战役), 这团队就完蛋了。期望路飞在未来的航道上,注意培养一下自己不善于的能力。好消息是,从最近的「庞克哈撒德篇」和「德雷斯罗撒篇」战况来看, 路飞已经开始学会制定「寻找友军结盟」、「树立竞争对手标杆」这样的初级战略方案了。

海贼王的世界,真的就是现实中「海盗」的正式写照吧。完成不可能的目标,成就团队,成就自己。

「世代传承的意志,时代的变迁,人们的梦,只要人们继续追求自由的答案,这一切的一切都将永不停止.这世界……没错!一个追求自由任凭选择的世界,就在我们的眼前无限地延伸,如果我们的梦想可以引导你的方向的话,就去追寻吧!在名为信念的旗帜下」


原文链接: https://blog.alswl.com/2016/04/onepiece-startup/
欢迎关注我的微信公众号:窥豹

3a1ff193cee606bd1e2ea554a16353ee
alswl's avatar

Windows management for hacker

团队里的同学有时看见我键指如飞,可以用快捷键将 Mac 的窗口玩转于手心。他们表示酷炫非常, 心生羡慕的同时,希望掌握这门技艺,我就把使用的 Phoenix 介绍给大家。结果过了一段时间, 发现普及率并不高,本着好为人师的精神,今天我就来八一八这款优秀的桌面管理工具。

在介绍我使用的工具之前,我要先介绍一下我选择的原因和历史。

ps:配图是我长草多年的 Ergodox Infinity。(@夫人,看到这里的时候,请留步思考 5s)。

Alt+Tab = 苦难的历史

当我还年轻的时候,曾经对 Alt+Tab 这个快捷键愤慨无比,觉得这种设计虽然简单但是很蠢。

  • 命中率低。要在茫茫图标中查找自己需要的窗口,如果开了 20 个应用,切换到一个非常用窗口至多可能需要 20-1 次按键。 不要跟我说有 Alt+Shift+Tab 的反向循环操作,估计大部分用户都不知道。而且 正向/反向 切换伤脑子。
  • 交互方式耗时。由于 Alt+Tab 的操作需要用户进行反馈才能进行,是 眼-手-眼-手 的操作反馈方式, 这种交互模式费时费脑子。跟这种需要实时反馈对立的交互模式应当是类似「一键呼出」的操作。
  • 在大显示器、多显示器的环境下面,Alt-Tab 模式没有做任何优化
  • 频繁切出窗口进行切换,容易让人分神,比如切换时候看到某个播放器的标题,在放一首喜欢的歌,很可能吸引过去。

有人会问,难道不优化都是缺陷么? 我的答案是肯定的。Alt+Tab 是几乎除鼠标之外的唯一一种窗口操作方式。如果它不能跟上时代的步伐, 对大显示器、多显示器做优化,那就是不作为,不努力,不上进。跟不上变化是要被淘汰的!

平铺式窗口管理器

在被 Windows Alt-Tab 虐了多年之后,我长大了,开始接触 Linux,但是这种痛苦仍然时刻包围着我。 当我分期购买一个外置显示器之后,这种痛苦到达到了顶端。 恰好彼时我是 ArchLinux 的信徒,很快就发现了一片桃源: 自己安装 Window manager

我重新认识了桌面系统世界,除了介绍常规的 Gnome / KDE / xfce 之外,还有一类窗口管理系统,他们叫做「平铺式窗口管理器」。 (严格来说,Gnome / KDE / xfce 属于 Desktop environment,层级比「窗口管理系统」要高,我这里不做严格区分)。

什么是平铺式窗口管理器?来一个直观的认识:

官方的解释是……呵呵,自己点进去看解释吧。平铺式桌面管理器(包括同时支持平铺式和堆叠式的混合式桌面管理器)给我带来了新的认知, 原来桌面系统是可以进行接口编程的,我不那么 care 到底是哪种风格,我 care 的是, 能否通过编程定制来解决我的 Alt-Tab 问题。

我期望的窗口管理模式

  • 快速启动最常用的应用,同时也能将其快速呼出
  • 对大屏幕友好,现在显示器普遍是大屏幕,可以自由控制窗体的移动,方便多个窗口同时进行操作
  • 对多屏幕友好,多屏幕间的切换,要友好。可以快速屏幕间切换
  • 对键盘友好,对鼠标友好,全键盘操作模式,但同时要对鼠标友好,比如鼠标跟随焦点功能,毕竟一些操作还是鼠标方便
  • 帮助集中注意力,将操作界面隔离成多个目的区分的空间,比如写作时候,期望只有一个 Evernote + Chrome 在眼前

我在 Mac 下面的方案

呜呼,感谢伟大的 Jason Milkins 做了一堆尝试, 创造了一堆乱七八糟的桌面管理器。然后感谢 Kasper Hirvikoski 在 Jason 拍拍屁股走人之后,接过了 Jason 的棒子,将 Phoenix 这个项目快速推进,解决了一堆导致不可用的 bug, 并新增了很多特性。

回到要介绍的主角身上,Phoenix

A lightweight OS X window and app manager scriptable with JavaScript

基本特性是:

  • Javascript 作为配置文件,定制性超级强
  • 支持 App / Window / Space / Screen 等对象的操作

基于 Phoenix,我达成了我的窗口管理模式的目标:

  • 快速启动
    • 使用 Option + ` / 1 / 2 / 3 / 4 / 8 / 9 / e / a / s / z /, / . / / 启动
    • iTerm / Chrome / Safari / QQ / Bearychat / Wechat / Neteasy Music / MacVim / IntelliJ IDEA / Macdown / Mail / Evernote / Finder
  • 窗口操作
    • Option + - / =
      • 大小控制
    • Option + m
      • 窗口移动到屏幕中央
    • Option + Space
      • 鼠标找回到窗口中央
  • 屏幕内操作
    • Option + J / K
      • 焦点在同屏幕窗口切换
    • Ctrl + Option + J / K / H / L
      • 窗口移动
  • 屏幕间操作
    • Option + H / L
      • 焦点左右屏幕切换
  • Space 操作
    • Option + I / O
      • Space 左右切换
    • Option + Ctrl + I / O
      • 将当前窗口移动到相邻 Space
    • Option + Enter
      • 将当前窗口移动到 Work Space
    • Option + Delete
      • 将当前窗口移动到 Park Space
    • Work / Park Space 就是用来帮助我集中精力的

我的配置文件在 https://github.com/alswl/.oOo./blob/master/.phoenix.js 我的配置文件可以开箱即用,但这是我自己的工作模式,想要获得自己最舒适的效果,需要自己进行一些研究和定制。

其他一些替代方案


我曾经写过一篇 Linux 程序员的 Mac 安装记录, 告诉大家我在 Mac 上面常用的工具、包管理器。


原文链接: https://blog.alswl.com/2016/04/windows-management-for-hacker/
欢迎关注我的微信公众号:窥豹

3a1ff193cee606bd1e2ea554a16353ee
's avatar

Firefox 插件:TooManyTabs

TooManyTabs 简直是标(tuo)签(yan)党(zheng)的福音啊!每每在一大堆待读标签里面翻找要看的网页简直要疯掉!包括我和我的内存 ▔f゚゚▔

但只是简单的插件介绍的话是不足以让我这个万年懒癌晚期开工写博的,主要是这货配合 Firefox 的帐户同步功能的时候有点儿坑:一开始没有给分组重命名的时候,几个电脑的 TooManyTabs 一同步就全乱套了;直到我把各个电脑的分组都分别命名,然后又费了老劲儿地整理一番之后才搞定。

让我觉得这个插件很神奇的是,它是用 Firefox 的书签功能来保存标签的,但又不是简单地保存网址,因为标签恢复之后其前进后退的历史也都一并恢复了。看来 Firefox 的书签还有很多的隐藏功能有待发掘啊。

最后的最后,既然是通过书签功能来保存和同步的,那如果 Firefox Android 的书签功能能再给点儿力就好了,让我可以直接把网址保存到指定的分组下面,这样就不用先发送到电脑上再(能用电脑时)TooManyTabs 了(再加上 Firefox Android 的发送标签功能有时还会丢标签)。

以上

alswl's avatar

技术之外

这是一天一本书的第二次执行,这次选了一本比较薄的书,上周的书看了一天,脑仁疼。


在我组织团队新兵训练营(入职之后一段时间内容集中的培训)时候, 经常和新同事聊到一个词:软实力。 我将其描述为专业技能之外的能力。每个人都这种能力的解读可能会不一样, 我将其拆解为:「对工作的热情;观察力和反思能力;做计划和决策是否周全」。

这种拆解是不全面的,「多年」以后,我意识到,硬实力考衡的是专业的能力, 而软实力则是考衡人的因素。这种晚来的意识让我在一段时间里面, 将自己的工作陷入困境,并且得不到解药。

Google 的两位工程师 Brian W. Fitzpatrick 和 Ben Collins-Sussman 写了一本书极客与团队,通过他们的视角, 告诉大家想要在团队中获得成功的另一面。不要被书名误解,我觉得「开发者和团队」是更好的名字, 虽然没那么酷。

要在团队中获得成功,你必须以谦虚尊重信任为核心原则。

要做的第一件事情,应该就是沟通了。让自己成为一个玻璃玲珑人, 其他人可以看到你的方向、目标和里程碑,同时可以看到你的进展和问题点。 这样不但可以获得工作中的肯定,当个人的目标设定和团队出现偏差, 又或是开发过程中在一个点停顿了太久,可以有其他人参与进来或直接伸出援手。

这种透明度对上对下都应该如此。团队的领导, 应当在开发周期内的早期就明确告知团队愿景、目标和设定的里程碑。 产生共鸣的愿景可以让人对目标有渴望,对自己工作有认同。 各位还记得中国中小学开学第一周里,大多都有一个开学典礼讲话。讲的好的领导, 会阐述自己的教学理念,去年取得的成绩,今年的教学着重点。 讲的差的领导就是泛泛而谈,每年都是一套话术,完全看不到长进。

缺失沟通,还会将个人陷于单打独斗的境地,一个篮球队需要 5 个人大, 一个人牛逼没屁用。

提高工程质量的一个有效手段就是 CI(持续集成),将开发过程中一点点的小进展都以一种机械的方式呈现出来, 并进行测试。另一个有效手段是 Code Review,不但推荐要 CR,更是要尽早、快速的 CR。 避免屎积压多了拉,太臭。

我突然想到一条实践:即便是做一个人的项目,在精简程度上也保持最小的一个阈值, 想象明天就要长假,工作要交给别人维护,如何在交付物里面有足够的信息让其他人知晓细节。 而不是丢给后继维护者一句冰冷的话:「看代码」。

沟通必须是有效的,我想任何人都不想听一个嘴碎的人在那边逼逼一下午。 有很多结构化、一部的沟通可以显著提高沟通效率: 项目看板、设计文档、Code Review、代码注释、数据字典等。

第二个重要的观点是,接受失败,承认自己不是无能的。你可能很聪明,但所做的事情不一定完全都是正确的, 连上帝都会犯错,何况是普通人。犯错不可怕,但是犯错还认识不到可怕。犯错并且认识到了, 但是拒绝承认错误的人,不是可怕,而是应该要被淘汰,这类人会极其难以合作。 如何你周围都是这样的人,或者你上司是这样的人,也许你可以考虑换一个地方,在拉钩搜索「堆糖」试试吧。

关于接受失败的另外一个隐含后续发展就是「成长」。意识到这个世界是动态发展的, 「要以发展的眼光看待事物」是一个非常非常有用的认知。 能自己意识到失败,并且会主动复盘,重新认知自己的人,往往会成长的极为迅速。 关于成长的话题可以讨论很深,以后可以单独拎出来讨论。

书中提到一个失败后回顾的清单:

  1. 简要
  2. 时间的时间线,从发现到调查,再到最终见过
  3. 事件发生的主因
  4. 影响和损失评估
  5. 立即修正问题的步骤
  6. 防止事件再次发生的步骤
  7. 得到的教训

我就哈哈哈了,这不就是我大堆糖的故障报告模板么?

第三点,如何成长?简单来说,去冒险,去承担自己能力之外的任务, 去挑战没有经历过的任务。有一条彼得定律:「在组织或企业的等级制度中, 人会因其某种特质或特殊技能,令他在被擢升到不能胜任的职位,相反变成组织的障碍物(冗员)及负资产。」。 前半段含义是,大部分情况下面,并不是具有了相应能力才去承担,而是试着去承担任务。 无论成功与否,对当前去挑战的人来说,都能够得到历练,从而能力得到提升。

第四点是:成为 Leader,而不是 Manager。 一个团队是一艘危机四伏的海面上一只船,如果没有一个船长,那么就前途叵测。 在职业生涯的某些阶段,你可能自然成为船长,也许是一个项目的船长,也许是一个小 Team 的船长。 那么切记,船长是 Leader,而不是 Manager,是能力综合,可守可攻,顺风时候会把舵, 缺人时候可以顶任何岗位的船长。而不是手持大鞭的 Manager。 我觉得新闻联播里面描述的人民公仆,就是一个很好的 Leader。

一年多前之前和铁柱聊过,一个 Leader 是否需要要以能力服众。 我仍然保持当初的观点:「是的」。在目标管理、方向把握上面, 强大的技术背景可以有魄力的开展工作,挖掘新技术,推动变化。 在遇到困难时候,可以决策、解决问题。 这是由这个行业特质决定的,互联网是依赖创造力的脑力劳动,而不是根据人数线性增加产出的体力劳动。

但毕竟不是每个人都一定拥有 Leader 特质,难道就要一辈子做技术得不到上升? Google 的一种做法,可以很好解决这个问题。分离 TL(techlead)和 TLM(techleadmanager), 前者更着重技术,后者不但关心技术,还关心手下工程师的成长。 用国内互联网的职责分工描述,大概就是有技术专家和团队负责人的区别。

关于这条,书中的几点最佳实践非常棒:

  1. 放下自负
  2. 做一个禅师(保持冷静和理性)
  3. 成为催化剂
  4. 当一个导师
  5. 设置明确的目标
  6. 坦诚(三明治赞美法)
  7. 记录快乐程度

最后聊一下对书本身的评价。黄易山在 Quora 写过一段非常有名的 为什么工程师管理这么难?。 这本书讨论的内容要比黄易山那篇回答范围更大,讲述的也更详细(废话,这是书)。 作者是典型的工程师,书目结构易读,第五章从反模式来思考问题非常赞。

我读过几本技术管理相关的书籍,印象深刻的有两本,一本是温伯格的成为技术领导者,另外一本是此书。温伯格的行文比较跳跃、比较抽象,不容易读。 而这本书不但通俗异动,也添加了非常具有可操作性的最佳实践。 从创造力驱动的角度出发,技术开发者都是管理者。因为他们需要设计方案,创造价值,而不是重复劳动, 所以我推荐每个开发者阅读。

好了,学习够了充分的理论,下面就是做起来了,「知行合一」。


开给自己的处方:

  • 上面提到的最佳实践
  • 谦逊:谦逊一些,低调一些,向他人学习
  • 坚毅:认准目标,稳步前行,不放弃
  • 信心:信念也许可以重建,但是对自己始终保有信心,也许会错,但是要相信自己的判断
  • 开会技巧:超过 5 人的会用单向宣讲更有效

原文链接: https://blog.alswl.com/2016/02/team-geek/
欢迎关注我的微信公众号:窥豹

3a1ff193cee606bd1e2ea554a16353ee
jaseywang's avatar

OpenVPN Connectivity Issue in Public Network

We established a ovpn tunnel between 2 IDCs in September 2014, and we have monitored the availability and performance of two ends for a long time. The geographical distance between 2 IDCs are quite short, but with different telecom carries. mtr shows that there exits ahout 7 hops from one end to the other. The below screenshot shows the standard ping loss.


The result is quite interesting. At first we used UDP protocol, and we often experienced network disconnection issue, later we switched to TCP, and it improved a lot. From the digram, the average package is 1.11%.
Why 1.11%, what I can explain is the tunnel is often fully saturated during the peak hour, and this can't solved at the moment, so no matter what protocol, the package loss should exists. Another possible reason is the complexity of public network which I can't quantitate.
The current plan works during current background, but no "how many 9s" guarantee. Anyway, if we want to achieve more stable connectivity, a DLL(dedicated leased line) is a better choice.

alswl's avatar

一例 Timeout 故障

早晨刚到公司, HAProxy 报警,Trtornis(第三方云存储网关,用来统一管理阿里云和七牛云的对象存储) 全飘红。

检查日志,并没有 ERROR 信息,但是大量 WARN 报错。

WARN  [2015-12-09 11:01:02,730] org.eclipse.jetty.util.thread.QueuedThreadPool: dw{STARTED,8<=50<=50,i=0,q=1024} rejected org.eclipse.jetty.io.AbstractConne
ction$2@62c021c6
WARN  [2015-12-09 11:01:02,731] org.eclipse.jetty.io.SelectorManager: Could not process key for channel java.nio.channels.SocketChannel[connected local=/10.
1.1.78:8350 remote=/10.1.1.74:63290]
! java.util.concurrent.RejectedExecutionException: org.eclipse.jetty.io.AbstractConnection$2@62c021c6
! at org.eclipse.jetty.util.thread.QueuedThreadPool.execute(QueuedThreadPool.java:362) [tritonis-shaded.jar:0.0.1-SNAPSHOT]
! at org.eclipse.jetty.io.AbstractConnection$FillingState.onEnter(AbstractConnection.java:379) ~[tritonis-shaded.jar:0.0.1-SNAPSHOT]
! at org.eclipse.jetty.io.AbstractConnection.next(AbstractConnection.java:273) ~[tritonis-shaded.jar:0.0.1-SNAPSHOT]
! at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:563) ~[tritonis-shaded.jar:0.0.1-SNAPSHOT]
! at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:82) ~[tritonis-shaded.jar:0.0.1-SNAPSHOT]
! at org.eclipse.jetty.io.SelectChannelEndPoint.onSelected(SelectChannelEndPoint.java:109) ~[tritonis-shaded.jar:0.0.1-SNAPSHOT]
! at org.eclipse.jetty.io.SelectorManager$ManagedSelector.processKey(SelectorManager.java:636) [tritonis-shaded.jar:0.0.1-SNAPSHOT]
! at org.eclipse.jetty.io.SelectorManager$ManagedSelector.select(SelectorManager.java:607) [tritonis-shaded.jar:0.0.1-SNAPSHOT]
! at org.eclipse.jetty.io.SelectorManager$ManagedSelector.run(SelectorManager.java:545) [tritonis-shaded.jar:0.0.1-SNAPSHOT]
! at org.eclipse.jetty.util.thread.NonBlockingThread.run(NonBlockingThread.java:52) [tritonis-shaded.jar:0.0.1-SNAPSHOT]
! at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635) [tritonis-shaded.jar:0.0.1-SNAPSHOT]
! at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555) [tritonis-shaded.jar:0.0.1-SNAPSHOT]
! at java.lang.Thread.run(Thread.java:745) [na:1.8.0_51]

这是七牛 API 调用出现问题,通过应用 Metrics API 检查 Threads:

http http://A-DOMAIN:8351/threads | sort | uniq -c | sort -gr G qiniu

正常的实例:

异常的实例:

确认是七牛服务慢导致现成爆掉,但是 46 这个值来自于哪里?

检查了 App 的配置文件,的确配置了 maxThreads 为 50:

server:
  gzip:
    enabled: false
  requestLog:
    appenders: []
  maxThreads: 50
  applicationConnectors:
    - type: http
      port: 8350
      acceptorThreads: 2
      selectorThreads: 2
  adminConnectors:
    - type: http
      port: 8351

一般对应服务不稳定,有个简单策略,超时,那么七牛服务超时是多少?找了一下相关代码,下巴都没合上:

# com.qiniu.http.Client
    this.httpClient.setConnectTimeout((long)Config.CONNECT_TIMEOUT, TimeUnit.SECONDS);
    this.httpClient.setReadTimeout((long)Config.RESPONSE_TIMEOUT, TimeUnit.SECONDS);

# com.qiniu.common.Config
public static int CONNECT_TIMEOUT = 10000;
public static int RESPONSE_TIMEOUT = 30000;

超时时间 8h,吓得我赶紧关掉了显示器。 赶紧将这个 Connection 改成了 2 分钟,Connect 改成了 5s。

咨询了七牛的工程师故障当天的状况:

sorry 忘了回复,问题已经定位,早上因为有个别用户list请求数量过大,导致了整个list接口出现大量满请求,影响返回是list接口的调用 和 portal上内容管理界面列取文件key的速度,目前已经恢复

大量慢请求,这个已经内部有告警和恢复机制,11点25恢复的

国内一线的云服务供应商也会出这样不可用故障,即便是有服务放在云上,还是要自己留一个心眼,多关注可用性啊。


原文链接: https://blog.alswl.com/2016/02/a-timeout-fault/
欢迎关注我的微信公众号:窥豹

3a1ff193cee606bd1e2ea554a16353ee
alswl's avatar

一次艰难的 Wiki 升级

公司使用 Confluence 管理自己的知识库, 现在使用的版本还是 3.0.1,而最新的 Confluence 版本已经是 5.4+。 新版本加入的一些现代化 Web 系统的新特性很吸引人(拖拽上传,可见即所得编辑), 在群众的强烈呼声下,我着手开始升级。

官方的升级路线很扯,3.0.1 的升级路线是:

  • 3.0.1 -> 3.5.17
  • 5.0.3 -> 5.4.4

中间两次大版本升级,第一次原因不明,第二次是更新了 markup 渲染引擎, 改为 HTML 格式类型的渲染模式。

由于一些原因,我们系统还跑在 embedded 模式下(其实就是 HyperSQL),这种大版本升级, 需要先从内置库升级到外部数据库,比如 MySQL。

苦逼旅程就开始了。

From embedded to MySQL

更新内置库到外部库的操作流程:

  • 导出当前的数据备份,包括附件,我导出后 1G+
  • 使用当前同版本(3.0.1)安装一个全新的 wiki,注意下载 JDBC-connector
  • 安装之后,配置好 MySQL,开始导入之前准备好的备份
  • 悲剧上演

遇到了错误:

Import failed. Hibernate operation: could not insert: [com.atlassian.confluence.core.BodyContent#12028015]; SQL []; Duplicate entry '12028015' for key 'PRIMARY'; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '12028015' for key 'PRIMARY'

官方文档 https://confluence.atlassian.com/doc/troubleshooting-xml-backups-that-fail-on-restore-199034.html 让修改重复键数据,好吧,我改,搜索一把重复主键,将备份里面的 entities.xml 弄出来。

# 格式化
awk '{s=s $0}END{gsub(/></,">\n<",s);s=gensub(/>([^ \n>]*)</,">\n\\1\n<","g",s);print s}' entities.xml > entities.xml.format
# 找重复主键 cat entities.xml.format G 'content" class="Page' -A 2 G -E '[0-9]+' | sort | uniq -c | sort -gr L cat entities.xml.format | grep 'name="id"' -A 1 -B 1 | grep -E '[0-9]+' -B 2 L

操作过程中,发现有数据就一条数据(grep entities.xml),还是插入重复(13238835)。 官方文档解释是,内置数据库的锁有时候会不灵,插入重复键。 于是决定再试试去掉主键约束方案,大不了那个数据我就不要了。

ALTER TABLE BODYCONTENT DROP PRIMARY KEY;

结果还有其他 PK 约束,我于是一条一条解开,然后……还是不行,真是作了一手的好死。

结论是,这数据错误了太多,已经无法手工修复。

横插一刀的 Emoji 😊😢💗

导入时候报了这么一个错误:

Caused by: java.sql.SQLException: Incorrect string value: '\xF0\x9F\x8C\x8D\xE5\x9B...' for column 'BODY' at row 1 org.xml.sax.SAXException: Error while parsing 2015-10-19 23:14:13,108 ERROR [Importing data task] [confluence.importexport.impl.ReverseDatabinder] fromXML Error processing backup: -- referer: http://10.1.2.155:8087/setup/setup-restore-start.action | url: /setup/setup-restore-local.action | userName: anonymous | action: setup-restore-local org.xml.sax.SAXException: Error while parsing net.sf.hibernate.exception.GenericJDBCException: could not insert: [com.atlassian.confluence.core.BodyContent#12028161]

这是 Emoji 编码的问题,理论上 MySQL 换到 5.6+,更新 encoding 就可以了。

但是……Confluence 的建表 SQL 爆出了 255 varchar 超过 1000 限制的错误 ,我尝试使用 innodb_large_prefix 似乎可以解决(因为重复键的问题,导致导入已经行不通)。

另外 innodb_large_prefix 是 5.6.3 才有的,只能升级 MySQL, 并且需要创建表时候使用 DYNAMIC 参数。

弄个 Emoji 这么绕,这导致我直接弃用了 MySQL。

如果是正常迁移,不遇到重复键,Emoji 的问题,可以参考官方的文档,完成平滑迁移:

  • https://confluence.atlassian.com/doc/migrating-to-another-database-148867.html
  • https://confluence.atlassian.com/doc/database-setup-for-mysql-128747.html
  • https://confluence.atlassian.com/doc/upgrading-confluence-4578.html
  • https://confluence.atlassian.com/doc/upgrading-confluence-manually-255363437.html
  • https://confluence.atlassian.com/conf56/confluence-user-s-guide/creating-content/using-the-editor/using-symbols-emoticons-and-special-characters

妈蛋,自己干

上面这么点东西,陆陆续续花了我两周的时间(晚上)。已经确认走不通平滑迁移,那就别怪我手段糙了。

使用 API 导出后直接导入,这种做法最大问题是不平滑,会丢掉 Wiki 修改的历史记录, 在和各个业务方沟通之后,最后达成了一致:可以暴力升级。

升级流程:

  • 准备最新 Confluence 新站点
  • 关停站点
  • 导出数据,包括 Page、评论、附件
  • 导入 Page,评论,附件
  • 启动旧站点,开启只读模式
  • 启用新站点

官方有一个 Universal Wiki Converter, 我在 Bitbucket 上面找到了源码,但是已经不可工作了。 虽然宣称「The UWC will however save you 1-2+ weeks of scripting development time, compared with starting from scratch, for many of the most common conversion cases.」 但并没有卵用。

不行就自己随便搞搞好了,看了一下开发需要的 Conflunce API, 和尤其贴心的新版本 RESTful API,就开始搞了。

写迁移代码,在这里 atlassian-confluence-xxoo,已经开源了,只使用过一次,成功的从 3.0.1 迁移到 5.4.4, 理论上,支持任意版本的 3.x/4.x Confluence 迁移到最新。

使用 python app.py -h 查看帮助,不行就看看代码。

希望有迁移需求的同学,搜索到这里能够获得一些帮助。


原文链接: https://blog.alswl.com/2016/01/confluence-upgrade/
欢迎关注我的微信公众号:窥豹

3a1ff193cee606bd1e2ea554a16353ee
alswl's avatar

SS with Haproxy

以前用自己的 SS,Linode 美国,后来 Linode 日本,但是始终拼不过上海电信的国际带宽。 经常不稳定,丢一半的包。

于是买了 SS 服务, 9 台服务器,自己挑觉得速度快的服务器。

但一直固定某台服务器也会偶尔出问题,导致邮件出不来,网页打不开。 需要手动切换一下服务器。 于是用 HA 做了一个本地代理,调整了一些参数,让 SS 总是有快速的服务器供选择。

结构:

+-----------------+                                                  +----------------+
|                 |                                                  |                |
|    Server 1     |>>>>v                                           >>|   Mail.app     |
|                 |    v                                           ^ |                |
+-----------------+    v                                           ^ +----------------+
                       v                                           ^
+-----------------+    v    |----------------+      +------------+ ^ +----------------+
|                 |    v    |                |      |            | ^ |                |
|    Server 2     |>>>>>>>>>|    HAProxy     |>>>>>>| SS-Client  |>>>|   Browser      |
|                 |    ^    |                |      |            | v |                |
+-----------------+    ^    +----------------+      +------------+ v +----------------+
                       ^                                           v   
+-----------------+    ^                                           v +----------------+
|                 |    ^                                           v |                |
|    Server 3     |>>>>^                                           v>|   Evernote...  |
|                 |                                                  |                |
+-----------------+                                                  +----------------+

配置:

global
    ulimit-n  4096


defaults
    log global
    mode    tcp
    timeout connect 1s
    timeout client 1s
    timeout server 1s


listen stats
    bind 127.0.0.1:12222
    mode http
    stats enable
    stats uri /
    stats refresh 8s


listen ss
    bind 127.0.0.1:1081
    mode tcp
    option tcpka
    #balance source
    balance roundrobin
    log global
    maxconn 1024

    timeout connect 200ms
    timeout client 600s
    timeout server 600s
    timeout check 80ms  # for office / home
    # timeout check 400ms  # for starbucks
    retries 1
    option redispatch
    option tcp-check

    server s1 host1:port maxconn 20 check inter 2s rise 30 fall 6 backup
    server s2 host2:port maxconn 20 check inter 2s rise 30 fall 6
    server s3 host2:port maxconn 20 check inter 1s rise 60 fall 6

挺稳定,很快速。

update: 2015-12-15,添加 backup 项,选一台最稳定的做 backup,避免所有连接都超时。 update: 2015-12-13,添加 redispatch / retries 项,换机器重试, 大幅提高可用性,注意,可能在非幂等状态下面产生未知错误。

在跑的 node,有些延迟高,被干掉了。

看 1080P 也挺顺畅。


原文链接: https://blog.alswl.com/2015/11/ss-with-haproxy/
欢迎关注我的微信公众号:窥豹

3a1ff193cee606bd1e2ea554a16353ee
sunjw's avatar

fHash 2.0.0 for Mac OS X

fHash 2.0.0 for Mac OS X is available on Mac App Store now!

fHash is an open source files’ hash calculator for Windows and Mac OS X

  • MD5, SHA1, SHA256 and CRC32.
  • Drag & drop.
  • Integrated with context menu in Explorer/Finder.
  • Support multiple files.
  • Support 10.10 and later Mac OS X.
  • English and Simplified Chinese UI.

The icon “希” is from hash in Chinese “哈希”.
Thanks for Trend Micro Mobile Club’s support.

Project Site https://github.com/sunjw/fhash

LICENSE
GPL 2.0 for codes hosted on GitHub.
May use other licenses for binary package on other distribution sites.





sunjw's avatar

JSToolNpp 1.16.10 Released

What’s New in JSToolNpp:

  • 1.16.10
    • Fix configuration saving bug.
  • 1.16.8
    • Fix regex related bug.
  • 1.16.6
    • Fix negative number bug.
    • Fix some expression format bug.
  • 1.16.5
    • Fix regex detection.
    • Fix some crash bugs.

Project site:
http://www.sunjw.us/jstoolnpp/

Download links:
SourceForget.net

jaseywang's avatar

Can Venta Airwasher Effectively Reduce PM2.5 or PM10?

There is not official tests state that whether Venta airwasher is able to reduce PM2.5 or PM10 effectly, hence, I made some tests with the help of Dylos air particle counter during the good weather, PM2.5 index less than 100 means good or moderate, PM2.5 larger than 100 which means unhealthy or even hazardous. My bedroom's door or window is closed all the time and it's a confined space.

The answer is partially efficient. In centain conditions, it works, other times, no.

When the PM2.5 outside is less than 100, Venta can effectly keep the indoor PM2.5 around 30 or even less.

However, when outside is unhealthy, which means the index is above 100 and beyond, it really can't effectly reduce the particles inside, and the metrics I got from Dylos indoor have positive correlation with outside index, sometimes, Dylos even get 100 or even more which is totally unacceptable for people in the bedroom.

Now, the answer is quite clear, if the weather outside is good, just keep the Venta open and it can handle. When outside is unhealty, you really need to turn on your air filter, and don't rely on it, you need HEPA filter.

What about PM10? Venta seems work all the time, no matter good or bad outside, it can keep the index under 100 or lower, most of time, my bedroom PM10 is around 50, sometimes, after long time filter without opening the door, you can see single digit.

alswl's avatar

几步拥有一个安全密码

给团队非开发同学写的邮件,对其他人也有些意义,遂贴出来。


这个互联网越来越不安全 https://www.baidu.com/s?wd=%E5%AF%86%E7%A0%81%E6%B3%84%E9%9C%B2%E4%BA%8B%E4%BB%B6。密码数据库泄露,黑客暴库攻击,社会工程学攻击层出不穷。我给大家介绍几个小方法,轻松提高自己各类密码的安全等级。

先给个地址,大家可以测试一下自己常用密码的复杂度:https://howsecureismypassword.net/

认为自己电脑水平还可以的,请直接翻到文章最后。

如何管理密码

给普通用户的建议:

  • 密码设置复杂一些,不要使用生日、日期、姓名等有意义的信息
  • 使用一套合理的密码生成策略

重点来了,密码生成策略:

  • 选择自己喜欢的诗词或者某句话,比如「床前明月光,疑是地上霜」,取其拼音的第一个字母 cqmygysdss
  • 将每个句子第一个单词大写,cqmygysdss -> CqmygYsdss
  • 加上对应网站的信息,比如 163:CqmygYsdss163,qq: CqmygYsdssqq

大功告成,你的密码安全级别提升了。

----------------------------- 高手的分界线 -----------------------

给高水平选手推荐的方案:

  • 核心思想:密码分级,分离普通密码、重要密码(财务相关等)。
  • 普通密码使用大小写英文和数字混合
  • 重要密码和财务相关密码,独立生成密码,保存到 1Password / KeePass 等工具中,每个应用软件独立开来。
  • 推荐 KeePass,免费,参考 http://www.iplaysoft.com/keepass.html

常见弱密码和暴力破解需要时间

  • 123456 / 不需要
  • duitang / 2s
  • huhela1993 / 10天
  • huhela_1993 / 48年
  • CqmygYsdssqq / 3千年
  • ]W?852HCMHFUYzrz,F / 10000^5 年(Keepass 生成的密码)

最后

针对任何在邮箱中向大家索取:

密码、员工信息、通讯录、组织架构等的邮件,请大家注意确认对方的真实身份,不要轻易透露重要信息。


原文链接: https://blog.alswl.com/2015/10/a-security-password/
欢迎关注我的微信公众号:窥豹

3a1ff193cee606bd1e2ea554a16353ee
alswl's avatar

怎么打日志

需要解决的问题:

  • 业务日志打太乱,没有位置和格式约束
  • 什么情况打日志,如何避免滥用,提高日志作用

打日志最佳实践

Dropwizard 列出的打日志原则:

Be human readable.

Be machine parsable.

Be easy for sleepy ops folks to figure out why things are pear-shaped at 3:30AM using standard UNIXy tools like tail and grep.

eg.:

TRACE [2010-04-06 06:42:35,271] com.example.dw.Thing: Contemplating doing a thing.
DEBUG [2010-04-06 06:42:35,274] com.example.dw.Thing: About to do a thing.
INFO  [2010-04-06 06:42:35,274] com.example.dw.Thing: Doing a thing
WARN  [2010-04-06 06:42:35,275] com.example.dw.Thing: Doing a thing
ERROR [2010-04-06 06:42:35,275] com.example.dw.Thing: This may get ugly.
! java.lang.RuntimeException: oh noes!
! at com.example.dw.Thing.run(Thing.java:16)
!

splunk 的最佳实践:

  • 使用清晰的键值对:key1=val1, key2=val2
  • 对开发者易读
  • 全部添加时间戳
  • 使用唯一标记,比如 user_id, transaction_id
  • 使用文本
  • 使用开发者易用格式,比如 JSON
  • 尽可能多加一下数据
  • 标记调用来源,比如方法名,类名
  • 将多行事件拆分

除了一些浅显易懂的原则,还是 dropwizard 的三条原则和解决方案靠谱。 但是两篇文章都没有告诉如果在复杂系统里面记录有用的日志,打日志生命周期是怎样的。

既然找不到,我就就自行想想如何打有意义的日志。

日志不怕多,而是怕繁杂难搜索,产出的数据无意义难追踪问题。所以最关键是找到一个合理通用的方式组织起来即可:

  • 按照模块名 com.duitang.service.module.aaa 打日志,比如 com.duitang.service.module.aaa.log
  • 一个模块一个日志,模块复杂之后,可以拆分,com.duitang.service.module.aaa.core.log / com.duitang.service.module.aaa.query.log,其实在这个时侯,这个模块本身由于复杂性也会面临拆分。
  • 不分离 error / info 日志,放到一个文件,通过 grep 或者日志工具分离
  • 日志文件一定要 rotate,磁盘存储固定时间 N 天,远程文件存储固定时间 M 天,TTL 删除。

补充一个日志常见使用场景:

  • 外部资源调用
  • 状态变化
  • 系统入口和出口
  • 业务异常
  • 非预期执行

参考文档:


原文链接: https://blog.alswl.com/2015/10/how-to-log/
欢迎关注我的微信公众号:窥豹

3a1ff193cee606bd1e2ea554a16353ee
jaseywang's avatar

Metrics Dashboard Comparision for Linux Desktop

I need to know how good/poorly my desktop(Loongson & Rasberry Pi 2) is running, so there should at least exits a solution that can see its realtime and history metrics of the the system like cpu, memory, network, disk io etc.

Also tools like Glances, nmon, sar(sysstat) or atop provide a nice realtime metircs, it can't get its history data easily which it more suitable for massive production use, also no web dashborard for a nicely view, so I just pass them. Systems like Icinga, Nagios, Graphite, Zabbix, etc. are so heavy and complicated which are also not convenient for a 2GB memory desktop use.

Below are some tools that may help you for setting up a tiny to small monitoring/metrics system. At the end, I'll pick up the most suitable one for my own.

psdash
A dashboard overview of the system using psutils and Flask.

The installation is quite straightforward with pip packager manager. Unfortunately, As my Debian jessie testing version, I happened to a "PROTOCOL_SSLv3 is not defined" bug that stop the system running, you can modify the python gevent lib to work around(1, 2).   

The web dashboard is qute lighweight and nice, you can get the realtime data from the every-3-second refresh web automatically, the fatal problem is it can't get the history data and charts.

Linux-dash 
Quite similar to psdash, but comes with more technical stack like Node.js, Go, and PHP. It's offical slogon is "A simple, low-overhead web dashboard for GNU / Linux. (~1MB)", besides that, not much eye-catching point.

ezservermonitor-web 
Besides all the feature psdash and Linux-dash, it also has a simple trigger configuration, Say, for the load average metric, it will be displayed with gauges, when is less than 50%, it's green, when is more than 76% util, it turns to red. Also, it has the built-in ping and servers check function, which is handy for your family internal use.

Ezservermonitor also has a console based tools called EZ SERVER MONITOR`SH, without web interface.

Web VMStat 
It’s a small application written in Java and HTML which displays live Linux system statistics. It just takes over vmstat command in a pretty web page with SmoothieCharts and websocketd in realtime.

munin 
The last is usually the best. Yes, it's the only one I want to recommend for you, no matter how many desktops you have, Munin can handle them easily and more importantly, it only taks you few minutes depending on your network quality if you use apt/yum to install it directly, the real out-of-box product. it used RRD as it's backend storage engine. by default, 300s interval which I think it's enough for most of desktop users, with at one year history. 

If you are unsatisfied with its hundreds of plugins, just write a new one or porting from any Unix platform by yourself by any scripts language. This is its killer feature.

After the above comparision, you could choose ones according to your demands.

Want near-realtime(5m or so) and history data with charts? Munin is a best option.

Want realtime data, without history data? psdash, Linux-dash, Web-VmStat are those you're looking for.

Still not satisfied? You may consider using Graphite, Zabbix if you have system admin experience, since both are enterprise level open source product.

alswl's avatar

Redis 集群扩容

几乎每一个网站都需要用户登录状态系统,其中核心是存储 Session 的用户登录状态存储系统。 主流的实现之一是使用 Redis 存储用户登录信息,Redis 特点是功能简单、无依赖、 存储结构丰富、有持久化功能。 我大堆糖的 Session 存储系统也正是基于 Redis。

可是 Redis 也存在一些问题,比如 Redis 自身没有 Sharding 功能,Replication 也是在逐步完善完善过程中 (2.4 支持 Replication,2.8 加入 Replication partial resynchronization 功能)。 纵观当下流行的 DB 系统,哪个不是自带这两个特性,这两个分布式特性应该成为新出产的 DB 系统的标配。 而且作者还经常发布延期,放烟雾弹,不知道 Redis 自带 Sharding 特性要等到何年马月。

随着业务规模的扩大,单台 Redis 实例不能满足需求。 考虑到 Redis 也是久经考验的战士,替换掉他成本比较高,那就对 Redis 进行扩容。

扩容的基本要求是:

  • 扩大系统容量,成为分布式系统,未来有横向扩展
  • 业务不中断
  • 保证原始数据的可用性

Google 了一下,有两个项目可以参考: https://github.com/idning/redis-mgr 和豌豆荚的 Codis

研究了这两项目的代码之后,发现前者存在几个问题: 需要停机进行操作。 后者提供了完整一套解决方案,Server/Proxy/Config Manage,对我这次迁移来说,太重了, 而且项目比较新,风险高,只能用来参考实现方法。

最后我决定参考 redis-mgr 的方案,然后使用两种方式同步数据: 系统运行中打上 patch 完成数据的动态迁移;后台跑迁移数据脚本。

方案的关键词

dump / restore / pttl

核心的操作流程是: 使用 dump 命令导出数据,restore 命令恢复数据,pttl 命令获取设置 TTL。

Presharding

Redis 官方没有 sharding 方案,但提供一种策略 presharding。 Redis 作者写了一篇Redis Presharding。核心是: 提前做 2^n 个实例,避免扩容时候数据迁移,一般使用 2^n 个实例, 目的是为了能够自然地乘以 2 进行拆分。 这些实例可以分开放,也可以放在同一台机器上面。

我这次操作,将 OLD_CLUSTER 的一个实例拆分为了 NEW_CLUSTER 的 32 个实例, 跑在 4 台服务器上面。

Twemproxy

Redis 的 经典款 Proxy,用来实现对多个 Redis 实例 sharding, Twitter 出品,链接

一致性 Hash

传统的 Hash 方法是 hash(key) = value % nodes.length, 当加入、减少 Hash 节点时候,会导致 hash(key) 的全部失效。 典型的一致性 Hash 算法,Ketama 通过环状 node 分布,解决了这个问题。

Twemproxy 还提供 modula 和 random 两种分布式方案。

db 大小预估

Redis 只能查看整个实例内存尺寸。不能查看单个 db。 使用 ramdomkey 做抽样检查,取 1% key 抽样,估计单个 key 大小, 然后做 benchmark 估算操作性能,估算操作时间。

动态迁移数据

为了在迁移过程中保证服务可用,需要将数据兼容 Redis 集群 OLD_CLUSTER / NEW_CLUSTER, 业务代码必须同时支持访问两个集群,做法也很简单

  • 访问 NEW_CLUSTER
  • 有数据则继续操作,无数据则访问 OLD_CLUSTER,获取数据 DATA
  • 将数据和 TTL 保存到 NEW_CLUSTER

将这个逻辑封装成一个 client,替换掉原来 Redis Client 即可。

注意,这个点可能产生幻读,读取 key 和写入 key 有个时间差, 但是我处理的 session 是 immutable 数据,不会出现问题。 而如果将 Redis 用作 Persistence,就要评估对业务的影响了。

后台迁移数据

依赖用户访问来进行迁移,效率太低了,这个迁移时间和最长 TTL 时间相当, 需要主动将这个数据从 OLD_CLUSTER 迁移到 NEW_CLUSTER

方案也很简单,使用 randomkey 获取一批数据,然后按动态迁移数据方法进行迁移。

pipeline

如果在业务逻辑中使用了 PIPELINE,就会遇到问题,需要改写掉业务方案, 待迁移完成之后,再进行恢复。

multikey

mget / mset 等多键操作方法需要注意拆解 key,然后一一 dump / restore / ttl

正式操作

线上操作的数据:

# 0.483 g db0
12:05:19,444 - __main__ - INFO - v, dumps keys 1014/1375371/..., time: 974.183676004
# 4.552 g db1
17:38:36,422 - __main__ - INFO - v, dumps keys 1392/7711834/..., time: 3076.41647506

原文链接: https://blog.alswl.com/2015/07/redis-migration/
欢迎关注我的微信公众号:窥豹

3a1ff193cee606bd1e2ea554a16353ee
alswl's avatar

带理想的执行者 - 柳比歇夫的一生

作为战斗民族的俄罗斯民族,不但能在热带风暴级的灿鸿中进行正常起降, 历来也盛产各种奇葩人物。 最近我有看到一本描述一个科学家的如何生活的书,叫「奇特的一生」。 让人拍案称奇。

主人公是一位名叫柳比歇夫的科学家,想必他在「回首往事时候没有因为虚度年华而悔恨」, 因为他将自己的一生都精确的奉献到分类学、地蚤研究上面。 他的工作投入,不是单纯激情洋溢投入,而是精确到分钟级别的投入, 是奉献完整一生的投入。

除了学术上面的成功,他的时间记录法也很牛逼,甚至让苏联科学院进行研究。怎么描述他的牛逼呢?如果他生活在今天,大致会这样写:

今天我 19:00 - 19:25 看了新闻联播,感受到社会各阶级在党的领导下面获得令人振奋的成绩

19:25 休息了一会,避过无聊的天气预报时间

19:30 - 20:30 学习了「XXX 的讲话精神研究」

附加工作:20:35 - 20:40 小解,顺便刷了一会朋友圈作为今天的娱乐放松,评论了隔壁老王老婆的出行照片

看到没有,他精确的记录了自己的时间使用记录,犹如脑子里面有个精确的 GTD 管理器,那个年代 David Allen 还没出生呢,肯定也谈不上了解 Get Things Done。苏联真是靠各类牛逼人物撑起了一战、二战时候工业大跃进和各种黑科技的崛起。

简单记录时间当然不能带来效率和成果的提升,柳比歇夫明显没有纳什这种神经病天才的聪明,他能获得这样比较高的成就,必须还有真正的大招。

第一招是他有坚定的奋斗目标:「创立生物自然分类法」。我们暂不管这不出名的法则是什么,能在 1918 年(他时年 28 岁,我擦,和我现在差不多大啊)就提出自己一生奋斗的目标,着实了不起。 从书中的记录来看,28 岁的青年教师柳比歇夫, 在日记里面描述他想要做什么事情,需要具备什么素质, 并且在估算重要里程碑时间点。

我估摸着,在战争中成长起来的人,不仅仅需要幸运,还需要勇气和坚韧意志, 19 世纪初年,沙皇政权摇摇欲坠,布尔什维克们应该还在密谋革命造反, 民众生活艰苦(感觉好像民国时期的中国)。 这种时代的大环境造就了各阶级人物命运的跌宕起伏,附带效应是出产优秀的文人和思想家。 柳比歇夫既然在这个时期成长,那也会渴望建树功勋,做点改变世界的事情。

第二招是,他就去做了。

「听过了很多大道理,却依然过不好这一生」,无非是缺在执行。 柳比歇夫的时间记录法,不是简单作为一个日记本在使用, 关键点在于回顾和计划。通过过去的记录,分析自我,总结经验,判断未来。

哎哟,道理说起来都简单,做起来都难,柳比歇夫作为一个很好的例子, 告诉我们,一个资质普通的人,决定牺牲自我,奉献到某个特定事业时候, 可以获得的成绩,他完成了「立言」。

在这个物欲横流,信息多得炸掉脑仁,各类媒体都在抢占眼珠的时代,能够不忘初心, 去做点实际的事情,是一件不容易的事情。 对快 30 的我来说,别说「立言」,就光「立命」, 就已经让我困苦不堪,在这和平年代,没有家仇国恨美国梦,想要做点牛逼的事情,就得需要勇气和爱来驱动。


原文链接: https://blog.alswl.com/2015/07/liu-bi-xie-fu/
欢迎关注我的微信公众号:窥豹

3a1ff193cee606bd1e2ea554a16353ee
jaseywang's avatar

Router Matters

We are transfering PBs of our HDFS data from one data center to another via a router, we never thought the performance of a router will becomes the bottleneck until we find the below statistic:

#show interfaces Gi0/1
GigabitEthernet0/1 is up, line protocol is up
  Hardware is iGbE, address is 7c0e.cece.dc01 (bia 7c0e.cece.dc01)
  Description: Connect-Shanghai-MSTP
  Internet address is 10.143.67.18/30
  MTU 1500 bytes, BW 1000000 Kbit/sec, DLY 10 usec,
     reliability 255/255, txload 250/255, rxload 3/255
  Encapsulation ARPA, loopback not set
  Keepalive set (10 sec)
  Full Duplex, 1Gbps, media type is ZX
  output flow-control is unsupported, input flow-control is unsupported
  ARP type: ARPA, ARP Timeout 04:00:00
  Last input 00:00:06, output 00:00:00, output hang never
  Last clearing of "show interface" counters 1d22h
  Input queue: 0/75/0/6 (size/max/drops/flushes); Total output drops: 22559915
  Queueing strategy: fifo
  Output queue: 39/40 (size/max)

The output queue is full, hence the txload is obviously high.

How awful it is. At the beginning, we found there were many failures or retransmissions during the transfer between two data centers. After adding some metrics, everything is clear, the latency between two data centers is quite unstable, sometimes around 30ms, and sometimes reaches 100ms or even more which is unacceptable for some latency sensitive application. we then ssh into the router and found the above result.

After that, we drop it and replace it with a more advanced one, now, everything returns to normal, latency is around 30ms, packet drop is below 1%.

jaseywang's avatar

Trips to HCMC, Nha Trang, Phnom Penh and Siem Reap

Last month, Kiki and I went for a vacation, here are some experience and tips.

For HCMC, it’s quite modern, as invaded by France before, it still exits a lot culture from France, like iced coffee and French baguette, both are delicious and cheap. HCMC is also famous for its French cuisine, we went to one of the best French restaurants in HCMC, but not so impressed, the only impression is so many ants on the desk, maybe we don’t know how to enjoy French cuisine.

Later, we flew to Nha Trang, the beach is fancy, we spent a few nights in Sheraton, it’s location is really good, walking distance to most of the restaurants, and Lanterns restaurant is one of the best we ever met, authentic Vietnamese cuisine, we went there many time, from breakfast, launch to dinner. We were too busy eating and drinking to enjoy the free beach chair, umbrella and the best public beach sections in Nha Trang provided by hotel, later we regretted. After that, we went to mia, the so called best resort in Nha Trang, generally speaking, its still better than others, but didn’t give us as much as we expected. It’s location is far aways from city center, and nothing when step out of the resort, so you need to take the shuttle bus to go into the city or just stay in the resort, enjoying its private beach. I had to say, the beach here is even better than those in the city, since the whole resort only holds about two dozens of customers, the beach is quiet and clean at any time, you can play canoe and surfing, but the food here is just the average level with top price. For drinks, they have happy hours, buy one get one free, so Kiki and I ordered 4 cups of mojito, watching the night sea view, till midnight, nobody but we two, totally drunk.

As saied before, the coffee here is wonderful and price is reasonable, so just pick up one when you need a rest. Also the mongo shake is worth trying, we tried dozens cups of shake during the staying in Vietnam, even later in Cambodia, we still ordered the same, most of them are between $1 to $2, enjoy. So many Russians, there are probably three languages in the city, Vietnamese, English and Russian.

We had a terrible experience while taking the Mai linh taxi. The first day when we arrived at HCMC airport in the midnight due to flight delay, we halled a Mai linh, and told him by meter, but when he took us to the hotel, he forced us to pay 100, 000VND, which was 30RMB, just a 5 minutes tour, we tried to explained to him, the only word he said is one hundred, well, we surrendered. Later, we only hall vinasun taxi, which was quite fair, by meter by default.

Cambodia, the country is still less developed, and the infrastructure is quite poor. We went through a big storm on 18 May, then came to power outage. Luckily, our hotels equipped with a power generator, and didn’t have much effect for us. Though May is between dry and rainy season for Cambodia, we went through some rain almost every afternoon, usually lasted for less a hour, quite fresh after rain.

The traffic in Cambodia is better than Vietnam, cuz not so many motorcycle, As for eating, I have to say, there is really not much to choice, the taste is really weird, and the sanitation conditions also make us upset. At last, we went to Lotteria to make us alive, which is in sorya shopping center. Most of its food is imported from other countries, like Thailand, Vietnam and China, the local food is really limited, only some honey and pepper. when you walk around in Lucky market, maybe the best market in Cambodia, it’s pricy even compared to some high end markets in Beijing.

Although Cambodia cuisine is not my cup of tea, there is a special sauce that really makes me high, I never taste that before, it’s called kampot pepper sauce, kampot pepper it one of the best peppers in the world.  The recipe is quite simple,  just mix kampot pepper with green lemon, sugar and salt. It makes you feel cool in hot summer, especially when eating with BBQ. we had this sauce several times later in Siem Reap.

We bought Mekong company ticket from HCMC to PP, sat on the rear, the last row, air conditioners broken, hot to die. when arriving at mod bai, the checkpoint at Vietnam border, it’s really chaotic compared to Bavet which is the Cambodian border entry passport checkpoint. What you need to do is to pay $30 to get the e-visa with extra $5 as the service fee for the bus steward when getting on the bus, no security check at all. Later when we prepared to buy the ticket from PP to Siem Reap, we bought from Giant bus company, a little pricy, but I assure you, worth it, don’t save a small bucks, you have to sit on the bus for almost 7 hours.

Note that, people in Cambodia don’t accept old 100USD, even with a little creases and tears, so give them brand new 100 USD.

We chartered a tuk-tuk when circling around Angkor Wat for 2 days. After some discussion, we decided to take the grand tour first which was quite far, about 30 kilometers, the view on the way is beautiful, the typical tropical scene. when we arrived at waterfalls, due to it was dry season, no water at all. But we happened to see the guard on the top of the mountain, we said hi since he was the only one we can talk, he was quite nice and led us to show some carving on the surface and its history. The second day, we took the classic route, grand and peaceful, worth the money to buy a 3-day pass ticket.

The driver is the one who picked up us when arriving at the bus stations in Siem Reap, he was quite nice, introduced us a lot history about Angkor. We underestimated how much water we need for the whole day, so we just took a 1.5L bottle of water with us, fortunately, the driver prepared a iced box with bottles of water in his tank under the seat, or we may get a sunstroke.

Last day, we went to a shooting range, about 40 minutes by taking the tuk-tuk. We fired some AK-47 and M16. Cambodia may be the only country for tourists to fire a RPG and other heavy weapons, so, don’t miss that, once in a life time opportunity. One thing to keep in mind is that the country is quite corrupted, you can get the direct feeling when crossing the custom check or taking pictures in Angkor Wat, so just don't give a shit to them, and pretend not to understand what they are saying. One reason of this is they are spoiled by Chinese tourists, so more and more dollors go to their pockets.

We took nothing back but a bag of kampot black pepper corn, so fragrant and fresh.

jaseywang's avatar

How Many Non-Persistent Connections Can Nginx/Tengine Support Concurrently

Recently, I took over a product line which has horrible performance issues and our customers complain a lot. The architecture is qute simple, clients, which are SDKs installed in our customers' handsets send POST requests to a cluster of Tengine servers, via a cluster of IPVS load balancers, actually the Tengine is a highly customized Nginx server, it comes with tons of handy features. then, the Tengine proxy redirects the requests to the upstream servers, after some computations, the app servers will send the results to MongoDB.

When using curl to send a POST request like this:
$ curl -X POST -H "Content-Type: application/json" -d '{"name": "jaseywang","sex": "1","birthday": "19990101"}' http://api.jaseywang.me/person -v

Every 10 tries, you'll probably get 8 or even more failure responses with "connection timeout" back.

After some basic debugging, I find that Nginx is quite abnormal with TCP accept queue totally full, therefore, it's explainable that the client get unwanted response. The CPU and memory util is acceptable, but the networking goes wild cuz packets nics received is extremely tremendous, about 300kpps average, 500kpps at peak times, since the package number is so large, the interrupts is correspondingly quite high. Fortunately, these Nginx servers all ship with 10G network cards, with mode 4 bonding, some link layer and lower level are also pre-optimizated like TSO/GSO/GRO/LRO, ring buffer size, etc. I havn't seen any package drop/overrun using ifconfig or similar tools.

After some package capturing, I found more teffifying facts, almost all of the incoming packagea are less than 75Byte. Most of these package are non-persistent. They start the 3-way handshake, send one or a few more usually less than 2 if need TCP sengment HTTP POST request, and exist with 4-way handshake. Besides that, these clients usually resend the same requests every 10 minutes or longer, the interval time is set by the app's developer and is beyond our control. That means:

1. More than 80% of the traffic are purely small TCP packages, which will have significant impact on network card and CPU. You can get the overall ideas about the percent with the below image. Actually, the percent is about 88%.

2. Since they can't keep the connections persistent, just TCP 3-wayhandshak, one or more POST, TCP 4-way handshake, quite simple. You have no way to reuse the connection. That's OK for network card and CPU, but it's a nightmare for Nginx, even I enlarge the backlog, the TCP accept queue quickly becomes full after reloading Nginx. The below two images show the a client packet's lifetime. The first one is the packet capture between load balance IPVS and Nginx, the second one is the communications between Nginx and upstream server.

3. It's quite expensive to set up a TCP connection, especially when Nginx runs out of resources. I can see during that period, the network traffic it quite large, but the new request connected per second is qute small compare to the normal. HTTP 1.0 needs client to specify Connection: Keep-Alive in the request header to enable persistent connection, and HTTP 1.1 enables by default. After turning off, not so much effect.

It now have many evidences shows that our 3 Nginx servers, yes, its' 3, we never thought Nginx will becomes bottleneck one day, are half broken in such a harsh environment. How mnay connections can it keep and how many new connections can it accept? I need to run some real traffic benchmarks to get the accurate result.

Since recovering the product is the top priority, I add another 6 Nginx servers with same configurations to IPVS. With 9 servers serving the online service, it behaves normal now. Each Nginx gets about 10K qps, with 300ms response time, zero TCP queue, and 10% CPU util.

The benchmark process is not complex, remove one Nginx server(real server) from IPVS at a time, and monitor its metrics like qps, response time, TCP queue, and CPU/memory/networking/disk utils. When qps or similar metric don't goes up anymore and begins to turn round, that's usually the maximum power the server can support.

Before kicking off, make sure some key parameters or directives are setting correct, including Nginx worker_processes/worker_connections, CPU affinity to distrubute interrupts, and kernel parameter(tcp_max_syn_backlog/file-max/netdev_max_backlog/somaxconn, etc.). Keep an eye on the rmem_max/wmem_max, during my benchmark, I notice quite different results with different values.

Here are the results:
The best performance for a single server is 25K qps, however during that period, it's not so stable, I observe a almost full queue size in TCP and some connection failures during requesting the URI, except that, everything seems normal. A conservative value is about 23K qps. That comes with 300K TCP established connections, 200Kpps, 500K interrupts and 40% CPU util.
During that time, the total resource consumed from the IPVS perspective is 900K current connection, 600Kpps, 800Mbps, and 100K qps.
The above benchmark was tested during 10:30PM ~ 11:30PM, the peak time usually falls between 10:00PM to 10:30PM.

Turning off your access log to cut down the IO and timestamps in TCP stack may achieve better performance, I haven't tested.

Don't confused TCP keepalievd with HTTP keeplive, they are totally diffent concept. One thing to keep in mind is that, the client-LB-Nginx-upstream mode usually has a LB TCP sesstion timeout value with 90s by default. that means, when client sends a request to Nginx, if Nginx doesn't response within 90s to client, LB will disconnect both end TCP connection by sending rst package in order to save LB's resource and sometimes for security reasons. In this case, you can decrease TCP keepalive parameter to workround.

alswl's avatar

2015 沪港 Hackathon

我想参加黑客马拉松很久了,去年就观战过 2014 沪港黑客马拉松。 但苦于需要连续两天时间,一直没有下定决心参加。

上周末本来安排了两天的团队会议,后来临时取消, 给了我充足的时间。于是我在 27 号周五下午,报名参加了 27 号晚上开始的 2015 沪港 Hackathon。

独自作战的我,在现场寻觅了几个小伙伴组了一个队伍,其中有 小草 / tevin / 女王 / Doris / n1k0。 小草负责树莓派服务端,tevin 负责传感器取数据,我……负责前端游戏逻辑,哈哈哈,我已经三年没有写前端代码了,为了团队职责分工,只能赶鸭子上架了。

经过现场的头脑风暴,我们最后选定了基于树莓派的游戏: 通过感应玩家的手掌移动距离来玩类似节奏大师的音乐游戏。

具体的游戏原理,可以在 基于树莓派的体感音乐游戏 看到, 源码也在 Github 这里。

最关键是,我们这支临时凑齐的战队,经过两天一夜的奋战,拿了一等奖(第二名),拥有了去香港参加国际赛资格和一堆乱七八糟的云服务器现金券、折扣券!!!撒花~~撒花~~

第一次参加 Hackathon,就能获得这个奖,觉得自己蛮幸运, 和队友合作愉快,和队友通宵到三点多调整音乐命中算法,然后在早上 6 点钟被冻醒。五音不全的我花了几个小时将超级玛丽五线谱转化为发音库能够理解的符号描述。

虽然最后由于时间原因不能和队友去香港参加比赛,但是仍然祝愿他们在香港能够继续加油。各位队友,Have fun。


原文链接: https://blog.alswl.com/2015/04/2015-hk-sh-hackathon/
欢迎关注我的微信公众号:窥豹

3a1ff193cee606bd1e2ea554a16353ee
alswl's avatar

读《遇见未知的自己》

「遇到未知的自己」这本书讲的是如何来认识自己,理解自己的情绪,和自己内心沟通。

豆瓣链接 / Kindle 版本购买链接

下面我写的倒不是什么读后感,而是我自己去解决自己焦虑和压力的一个探寻过程,希望对你有帮助。

不完美

最近一年来,我生活和工作状态持续处于亚健康状态。表现出来的状况是:工作上对产出的成绩不满意, 对自己不够自信,追寻的目标(创业)变得模糊和不可量化;生活受到影响,开始陷入到哪里去的困惑。 很久没有兴奋的迎着早晨第一缕阳光蹦着下楼梯,人变得焦虑和烦躁。

这种压抑,让我回忆起小时候周日晚上动画片的结尾曲,昏黄、萧瑟,似乎一切都走向终结。 哈,好在我不是那么消极的人,我热爱生活,期望创造价值,渴望别人的认同, 我不愿意自己长期陷入这种低潮。

我做了一些尝试和挣扎:给自己添加束缚(变成一个“工作日素食者”),计划自己工作和生活(践行 GTD), 意识到自己情绪抖动并尝试控制自己的情绪。 针对能力上面不足,我也去阅读了彼得德鲁克的系列丛书,柯维的「高效能人士的七个习惯」。 这些措施的确帮助了我,我觉得自己比以前更强大。 但是始终没有彻底解决我的问题,无法治愈我内心的恐惧和压抑。

我意识到:我在焦虑,继而恐惧、自卑,害怕面对未知,更害怕面对不完美的自己。

求解

周围能提供帮助的人不多,好在有互联网。第一个药丸在知乎,我直接搜索了关键词「自我」和「自卑」。 知乎关于认识自己有几个相当不错的问答,从中得到的观点有下列几个:

  • 真正的进步不是那么焦虑的自我怀疑,而是带着自我接纳体会进步的喜悦 via
  • 我们的一生就不断地在优越感和自卑之间切换 via

其中有若干个回答有提到「遇到未知的自己」,我也对这本书略有耳闻,于是从亚马逊上购买了, 没想到仅仅花了花了几个小时就一口气读完,很久没有这么舒畅的读完一本书了。

身体、情绪、思想

从书中收获最大的一点,就是和自己的内心进行沟通:通过运动来提高自己和身体的合一; 通过臣服来客观认同理解自己的情绪;通过冥想来和自己的潜意识沟通,了解自己的内心, 增强对自己和外部的觉察能力。

认识自己的一个重要途径是回溯,重新和过去的自己沟通。这种感觉很奇妙, 似乎在看另外一个生命。他慢慢长大,人生起伏,自信自卑,受到家庭的教育, 被师长激励,选择新的挑战,遇到新的困难和低谷,他的优柔寡断,他的努力和堕落, 他受到的爱护和痛苦。

到哪里去?这个问题我依然没有找到很好的答案,虽然有「爱、喜悦、和平」这个大一的概念, 但实在不够落地,就像「内圣外王」一样,太过于笼统。

即便到哪里去没有一个明确地答案,但是当和自己的内心的达成一致,能够坦然面对自己的不完美, 未来的不确定之后,不管往哪里走,都不是在原地转动。

现在我学会了运动,每周两次 5km 跑步,跑步时候感觉自己释放恐惧,追逐目标。 还在学会如何臣服自己的情绪,面对未知做的决策即便会不安和恐惧,也不至于影响自己太久。

糟粕

这本书里面有几段鸡汤文,什么「水知道答案」,女主回忆自己诞生过程而泪流满面, 显得很鬼扯。作为工程师的我,对这些内容表示质疑。

End

这本书不完美,但是它在一个合适的时间点给了我启发和帮助。我很感谢它,如果你有和我类似的困惑,建议去读一下。


原文链接: https://blog.alswl.com/2015/03/real-self/
欢迎关注我的微信公众号:窥豹

3a1ff193cee606bd1e2ea554a16353ee
sunjw's avatar

Build CyanogenMod 12 for Nexus 5

工作之后好久不写博客了,这里都要长草了。。。

之前写过一篇 Build CyanogenMod 10.2 for Nexus 4,现在手机换成 Nexus 5 了,系统也升级到 CM 12 了,写一篇 How to 总结一下。

1. 你还是要准备一台机器(虚拟机也行),装上 Ubuntu,Ubuntu 14 LTS 是个不错的选择,LTS 会有很长时间的软件更新支持。硬盘分的大一些,100GB 起步吧,内存 3G 起,其他随意。

2. 系统装好之后,按照 CM 官方的指导 How To Build CyanogenMod Android for Google Nexus 5 (“hammerhead”) 安装必要的软件。需要注意的是,编译 CM 12 需要 OpenJDK 7,所以安装时,将 openjdk-6-jdk openjdk-6-jre 换成 openjdk-7-jdk openjdk-7-jre,另外 Ubuntu 14 已经没有 ia32-libs 这个包了,需要安装 lib32z1 lib32ncurses5 lib32bz2-1.0 作为 32 位兼容运行时。

3. 之后就可以按照手册准备 repo,使用 repo init -u https://github.com/CyanogenMod/android.git -b cm-12.0 初始化 repo。

4. repo sync

5. breakfast hammerhead

6. 这里建议在 breakfast 之后再 repo sync 一次

7. Nexus 5 的私有库在这里有 https://github.com/TheMuppets/proprietary_vendor_lge,可以将其添加到本地 repo,打开同步好的源码目录,进入 .repo/local_manifests 目录,在其中创建 lge.xml,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<manifest>
  <project name="TheMuppets/proprietary_vendor_lge.git" path="vendor/lge" remote="github" />
</manifest>

之后再 repo sync 一次。

8. 到这里源码同步完成,可以 brunch hammerhead 啦。

需要注意的是,请使用多种方法科学上网。

's avatar

我的 MBP SSD 优化过程

嗯,这次的标题终于没那么长了 ;-)


给我的 MBP 换 SSD 已经是很久之前的事了,当时就已经折腾过一次优化了,不过没有相应的记录。最近发现了新装的 Yosemite 的一些老是搞不定而且连原因都不知道的问题(其中一个可以参见我在 AskDifferent 上的提问),一怒之下重装之,自然对 SSD 的优化也要重新搞一遍。搞的过程中发现有的手段能用,有的却不行,特此记录。

优化手段的主要来源是朋友的一篇博文及其中的链接。

注意:操作有风险,动手须谨慎哟。因为我是不(lan)会(de)做太多解释的,所以你一定要在操作前搞清楚那些个命令的作用哟,特别是像 rmmv 啦之类的危险命令哟,可别说我没提醒你哟 -_-

Firefox 缓存设置

虽然 Firefox 不是我的主役浏览器,但有时还会用用,姑且改之。总之就是打开 about:config 页面,修改如下设置:

  • browser.cache.disk.enable 改为 false
  • browser.cache.memory.enable 改为 true

这样 Firefox 就只会把缓存的东西都放在内存而不是硬盘(也就是 SSD)上了(应该吧)。

关掉根文件系统的「上一次读取时间」特性

「上一次读取时间」指的虽然是文件上次被读取的时间,但这个信息是会写到硬盘上文件的元数据中的。想想系统文件还不是整天被读过来读过去的,但这个时间信息又没什么用,果断禁之!以 root 身份创建(如果之前没有的话)并编辑文件 /etc/fstab,增加如下一行(或者修改原有配置,增加 noatime 挂载属性):

/dev/disk2 / hfs rw,noatime

其中的 /dev/disk2 自然要换成你的 SSD 对应的磁盘文件路径啦。

禁用冬眠模式

执行如下命令即可:

$ sudo pmset -a hibernatemode 0
$ sudo rm /var/vm/sleepimage

PS. 冬眠模式是为了在电池耗尽时能保存机器的运行状态,防止意外丢失数据的。一般认为电池耗尽这种情况很少会发生,毕竟当你发现电池快用光时肯定就会到处找插座了嘛。结果我最近偏偏碰到了一次,而且还是在插着电源的时候!一开始还怀疑难道我的电源线又挂了?!结果后来试着把电脑端反过来接就好了。真是莫名,看来这根电源线也大限将至么……不管怎样,有问题的时候电脑端的 LED 指示灯虽然亮着但是很暗几乎看不出来,难道这表示电压不足?有知道的人么?(不过首先,得有能看到我这篇博的人(泪奔

减少临时文件的读写

RAMDisk

我在试了网上的方法之后,发现如果用了 RAMDisk,则系统很快就会卡死。据陈同学说是 OS X 的 RAMDisk 实现有问题,嗯,所以就要想别的招了。

将临时文件目录移到机械硬盘分区

因为我是将……好吧其实我也不记得主硬盘位放的是哪个硬盘了,Any way :-),SSD 和原机械硬盘现在都被我挂着用,于是就可以考虑将那些个临时文件目录移到机械硬盘上,减少对 SSD 的读写。

网上说可以将 /private/tmp/private/var/run 两个目录挂载到 RAMDisk 上,那想必也是可以挪到其他地方的吧;不过经实践,只有前者可以,后者(至少用这里的软链接方式)会导致系统启动出错,只好作罢。

/private/tmp 移到机械硬盘上的方法:

$ sudo ditto /private/tmp /Volumes/your_hdd_name/private/tmp
$ sudo rm -rf /private/tmp
$ sudo ln -s /Volumes/your_hdd_name/private/tmp /private/tmp

然后其实用户的家目录也可以这样挪到机械硬盘上,不过要注意数据的迁移,所以命令稍有不同:

sudo ditto /Users /Volumes/your_hdd_name/Users
sudo mv /Users /Users.bak
sudo ln -s /Volumes/your_hdd_name/Users /Users
sudo rm -rf /Users.bak

另外还有一个目录 /private/var/log,也就是系统日志目录,我觉得也有必要挪一下,但又怕像 /private/var/run 那样失败,所以一时没有折腾。

禁用 Safari 的 Webpage Previews 功能

我好像没做这条,因为我不怎么用 Safari 的说。

关闭 Spotlight 索引

因为我也不怎么用 Spotlight,所以这个我也(跟上一条连起来,感觉有点儿怪怪的哈)关了,记得就是去系统配置里面把所有的勾都给取消掉就行了,嗯。

关闭「时间机器」功能

反正我是没开过,反正我的重要数据(代码啥的)都在网上有仓库,图片有 Google+ Photos,电子书……你懂的。

据说是该功能在你没插备份盘时会往系统盘备份,要不要关就看你的实际需求啰。

禁用「自动休眠硬盘」选项

就是系统配置、节能器里的那个「Put the hard disk(s) to sleep when possible」。不过我开着这项,因为我还有个机械硬盘嘛。

Trim Enabler

这个是重头戏哟,所以放在最后(不过这样是不是就会被人忽略掉啊,嘛不管了)。

原文中的链接已经坏了,因为那个链接的作者更新了一篇新的,所以把老的给删了,新链接在此。建议好好阅读学习哟。不过对于最新的 Yosemite 来说,有一个更新的脚本

虽说是重头戏,但工作相当于全都丢给别人了嘛,不过这种细节你就不要在意啦。

打完收功!

's avatar

Keepalived 实现双机热备并对关键进程进行监控

三连更有木有!(对不起我网文看多了,而且中间其实断过两天的,我不会告诉你其实是我把这事儿给忘了的)

另,这篇照例是工作需要。


使用 Keepalived 实现多机热备(无负载均衡)时,除了对网口状态的监控外,一般还要对系统关键进程(如 Web 服务器的话就是 nginx 或者 httpd 之类的)进行监控,这时就要用到 vrrp_script 配置。网上能找到的 vrrp_script 示例都使用了 weight 选项,以实现基于优先级机制的切换,我在使用中遇到了一点儿问题,总结一下。

一般在使用双机热备时,如果当前主机——假设为 A ——出了问题(关键进程挂掉,优先级降低,低于备机),这时会自动切换到备机——假设为 B ——继续完成工作,同时等待管理员对 A 的问题进行修复。当 A 的问题修复时,我们不希望 A 马上切换为主机,因为这样有可能断掉用户正在使用的连接,体验不好。在 keepalived 中,通过给 A 增加一条 nopreempt 配置,就可以防止 A 主动抢回主机状态。

这时问题就来了。当 A 的问题(关键进程恢复)修复之后,A 的优先级会恢复到原来的值,但由于 nopreempt 的存在,A 并不会立刻抢占。这时就相当于 A 作为备机,却有更高的优先级。如果 B 之后出了问题(关键进程挂掉,优先级降低),却不会导致切换,因为 B 在出问题之前,其优先级就已经比 A 低了。

网上有文章说,遇到这种情况,可以在 B 的进程监控脚本中把 B 的 keepalived 进程杀掉,来强制触发切换动作,这种作法显然并不优雅。有没有更好的方法呢?

注意到,当拔网线触发切换时,被拔网线的机器上,keepalived 并不是降低了自己的优先级,而是进入 FAULT 状态(除 MASTER 和 BACKUP 之外的另一种状态),这时是不会有上述由优先级引起的问题的。所以新的思路就是,当进程监控失败时,不要降低优先级,而是令 keepalived 进入 FAULT 状态,同时在主备两机都加上 nopreempt 选项。

至于怎么令 keepalived 进入 FAULT 状态,也很简单,直接去掉 vrrp_script 中的 weight 选项即可。虽然谁也没说 weight 是必选项,但因为网上能搜到的示例都是配了 weight 的,导致很多人(至少包括我)会主观认为这是必选项,从而无法意识到还有其他方法。当然这也跟 keepalived 的文档不太完善有关。

PS. 让我发现 weight 不是必选项的来源是……找不着了,囧。

's avatar

LoadRunner 中进行 HTTP 测试时使 Keep-Alive 生效的注意事项

工作需要,在使用 LoadRunner 进行 HTTP 测试时,为了使每个虚拟用户在不同的循环周期中都能保持长连接,则除了要打开 Keep-Alive 运行时配置(默认打开)之外,还有两个选项需要修改,这两个选项都在运行时配置的「Browser - Browser Simulation」中:

  • Simulate browser cache :取消勾选。禁用对浏览器缓存机制的模拟,令虚拟用户确实的每次都真正发起与 Web 服务器的对话,而不是只读取一下缓存。

  • Simulate a new user on each iteration :取消勾选。不要在每次执行 Action 之前重置虚拟用户的状态,不然会把原来的 TCP 长连接也重置掉,Keep-Alive 就没用了(更准确地说,是在不同循环周期之间就没用了)。

's avatar

VS2010 环境中调试 IE ActiveX 控制时断点不起作用的问题

又是惯例的长了好长时间的草,今天先来篇短的。

工作需要,在使用 VS2010 开发调试 IE ActiveX 插件时,默认条件下下的断点不起作用,这是因为选错了调试器,在工程属性 - Debugging 中的 Debugger to launch 项,选择 Web Browser Debugger ,然后在 HTTP URL 项填本地 HTML 文件的绝对路径,以 file:/// 打头就可以了。

's avatar

OpenSSL 与 WinSock2 配合使用时遇到的一个坑

今天才发现,这里已经两年多没有被照看过了,估计连树都要长出来了吧。


在用 Windows 的 WSAEventSelect 模式进行网络编程时,比较固定的一个模式是这样的:

...
WSAEventSelect(sock, events[0], FD_READ);
...
while (1)
{
    ret = WSAWaitForMultipleEvents(1, events, FALSE, timeout, FALSE);
    if (ret >= WSA_WAIT_EVENT_0 && ret < WSA_WAIT_EVENT_0 + 1)
    {
        eventIndex = ret - WSA_WAIT_EVENT_0;
        if (eventIndex == 0)
        {
            WSAEnumNetworkEvents(sock, events[0], &networkEvents);
            if (networkEvents.lNetworkEvents & FD_READ)
            {
                ...
                recv(sock, buf, sizeof(buf));
                ...
            }
            ...
        }
        ...
    }
    ...
}
...

当要处理的 sock 属于一个 OpenSSL 连接时,只要把当中的 recv 换成 SSL_read 就行了,当然前面还得加上些 readWaitonWrite 之类的标志位检查啥的,这里就不详细列举了,请参考《Network Security with OpenSSL》一书中的 5.2.2.3 小节。

但我在实际使用中(事实上是在压力测试中)发现,当程序运行一段时间之后,WSAWaitForMultipleEvents 就会忽然不再返回可读信号了,从而导致对该 socket 的接收操作完全停止。

经过各种调试手段,甚至是在 OpenSSL 中加入调试输出之后,终于发现问题出在 SSL_read 的实现机制上,貌似 OpenSSL 实现了某种程度的读写缓冲(具体没细看),使得对 SSL_read 的一次调用,并不一定会触发其对底层 socket 的读取操作。而如果没有对底层 socket 的读取操作,那么 windows 的对应 event 对象就不会被 reenable (参考 MSDN 中对 WSAEventSelect 接口的说明文档),从而导致 WSAWaitForMultipleEvents 不再对该事件作检查。

原因找到了,那么相应的解决方法也就不难发现了,对于我的使用场景来说(不完全是像上面的示例片断那么清晰),最简单的作法就是在 SSL_read 之前加一个空的 recv 调用,其传给 recv 的第二、三个参数的值分别是 NULL 和 0 ,这样就能强制触发 windows event 的 reenable ,同时又不会影响到 SSL 对象内部的读取状态了。

PS. 遇到这个问题时,最重要的一步其实是如何能够稳定而快速的复现问题。一开始做压力测试时,只有在连续跑个一天以上时才会不定时的出现,导致效率很低;后来偶然发现,通过虚拟机搭建的受限环境,反而能很快复现问题。想来这也是另一种形式的压力测试吧,正常的压力测试是保持环境不变,加大请求压力;这里则变成了保持请求不变,同时压缩可用环境。

sunjw's avatar

Notepad++ plugin local exploit

前段时间,在用 npp 分析一个 html 文件时,准备把其中的混合在一起的 js 分离出来,在 js 前后换行时 npp 崩溃了,感觉是哪里 bug 了,重试了几次都能很稳定的重现。本着张银奎老师的精神,上调试器。用 windbg 附加到 npp 进程,重现这个 crash 后,停到调试器内,看了下栈。

很明显栈溢出了,返回地址都是文本中的字符,溢出发生在 NotepadSharp.dll 也就是 Notepad# 插件中。Npp 上的插件基本都是开源的,那就把代码搞下来,用 VS 编译了个 debug 版,放进去再次触发崩溃,发现 PluginDefinition.cpp 的 void Newline() 函数中

char line[9999];

::SendMessage(curScintilla, SCI_GETLINE, line_number – 1 , (LPARAM)line);

将当前光标上一行的文本内容拷贝到 char line[9999] 这个 buffer 中,所以很自然,当文本行内容很长,就栈溢出了。

既然溢出在栈上,通过覆盖返回地址和栈指针,在 XP 上应该可以轻松控制程序执行流。不过这个插件的 release 版是在 VS2010 上编译的,打开了 /GS 参数,所以在函数中有 security cookie 检查,而且 security cookie 是随机值。

试图覆盖返回地址时,也会将 security cookie 覆盖,所以导致函数返回之前就出错。

在 Yuki 的指点下,加长文本行的内容,向后继续覆盖,试图覆盖到 exception handler。再次触发后,eip的确变成了覆盖的内容,不过并非覆盖了 exception handler ,而是覆盖了某个窗口消息循环中的回调函数指针。

这是原始的 0x000da8dc 开始的内容,后面会看到 0x000da8dc 存储的是 edi,0x000da8f4 存储的则是回调函数指针,原始的内容是 User32.dll 的 CallWindowProcW 函数指针。经测试,这里应该是 Explorer 插件的回调函数,其实没有Explorer 插件也可以在附近的位置覆盖到其它窗口回调。当按回车后,::SendMessage 不仅拷贝了字符串,还触发了多个插件注册的回调函数。所以在 NotepadSharp 的 Newline 函数还未返回前,其他插件,例如 Explorer 之前注册的窗口回调就被调用了。于是通过很长的溢出,覆盖了其他的栈空间,避开了 security cookie 检查,从而能控制 eip。通过覆盖,在 html 文件对应位置填写 0x7E47B533,也就是 Windows XP SP3 en 上的 jmp edi 指令。

并且在 0x000da8dc 填上 EB1C,短跳转 1C 个字节。之所以要跳转是因为 edi 在之前覆盖的函数指针前方,如果需要执行更多的代码,空间似乎有点不太够,通过跳转来到指针位置的后方。在跳转目标地址填上 int 3 也就是 CC。触发漏洞后,成功的停在 windbg里面。后面就可以用 shellcode 弹个计算器什么的了,不过查看寄存器,发现 ebp 指向的地址不可写,就把 esp 的值赋给 ebp,当作新的栈。最终,贴上一段 metasploit 的 shellcode,成功的在 npp 中按一下回车,导致 npp 崩溃,并弹出了计算器。

这个问题已经报告给 npp 的插件管理者,也报告给了 Notepad# 的作者,不过似乎该插件作者已经很久不更新该插件,也没有留下有效的联系地址,似乎一直对该问题没有响应。如果已经安装了 Notepad# 插件,最好将其卸载,虽然被攻击的可能性不大,但是崩溃什么的也不好。

sunjw's avatar

JSToolNpp 1.16 Released

What’s New in JSToolNpp 1.16:

  • Performance improvement.
  • Move download back to SourceForge.net.

Download links:
SourceForget.net Download
Google Code Download

sunjw's avatar

Build CyanogenMod 10.2 for Nexus 4

一直想尝试着自己编译 CM,毕竟如果想修改系统 Apk,就有可能需要编译整个系统。于是在开了一台 Ubuntu x86_64 虚拟机(这是一个大坑,等会儿说)作为编译机器,参考的主要是 CM 官方 wiki:http://wiki.cyanogenmod.org/w/Build_for_mako?setlang=en。根据 wiki 指示准备好基础环境后,开始 repo sync,总共下载了 6GB 多的工程代码。总的来说,一直到 wiki 的 “Start the build” 一节,都没有问题,照着做就好了。不过当执行 brunch mako 之后,坑就一个个冒出来了。

第一个坑是磁盘空间,一开始给虚拟机分配的磁盘小了些,明显不足以完成整个编译,于是又加了个 40GB 的虚拟磁盘,并且在该磁盘上编译。40GB 似乎够了,不过最后的事实证明其实 40GB 几乎不够,最终完成编译后只剩 400MB 了!所以编译 Android 还是分配一个大一点的磁盘吧。

第二个坑就是 x86_64,ARM 处理器目前还是 32 位的,包括编译使用的 repo 好像也是 32 位的,所以首先需要安装 32 位 c/c++ 运行库。之后真正的坑在于 JDK,wiki 中没有明确说明 JDK 应该安装 32 位还是 64 位的,我一开始想 java 部分应该和架构没什么关系,就装了个 64 位 JDK,结果开始使用 javadoc 编译 Droiddoc 时,make 出错了,显示错误 45,一开始在论坛上查了一下,说是应该使用 Oracle JDK,于是尝试安装了 64 位的 Oracle JDK,结果还是一样出错。后来仔细看了论坛,发现其实问题是 64 位 JDK。也不知道为什么,64 位 JDK 的 javadoc 就是会导致编译错误,但是之前的 jar 包都编译正常。通过 apt-get 安装了 32 位 Open JDK 后,问题解决了。

第三个坑是内存,一开始只给虚拟机分配了 1GB 内存,结果在编译 framework 时,malloc.c 中的 assert 出错了。。。关机,调整到 2GB,编译终于继续下去了。

第四个坑是 CM 加入的 Focal,在编译 external/Focal 中的链接库时总是出错,论坛上说 Focal 不太稳定,可以不编译。好吧,暴力一点,直接把 external/Focal 和 apps/Focal 删掉了,编译继续。

最终花了一个下午+半个晚上(不包括 repo sync 的时间,这个根据网速会有所不同,可以自己算算下载 6GB 需要多久),终于编译出来了。好了,能编译了,下一步就是准备尝试修改了。

 

sunjw's avatar

fHash 1.8 Released

增加 Shell Extension 实现的高级右键菜单支持
修正其他 bug

Use Shell Extension to implement context menu shortcut
Fix other bugs

Download:
https://code.google.com/p/fhash/downloads/list
http://sourceforge.net/projects/fhash/files/1.8/

sunjw's avatar

用 WinDbg 调试符号不全的程序

其实也就是记录一下用 WinDbg 调试 fHash 的一次过程,这个 bug 是由于没好好看文档造成的,我需要让 fHash 知道当前运行的操作系统是 32 位还是 64 位的,之后好去选择正确的 shell extension 文件。查文档看到了这个函数 IsWow64Process ,之后错误的以为 kernel32 中有这个函数就是 64 位操作系统,结果就已这个逻辑写下了错误的 64 位系统判断函数,和错误文件读取逻辑。


tstring tstrExeDirPath(pszExeFullPath);
tstring::size_type idx = tstrExeDirPath.rfind(_T("\\"));
tstrExeDirPath = tstrExeDirPath.substr(0, idx);

tstring tstrShlExtDll = tstrExeDirPath;
tstrShlExtDll.append(_T("\\fHashShlExt"));
if(IsWindows64())
    tstrShlExtDll.append(_T("64"));
tstrShlExtDll.append(_T(".dll"));

WIN32_FIND_DATA ffData;
HANDLE hFind = FindFirstFile(tstrShlExtDll.c_str(), &ffData);

bool bRet = (hFind != INVALID_HANDLE_VALUE);

if(bRet)
{
#if defined(UNICODE) || defined(_UNICODE)
    wcscpy_s(pszShlDllPath, MAX_PATH, tstrShlExtDll.c_str());
#else
    strcpy_s(pszShlDllPath, MAX_PATH, tstrShlExtDll.c_str());
#endif

   FindClose(hFind);
}

return bRet;

结果很自然,在 32 位的 Windows XP 上没有执行正确。但是测试环境没有 VisualStudio,而且用的是 Release 编译,也没有带着符号文件,这怎么调试呢。Windows XP 上装有 WinDbg,那么就用它来调试。

启动 fHash 和 WinDbg,附加到 fHash 进程,由于没有 fHash 的符号,于是考虑在 FindFirstFile 函数上下断点:

bp kernel32!FindFirstFileW

Go,开始运行,点击“添加右键菜单”,WinDbg 停在 FindFirstFile 断点上,回到 Call Stack 的上一层:

.frame 01

看到 call 之前第二次压栈 push eax,检查 eax 对应内存地址的数据:


r eax
db 003b7ed0

看到指针指向的字符串是 …/fHashShlExt64.dll,说明之前 IsWindows64() 返回的 true。也就是说 kernel32 中有 IsWow64Process 函数,我们来看一下。

在 GetProcAddress 函数上下断点:

bp kernel32!GetProcAddress

再次尝试添加右键菜单,WinDbg 停在 GetProcAddress 断点,我们让它 Step Out,之后停在 IsWindows64() 函数里面,看看返回值 eax:


r eax
u 7c81f2b9

可以看到反编译该内存地址就是 IsWow64Process。

sunjw's avatar

JSToolNpp 1.15 Released

What’s New in JSToolNpp 1.15:

  • Change name to JSTool.
  • Added simple search in Json Viewer.
  • Fixed Json Viewer and editor linkin bug.
  • Fixed Json Viewer utf-8 bug.
  • Other tweaks.

Download links:

http://jstoolnpp.googlecode.com/files/JSMinNPP.1.15.uni.zip

http://sourceforge.net/projects/jsminnpp/files/latest/download

sunjw's avatar

Apple WWDC 2013 Keynote 全场视频下载

刚刚 Apple 放出了下载地址,各位猛击即可:

http://itstreaming.apple.com/podcasts/apple_keynotes/wwdc2013_ipod.m4v

Wang Congming's avatar

2012:变革

没有哪一年,像 2012 这样出乎我的预料。这一年,我从被认为拥有完美纯粹初恋的好男人,成了一个没人要的老男人;这一年,我刚刚驾驭了中层的工作,又紧接着面对进入高管层的挑战;这一年,承受了很多、学会了很多、中庸了很多。

– – –

事情来得那般突然,以致于我要面临从零开始生活的挑战。这真的是一个挑战,最搞笑的例子是,买个床垫回来发现竟然小那么多,我以为天下的床都一样大呢!再举个例子,我那时在商场对着 “x件套” 久久思索:怎么这些被子都这么薄呢?呵呵。。。慢慢地,下班逛一趟超市,提一堆东西回来成了一个习惯。那两个多月逛的超市,比之前 20 多年还要多吧。我终于意识到了大部分人早就意识到的结论:楼底下有个大超市,生活是多么的方便啊!

寂寞,可以让一个男人腐朽,也可以磨炼他的情怀。现在这里的一切,就只有一个你了,活得有没有品质,再没有借口了!好在,咱自我进化的能力尚足以慰藉。生活逐渐朝着自己想要的方向前进,有什么不可以呢。

2012,一向对尺寸不敏感的我,开始时常去测量,因为买东西必须有所参考;一向对金钱不敏感的我,偶尔也会对一眼售货单,并感慨通货膨胀的厉害。2012,我研究起了如何更有效率地清洁玻璃、如何更好地吸尘;我研究起了空气净化系统、地热供暖系统。2012,我甚至花了些时间研究休闲娱乐,如果既有时间又有钱,可以去哪玩呢?我开始去寻找花鸟鱼虫市场,养起了小动物(起初,它们只是装饰的工具,我认为在这样一个屋子里需要一点动态。买回来才意识到,这是一条条鲜活的生命,它们也有自己的习性、也会饿、会生病、会⋯⋯必须对它们的生命负责)⋯⋯2012,我在生活,学到了很多;却也偶尔无奈于一个人吃饭、看不到温馨的笑容。

– – –

2012,团队有了极速的扩张。我们同时涉及云计算、移动互联网和物联网;同时涉及 Web、App、C/S 和三维仿真;同时涉及平台研发、通用产品、专业产品和各异的项目业务。对于不愿丧失细节掌控力的人,这意味着太多。意味着必须对团队建设、业务需求、专业理论、产品设计、技术架构都有足够的投入;意味着必须对战略规划与落地有热切的期盼、对商务压力有坚强的意志、对团队成长与波动有沉稳的心理;意味着在期盼、压力和问题面前保持激情,并向团队传递信心与决心;意味着必须时刻准备进入全新的领域,并保持勤奋。

职业是一个舞台。在这个舞台上,演绎了自己的青涩,也演绎着一段极速成长的青春。

– – –

回首,我看到了悲哀,在最重要的事情上如此失败(好在,仍然让大部分人记住了自己爽朗的笑声、让领导可以向自己不必顾忌地泄压、让团队相信我们能成)。

如果说有什么值得欣慰,一些困境和压力可以 HOLD 住、一些事情做起来似乎有些天赋、一些毛病可以快速地自我进化掉,这些也许算吧。至少,这样说可以让自己感到高兴 :)。

也有很多不太满意的地方。这一年,仍然做了很多无聊的事情,浪费了很多时间;一些好的做法,仍然没有形成稳固的习惯、仍然在波动;有些时候,仍然可以明显扑捉到自己很天真、很幼稚(虽然希望能够装不懂,保留某些幼稚);对父母、亲朋的关照仍然还很不够。。。

这一年,我这个所谓的 geek,好像对科技失去了敏感。真正的 geek,应该是像 Elon Musk 这样的家伙,IT 之外,对数学、物理、生化⋯理论和应用都有着前卫的理解和尝试。在这方面几无收获,甚至要看记者写的新闻才知道发生了什么事。反倒是,生活上有了些长进。这是一种可怕的中庸么?⋯⋯只能勉强找一句话安慰自己,“花一些时间深入理解生活,有助于激发更有价值的创新”(倒真的在做家务的时候,想到了一些很好的工作上的创意~)。

– – –

单身男人的好处,是他有很多寂寞的时间。
直面寂寞,成就了情怀;
静观内心,凝聚了真我;
来去无虑,释放了豪情。

展望,希望 2013 是升华的一年。

除此之外,以下几条要做到!

  • 驾照。时间不是借口!
  • 多读书。2012 读了一些书,工作有关的、无关的,但还远远不够,要继续!

    2012 感觉还是太“文”了一点,2013 要恢复一下“野性”~!

  • 健身。2012 开了个好头,2013 要常态化!
  • 滑雪。这个这个,这才是符合我口味的运动~!虽然现在还很菜鸟 :(   要多练!
  • 潜水执照。要拿到 OW 执照,AOW 就更好了~。向海洋深处进发,哇哈哈,向往~! 12

– – –

莫为浮尘遮望眼,风物长宜放眼量!
有太多激动人心的事了,Can’t keep waiting!

Wang Congming's avatar

一名程序员的中期答卷 — 序

今天,我过了一个有工作也不干的周六。悠闲的时光,除了轻松惬意,也给人思绪。我忽然觉得,应该停下来,写点什么。

回首来南京工作的经历,我手中的素材开始慢慢变厚,然后又由厚变薄。现在,又要开始变厚了⋯。所以,是时候对前面的东西做点记录吧,当着年轻的记忆 ;) 。否则凭我的毛病,过了这个阶段就会觉得不值一提,然后慢慢忘记了。。

 

如果用一个词形容这两三年的工作,我觉得是“丰厚”。从一个人,到几个人、到几十个人;从锋芒毕露,到内敛、到担当、到承受。而北漂的那几年,感受的是神奇、纯粹的快乐。大概,“浪漫”完了,该负责任了 ^_^ 。

 

作为所谓的序篇(哈哈~),还是回忆回忆自己是怎么成为程序员的吧。这个,说起来就比较悲催了。牛人们的经历都是这样的:

我有个 Geek 老爸,在我很小的时候、世界上第一代 PC 诞生的时候,他在车库里自己捣鼓出了一台。然后,他用汇编写了好几个游戏给我玩。从此,我爱上了他那台电脑,以及编程。。。

我第一次接触计算机已经到了高中一年级(还没有网络),对着屏幕不知道该干什么,然后看着个别已经打字打得噼里啪啦的同学表现出一副无辜的样子。等到我可以经常使用计算机,已是快 2000 年的时候了(还是在网吧)。当时,其他同学都在忙着聊 QQ、玩游戏,我却在好奇怎么可以让网页上的文字一闪一闪的。然后,我用记事本看 HTML 的样子吓到了网吧老板,他警告我不准黑他们网吧,我倒是想啊~。

 

刚上大学那会,已经可以假模假样搞网站了。比如当时常见的,资源搜集与下载站等等。不知从某个时机开始,才尝试步入“正途”,思考说所谓的“计算机科学与技术”是学啥的(因为我不是计算机专业的)。后来找啊找,找到了三个靠谱的来源,分别是清华大学、兰州大学和 MIT,它们对于这个专业该做什么、怎么做,开放的资料给了我很大的帮助。记忆最深刻的,是当时我老旷课,拿着本《数据结构》的书躲在学校无人的楼顶上,想啊想,到底什么是 TMD 的“抽象”。。。现在回想起来,因为自己的性格,我对这个学科从完全的无知到稍微有一些理解,都是自己躲在角落里通过网络和书搞懂的。如果那时候能有个人点拨,应该会快很多吧。

 

再然后,我爱上了一个叫着“人工智能”的东西,因为前面的缘故,对传说中的 MIT 充满着向往。毫无道理地,我一直认为自己是个天才,我要去伟大的 MIT、做出伟大的科研成果、造出伟大的智能机器、然后取得伟大的商业成功⋯⋯。其实从中学的时候开始,我就有伟大的天才、伟大的科研成果、伟大的商业成功的梦想。然而这一刻,梦想突然变得这么清晰,那就是 MIT、人工智能!仿佛触手可及。

 

最后,经过挣扎,我发现我去不了 MIT。人生第一次感到绝望。即使高考考得不好,我仍然相信自己是个天才,这还没完。然而这一次,我要彻底放弃所谓的人工智能、MIT 的执念了,我得去找工作了;我得承认,自己只是茫茫人海中的普通一员了。那一夜,我为自己,在绝望中痛哭。仅有的一次。

 

从此,我成了一名快乐的程序员 :)。

Wang Congming's avatar

如果可以穿越,我想对年少的自己说:

勿有比较之心

这个环境,早早地向你灌输“人中龙凤”、“人上之人”的观念,仿佛人生的唯一目的就是要攀援一个等级,而成功的唯一途径便是将他人踏在脚下。权势与财富是唯一的衡量。

 

有一天,你会问自己,为什么来到这世上,所求为何?人生百年,似有一段光景,然比日月星辰、沧海桑田,实如蝼蚁,白驹过隙。人生所向,不过死得其所,求得内心安宁。

 

然纵渺小,你定也不甘心来这世界白走一趟,你定然想见识见识这种种之美。

想见识星宙斗转的自然之力,在苍穹的一角,仅这一颗小小星球上的千奇壮美已令你痴然。你不会“人定胜天”,而会充溢朝圣的心情,想用脚丈量大地、用心聆听安宁。

想见识千万年来人类文明的足迹,这个孤独的、智慧的、高傲的,狂躁的、愚蠢的、野蛮的种族,它走过了怎样的一条道路。因为,那是你来的地方,也将是你去的方向。历史透过尘埃在你面前呈现,而你也将化作尘埃组成历史。

 

终于,你在自然与历史的洪流中洗去了纤尘,变得温和谦恭。你爱悯地看着这个世界,在美与绝望之间,不再想只当一个旁观者。跟随你的心,去做点什么,你就没有白过。也许,你所选之道,已不同于环境的熏陶,你知道,那已不再重要。在广袤的时空间,人类有多样的价值,却没有唯一的“成功”。多样与不确定正是生命美与尊严之所。

 

你将不会后悔,因为你不仅曾经来过,而且清醒地活着;你体验了美,而且努力创造了美。

 

 

优秀是一种习惯,珍惜时光、重在实作

你在时间的无涯的罅隙中寻找到了一个渺小的人类存在的价值,你在生命独特的尊严的鼓舞下树立了前行的方向。你定然想做到更好,那么,请将自己从过往的缅怀、未来的畅想和当下的忧虑中释放开来,重要的,是迈出你的步伐。把想法变为行动,把行动做到优雅,让优雅成为习惯。

 

 

任何煎熬,待你挺过去之后,都将成为一种荣耀

不断的挑战,使你的生命变得丰满。不要倒下,因为待你挺过去之后,任何煎熬都将成为一种荣耀。这锤炼的是你的胸怀,使你装得下更大的未来。

Wang Congming's avatar

RSS feed 迁址

曾几何时,FeedBurner 有一统天下之势,咱也没能免落俗套,热热闹闹地烧了一把火。。

 

承蒙有些朋友订阅了我这小破文章,给大家知会一声,您要不嫌我啰嗦,烦请订阅 http://wangcongming.info/feed/ ,老的 FeedBurner 地址可能会在不日废除。

 

谢谢! ^_^

 

Wang Congming's avatar

我是怎么管理密码的

应推友要求,介绍下我现在是怎么管理密码的 :) 。

这个方法不是我首创,我也是受别人启发,但可以简单论述下为什么这个方法好~。

 

这个问题的背景,是这个国家的互联网行业,开发者素养差、管理者道德低的普遍现状(当然,国外也有很多不负责任/不懂如何负责任的网站)。我们对于所有网络服务,普遍处于不信任的状态(特别是国内还多了官僚监管这一层),但是有的时候又不得不用。那么,作为用户,我们如何保护自己的信息安全(这里只谈密码)?

 

理想状况:

  1. 每个网站都使用不同的密码;
  2. 而且都很复杂。

2 保证了如果某个网站技术不太行,保护措施做得不够好,你的密码也不会轻易被破解;而 1 保证了即使在极端情况下,也只会被泄露这一个网站的密码,不影响你在其它网站上的账户。

 

然而,要用人脑来记忆不同网站的不同的复杂的密码,显然是不可行的。所以我们要借助一些辅助措施。有一类软件提供这样的功能,需要的时候,它可以帮你生成一个随机的很复杂的密码,同时把它存起来。你可以注明这是什么网站的密码,然后用一个主密码把这些随机密码都加密起来。所以,软件负责记住不同网站不同的复杂密码,而我们人脑只需要记住一个主密码。

 

这是个好主意,但是还不够好。问题在于那些不同网站的密码都是随机生成的,这也就注定了我们的焦虑(我们失去了把控,只能完全依赖于一个黑盒)。我们总会担心记录弄丢了、损坏了、出门在外忘带了(同步?没网络呢、别人的机器呢?),或者某种平台这个软件不支持等等。总之,我们被 lock-in 了!

 

我现在的方法是,不使用随机密码,而是用主密码+网站名,然后算 sha1。即:

$ sha1sum

password+csdn

^D

对,出来一串“乱码”,就用这串乱码做密码。这样做的好处,只要密码后面的网站名不同,生成的“乱码”就完全不同,即使有几个网站同时不靠谱、同时把你的密码(即刚刚生成的“乱码”)泄露了,你的主密码是绝对安全的(当然,要搞复杂一点)。另一方面,你知道这些“乱码”是怎么生成的,所以你不再依赖于某一个单一的软件,因为 sha1 算法是整个计算机系统的重要基础设施之一,哪里都有它的踪影、久经考验。

 

哦,你会说,这是程序员的玩意儿,非程序员怎么办?非也,算 sha1 的小软件,可是比密码管理软件分布广泛多了。Windows/Linux/Mac OS、手机、平板⋯哪个平台上没有?不但有,而且很简单、很好用,绿色环保!就算没有,照着公开的算法写一个也不是什么难事。

所以,我们与其小心翼翼地备一个密码管理系统,不如大大方方地随便哪放一个算 sha1 的小软件。而且你可以下一个在手机上,真是走到哪用到哪啊。不是有很多人说,非硬件级的方案不用么?手机,这是最好的硬件方案了。:)

还有很重要的,如果因为种种原因,你要用一个公用/别人的电脑,就会对配置 1Password + Dropbox 感觉不爽,毕竟有你重要/隐私的数据在里面,同步下来总不太好。算 sha1 的小软件就无所谓了,随便下一个、随便算一下,然后叉掉就行了(要找一个熟悉的,确保不会保存历史记录)。

 

当然,算法只是一方面,为了方便起见,我们不希望每次都算一遍。所以,可以配合浏览器原生的记住密码、自动填写、自动同步的功能一起使用,我觉得堪称完美了(特别是 Firefox 还可以设置主密码保护已记住的密码)。

我已经使用这个方法一小段时间了,个人感觉良好。唯一的不爽是,有些 SB 网站居然对密码长度有限制,要求不超过 16/20/25/… 位。遇到这种情况,只能一边骂 SB 的同时,一边截短。。。

 

关于 1Password:

1Password 做得太优雅了(只用过 Mac 版),以致于我很乐意帮它做个广告(虽然我没再用它了⋯)。1Password 几个很好的功能:

  • 自动生成随机复杂密码:跟上面主要的思路相冲突,这功能等于没用;
  • 自动记忆、自动填写:浏览器原生支持,相比之下没有优势,等于没用;
  • 密码整理/管理非常方便:这是它比浏览器原生的功能强很多的地方。但是密码之所以需要整理,是因为每一个都很独特、都很重要,我们需要搞得它井井有条。但是当我们用了上面 sha1 的算法之后,会突然觉得没必要整理了。我们让浏览器记住,只是为了它能帮我们自动填写,方便一点而已。实际上,那些密码存在数据库里到底是什么鬼样子,我们一点都不担心,反正需要的时候再算一遍好咯。所以,不是 1Password 做得不够好,而是这功能对我已不再重要。

呵呵,好像说得它一点用处没有的样子,其实我们也可以用 1Password 记上面 sha1 产生的密码,它的好处比如:

  • 虽然密码整理已不再重要,但是它毕竟满好用的,说不定我们什么时候想整理整理呢?
  • 1Password 是跨浏览器的。如果用浏览器原生的记忆密码的功能,并且 Firefox、Chrome 同时用的话,同一个网站的 sha1 可能需要算两遍呵呵。而 1Password 是跨浏览器的,算一次就可以。
  • 除了网站的密码,1Password 还可以用来记信用卡都资料。

另外,如果不太喜欢上面说的所谓的算 sha1 那么 geek 的做法,也是用 1Password 的一个很好的理由,它是非常用户友好的。与 1Password 类似的,可以用开源且恰好免费(好吧,我是想给你一个印象:开源不一定免费)的 KeePass。

(完)

Wang Congming's avatar

用户:如何判断某网站程序员是不是傻逼(关于密码)

Update:这篇日志用的戏谑的语气,严谨地说来,当然不一定是程序员的责任,比如历史遗留、领导的要求、业务需要、进度限制等等⋯。即使是程序员自己的问题,如果只是经验不足,而非主观故意,也应该是可以理解的,毕竟谁没有年轻的时候呢。日志中用了“傻逼”这个词,有些人看得比较严肃,颇有些不满,在此致歉了 ^_^ 。

——

虽然最近很忙,但我发现负荷高了大脑会罢工,正经事想不进去⋯。于是乎,写篇休闲科普文章吧。:)

 

那啥,CSDN 600 万用户密码刚泄完,天涯网又泄露了 4000 万用户的密码出来。看起来像是有组织、有预谋的,越来越热闹了~。前面一篇日志,我从开发/设计者的角度,记录了几条实践要求,怎样保护用户密码才靠谱

 

那么,对于普通用户来说,如何判断一个网站的技术靠不靠谱?有没有什么现象,是不需要懂任何技术,只要你发现了,就可以对这个网站露出鄙夷的神情,骂它傻逼的?试着总结几条(只谈跟密码有关的):

  • 如果一个网站,能够通过邮箱、手机、安全问题等,找回密码,那它就是傻逼。这里注意“找回密码”和“重置密码”的区别,“找回密码”是把你原来的密码原样告诉你;“重置密码”不告诉你原来的密码,但是经过可靠的验证后允许输入一个新密码。前者是傻逼,后者不是。
  • 如果一个网站,要求密码不能超过多少个字符,或者不能包含特殊字符,那它就是傻逼。密码字符个数,有下限要求的,是好人;有上限要求的,是傻逼。
  • 如果一个网站,密码不区分大小写,是傻逼。
  • 如果一个网站,改密码的时候不先验证老密码,是傻逼。
  • 如果一个网站,登陆时不使用 HTTPS,是傻逼。这个不一定,有可能程序员不傻逼,老板是傻逼。。。
  • 如果一个网站,改密码后不要求重新登陆,很可能是傻逼。这个非绝对,只对允许一个账号同时多处登陆的网站有效(即使这样也非绝对傻逼)。总之,对允许一个账号同时多处登陆的网站来说,改密码后要求重新登陆的,是一般人;不要求重新登陆的,有可能是 SB,也有可能是 NB(但是 SB 的比例很高,NB 的比例很低,所以可以默认是傻逼⋯)。

 

好吧,一时半会就想到这么几条。欢迎补充 :) 。

 

Wang Congming's avatar

怎样保护用户密码才靠谱

CSDN 密码泄露,把我的也给泄掉了⋯⋯好几年没用了,躺着也中枪啊。幸好只是一个低级别的密码。忙着改密码的同时,也在考虑要不要启用 sha1(master password + domain name) 的密码方案。。。

 

好吧,还是说点有趣的,怎样保护用户密码才靠谱?

1、数据库存储

原始:

save2db(username, password)

进化:

save2db(username, md5(password))

再进化:

random salt = GUID

save2db(username, salt, md5(password.salt))

靠谱方案:

const globalSalt = [a-z]+[A-Z]+[0-9]+[%$#@!~*…]+

random privateSalt = GUID

save2db(username, privateSalt, sha1(globalSalt.password.privateSalt))

可接受的简化方案:

save2db(username, sha1(domain.password.username)

 

为什么要搞个 globalSalt?

globalSalt 写在代码中,也就是说,万一你的数据库泄露了(代码还没泄露),仍然有一道安全防线在保护你的用户。只有一个 salt 的情况下,虽然使得字典攻击变得更难,但如果用户密码不够长、不够复杂,暴力攻击仍是很轻松的事(因为既然你的数据库已经泄露了,用户的 salt 自然也就露掉了)。而有了 globalSalt,只要代码仍然安全(攻击者不知道你 globalSalt 的值为多少的情况下),它就能保证跟用户的密码组合起来变得非常长、非常复杂,超出了暴力破解的实际可能。

 

可不可以只用一个 globalSalt?

不可以。否则,好事者就可能在知道你 globalSalt 的情况为你这个网站专门生成一个字典,一次性搞定所有用户。privateSalt 的存在,使各个用户的 salt 都不一样,因此需要为每个用户生成一个专用的字典,不具备实际可行性。简单地说,globalSalt 防暴力攻击、privateSalt 防字典攻击。

 

简化方案中,可不可以不要 domain,只用 username?

不可以。一、有的用户,即使是用户名 + 密码仍然不够长,抗不了暴力;二、我们不仅是要(尽可能)保证 salt 的全站唯一,而是要全球唯一。有些用户名如 admin, root, webmaster 等非常通用,起不到 salt 应有的作用。

 

2、修改密码

    • 改密码,必先验证老密码,而不是登录着就让改。否则,就给 Session Hijacking 变成进一步的灾难修了条大道。
    • 如果老密码输错了,启用验证码保护。否则,就是 Session Hijacking 后暴力破解的后门。
    • 清 session,使所有登录的(如果支持一个帐号同时多处登录)cookie 失效。改了密码,就应该要求该用户所有会话重新验证。因为用户密码已经改了,表示原密码不再是有效的,甚至可能已经泄露了。

     

    3、其它

      • 传输密码时用 HTTPS;
      • 最好全程 HTTPS,杜绝 Session Hijacking(WIFI 什么的,最容易中招了);
      • 避免 Session Fixation(永远重新生成,不通过 URL 传输 SID);
      • 除特别需要,httpd 进程对代码目录只应该有读权限,而不应有写权限。你懂的,后门啊什么的最讨厌了~;
      • 定期 $ git status(.git 目录为 root 专属),如果不是 working directory clean,那就该报警了喂!
      • Injection、XSS 什么的,呃⋯超纲了,打住~。

       

      虽然都是很简单的事,但如果一条条验证,估计国内绝大部分网站都能找出问题,包括那些人们觉得很牛逼的(CSDN,程序员的社区啊,“全球最大”啊…存储密码用的是最原始的方法)。关于保护用户信息安全,你还有什么想法?

      Wang Congming's avatar

      The so called “social software development”

      昨儿晚上,我居然因为这个想法失眠了!

      ⋯⋯此刻,当一天的繁碌谢幕,静坐窗前,我想,我必须得把它写出来。

       

      话从头说起,昨天我发了一条推

      想问下有没有这样的网站:比如我想要一个手机应用,市场上没有、又不能自己开发,我就上去登记一下,写明自己的需求与设想,并投一点钱。其他人看到后,也可以补充完善,并追加小笔金额。待感兴趣的人与累积的金额够多,就会有开发者接招,把它做出来。版权仍归开发者,但初始用户有终身使用权。有么?

      之所以在推上问,是因为我觉得这个想法很普通,这样的服务应该已经存在,只是我不知道而已。可令我意外的是,答案居然是——没有。

       

      好吧,在开始之前,我想先总结一下我所了解的相关服务

       

      Kickstarter

      如果你有特别的创意,但是缺乏一些启动资金,可以把你的想法及现有的条件登记在此。社区用户如果认同你和你的创意,就会给你捐助。如果筹到了足够的启动资金,你就可以拿着这笔资金正式开始你的计划。当然,你需要承诺,项目成功之后给予当初的捐助者一些报答

      还有一些特点,比如:

      • 你的项目必须具备技术、艺术等方面的创意,而不是纯粹的慈善项目。红十字会类似的组织被明文禁止在此募捐;个人如果是 “我没有饭吃了” 或者 “我要买房子,需要贷一笔款”等,也是被禁止的。
      • 确保用户是因为热爱你设想的创造性的事物本身,而非作为经济上的投资,才给你捐助的。因此,承诺回报捐助人股权的行为是被禁止的。鼓励的回报形式包括优先获得产品的测试版等与项目相关的特别的纪念品。
      • 既然你不能在此煽情乞讨、也不能以所谓的高回报盲目用户,冷静的用户社区很适合作需求调研的窗口,测试你的计划是否契合大众的口味。因为如果到了截止日期,筹集的款项达不到你启动资金的要求,它们将被全部退回。捐助人的钱是安全的,而你也不会有拿了钱不干活的道德困境。

      也许很多人看完后的第一反应是谁会投钱、遇到骗子怎么办⋯⋯。别说,还真有很多人在投。Kickstarter 已经达到了百万用户的规模,并成功为各类创意活动募集了近1亿美元的启动资金。除了服务本身要提供一定的保障机制,整个社会大的信用体系、高质量的创意人群和宽容友善的创新环境,都让我们不免有些羡慕。

      C2C 作品:

      点名时间:比较忠于 Kickstarter 的思路(虽然强调得不够),可惜的是上面项目的质量不高,一眼望去感觉都是明信片 =_=! 。另外,所谓创意活动,应该是项目还没有启动,需要一笔启动资金;捐助人需要评估并承担项目失败的风险,而在项目成功后取得一些特别的回报。在点名时间,感觉有的项目把它当淘宝用了,明明东西已经做出来了(或者不存在任何困难),只不过多一个销售渠道。

      积木网:比较贪大,想要多囊括一些应用,最终搞得有形而无神。或者说,它是另外一套思路,不评论。

      总的来说,Kickstarter 是一个好东西,但是跟我想要的不是一回事。在 Kickstarter,你有创意,在此募集资金,并亲自实现自己的想法;我想要的服务,用户有一个需求,但无法自己实现,因此他在此悬赏,很多用户一起悬赏,最终有人接受挑战。其出发点,一个是要实现自己的创意、一个是要满足自己的需求。

      开个玩笑呵呵,Kickstarter 像是明星在台上表演,下面一群观众在欢呼;我想要的,是一群民众遇到了困难议论纷纷,牛仔望着他们渴盼的神情,提枪策马而去,迎接挑战。

       

      威客类网站

      先说句废话呵呵,一般说国外服务的时候,都可以指名道姓;而说国内的服务,都需要说“xx类”,威客类、SNS 类、微博类⋯⋯。我们总是在一窝蜂,什么时候可以不这么功利、可以有自个独特的品味呢?敢不敢弄点赚钱以外的追求、弄点发财以外的梦想?⋯⋯

      话归正题,所谓“威客”这个看似舶来品实际上是国人发明的词汇,意思是你有什么特长(如设计、开发、文案),然后在网上提供服务,获取报酬。用猪八戒网的口号来说,你在淘宝上买商品、在威客上买服务。比如你们公司要做一个官方网站,就可以上去发布一个需求,可以模拟现实世界中的商业招标的流程。

      你可能会说,这不是很好么?Yeah, that makes sense, but it’s not awesome! 用自己的技能赚钱,自然是木有什么可说的,但是这太商业了,太商业了,太商业了!各种功利钻营,一点都不 Cool!(顺道说一下,猪八戒网专门搞了个发需求、返现金的功能,这是现实世界中拿回扣的数字版么?真够中国特色的呵呵。还大张旗鼓作为一个独特功能来宣传,中国人到底还要不要脸?还有没有底线?)

      那么,如何才算 Cool?首先,整个社区应该是友善的,用户欣赏开发者、开发者从用户获得灵感与支持;其次,开发者必须保留对作品的所有权,雕琢以求完美,而不是被商业买断,为了混饭敷衍了事。当然,还有其它措施,容后详述。我想要的,是一股不排斥金钱、可实际运作,但不会铜臭泛滥、没有尔虞我诈,友善、良性前进的力量。

      补充:CSDN 的项目交易频道、国外的 freelancer 都跟上面说的类似。TaskRabbit 稍微有趣一点,与其它主要要求在电脑前工作的网站不同,TaskRabbit 上发布的主要是需要跑腿的体力活。

       

      Quirky

      如果你有一个产品构想,但是无法自己实现,可以考虑把它写到 Quirky 上。它背后有一个专业的团队,会评估你所构想的产品的市场可行性;如果他们觉得可行,就会把产品生产出来,拿到零售市场上去卖;而你作为产品的“发明者”,可以得到一定的利润。

      这个东东有点意思,但它不是我想要的。一方面,它专注于现实的生活用品的设计制造,而非软件开发;另一方面,我想要的是一个社会协作的状态,而非以一家公司为核心甚至全部。

      C2C 作品:

      还没发现呵呵,这玩意儿重点是它的设计制造能力和零售渠道,编程技术反而没那么重要。Copy 有难度啊⋯。

       

      好吧,废话够多了,该回到我们正题了:

      How to be awesome?

      我的设想:

      • 开发者必须保留对作品的完全所有权,而非商业买断。只有这样,才可能斟酌以求完美,而非敷衍混饭吃。
      • 作品必须开源。只有开源,作品才能经过同行评审,迫使每一个开发者自觉提高对自己的要求;只有开源,作品才能成为我们公开的名片,才能激发我们的个人荣誉感,而不只是为生计奔波。只有开源,个人的努力才能汇聚成社会的财富,而非沉寂于遗忘的角落。
      • 用户悬赏的金钱必须收下。是的,这是骄傲,收钱不伤感情。
      • 作品完成后,鼓励拿到各种 market 推广,以使(悬赏人之外的)更多用户受益;鼓励收费,付出需要认可。
      • 鼓励每个用户悬赏小笔金额,而不是相反。多用户共同悬赏,而非单用户买断。
      • 用户可以悬赏开发一个全新的产品,也可以悬赏完善现有的开源项目(于是,这就是与前面的开源要求相呼应了,不开源,作者撒手后用户就再也得不到支持)。

      “Social” 体现在哪些方面?

      • 需求搜集:多用户共同协作、启发、完善。
      • 悬赏资金:许多用户各投一点小钱,而非一人独自承担。
      • 项目推介:不只是开发者自己推广,用户也会努力一起推广。因为更多人参与悬赏,每个人需要投入的金额就会更低、吸引开发者接招的可能性就会更大。
      • 开发资源:因为开源,开发者可以站在前人的肩膀上,在庞大的开源宝库的基础之上进行重用与创作。
      • 开发过程:因为开源,整个开发过程是透明的,社区可以适时了解产品的开发状态,甚至参与开发。Release early, release often? We release at the first step, release at every step!
      • 后期维护:因为开源,即使作者消失了,他的作品永远流传我们的世界,并可被后人继续完善。

      对用户的价值?

      • 一个人只需悬赏很少的金额,就可以找到有品味的程序员帮你实现需求。基本无疑会超越外包公司的质量,可说是物美价廉。
      • 有共同爱好的用户和你一起脑力激荡,很可能比你一个人提所谓的需求,更全面、有创意。
      • 开发过程全透明,你得到的不再是一个黑盒子。
      • 作品完成后,作者很可能会无条件长期完善;即使原作者不再完善,你也很容易能找到其他人。
      • 最后,这整个过程都很 Cool(是的,这很重要!)。

      对开发者的价值?

      • 你可以在这上面得到很多创意和启发。很多时候,我们不是没有时间,而是不知道可以做点什么有实际价值的东西(特别是对学生、freelancer 来说)。
      • 你可以了解有多少人关注这个项目,这相当于帮你把前期的需求调研做好了;而且,有一笔启动资金送到你的手里。还有比这更棒的么?
      • 在这里活动的用户,很可能是很有经验的程序员、设计师、产品经理,他们可能只是没有时间亲自实现自己的所有需求。因此,你面对的不再是无知却又把自己当爷的客户,而是一群很有素养和经验的人,他们尊重你的努力,甚至会夸赞你代码的优雅;他们不只是提需求,甚至还提供创意、设计、技术和管理的经验。这将是你和用户共同创造的值得骄傲的作品,而非相互埋怨和忽悠后的垃圾。
      • 你拥有作品的完全所有权。
      • 因为开源,这不再是一做就扔的项目,而会高高挂在你的 Profile 中成为骄傲的记录。甚至在你退役后,江湖仍会流传你的传说。:-)
      • 这整个过程都很 Cool(是的,这很重要!)。

      对社会的价值?

      • 用户享用了很棒的产品;
      • 开发者获得了金钱、经验和荣誉;
      • 产品开发尽可能继承了前人的成果、产品完成后尽可能惠及了更多的用户,不重复造轮子节约了社会资源;
      • 结果开源,促成人类公共财富量的增长;
      • 鼓励完善已有开源项目,促成开源库整体质的提升。

      Wow! 这将是一个颠覆吗?定位?

      No,这不是什么颠覆,它更多是对其它模式的补充,而非替代。实际上,这可能只会是一个小众服务,公益性质的、在软件开发及相关群体内部受推崇,但普通用户不太熟悉的服务。实现这个服务,应该能养活自己,甚至还能赚点小钱,但肯定不会让你发财。

      但这个想法是如此之酷,以致于我很想要把它做出来。程序员写了那么多软件,难道就不能为自己写一个么?⋯⋯好吧,其实我打这么多字,是想有人实现我这个愿望,因为我最近确实是没有时间精力⋯⋯。

      目标用户?

      所有用户都可参与需求与悬赏,但应该还是以软件开发相关的群体为主,就像上面说过的:程序员、设计师、产品经理、Geek、软件爱好者等。

      至于如何推广到其他用户群体,这是后话,不能太乐观呵呵。

      目标开发者?

      学生、自由职业者、部分有余力的在职程序员。

      需要注明的是,虽然程序员能在这里赚到钱,甚至有可能赚大钱,但是最好不要把这当成主要的养家糊口的来源。而应该是一种乐趣与荣誉。要强调这种感觉。

      另外,这也是有经验的程序员培养新生程序员的训练场,所以我们会要求掌握 Github 这样的工具作为 Best Practice、培养基本的职业素养,而非像皮包公司那样纯粹赶工。程序员的集体行为主义 Ah?

      市场?推广?

      Social, social, social…。国内的、国外的,可以连接的都连接上吧,用户和开发者都有很强的动机将项目推广到他们的圈子里去。不被吸引的程序员不是好程序员,不被吸引的 Geek 不是好 Geek。^_^

      下一步怎么办,怎么推广到普通用户中去?下一步再说,能把第一步做好,已经很 Cool 了~!

      商业模式?

      用户投的悬赏金,在交给开发者之前就是很好的现金流啊。另外,你会想要贪污其中的 10% 么~。

      还有什么模式?广告啊、社区啊、招聘啊⋯⋯。

      总之,你应该能赚点酒钱(哦,忘了说,还有荣誉),但别抱着发财的心态来。

       

      最后:

      我承认我受原教旨黑客精神影响很深,有些理想主义。但我还没决定要做,主要是业余时间精力实在有限。写出来,是希望有认同这个想法,正好又有机会的童鞋把它做出来。。。

      另外,@nan8_8 童鞋贡献想法说,可以用同样的思路,悬赏人把这服务本身做出来。什么,A compiler that compiles itself ? 太 Cool 了!难道我现在应该正正经经写个需求,然后投入第一笔悬赏金,宣布启动么?冷静,冷静,先看看反馈再说⋯⋯ ^_^ 。

       

      Credit to: @lidake007 @libuchao @Leaskh @BreeStealth @soulhacker @klx1204 @zxq3206 @nowBOK @clc4214 @hanfeiyu @likuku @MissevanNews @sankmaru79 @nan8_8 @kenny_yuan @xmdingsky @ursatong and more…

       

      —-

      P.S.: 推荐大家看一个公开课。在这个个人越来越独立的时代和潮流下,传统的庞大的公司组织显得臃肿而乏味,我们需要思考,如何创造一种模式适应工作越来越个性化的需要。这个课程,让我不自觉地关注了以上那些服务。

      's avatar

      修改 OpenVPN 实现加密算法的自动协商

      另一篇博中的分析可知,OpenVPN 中有两个加解密通道。一条是标准的 SSL 协议通道,被 OpenVPN 用于协商自己所用的密钥。这个通道的加密算法当然也是通过 SSL 协议来进行协商的,可以通过 --tls-cipher 选项来进行配置。另一条是 OpenVPN 自己的加解密通道,用于交换实际的数据,也就是虚拟网卡抓到的 IP 报文。这个通道的加密算法则是通过 --cipher--auth 两个选项,分别在通调两端指定的。

      对于第二条通道的加密算法,必须要同时在两端分别指定一致的选项,有时候不是很方便(当然,我研究的还是 2.1.1 版本的 OpenVPN ,不知道最新的版本还是不是这样)。比如说,我想通过在服务端修改配置,指定加密算法,然后让连接我的客户端自动用同一个算法。最简单的修改思路,就是借用第一条通道中的算法协商机制,从 SSL 对象中取得协商出来的算法。

      具体做法就是:

      首先,在函数 do_init_crypto_tls_c1() 中,去掉对函数 init_key_type() 的调用。这个调用就是根据 --cipher--auth 选项进行算法配置的地方,我们要动态协商,自然是不需要这个了。

      但同时,这会引起接下来一个步骤的错误。在函数 crypto_adjust_frame_parameters() 中,会根据之前配置的算法进行报文中密钥空间的分配。现在还不知道算法,怎么知道要分配多少空间呢?就只能改成最大值了。分别改为 MAX_CIPHER_KEY_LENGTHMAX_HMAC_KEY_LENGTH 就行了。当然,这样改不仅浪费空间,而且也不够严谨,因为 key length 和 IV length 不是一回事,却只能都用 MAX_CIPHER_KEY_LENGTH 来初始化。

      最后就是在 SSL 协商好之后,从里面取加密算法了。具体位置在 key_method_2_write()key_method_2_read() 两个函数中,对 generate_key_expansion() 函数的调用之处了。在调用之前,初始化一下 key_type 就行了:

      struct key_type *key_type = (struct key_type *) session->opt->key_type;
      key_type->cipher = ks->ssl->enc_read_ctx->cipher;
      key_type->cipher_length = EVP_CIPHER_key_length (session->opt->key_type->cipher);
      #if OPENSSL_VERSION_NUMBER >= 0x010000000L
      key_type->digest = ks->ssl->read_hash->digest;
      #else
      key_type->digest = ks->ssl->read_hash;
      #endif
      key_type->hmac_length = EVP_MD_size (session->opt->key_type->digest);
      

      这样就行了。

      's avatar

      编写自己的 Web 版 Google Reader 客户端

      最近花在路上的时间明显变多了,手机就成了我打发这些路上时间的利器。小说神马的看太多也会腻,正好 Google Reader 中也积累了大量的未读文章,因此用手机看看订阅文章就成了一个比较好的选择。

      但多次使用下来,却也发现了很多 Google 原版 Web 界面的不方便之处。Google 提供了两个版本的 Reader 界面给手机,一个是苹果风格的 /i/ 界面,设计得很漂亮,功能也很全,用 Nokia 手机也能看,但太费流量(未证实,只是感觉,大概是因为一次加载了很多的文章吧),而且在网络状况不好的时候,体验不太好。另一个是 /m/ 界面,很简洁,选项不多,但也总有些这样那样的细节另我不太满意,下面详述。然后又去找 Nokia 的 S60v3 客户端软件,总之是没一个喜欢啦。作为一个 Geek (至少是自称),在这种情况下,最佳选择自然就是打造一个自己专属的解决方案啦。

      正好蹭着同学的一个 PHP 空间(其实也有 Python 和 Ruby 可选啦,但总觉得会很折腾,还是 PHP 好点,虽然折腾也少不了,见下),于是花了几天时间,连复习 PHP (以前只学过没用过,主要还是看 w3schools 的教程和查 php.net 的函数文档)、研究 OAuth (参见这篇文章,不过 Web 应用稍有不同)、研究 Google Reader API (参见这系列文章)、编写代码,终于搞定了一个很阳春的 Google Reader Mobile 版,刚刚满足我的手机阅读需要。

      以下就是我针对 Google 原版所做的,针对我个人的改进:

      • 保存期限的问题:以前也有感觉,这次趁着做这个东东,又确认了一下:Google 只会保存 30 天内的未读文章,超过 30 天的就自动标记成已读了。我希望能保存下来慢慢看,所以就搞了一个简单的 SQLite 数据库,定时把曾经是未读文章的 id 号都给保存下来,直到我通过这个界面读过之后再删除。嗯,这个功能是最重要的。
      • 先看最老的文章:当然,通过这两天对 API 的研究,我已经知道怎么在 Google 的 /m/ 界面中实现这个功能了,不过为了上一个功能,打造自己的界面还是必要的。
      • 重复载入的问题:当我要给一篇文章加星的时候,我一般都希望能直接看到下一篇文章,而不是再看一次被加星的这篇。这也是一个很小的改进啦,不过用起来很舒服,既节省了流量,也节省了时间。
      • 加载文章的时候先不要标记为已读:由于手机的网络状况通常不太好,当我浏览完一篇,想加个星标的时候,结果页面却打不开了,这种情况经常发生。所以,我就把标记为已读的这个步骤,挪到了看一下篇之前,或者加星标之后。这样,就比再去已读文章里面找要来得方便。
      • 去掉 /m/ 的所有其他功能:当然,这不是一个改进啦,只是我不需要而已。既然是用手机来看,当然就没有那么多需要啦,只要能实现浏览与加星标就好了。需要细看的,就加个标,等有电脑的时候再慢慢看。本来, Google Reader 的星标功能,除了作为 ToRead 列表外,就没什么其他用处了(当然,以后也许会跟搜索整合,然后优先显示神马的,那就不管了)。不过话说回来,功能的简化其实也是一种改进啦,最近刚好看到 InstaPaper 的作者也这么说来着。
      • 功×夫×网:不过其实 Google Reader 并没有被封的很严,倒是我自己的实现招来了这个问题,见下。

      简单来说,就是按顺序,对所有文章,看,(可选地)加星标,然后下一篇。需求往往就这么简单啊。

      当然,在做的时候,还是遇到过各种稀奇古怪的问题( Neta :编程好难啊),这里也一并记录下。

      先是在自己机器上 OK 了,等部署到空间的时候却报错: an error occured while processing this directive 。同样的脚本,放到其他目录就没事,还以为是 Apache 的权限问题,也没找到有什么特别的配置。直到有机会查看 Apache 的错误日志,才发现原来就是因为新加的这个目录有组的写权限,而 Apache 不允许这样,真是吐血。话说加上组的写权限,也是为了 SQLite 来着,因为 SQLite 会在数据库所在目录中写事务日志文件。

      然后就是发现,我用的 SQLite3 的 PHP 接口,在空间的 PHP 5.2 版本中还没有呢。真是悲剧。然后又吭哧吭哧地用 PDO 接口重写了数据库的代码。

      等到好不容易改好啦,用电脑访问也一切正常,结果用手机访问的时候,看不了两个页面,就会出现“连接被远程服务器关闭”的错误。一开始还没有往功×夫×网的方向想,只以为又是服务器的权限问题(我为什么要加“又”?),来回地看日志,切换测试,不停地折腾。直到在电脑上偶然看到,该网页需要翻×墙的标志,再一看 URL 才恍然,原来是我传的一个 GET 参数,是该文章的订阅源 URL ,而有些文章是从 Feed×Burner 订阅的,这才触发了功×夫×网。真是躺着也中枪啊,赶急加一个 Base64 编码搞定。

      不管怎样,一个简陋的阅读器就这样被实现出来了。鉴于我近期还不想烧钱换手机,这个东东应该还是很有用武之地的吧。不知道 Google 出的 Android 客户端有没有 30 天的限制。

      最后的最后,该版本仅限我个人使用,因为我没有实现多用户的功能,所以大家(如果有的话)也就不要去尝试访问了。我之后会把代码给开源的,到时随你(如果有的话,嗯)折腾。


      Update: 代码已上传

      Update 2012-12-31: 现在有了 Android 手机,已经开始用 gReader 这个客户端了,除了“延迟标记已读”这个功能还没有之外,其他所需功能已经都有了,而且还是离线阅读哦(我自己的版本就没有实现离线阅读这个功能,因为太麻烦)。不过我的数据库里面已经存了 7000 多篇文章没看了……囧

      's avatar

      [转载] Compilers: what are you thinking about?

      最近在翻看以前加星的 Google Reader 文章,把有用的整理出来打标签,然后就看到了这篇。原文作者的博客现在不知道被丢到哪边去了,搜也搜不到,转到这里,权当保存一下吧。

      原文链接:Compilers: what are you thinking about?。当然,已经打不开了。


      Compilers: what are you thinking about?

      Author: Rotten Cotton

      My recent post Compiler bibliography, a motley list of compiler papers that had been sitting in a box in my attic, generated a surprising amount of traffic: over a thousand unique visitors. But no comments. For those of you interested in compiler design, I ask, what are you trying to understand? What are you trying to do?

      I imagine some of you are interested in understanding how compilers work and, more importantly, how to build them. The first step in learning to design compilers is to build one. (Tautologous, no?) To start, I recommend following the syllabus of a compiler design course. Googling “compiler syllabus” returns a massive list of syllabi for compiler for compiler classes. The first link, a compiler design class, at Portand State University, follows Louden’s book Compiler Construction to build a SPARC compiler for a toy programming language, PCAT (pdf). MIT’s compiler design class, 6.035, is on OCW. Following one of these classes will teach you the basic challenges involved in designing a compiler and the organizational principles that have emerged to solve these problems. If you’re ambitious, start with a free C front end and build a C compiler for your favorite architecture. There is ckit for ML, Language.C for Haskell or clang (part of the LLVM project) written in, I think, C++. There are others, no doubt. This way, you won’t have to write a front-end and you will have a huge source of potential examples on which to test your compiler and, when your start to design optimizations (the fun part), explore your compiler’s performance.

      For a few years after I first took 6.035, whenever the course was taught again and the project (toy language) for that semester was announced, I would spend a long caffeine-fueled weekend hacking out a new compiler. I must have build three or four compilers this way, targeting different architectures and exploring various design choices. This was a very valuable experience.

      Once you understand the basics of compiler design, what’s next? There are a number of directions you can go.

      You can study programming language features and their implementation in a compiler and runtime. For this, I recommend a book like Scott’s Programming Language Pragmatics or Turbak and Gifford’s Design Concepts in Programming Languages. I used a draft version of the latter in MIT’s Programming Languages class, 6.821. This will move beyond compiling the standard C- or Pascal-like toy languages standard in most first-year compiler courses. Learn about semantics: denotational, operational and axiomatic. This is important if you (a) want to build a correct compiler, and (b) as a theoretical foundation for program analysis.

      At the other end of the spectrum are computer architectures. Go learn about computer architecture: instruction set architectures and micro-architectures, their implementation. There are many interesting architectures out there to study. Write assembly language programs by hand. Learn to extract maximal performance form an architecture on small examples. Pay attention to the techniques used to extract performance when hand-coding. These will be the basis for compiler optimizations. The standard text for computer architecture is probably Hennessy and Patterson’s text, Computer Architecture: A Quantitative Approach.

      A deep understanding of both the semantics of your input programming language and your target (micro-)architectures is essential to building a compiler that generates high-performance code. Program analysis and optimization is the bridge between the input program and assembly language. Start with a book on advanced compiler design like Muchnick’s Advanced Compiler Design and Implementation (or maybe the new dragon book, I haven’t looked at it) or start diving into the literature or program analysis and optimization.

      What are you trying to understand about compilers?

      's avatar

      [翻译] 用 Ruby 写编译器之六:匿名函数 lambda

      原文链接:http://www.hokstad.com/writing-a-compiler-in-ruby-bottom-up-step-6.html


      既然上次已经提到说,我们其实是在从 Lisp、Scheme 还有类似的其他语言中借鉴各种要实现的功能(我没想过要把这个项目做成原创的⋯⋯或者至少也要等到以后再说吧),那么现在也是时候实现一些更加强大的功能了。

      那就来做延迟求值以及匿名函数吧

      Lambda ,又名匿名函数,可以像普通的数值或者字符串类型那样被当作函数参数来到处传递,也可以在需要的时候才调用(当然不调也可以)。同时,外层函数(也就是定义匿名函数的函数)作为它们的运行环境,在其中定义的局部变量可以被这些匿名函数所访问。这就形成了一个闭包。我们这次并不是要实现完整的闭包功能,只是开头的一小步而已,完整的实现要等到再后面了。

      其实说到底,正如编程语言中的其他很多功能一样,闭包也只是又一种语法糖而已。比如说,你可以认为这样其实是定义了一个类,这个类中有唯一一个需要被调用的方法,还有一些作为运行环境的成员变量(或者你也可以反过来用闭包来实现面向对象系统 – 这是由 Wouter van Oortmerssen 所提出来的观点。自从我发现 Amiga E 这个项目之后,作为作者的他就成了我的偶像。如果你是一个编程语言方面的极客的话,那你就一定要去看看 Wouter 所做过的东西) – 有很多功能其实都是相互正交的。

      ( blah blah blah ⋯⋯这个人在说自己很啰嗦之类的,就不翻了)

      那么,为了避免定义很多具名小函数的麻烦,同时降低函数重名的概率, lambda 允许你在任何需要它们的地方进行定义,并且返回一个表示所定义函数的值。

      我们现在所要增加的是像下面这样的东西:

      (lambda (args) body)
      (call f (args))
      

      第一个表达式会返回所定义的函数(目前来说,其实就是这个函数的起始地址),而不是去执行这个函数。而 call ,当然就是以指定的参数列表,来调用传给它的那个函数地址。

      那么,这跟“真正”的闭包又有什么区别呢?

      最重要的一点就是,当你在 lambda 中引用了外层作用域中的某个变量后,那么,当以后对同一个 lambda 进行调用时,这个变量还是可以访问的。这样的变量与 lambda 绑定在了一起。当然,只是得到一个函数的地址的话,肯定是实现不了这个功能的。让我们来看一种实现闭包的技术吧,这样你就能够了解大概所要做的工作了(工作量不大,但也不小):

      • 我们可以创建一个“环境”,一个存放那些被引用到的外部变量的地方,以使得当外层函数返回之后,它们也可以继续存在。这个环境必须是在堆中,而且每次对外层函数的调用,都需要创建一个新的环境。
      • 我们必须返回一个可以用来访问这个环境的东西。你可以返回一个对象,其中的成员变量就是被 lambda 引用到的那些变量。或者你也可以用一个 thunk (中文叫啥呢?),也就是自动生成出来的一个包含有指向这个对象指针的小函数,它会在调用我们的 lambda 之前将这个对象加载到预先指定的地方。或者你也可以用什么其他的办法。
      • 你必须决定有哪些变量需要放入这个环境中。可以是外层函数中的所有局部变量,当然也可以只是那些被引用到的变量。后一种作法可以节省一定的内存空间,但是需要作更多的工作。

      好吧,还是让我们先来把匿名函数本身给弄出来吧。就像以前一样,我会一步一步地说明所要做的修改,但我同时也会对之前的代码做一些整理。这些整理的部分我就不一一说明了。

      首先是对 lambda 表达式本身进行处理的方法:

        def compile_lambda args, body
          name = "lambda__#{@seq}"
          @seq += 1
          compile_defun(name, args,body)
          puts "\tmovl\t$#{name},%eax"
          return [:subexpr]
        end
      

      这个实现应该是很容易理解的吧。我们在这里做的,就是给要定义的匿名函数,生成一个形如 lambda__[number] 的函数名,然后就把它当作一个普通函数来处理了。虽然你也可以就把它生成到外层函数的函数体中,但我发现那样做的话,就会显得很乱的样子,所以我现在还是就把它作为单独的函数来处理了。然后我们调用 #compile_defun 方法来处理这个函数,这样的话,这个函数其实也就只有对用户来说,才是真正匿名的了。然后我们把这个函数的地址保存在寄存器 %eax 中,这里同时也是我们存放子表达式结果的地方。当然,这是一种很懒的作法啦,我们迟早需要为复杂的表达式,实现更加强大的处理机制的。但寄存器分配果然还是很麻烦的一件事情,所以现在就先这样吧(将所有的中间结果压入栈中也是一种可行的作法啦,不过那样比较慢)。

      最后,我们返回一个 [:subexpr] ,来告拆调用者到哪边可以得到这个 lambda 的值。

      之后是一些重构。你也许已经注意到了, #compile_exp 中的代码有点乱,因为要处理不同类型的参数。让我们把这部分代码给提取出来:

        def compile_eval_arg arg
          atype, aparam = get_arg(arg)
          return "$.LC#{aparam}" if atype == :strconst
          return "$#{aparam}" if atype == :int
          return aparam.to_s if atype == :atom
          return "%eax"
        end
      

      要注意的是,这里又出现了一个新的 :atom 类型。借助于此,我们就可以把一个 C 函数的地址传给 :call 指令了。反正实现起来也很简单。当然,我们还要在 #get_arg 方法中加上如下代码,以使其生效:

          return [:atom, a] if (a.is_a?(Symbol))
      

      然后,作为重构的一部分,对 :call 的处理被分离了出来,成为一个单独的方法:

        def compile_call func, args
          stack_adjustment = PTR_SIZE + (((args.length+0.5)*PTR_SIZE/(4.0*PTR_SIZE)).round) * (4*PTR_SIZE)
          puts "\tsubl\t$#{stack_adjustment}, %esp"
          args.each_with_index do |a,i|
            param = compile_eval_arg(a)
            puts "\tmovl\t#{param}, #{i>0 ? i*4 : ""}(%esp)"
          end
      
          res = compile_eval_arg(func)
          res = "*%eax" if res == "%eax" # Ugly. Would be nicer if retain some knowledge of what res contains.
          puts "\tcall\t#{res}"
          puts "\taddl\t#{stack_adjustment}, %esp"
          return [:subexpr]
        end
      

      看起来很熟悉对不对?因为它就是原来的 #compile_exp 方法,只不过是用 #compile_eval_arg 替换掉了其中的一些代码。另外有改动的地方,就是同时也用 #compile_eval_arg 方法来得到要调用的函数,并对可能得到的 %eax 做了些手脚,在前面加了个星号。

      如果你知道这是怎么回事的话,你也许已经开始寻思其他的点子了,而不管那是真正的好事,还只是开枪打自己的脚。上面的代码其实就相当于,你把任意一个表达式的值当作指向一段代码的指针,然后也不做任何的检查,就直接跳过去执行它。如果是往一个随机的地址进行跳转的话,你最有可能得到的就是一个段错误了。当然,你也可以很容易地通过这项技术,来实现面向对象系统中的虚函数跳转表,或者其他的什么东西。因此,安全性将会是以后必须要考虑的东西。还有就是,要实现对一个地址(而不是函数名)的间接调用,你必须要在这个地址前面加上星号。

      那么,现在的 #compile_exp 方法变成什么样子了呢?简单来说就是,变得整齐多了:

        def compile_do(*exp)
          exp.each { |e| compile_exp(e) }
          return [:subexpr]
        end
        def compile_exp(exp)
          return if !exp || exp.size == 0
          return compile_do(*exp[1..-1]) if exp[0] == :do
          return compile_defun(*exp[1..-1]) if exp[0] == :defun
          return compile_ifelse(*exp[1..-1]) if exp[0] == :if
          return compile_lambda(*exp[1..-1]) if exp[0] == :lambda
          return compile_call(exp[1], exp[2]) if exp[0] == :call
          return compile_call(exp[0], *exp[1..-1])
        end
      

      看起来很不错,不是吗?#compile_call 几乎跟之前的 #compile_exp 一模一样,只不过是把一些代码给提取出来,成为了辅助方法而已。

      那么就来简单地测试一下吧:

      prog = [:do,
        [:call, [:lambda, [], [:puts, "Test"]], [] ]
      ]
      

      (看起来也没那么糟不是吗?)

      编译运行之:

      $ ruby step6.rb >step6.s
      $ make step6
      cc      step6.s  -o step6
      $ ./step6
      Test
      $
      

      这篇的代码在这里

      之后的部分

      因为我合并了几篇文章,下面就列一下更新过后的,“已经写完但还需要整理的”文章列表。因为我肯定还会合并下面的某些部分的,所以我想我需要找时间开始写一些新的部分了(为了完成我所定下的 30 篇的计划)。

      • 步骤七:再看用匿名函数实现循环,以及对函数参数的访问
      • 步骤八:实现赋值语句以及简单的四则运算
      • 步骤九:一个更简洁的 while 循环语句
      • 步骤十:测试这个语言:实现一个简单的输入转换模块
      • 步骤十一:重构代码生成器,分离架构相关的部分
      • 步骤十二:对某些功能点的讨论,以及未来的前进方向
      • 步骤十三:实现数组
      • 步骤十四:局部变量以及多重作用域
      • 步骤十五:访问变长参数列表
      • 步骤十六:再看输入转换模块,重构以支持新功能,并用其解析它自己
      • 步骤十七:总结实现自举所需要的功能点
      • 步骤十八:开始实现真正的解析器
      's avatar

      [翻译] 用 Ruby 写编译器之五:整数常量,以及 if 语句

      原文链接:http://www.hokstad.com/writing-a-compiler-in-ruby-bottom-up-step-5.html


      上次我承诺会发布的更快一些,不过还是失败了⋯⋯作为补偿,这章的内容将会是原计划中的第 5,6,7 章内容的合并,因为这三章确实都很短。闲话少叙:

      处理数字常量

      到目前为止,我们只处理了一些实现所必须的数字常量,也就是当一个外部函数的返回值是数字的情况,而且没有做任何形式的类型检查。

      那么,就让我们来看一下 gcc 在 C 语言中是怎样处理各种类型(包括 long long 等)的整数的吧。当然,这次还是针对 32 位的 x86 架构:

      void foo1(unsigned short a) {}
      void foo2(signed short a) {}
      void foo3(unsigned int a) {}
      void foo4(signed int a) {}
      void foo5(unsigned long a) {}
      void foo6(signed long a) {}
      void foo7(unsigned long long a) {}
      void foo8(signed long long a) {}
      
      int main()
      {
        foo1(1);
        foo2(2);
        foo3(3);
        foo4(4);
        foo5(5);
        foo6(6);
        foo7(7);
      }
      

      我略掉了大部分 gcc 所生成的代码,如果愿意,你可以自己执行 gcc -S 命令来看。有趣的部分是对各个函数的调用,其生成的代码如下:

              movl    $1, (%esp)
              call    foo1
              movl    $2, (%esp)
              call    foo2
              movl    $3, (%esp)
              call    foo3
              movl    $4, (%esp)
              call    foo4
              movl    $5, (%esp)
              call    foo5
              movl    $6, (%esp)
              call    foo6
              movl    $7, (%esp)
              movl    $0, 4(%esp)
              call    foo7
      

      换句话说,至少在处理函数调用的时候,gcc 都会把各种整数类型统一作为 32 位的整型来处理,除了 long long 类型之外。因此,我们可以暂时忘掉 long long 类型,而只处理 32 位以内的整数值,这样我们就可以忽略类型处理相关的东西了。

      懒惰还真是一种病啊。

      我们同时也会略过浮点型。为什么呢?因为只要有整数运算,你就可以实现一个完整的编译器了,所以现在就加入对浮点型的支持完全只是浪费时间而已。当然这个东西以后总归是要做的。

      另外,当还年轻时,我们连 FPU 是啥都还不知道呢。尽管如此,我们依然可以用整数来模拟各种定点运算,一样可以完成很多事情。

      那么,我们真正要做的修改有哪些呢?

      在方法 #get_arg 中,在处理字符串常量之前,加入如下代码:

          return [:int, a] if (a.is_a?(Fixnum))
      

      在方法 #compile_exp 中,我们用如下代码来处理 #get_arg 的返回值:

          elsif atype == :int then param = "$#{aparam}"
      

      然后,就完事了。就这么简单。

      然后就是测试啦:

      prog = [:do,
        [:printf,"'hello world' takes %ld bytes\n",[:strlen, "hello world"]],
        [:printf,"The above should show _%ld_ bytes\n",11]
      ]
      

      插曲:针对原生数据类型的一些思考

      “纯”面向对象类型的语言很棒,但并不是对底层的代码生成器而言的,我想。不管你是否想要实现一个纯的面向对象语言,我仍然坚信,首先实现原生的数据类型和其操作,是非常有价值的。你可以在后面的阶段再来考虑是否要对用户隐藏它们,或者是让它们看起来像是对象,或者是透明地在它们和对象之间执行自动转换的操作,等等。

      要注意的是,Matz 的 Ruby 解释器(Matz Ruby Interpreter,简称MRI)就是这样实现的:里面的数字就跟“真正的”对象神马的完全不一样,但是解释器本身却尽其所能的对用户隐藏这一事实。不过我个人认为 MRI 做的还是不够。

      If ... then ... else

      如果没有某种形式的条件逻辑支持的话,我们的语言是没有太大用处的。几乎所有有用的语言都支持某种形式的 if .. then .. else 构造。现在,我们要实现的是像 [:if, condition, if-arm, else-arm] 这样的构造,而且是以 C 语言的形式来实现。也就是说, 0 和空指针都表示假,其他值则都为真。

      仍然是一个简单的例子:

      void foo() {}
      void bar() {}
      int main()
      {
        if (baz()) {
          foo();
        } else {
          bar();
        }
      }
      

      相关的汇编输出:

              call    baz
              testl   %eax, %eax
              je      .L6
              call    foo
              jmp     .L10
      .L6:
              call    bar
      .L10:
      

      对于大多数的语言和架构来说,这都是一个编译 if .. then .. else 时会采用的通用模板:

      • 计算条件表达式。
      • 对结果进行测试(这里用的是 testl 指令 – 在其他架构中比较通用的还有 cmp 指令,或者对寄存器进行自减)。 testl 指令比较它的左右两个操作数,并进行相应的标志位设置。
      • 然后,条件跳转到 else 语句处。这里,我们检查条件表达式的值是否不为真。在这种情况下我们用的是 je 指令,即“相等时跳转”( jump on equal ),也就是当结果相等时跳转(要注意的是,在大多数的 CPU 架构中,很多指令都会设置条件码,而不仅仅是显示的测试指令)。
      • 然后执行 then 子句。
      • 跳过 else 子句,继续执行整个 if 语句之后的部分。
      • 生成 else 子句的标号,以及其中的指令序列。
      • 生成 if 语句的结束标号。

      其他还有很多不同的变种,比如根据条件表达式取值的概率,或者某个架构中是否跳转的执行代价,进而调整两个子句的顺序等。不过就目前来说,上面的方法已经足够了。

      总之,编译的方法还是很简单的,应该说就是以上所述流程的直译:

        def ifelse cond, if_arg,else_arm
          compile_exp(cond)
          puts "\ttestl\t%eax, %eax"
          @seq += 2
          else_arm_seq = @seq - 1
          end_if_arm_seq = @seq
          puts "\tje\t.L#{else_arm_seq}"
          compile_exp(if_arm)
          puts "\tjmp\t.L#{end_if_arm_seq}"
          puts ".L#{else_arm_seq}:"
          compile_exp(else_arm)
          puts ".L#{end_if_arm_seq}:"
        end
      

      这段代码应该很易懂的,其实就是对所有子句 – 条件, then 子句,以及 else 子句 – 分别调用 #compile_exp 方法,并在其间插入所需的辅助指令,同时用 @seq 成员来生成所需的标号。

      为了使其生效,我们在 #compile_exp 方法中的 return defun ... 之后插入如下代码:

          return ifelse(*exp[1..-1]) if (exp[0] == :if)
      

      下面是一个简单的测试:

      prog = [:do,
        [:if, [:strlen,""],
          [:puts, "IF: The string was not empty"],
          [:puts, "ELSE: The string was empty"]
        ],
        [:if, [:strlen,"Test"],
          [:puts, "Second IF: The string was not empty"],
          [:puts, "Second IF: The string was empty"]
        ]
      ]
      

      这里是最终结果

      如往常般,执行的方法如下:

      $ ruby step5.rb >step5.s
      $ make step5
      cc   step5.s   -o step5
      $ ./step5
      ELSE: The string was empty
      Second IF: The string was not empty
      $
      

      有关循环的一些思考

      非条件循环是很容易实现的,不过我们需要实现它吗?显然不需要。我们已经可以用递归来实现循环了,又何必要乱加东西呢?

      不过,要令它工作良好,我们还需要实现尾递归优化,可是我现在还没有做好准备。尾递归优化,或者更一般的形式 – 尾调用优化 – 所说的情况是,在一个函数的末尾,调用了一个需要相同或者更少个数参数的函数,并返回它所返回的值。在这种情况下,你可以将当前函数的调用栈,复用给被调用的函数来使用,并且是通过 jmp 而不是 call 来调用这个函数。 jmp 指令不会在堆栈中压入一个新的返回地址,因此当这个被调用的函数返回,返回到的就是当前函数的调用者那里,而不是当前的这个函数。

      这就同时完成了几件事情:首先,也是最重要的,就是堆栈不再会随着调用而增长了。其次,我们能够省掉几个指令周期。有了尾调用优化,再配合其他几个优化之后,你就可以这样来写循环,而不用担心堆栈溢出的问题了:

      [:defun, :loop, [], [:do,
        [:puts, "I am all loopy"],
        [:loop]
      ],
      [:loop]
      

      换句话说,尾调用优化意味着,对任何形如 (defun foo () (do bar foo)) 的函数来说,堆栈的使用率都会从原来的成比例增长减少为定值了。

      当前的版本已经可以编译上面的代码了,不过它会很快用完堆栈并且崩溃掉的。不是很令人满意啊。

      果然(原文: I sense a disturbance in the force ),两位读到这篇文章的极客都指出了堆栈增长的问题。

      现在,让我们先忽略这个问题吧,同时注意下对堆栈空间的使用好了。之后,我们会实现一个专门的循环结构的。目前就这样吧 – 如果以后我实现了尾调用优化的话,我们还可以重新考虑在运行时库中实现一个循环构造的方案。

      不管怎样,我们现在可以写出一个无限循环了⋯⋯不是太有用,不是吗?

      当然,我们其实也已经可以写 while 循环了:

      (defun some-while-loop () (if condition (some-while-loop) ()))
      

      看起来不是太好,不过确实可以工作。但看起来总归还是太丑了,所以我们总归还是要实现一个正尔八经的 while 循环的。

      我不是一个 Lisp 程序员。我没办法处理那么多的括号⋯⋯不过 Lisp 的语法确实很适合于一门还没有语法的语言。到了一定的阶段之后,我会实现一个完整的解析器的。也许是出于偶然,我已经实现的和将要实现的很多东西在某种程度上来说都是取自于 Lisp 的,至少看起来是这样的。如果你能适应 Lisp 的语法的话,这个语言还是非常强大的。就算你不打算用它来开发你的程序,好好地学一下这门语言也是非常值得的。

      在我想来,很多看起来像是从 Lisp 中得来的点子,大概都是来自于我花在学习 Lisp 的有限经验。

      下一篇:匿名函数,也许不止。

      's avatar

      [翻译] 用 Ruby 写编译器之四:自定义函数,以及运行时支持

      原文链接:http://www.hokstad.com/writing-a-compiler-in-ruby-bottom-up-step-4.html


      抱歉,又拖了很长时间。要忙的事情实在很多。正如上一篇文章末尾提到的那样,这次要讲的是自定义函数,以及一个简单的“运行时库”。

      自定义函数

      一门编程语言如果连函数和方法都没有的话,那也就不能算是一门语言了。而且,实践表明,一门面向对象语言中的所有特性都可以通过过程式的语言要素来实现:一个方法也只不过是以一个对象为额外参数的函数而已。因此,增加对函数的支持就是实现一门语言的核心所在。

      其实,这个东东也是很简单的啦。跟以前一样,还是让我们来看一下 C 语言中的函数是怎么实现的吧:

      void foo()
      {
        puts("Hello world");
      }
      

      gcc 生成的汇编代码是这个样子滴:

      .globl foo
              .type   foo, @function
      foo:
              pushl   %ebp
              movl    %esp, %ebp
              subl    $8, %esp
              movl    $.LC0, (%esp)
              call    puts
              leave
              ret
              .size   foo, .-foo
      

      其中的函数调用现在应该很容易认了吧。剩下的就是简单的样板代码了:

      在函数的开头,首先是将寄存器 %ebp 压入堆栈,然后拷贝寄存器 %esp%ebp 。而在函数的最后, leave 指令就是前面两条指令的逆操作,而 ret 指令则是从堆栈中弹出要返回到的指令地址(也就是调用该函数的那条指令的下一条指令)并跳转。为什么在这里要将 %esp (也就是堆栈指针)拷到 %ebp 呢?嘛,一个很明显的好处就是你可以尽情的申请堆栈空间,然后在完事时简单地将 %ebp 拷回给 %esp 就行了。从上面就可以看到, GCC 已经充分利用了这一点,直接用 leave 指令来处理调用函数时对参数所申请的空间 – 反正手工释放也只是浪费时间而已。

      这么说来的话,要做的事情应该就很简单了啊。

      首先需要修改方法 Compiler#initialize ,创建一个用来保存所有函数定义的哈希:

        def initialize
          @global_functions = {}
      

      然后增加一个输出所有函数定义的方法:

        def output_functions
          @global_functions.each do |name,data|
            puts ".globl #{name}"
            puts ".type   #{name}, @function"
            puts "#{name}:"
            puts "\tpushl   %ebp"
            puts "\tmovl    %esp, %ebp"
            compile_exp(data[1])
            puts "\tleave"
            puts "\tret"
            puts "\t.size   #{name}, .-#{name}"
            puts
          end
        end
      

      可以看到,这里也同时包括了 .globl.type.size 之类的东西。 .globl 的意思就是你想让这个函数也能够从其他文件(也就是编译单元)中调用,这在链接多个目标文件的时候是很重要的。我想 .type.size 主要是用在调试的时候,分别用来表示一个符号对应的是一个函数,以及这个函数的大小。

      除了这些之外,这个方法就很简单啦 – 它会通过调用 #compile_exp 方法来完成实际的工作。

      我们再来增加一个用来定义函数的辅助方法:

        def defun name, args, body
          @global_functions[name] = [args, body]
        end
      

      然后在方法 #compile_exp 中增加如下的几行代码:

          return if !exp || exp.size == 0
          return defun(*exp[1..-1]) if (exp[0] == :defun)
      

      之所以要增加第一行代码,一方面是出于健壮性的考虑,同时这也允许我们用 nil 和空数组来表示“啥也不做”的意思,当你要定义一个空函数的时候就会用到这一点了。这样一来,第二行代码就不需要去检查将要定义的是不是一个空函数了。

      不知道你注意到了没有,我们其实已经实现了对函数的递归定义。像 [:defun,:foo,[:defun, :bar, []]] 这样的代码完全是合法的。同时你也许会注意到,这个实现会导致两个函数其实都是可以从别处调用的。好吧,现在是没关系的啦,我们以后会处理这个的(要么不允许编写这样的代码,要么就只允许外层函数来调用内层函数 – 我还没有决定到底要做哪个啦)。

      剩下的事情就是输出这些函数的定义了,因此我们在方法 #compile 中对 #output_constants 的调用之前增加如下的一行:

          output_functions
      

      增加对一个运行时库的支持

      首先,让我们将现在的 #compile 方法重命名为 #compile_main ,然后重新定义 #compile 方法如下:

        def compile(exp)
          compile_main([:do, DO_BEFORE, exp, DO_AFTER])
        end
      

      之后是对常量 DO_BEFOREDO_AFTER 的定义(如果愿意的话,你也可以把它们放在一个单独的文件中,我现在就直接把它们放在开头好了):

      DO_BEFORE= [:do,
        [:defun, :hello_world,[], [:puts, "Hello World"]]
      ]
      DO_AFTER= []
      

      你得承认,你想看到的应该是更加高级一些的东东,但那样就违背我们最初的目标了。上面的代码对于实现一个运行时库来说已经足够了。当然,你也可以用一些只能通过 C 或者汇编才能实现的东西,只要把包含那些函数实现的目标文件给链接进来就可以了,因为我们一直都是在按照 C 语言的调用规则来办事的嘛。

      让我们来测试一下吧。在 Compiler.new.compile(prog) 的前面加入下面的代码:

      prog = [:hello_world]
      

      然后编译运行:

      $ ruby step4.rb >step4.s
      $ make step4
      cc    step4.s   -o step4
      $ ./step4
      Hello World
      $
      

      你可以在这里找到今天的成果。

      对函数参数的访问吗?

      今天还遗留了一个任务:实现对函数参数的访问。这个的工作量可是不小的。放心,我不会忘了这个的,这将会是第八篇文章的主题。我也不会让你等太久的啦,这次一定 :-)

      's avatar

      [翻译] 用 Ruby 写编译器之三:语句序列,以及子表达式

      原文链接:http://www.hokstad.com/writing-a-compiler-in-ruby-bottom-up—step-3.html


      我本来是想要早点发表的,可是我这周又不行了 – 虽然整理一篇旧文只需要半个小时。不管怎样,这是第三章,而且我会在末尾大概列一下之后的大纲。由于我会试着把一些小的步骤组合成更有内容的章节(下面就有个这样的例子),因此原来的 30 篇文章已经被我给减到了 20 篇左右(当然,这只是我已经完成了的,后面还有新的呢)。

      用 do 语句将表达式给串起来

      到目前为止,上次的第二版程序只能编译一个单独的表达式。只是这样的话,并不是非常的有用啊。因此我决定实现一种支持顺序执行的结构,就像函数体那样的。当然,如你所想,这是很简单的。我会增加一个关键字 do ,而其作用就是顺序执行传给它的每一个(个数不限哦,或者说,只受限于内存的大小)参数表达式。看起来就像这样:

      prog = [:do,
        [:printf,"Hello"],
        [:printf," "],
        [:printf,"World\n"]
      ]
      

      要实现这个是非常简单的。我们只需要在函数 #compile_exp 的开头加入下列代码:

          if exp[0] == :do
            exp[1..-1].each { |e| compile_exp(e) }
            return
          end
      

      递归在这里的作用很重要哦 – 毕竟你是在处理一个树形结构,那也就需要在越来越深层的树结点之上调用实现编译的核心函数,而这当然也包括我们的下一个目标,即对子表达式的处理。

      子表达式,步骤一

      先来给出一个我们想要支持的用例:

      prog = [:printf,"‘hello world’ takes %ld bytes\n",[:strlen, “hello world"]]
      

      第一个需要改变的地方,在函数 #get_arg 中,我们在其开头加入如下的代码:

          # Handle strings or subexpressions
          if a.is_a?(Array)
            compile_exp(a)
            return nil # What should we return?
          end
      

      如果你这时已经试着用上面的代码来编译测试用例了的话,gcc 会报错给你的,因为我们现在只处理了 #get_arg 的返回值是一个字符串常量对应的序列号的情况,而这对子表达式来说显然是不适用的。

      子表达式,步骤二:返回值

      那么 gcc 是怎么处理这个的呢。让我们来看看下面这段代码:

      int main()
      {
        printf("'Hello world' takes %ld bytes\n",foo("Hello world"));
      }
      

      所产生的汇编吧(只截取 main 中相关的部分):

          subl    $20, %esp
          movl    $.LC0, (%esp)
          call    foo
          movl    %eax, 4(%esp)
          movl    $.LC1, (%esp)
          call    printf
          addl    $20, %esp
      

      应该说还是很直观的吧。gcc 首先会去调用子表达式( foo ),并且希望这个函数能够把它的返回值放入寄存器 %eax 中,然后就会把这个值作为参数拷到堆栈上,而不是什么字符串常量的地址。

      首先是要调整 #get_arg 函数:

        def get_arg(a)
          # Handle strings and subexpressions
          if a.is_a?(Array)
            compile_exp(a)
            return [:subexpr]
          end
          seq = @string_constants[a]
          return seq if seq
          seq = @seq
          @seq += 1
          @string_constants[a] = seq
          return [:strconst,seq]
        end
      

      唯一需要改动的地方就是返回值了,我们增加了一个表示返回值类型的标识 – 以后还会加入其他类型的。

      剩下的工作就是改写 #compile_exp 函数中的相关部分了。这时就不能直接收集 #get_arg 的返回值了,而是需要对每个参数都做相应的处理并直接输出(而这同时也是 stack_adjustment 需要修改的原因,因为已经没有 args 数组了):

          stack_adjustment = PTR_SIZE + (((exp.length-1+0.5)*PTR_SIZE/(4.0*PTR_SIZE)).round) * (4*PTR_SIZE)
          puts "\tsubl\t$#{stack_adjustment}, %esp" if exp[0] != :do
      
          exp[1..-1].each_with_index do |a,i|
            atype, aparam = get_arg(a)
            if exp[0] != :do
              if atype == :strconst
                param = "$.LC#{aparam}"
              else
                param = "%eax"
              end
              puts "\tmovl\t#{param},#{i>0 ? i*4 : ""}(%esp)"
            end
          end
      

      如你所见,并不是什么复杂的更改。我们只是检查了 #get_arg 所返回的类型信息,并相应的输出字符串常量或者寄存器 %eax 而已。随着我们加入更多要处理的情况,这个部分代码还会继续扩充的。

      你可以在这里找到最新版本的代码

      之后的计划

      这里只列出的基本完成的部分。我的计划是,当我开始着手写新的部分时,我会将重心放在一个简单的解析器上,以尽快实现编译器的自举(即,编译它自己)。

      • 步骤四:运行时,以及函数的定义
      • 步骤五:处理其他类型的常量值
      • 步骤六:条件表达式 if ... then ... else
      • 步骤七:循环语句
      • 步骤八:匿名函数( lambda
      • 步骤九:用匿名函数来实现循环,以及对函数参数的处理
      • 步骤十:赋值,以及简单的代数运算
      • 步骤十一:更简结的 while 循环
      • 步骤十二:测试我们的语言:开发一个简单的输入转换模块
      • 步骤十三:重构代码生成模块,并抽象出平台相关的部分
      • 步骤十四:对一些概念的讨论,以及今后的前进方向
      • 步骤十五:数组
      • 步骤十六:局部变量以及多种作用域
      • 步骤十七:可变长参数列表
      • 步骤十八:再看输入转换模块:测试新的功能点,以及自我转换
      • 步骤十九:确定自举所需要实现的功能
      • 步骤二十:开始实现真正的解析器
      's avatar

      [翻译] 用 Ruby 写编译器之二:函数调用,以及 Hello World

      原文地址:http://www.hokstad.com/writing-a-compiler-in-ruby-bottom-up-step-2.html


      我会选择 Ruby 来作为我的实现语言并没有什么特别的理由。在现阶段,语言的选择并不重要;不过,我确实很喜欢 Ruby。

      在这之后,我会采取一系列的步骤令所实现的语言向其实现语言靠拢。我的意思是,我想将编译器实现为可以自举的,即它应该能够编译自身。

      而这也就意味着,要么我的编译器需要至少支持 Ruby 语言的一个子集,要么就需要一个中间的翻译步骤,来将编译器中的实现翻译成它自己可以编译的语言。

      虽然这一点并没有限制你所用的实现语言,但那至少意味着你用来实现的语言跟你要实现的语言之间很相似,除非你对实现编译器的自举没有什么兴趣。

      这也同时意味着,如果你想实现编译器的自举,那你最好不要用实现语言中的什么复杂的特性。要记住,你得实现所有你用过的那些语言特性,不然,当你要开始做自举的时候,你就得对整个编译器的架构做大的调整了。那一点都不好玩。

      话说回来,使用 Ruby 来作为实现语言的一大优点(同样对于其他某些语言来说也是这样,比如说 Lisp ),就是你可以很容易地构建出一个树形的数据结构出来 – Ruby 的话就是用数组或者哈希, Lisp 的话就是用列表。

      这也就是说,我可以用数组来手工构建抽象语法树,从而避免了实现一个语法分析器的工作。耶!代价就是很丑,不过也可以接受的语法啦。

      Hello World

      Hello World 的话看起来会是这个样子的:

      [:puts, "Hello World"]
      

      这里,我们需要处理的东西非常简单:我们需要将一个参数压入堆栈,然后调用一个函数。

      那么就让我们来看一下怎样用 x86 汇编来做这件事吧。我用 gcc -S 编译了下面的这段 C 程序:

      int main()
      {
          puts("Hello World");
      }
      

      然后看看输出会是什么样子的。下面给出的是真正相关的部分,是与上一次的输出比较之后的结果:

              .section        .rodata
      .LC0:
              .string "Hello World"
              .text
      ...
              subl    $4, %esp
              movl    $.LC0, (%esp)
              call    puts
              addl    $4, %esp
      ...
      

      如果你懂一些汇编的话,就算以前没有写过 x86 的汇编程序,也应该可以很容易的看懂这段代码吧:

      • 这里首先定义了一个字符串常量。
      • 通过对堆栈指针的减 4 操作,在堆栈上申请了一段 4 个字节大小的空间。
      • 然后将之前定义的字符串常量的地址,放入刚刚申请的那 4 个字节的空间中。
      • 接着,我们调用了由 glibc 提供的 puts 函数(在这个系列中,我会假设你已经有了 gcc/gas + glibc ;Linux 的话这些东东应该已经有了)。
      • 最后,通过一个加 4 操作来释放堆栈空间。

      那么,我们要怎样在我们的编译器中实现这一切呢?首先,我们需要一种方法来处理那些字符串常量,通过在上次的 Compiler 类的实现中添加下面的代码(我的所有 Ruby 代码都在这里,这样你就知道该做什么了):

        def initialize
          @string_constants = {}
          @seq = 0
        end
        def get_arg(a)
          # For now we assume strings only
          seq = @string_constants[a]
          return seq if seq
          seq = @seq
          @seq += 1
          @string_constants[a] = seq
          return seq
        end
      

      这段代码就是简单地将一个字符串常量映射到一个整数上,而这个整数则对应着一个标号。相同的字符串常量会对应到相同的整数上,因此也只会被输出一次。用哈希而不是数组来保证这种唯一性是一种很常用的优化手段,不过也不一定非要这样做。

      下面这个函数是用来输出所有的字符串常量的:

        def output_constants
          puts "\t.section\t.rodata"
          @string_constants.each do |c,seq|
            puts ".LC#{seq}:"
            puts "\t.string \"#{c}\""
          end
        end
      

      最后剩下的就是编译函数调用的代码了:

        def compile_exp(exp)
          call = exp[0].to_s
          args = exp[1..-1].collect {|a| get_arg(a)}
      
          puts "\tsubl\t$4,%esp"
      
          args.each do |a|
            puts "\tmovl\t$.LC#{a},(%esp)"
          end
      
          puts "\tcall\t#{call}"
          puts "\taddl\t$4, %esp"
        end
      

      也许你已经注意到这里的不一致性了:上面的代码虽然好像是可以处理多参数调用的样子,但却只从堆栈中减掉了一个 4 ,而不是按照实际的参数个数而进行相应的调整,从而导致了不同参数间的相互覆盖。

      我们马上就会处理这个问题的。对于我们简单的 Hello World 程序来说,目前这样已经足够了。

      在这段代码中还有几点需要注意:

      • 我们甚至都还没有检查被调用的函数到底存不存在 – gcc/gas 会帮我们处理这个问题的,虽然这也意味着没啥帮助的错误信息。
      • 我们可以调用任何一个可以连接的函数,只要这个函数只需一个字符串作为参数。
      • 这段代码目前还有很多需要被抽像出去的地方,比如说得到被调函数地址的方法,还有所有那些硬编码进来的 x86 汇编等。相信我,我会(慢慢)解决这些问题的。

      现在我们可以来试着运行一下这个编译器了。你应该会得到下面这样的输出:

              .text
      .globl main
              .type   main, @function
      main:
              leal    4(%esp), %ecx
              andl    $-16, %esp
              pushl   -4(%ecx)
              pushl   %ebp
              movl    %esp, %ebp
              pushl   %ecx
              subl    $4,%esp
              movl    $.LC0,(%esp)
              call    puts
              addl    $4, %esp
              popl    %ecx
              popl    %ebp
              leal    -4(%ecx), %esp
              ret
              .size   main, .-main
              .section        .rodata
      .LC0:
              .string "Hello World"
      

      下面来测试一下:

      [vidarh@dev compiler]$ ruby step2.rb >hello.s
      [vidarh@dev compiler]$ gcc -o hello hello.s
      [vidarh@dev compiler]$ ./hello
      Hello World
      [vidarh@dev compiler]$
      

      那么,要怎么处理多个参数的情况呢?

      我不会再展示用来说明的 C 代码和对应的汇编代码了 – 进行不同参数个数的调用并查看其输出对你来说应该不难。相反,我就直接给出对 compile_exp 函数所做的修改了(完整的代码在这里):

        PTR_SIZE=4
        def compile_exp(exp)
          call = exp[0].to_s
      
          args = exp[1..-1].collect {|a| get_arg(a)}
      
          # gcc on i386 does 4 bytes regardless of arguments, and then
          # jumps up 16 at a time, We will blindly do the same.
          stack_adjustment = PTR_SIZE + (((args.length+0.5)*PTR_SIZE/(4.0*PTR_SIZE)).round) * (4*PTR_SIZE)
          puts "\tsubl\t$#{stack_adjustment}, %esp"
          args.each_with_index do |a,i|
            puts "\tmovl\t$.LC#{a},#{i>0 ? i*PTR_SIZE : ""}(%esp)"
          end
      
          puts "\tcall\t#{call}"
          puts "\taddl\t$#{stack_adjustment}, %esp"
        end
      

      这里做了什么呢?改动的地方没几个:

      • 这里不再是申请固定大小的堆栈空间了(上一个版本中是 4 个字节),而是根据实际参数的个数来相应的调整堆栈指针。我得承认,我不知道 gcc 为什么会做这样的调整 – 而且原因并不重要,虽然我猜这是为了堆栈的对齐。优化和清理以后再说,并且,当你不知道某事的运行机理时,那就不要去改变它。
      • 这之后,如你所见,参数被一个一个地放到堆栈上了。我们还是假定它们全都是相同大小的指针(因此在 x86 上就是 4 个字节)。
      • 同时你还可以看到,第一个参数是被放在堆栈中最靠下的位置的。如果你还没有写过汇编程序,并且无法想象出这是怎么回事的话,那就把它们画出来吧;还要记住,这里的堆栈是向下扩展的。当申请空间时,我们是将堆栈指针向下移动的,而拷贝参数时则是从下往上(用越来越大的索引来访问 %esp ,就像你访问数组时一样)。

      这个编译器现在已经可以编译下面这样的代码了:

      [:printf,"Hello %s\n","World"]
      

      至于以后嘛

      这就是我们踏出的第一步,而且我保证之后的步骤会越来越实际的,因为只要实现很少的几个功能点,我们就可以编译实际的程序了。而且我会努力令这些步骤更加精炼,更多的说明这样做的原因,而不是仅仅解释做了什么。

      下面我会处理多个参数的调用(译者:我们不是才处理过嘛),然后是语句序列、子表达式,以及对返回值的处理,等等。

      大约十二个这样难度的步骤之后,我们就会完成函数定义、参数传递、条件判断、运行时库,甚至是一个用来实现匿名函数的简单的 lambda (真正的闭包就要到后面了)。

      再之后,我们会实现一个简单的文本处理程序,来对一个比 Ruby 的数组和符号更好一点的语法提供支持(只是某种程度啦,真正的语法分析得再多等等)。

      's avatar

      [翻译] 用 Ruby 写编译器之一:一个简单的 main 函数模板

      原文链接:http://www.hokstad.com/writing-a-compiler-in-ruby-bottom-up-step-1.html

      [译者抱怨:翻译好麻烦啊。]


      我已经将这件事情搁置了很长时间了 – 这个系列中最早的文章甚至可以追溯到 2005 年的早期,而那时我还没有开始写这个博客呢。

      我灰常喜欢编译器技术,而且我也已经写过好几个小型的编译器了。不久前我开始用 Ruby 语言写一个简单的编译器,而且我也以发表为目的记下了大量的笔记。问题在于,当我一步步地完成这个系列文章的时候,我的博客却渐渐地,没落了(?原文: Problem was I was that by the time I was finishing up the steps I have so far, my blog was languishing, and so it’s been just gathering dust. 啊,我的英文好渣)。

      我已经写完了大概 30 篇文章,并形成了一个简单但是可以工作的编译器。我也许要花上一段时间来发表它们,因为这些文章均需要一定程度的整理,而且我能花在写作上的时间不是很多。不过根据具体的时间安排,我也有可能一周就发表好几篇。

      闲话少叙,第一篇参上:

      有关一些背景,以及一小段代码

      通常,在开始尝试编写一个编译器时,我会选择自顶向下的开发方式。换句话说就是,我会采取常见的策略,从设计一个语法分析器开始,而不管这个分析器是手写的,还是通过分析器生成器来生成。然后我会通过一系列的树形结构转换程序将 AST(即 Abstract Syntax Tree,抽象语法树)转化为符号表,同时进行错误检查,为树结点附加各类信息,并最终进行代码生成的步骤。在我转而使用 Linux 之后,我在这里就会选择生成 C 代码,前提是你的语言能够很好的契合 C 的语意。从这里也可以看出,C 是一门多么低层的语言了。

      不过,我的第一个编译器是用 M68000 汇编语言写的,而且其输出也是 M68000 的汇编程序。

      我是从允许内嵌的汇编代码片断开始自举这个编译器的,并在此基础上逐步地增加所支持的语法结构。

      首先我加入了对函数的定义以及调用的支持,并以此为基础构建我的整个编译器。然后我会增加基于寄存器的基本四则运算的支持,然后是局部变量,等等等等,但在同时也会解析汇编,以保证当遇到内嵌汇编代码片段中的寄存器时,还能够保持寄存器分配器的简洁(原文: but parse the assembly so that the register allocator would stay clear of registers used in the assembler that was interspersed. )。

      然后这个编译器就从编译汇编语言的语法,逐渐演变成编译我所设计的杂牌语言的语法,其中的汇编也越来越少。

      我想再自举一个编译器出来。不过这次我就不打算用汇编来实现它了。这次我会从底层 – 也就是代码生成器 – 开始。并且我会完成如下几个目标:

      • 实现的简洁性优先于其性能。要点在于,我希望能够将它重写为以其自身来实现。换句话说,我希望它能够实现自举( self hosted ,跟 bootstrap 有啥区别呢?)。由于开始的代码最终会被替换掉,那么也就没有必要浪费时间在使它可以运行的更快上面。性能问题大可以以后再考虑。
      • 不要做限制性能的决定。虽然性能并不是我们最初的目标,但也不能太大意,而导致今后不好对此进行改进。 Ruby ,说的就是你( Ruby 的运行时非常的动态)。
      • 实现的简洁性优先于惯例。不要仅仅因为一个特性很方便就去实现它,而仅实现那些可以帮助我们完成对编译器的自举的特性。而这就意味着,这些特性要能使我们的生活更加方便,而不仅仅是无谓地增加了实现编译器本身的难度。
      • 不要急着设计语言,我们要设计的是特性。我想首先实现一个灵活而强大的代码生成器,以及一个可以支持各种功能的简洁的 AST 。我的意思是像 Lisp 语言那样极富表达能力的语法结构,并尽量用 Ruby 来达到相似的目标(这段翻的太渣了,请参考原文)。
      • 我并不想深入学习 x86 的汇编语言,虽然我会以其作为我的目标语言。这样说有点夸张。我很熟悉 M68K 和 6510 的汇编语言,同时我也具备足够的知识来阅读 x86 的汇编程序。虽然我从来没有用它写过什么像样的代码,而且我也不打算这么做。我所需要知道的一切信息都来自于查看 gcc 产生的汇编输出。要善用 gcc -S 命令。虽然可能对 x86 的某些细节存在疑问,但我所具备的基础汇编知识已经足够我理解那些个汇编代码了。虽然在此过程中,我很可能会犯一些很愚蠢的错误,但我同时也可以从中学到很多知识(而且那些非常相信能够从我这里学到相关知识的人们也请注意了 - 我在这方面也只是个初学者哦)。

      听起来之后貌似会抄很多的小道,同时也会遇到很多痛苦的地方,不是吗?但这同时也会非常有趣哦!

      不管怎样,我们先从一段没什么实际用处的代码开始吧:

      #!/bin/env ruby
      class Compiler
      
        def compile(exp)
          # Taken from gcc -S output
          puts <<PROLOG
       .file "bootstrap.rb"
       .text
      .globl main
       .type main, @function
      main:
       leal 4(%esp), %ecx
       andl $-16, %esp
       pushl -4(%ecx)
       pushl %ebp
       movl %esp, %ebp
       pushl %ecx
      PROLOG
      
          puts <<EPILOG
       popl %ecx
       popl %ebp
       leal -4(%ecx), %esp
       ret
      EPILOG
      
          puts <<GLOBAL_EPILOG
       .size main, .-main
      GLOBAL_EPILOG
        end
      end
      
      prog = [:puts,"Hello World"]
      
      Compiler.new.compile(prog)
      

      说实话,这段代码嘛也没做。它只是把用 gcc -S 命令将下面这段代码编译出来的结果拆吧拆吧之后给打印出来了而已:

      int main()
      {
      }
      

      这可是一个完整可工作的编译器哟 – 某种程度上来说吧。不幸地是,不管用它编译什么程序,得到的都只是一段毫无用处的代码,因些可以说它本身也是同样毫无用处的。但我们总要踏出这最初的一步。

      你可以认为这个系列的文章是我的“意识流”。我做这件事仅仅是因为好玩而已。我并没打算坐下来,好好地完成一个多么赞的设计。我甚至会因为一时兴起而把一段代码给丢掉,或者之后又把它给找回来。

      你在这里将要看到的其实已经是我第二次的代码了:我所做的每一个 SVN 提交都对应于我的一篇文章,不过我会在文章中做很多额外的讲解。有时这些讲解会令我中途改变主意 – 但我不会对代码做太大的修改,除非我认为这个修改能令我的讲解更清晰,或者我一开始完全就想错掉了。就算是这样,我也多半不会做出修改,除了在那边标注一下我会在之后解决。有些时候,我也会把几个我做起来很麻烦,但解释起来又很简单的步骤合而为一。

      我在这里欢迎大家涌跃发言。不过要记住,虽然我会十分留意您的发言,但我已经完成了这个系列中的很大一部分,我不会因为某些意见而完全重写这些文章。不过我会留下一些笔记,并同时记住我之前都写了些什么内容。

      下次我会让这个编译器真正去处理它的输入的,我保证。


      [译者再次抱怨:翻译好麻烦啊。]

      Wang Congming's avatar

      工作日志:从突击成长,到稳定发挥

      我最近常想到“稳定发挥”这个词。

       

      政治课上有一句话,叫“螺旋式上升、波浪式前进”。拿到职场来说,刚工作的同事应该是这个样子。我们面前有很多可能,在哪个方向上突击一下都能形成一个波峰。

       

      不过,有波峰,即有波谷。一名成熟的职业人,不能随便进入波谷状态,更要为岗位和其他人负责。职责愈大,愈不能随便波动,发挥的稳定性愈显得重要。

      随着经验的积累,通过突击式的努力使自己的能力见识在短时间内有大的提高,变得困难起来。更多的,是平缓的、渐进式的增长。这个时候,稳定性和可预见性成为了核心价值。事情到了这样的人手里,你可以放心地忘掉,因为他总能在合适的时机给出最优的答案。

       

      要保持前进的步伐、保障稳定的发挥,心态和习性要跟上。

      平和的情绪、积极的态度、清醒的状态,始终如一。把神志不清 / 烦躁不安…从字典里划去。内部情绪、外部干扰都不再成为影响稳定发挥的因素。

      工作有序,形成适合自己的高效的工作方式与习惯。有再多的工作,都能井井有条。头脑清晰、合理安排,为重要的事情挤出时间,而非乱成一团糟。

       

      在心态与习性的保障之下,稳定地输出工作成果。同时,有目标、有规划、有大局观,能描绘出蓝图,并清楚每一步工作所处的阶段。享受一步步逼近目标,和一切尽在掌控的感觉,激发自己更大的潜能。

      Wang Congming's avatar

      豆瓣数据存疑

      Update:
      感谢 Ratooykjingleptoqwhiplus 等网友的提醒,之前提的两个问题都有了答案。:-)

      1、豆瓣用户量突然飙升,是因为 QQ 空间的“豆瓣读书” app;

      2、Wayback Machine 抓取网站的存档之后,不会立即显示,貌似要一年以上历史。

      另外,我后来看了下,其实豆瓣的用户都有个数字编号,从用户的编号和注册时间就可以得出准确的数据(第一位用户的编号是 1000001,所以用户数等于用户编号减掉 100w,不考虑被封杀和自杀的情况)。

      下面是完整的数据图形:

      可以看到,09 年 8 月开始的那一跳……。最高时,一个月新增了 900 多万,相当于它前 4 年总和的 2.5 倍多。

      不过我们也看到,一年之后,已经开始恢复正常了。这大概是 QQ 空间输血能力的上限了吧。

      最后,我随机测试了一些,QQ 空间给豆瓣输送的用户基本都是无效的,没有任何活动。

      (Update 完)


      今天统计了一下豆瓣的注册用户数。因为豆瓣有在首页显示相关数据的习惯,所以这个统计显得比较简单(假设数据是准确的)。

      翻开 Wayback Machine,可以看到,豆瓣最早在首页显示用户量的记录是 06 年 11 月 2 日

      以下即是我根据这上面的数据绘的图:

      很可惜,Wayback Machine 对豆瓣的最后一次存档是 09 年 6 月 30 日,之后就没有了。不知道是豆瓣的 robots.txt 禁止了抓取(看了一下,似乎没有),还是 Wayback Machine 自身的原因。

      上图的数据看起来中规中矩,没什么奇特。不过,我们看它的总注册用户量,最后一条记录显示,当时豆瓣的用户数刚刚达到 350 万。那是 09 年 6 月末,那么,今天豆瓣标称的用户数是多少?—— 4713 万。

      加上这条,图立马变形。

      05 年到 09 年,四年多,350万;

      然后 2010 年,一年半,4713 万,13 倍。

      虽然我没太关注,但似乎豆瓣近一年来没搞过什么大的营销推广;反倒是因为审查严格,很多用户自杀了帐号。13 倍的数据有点惊讶,不知道是不是我哪搞错了。

      Wang Congming's avatar

      SaaS 的通用架构(兼回答我在做什么)

      翻到以前画的几张图,拿来回答一下我最近在干什么(点击放大) ^_^  。

      做企业的业务逻辑,用 Web 的技术方式,这就是我们现在在干的事儿~。

      Wang Congming's avatar

      “国产”“双核”浏览器是个好东西

      Update: 新增了“世界之窗”浏览器的测试结果。

      作为一名亲美不爱国人士、一名技术至上的 Geek,我大脑里的信息接收系统,向来对国产的 IT 产品自动选择性过滤。

      不巧的是,这一年多来我不得不时常思考 Web 产品运营的问题,“因特网探险家6”像梦魇一样折磨着我……。当你瞅着 HTML5 激动不已的时候,IE6 会让你的汽泄得瘪瘪的。。。别以为 HTML5 只是 Flash 杀手,它有着许多许多现代 Web 开发不可或缺的特性,即使 IE8 也比它的六哥强太多了!

      那么,我们怎么办?期待盗版 Windows XP 一夜之间消失,IE6 寿终正寝吗?恐怕只是一厢情愿吧。这个时候我想到,“国产”“双核”浏览器是唯一可能在短期内改变大局的“人物”!

      所谓“双核”浏览器,就是在 IE 的 Trident 内核外,再集成一个 WebKit 内核。部分网站用 Trident 内核渲染,另外一部分用 WebKit 渲染。试想,如果这些浏览器默认使用 WebKit 内核,岂非是帮助在国内市场瞬间普及了 WebKit?这是何等激动人心呐!

      据称,360 浏览器在国内的市场份额约达 20%。我想,新的 Web 产品已经不得不考虑对它的兼容性,也即考虑对 WebKit 的兼容性,也即使用标准技术。当新的网站大多按标准行事时,对 Trident 的需求将越来越少,越来越多的浏览器会默认使用 WebKit。这是一个正反馈。

      以下是我这个勇敢的小白得出的测试数据(按市场份额排序):

      浏览器名称 主流版本 双核版本 是否默认 WebKit
      360 3.5 4.0
      搜狗 2.2 2.2
      QQ 4.8 5.0 beta3
      遨游 2.5 3.0
      世界之窗 3.3 4.0

      目前的情形,各浏览器(除搜狗外)的双核版本都还处于怂恿、造势阶段,都还没有成为默认的主流版本。而搜狗,虽然已经默认双核版本了,但并没有默认 WebKit。

      当各国产浏览器默认了双核版本,且双核版本默认了 WebKit 内核。“因特网探险家6”带给我的梦魇将有希望结束。当然,它结束的不只是我,而是这个时代 Web 开发者的痛苦。这是 360 们给自己树碑立传的机会,期待看到他们的作为。

      Wang Congming's avatar

      “SNS”和“微博”——理念相克,试图揉合可能适得其反

      SNS 和 Microblog 都已不是新鲜话题了,但有一个势头,SNS 想整合微博、微博想整合 SNS,我认为这不是好主意,可能会适得其反。

       

      SNS 是什么?是关系,是加好友。七大姑八大姨、同学发小、客户伙伴……总之,你认识的人都得加上。毕竟,亲戚朋友多总是好事。

      Microblog 是什么?是获取信息的途径。用来关注那些可能并不认识,但他们说的话使你感兴趣,能给你带来信息、思考或知识的人。

      于是,冲突产生了:

      • SNS 应该鼓励实名化,微博随便匿名;
      • SNS 双向确认好友关系,微博应该单向关注;
      • SNS 鼓励多加好友、只要是认识的人;微博推荐物以类聚,不介意事先是否认识;
      • ……

       

      拿 Twitter 这个显而易见的例子,如果我的某个发小在上面,我可能不会 fo 他。原因很简单,经过多年的各自发展,也许我们感情仍然很好,但隔行如隔山,我们可能很难有共同话题了。他发的他们那个行业很有意思的段子,可能我会觉得索然无味。上推是为了看一些有意思、对口味的信息,所以我不 fo 他。

      那么,另外一个例子——豆瓣。它的社区应该是 Facebook 型的还是 Twitter 型的?设想一下,因为你在豆瓣把你的姑姑姨姨们加为了好友,某天你想买本 IT 书籍的时候,豆瓣会向你推荐某某菜谱;某天你想欣赏科幻电影的时候,它向你推荐xx韩剧;……是何感触?所以豆瓣的社区也应该是 Twitter 型的(大概一两年前,豆瓣有一个改版,似乎没经受住 Facebook 的诱惑,在原有的“关注”基础上,又增加了个加好友的功能。当时我相当讨厌,就离它远了点,后来的某个时刻,发现这个功能又被砍掉了,幸甚!)。

       

      总结起来,凡是你想从所关注的人中获取有效信息的,或者系统根据所关注的人,通过算法自动向用户推荐有价值信息的,都应该是微博型的社区。而 SNS 适合生活化的东西,我觉得。

       

      后记:刚发过一条推:

      前几年,我订阅了很多互联网大佬的博客,带着崇敬的心情探头阅读这个行业。最近,我了解了一下大佬们的动向,发现他们主导的产品都关门鸟~。互联网跟体育一样,评论员当不好运动员。我还当不了评论员,努力成为一名运动员ing,嗯~!
      http://twitter.com/wcm/status/8904614644551680

      唔,敲自己脑袋,不要评论,要运动! @_@

      Wang Congming's avatar

      工作日志:我们不是一个人在战斗

      按:
      这是在公司内部社区写的。本意是为同事而写,但我想,它也正好记录了我自己的认识。所以,以后若有类似不涉及公司、产品及同事隐私的日志,也会将其记录在我的个人日志里。

      月度总结会议提纲:

      一、时机
      1、v1.0 发布、v1.5 15 天时间、业务接踵而至;
      2、团队磨合,有进步、还有陋习,不能就此停滞。

       

      二、追求卓越,拒绝平庸
      1、基础并不好,自知自省,努力;
      2、更新换代,有机会超越;
      3、再也不是单打独斗的年代,比得是团队凝聚力、战斗力。

       

      三、We Are One
      0:我们不是又一个混日子的团队

      1:友好、尊重、相互着想
      宽容、存大志,不要小心眼;
      审视行为习惯,是否会无意中给他人造成伤害/误会。

      2:主动性、全局观、责任感
      整个系统都与我有关,贡献创意、发现问题;
      参与、决策,not 螺丝钉;
      视野 -> 思考 -> 积淀 -> 施展。

      3:自我激励、团队价值
      像大海一样清洁环境,不像污染源一样毒害团队。

       

      四、We Could Be Better
      1:技术
      兴趣、执着、好奇心,不服输、成就感、自豪感……;
      内驱力,积淀技术、锻炼能力、创造自我价值。需要被监督的人是可耻的!

      2:效率
      工作方式,自省、总结;
      慵懒、分心的时候,告诉自己——时光正在无情地流逝。

      3:产品 & 需求
      技术不是玩具,它的目标是产品;
      若搞错了方向,更快的速度只使南辕北辙得更远,认真对待需求;
      为产品更优秀而激动,非为多一个工作任务而抵触。

       

      五、男儿心胸,志在千里
      1、卓越的技术、优秀的产品;
      2、可能,创造新天地;
      3、上层建筑,需要物质基础,我的职责我深知;
      4、价值 -> 回报;not 回报 -> 价值。

      Wang Congming's avatar

      价值的迷思

      实在无法把下面完整的意思,压缩到 Twitter 140 字的限制内。于是,发到 Blog 来凑数吧,呵呵~。

      一直想创建一个有价值的应用,而非利用用户的无聊浪费他们的时间。

      比如,豆瓣的书影音,是我认可的应用之一。但我意识到,使用这样的应用,表明用户开始了价值积淀。虽不一定积淀得多丰厚,但至少已经开始了。然而,有更多更多更多的人还处于粗放阶段,积淀?完全没有意识。

      潜在用户只有小部分能成为实际用户;
      但对用户素质有要求的应用,更只有总体中的很小部分能成为潜在用户;
      价值与市场容量两全,不是件易事。

      via these people and places