Recent Posts

Li Fanxi's avatar

2019新年好

2018年,我做了这些事

– 网络生活

写了3篇博客,除了按惯例交作业的两篇,就写了一篇折腾群晖NAS的经验分享文章,而且这篇文章我现在来看觉得又过时了,过几天得重写一篇,因为我找到更好的办法来解决相同的问题了。博客总访问量持续下跌,为25475 PageView,自作孽不可活。饭否消息21条,其中照片8张。

开源与自由软件方面,因为工作需要给gdbgui项目贡献了一个微不足道的功能。

年初立的Flag要给Linux Kernel Patch Statistics改版的计划,最终还是没有实现。要不继续当成2019的计划?

也许可以总结为在生活上投入的时间越多,在网络上投入的时间就越少?

买了一个fanxi.li的域名,虽然我一直觉得用名字拼音当域名不是很make sense。不过由于现在.li域名无法在国内备案,所以我也没(打算/办法)把它当主域名来用,只是用它来做跳转域名,比现有的域名简洁一点,可以少敲几个字符。

– 一堆IT产品

斐讯K3路由器:没下车就翻车的斐讯产品。K3性能强劲,但是散热硅胶有漏油的毛病,被誉为“漏油器”。主要是第三方固件不是太给力,官改固件因为内核原因也不完美。最终闲置了。

斐讯K2P路由器:0元购成功下车。不考虑0元购情况下,直接买已下车的版本,曾经最低价不到百元,是200元以下路由器中的无敌手。刷成“荒野无灯”版的固件后(暂不讨论第三方固件的安全性问题),使用稳定。非常满意。

斐讯悟空空气检测器:直接买的下车版本,做工不错,检测结果准确性嘛,PM2.5说得过去,HCHO的话,一句话,电化学传感器的都不准。好处是它已经被研究得比较透彻了,直接接入HomeAssistant很方便。

斐讯T1电视盒子:0元购成功下车。不看价格的话,斐讯一系列的产品的硬件确实都是堆料之作,做工扎实,配置也不错。不过家里盒子太多了, 这个最后还是闲置了。似乎现在有很多人拿它玩出了花,不过我没太关注。

天猫路由:内测产品。2018年出个百兆有线的无线路由器,说再多也没意义了。虽然硬件配置还不错,无线信号与实测速度也不错。但是初版固件功能极弱,除了可以上网,其它功能一概没有。对,端口映射都没有。

天猫精灵魔盒:内测产品。就是把天猫魔盒与天猫精灵融合在一起的一个产品。刚开始的使用体验惨不忍睹,正式上市的版本勉强能用。但是为了在电视开关机两种情况下提供不同的功能而引入的两种工作模式,依然让使用体验变得很诡异。最主要的重点在于,语音控制对于一个电视盒子来说,比用遥控器能带来哪些使用体验上的提升?几乎大部分场景下、遥控器都比语音控制要方便、快捷,我能找到的唯一一个点是:搜索指定内容。

华米手表青春版:Pebble 2不到两年就光荣牺牲了,跟很多人一样,双侧的橡胶按键因为老化,直接破损了。Pebble被Fitbit收购,已经又一次证明,被Geek看好的产品,通常都不是好产品。华米手表除了部分细节功能不如Pebble(比如免打扰模式无法设置只进行来电提醒、无法定制不同类型提醒的振动模式),以及表盘定制能力不及Pebble,其它功能都很让人满意,工作稳定可靠,不像Pebble还时不时蓝牙连接默默断开给你脸色看。没有选择小黑3是因为它的外观,没有选择别的智能手表大都是因为续航。

OLPC XO-1:十年前非常想要但不太买得到的OLPC,也就是当年所谓的“一百美元笔记本”,如果不了解的,可以搜一下相关的历史。在闲鱼上淘到了一个洋垃圾,毫不犹豫的下单了。放到今天,这东西可是真垃圾,但是它的一些设计即使到今天来看,也还是非常独特的,比如那个双模的显示器。玩了几个小时,不出意外的闲置了,纯当收藏了。

Dell 7060 Micro:前年买的Dell 7040 Micro台式机还是挺满意的,今年Intel牙膏挤多了,8代CPU提升很大,所以换成了Dell 7060 Micro,配上了顶配的8代i7标压CPU。尽量Dell给这个系列使用i7标压U的机器单独配备了专用的散热组件和大功率的电源,这种小机箱的散热还是不太够,i7无法满载运行。内存我已经加到了32G,但是还是没法满足工作中编译代码的需要(单CPP文件编译可能会耗费4G或更多的内存,开6个并发就废了),所以现在用着高配置的机器却只跑了个Terminal和IDE,编译这种费力的工作还是扔到远程服务器上去做了。

MikroTik RB750Gr3 + Arbua AP-205H:这个路由+AP的组合,是买来尝试软路由方案的,两个月后又闲鱼上卖掉了。RouterOS的设计和操作对于家用路由器来说有点反人类,而Arbua这种专业AP也会让初次使用者觉得摸不着手脑。在经历了尝试用群晖虚拟机跑OpenWrt、MikroTik+Arbua等几个家庭网络方案后,我回到了原来的AC68U硬路由的方案。我的结论是,在带宽和带机量都不足够大的家用情况下,除非在路由器上需要部署大量加解密、协议分析(比如去广告)这种重CPU的功能,否则X86软路由无论从性能还是功耗上,性价比都远低于同档次的硬路由。

EPSON L4168打印机:低端墨仓式一体机的网络爆款。从功能上来说,连供、彩色打印、复印、扫描、无线、自动双页,1300多块的价格完全对得起这个价格。但是我在十五年前花330块买过一个HP 3538打印机,从文本打印品质来说,可以秒杀这个新的L4168,所以新机器刚到手的时候还是颇为失望的。现在用了一段时间习惯了,也就不纠结了。

暴风酷播云:曾经5000多块的矿机,在矿难后有商家在闲鱼上抛售,价格从500多一路暴涨到现在的近800,估计不久又得崩盘。万由的双盘位机箱,华擎的J3455主板,单条金士顿DDR3L 8G内存,自带16G SSD可以当系统盘,满载功耗40W左右。相当于群晖DS918+的配置,用来做家庭小服务器/NAS/软路由还是很不错的。学习了Proxmox这个可以代替VMware ESXi的虚拟化管理系统,很不错,够用。

– 出行

海口/三亚四日:除了住酒店还是住酒店,除了游泳还是游泳,除了沙滩还是沙滩。有人说这就是正确的度假姿势。这次行程从浦发银行信用卡上撸了不少羊毛。

厦门三日:除了住酒店还是住酒店。这真不是我风格,我已经是老年人了吧?

苏州两日:走马观花跑景点,有点累。

– 其它

正在着手翻译一本Drill的书,预计2019年上半年可以出版,可能会是国内同体裁的第一本。原因无它,就因为我想出一本O’Reilly的动物封面的书。

我是一名坚持了十几年的Linux桌面资深用户,2018年年底,发现Windows 10自带的Windows System for Linux已经基本堪用,义无反顾地倒戈回了Windows。虽然Windows 10跟以前版本的Windows比,又丑Bug又多,但总体来说桌面体验还是比Linux强多了。macOS?我用了四年多,但似乎我的脑回路还是与它有点不兼容,我不太喜欢macOS所提供的交互逻辑。

展望2019年:

最近有句话很流行:“2018年是过去十年里最差的一年,却是未来十年最好的一年。”我并不觉得这句话很消极,每个人都应该有点忧患意识,有点抵抗风险的准备和能力(此处可以乱入卖保险的广告)。避免功利地去评估一件事情是否值得去做,尝试更踏实地做好一件件小事。不能期待一蹴而就,薄发来自于平时点点滴滴的厚积。

Phoenix Nemo's avatar

制作 Arch Linux 内存系统启动盘

之前尝试过 Arch Linux in RAM 完全运行在内存中的轻量业务系统,最近在维护一些物理服务器看到没有安装系统的服务器不断重启,想到了可以制作类似的内存系统启动盘,以高效完成系统测试、安装、远程维护等任务。

这时候就要祭出 mkarchiso 大法了。这是自动化制作最新版 Arch Live 镜像的工具集,当然也可用于制作定制化的 Arch 镜像。

准备

首先安装 archiso

1
~> sudo pacman -Syy archiso

它提供了两种配置方案,一种是只包含基本系统的 baseline,一种是可以制作定制 ISO 的 releng。要制作维护用 ISO,当然是复制 releng 配置啦。

1
2
~> cp -r /usr/share/archiso/configs/releng/ archlive
~> cd archlive

定制

整个过程不要太简单。先来了解下各个文件的用途:

  • build.sh - 用于制作镜像的自动化脚本,可以在这里修改一些名称变量或制作过程的逻辑。
  • packages.x86_64 - 一份要安装的包列表,一行一个。
  • pacman.conf - pacman 的配置文件,不用多说了吧。
  • airootfs - Live 系统的 rootfs,除了安装的包之外,其他的定制(以及启动执行脚本等)都在这里。遵循 rootfs 的目录规则。
  • efiboot / syslinux / isolinux 用于设置 BIOS / EFI 启动的配置。

[archlinuxcn] 仓库加入 pacman.conf

1
2
[archlinuxcn]
Server = https://cdn.repo.archlinuxcn.org/$arch

然后修改 packages.x86_64,加入 archlinuxcn-keyring 和其他需要预安装的包:

1
2
3
4
5
archlinuxcn-keyring
htop
iftop
iotop
ipmitool

按需修改即可啦。

要启动为内存系统,需要加启动参数 copytoram

修改文件 syslinux/archiso_pxe.cfgsyslinux/archiso_sys.cfg 文件,在启动参数后加 copytoram,像这样:

1
2
3
4
5
6
7
8
9
10
11
INCLUDE boot/syslinux/archiso_head.cfg

LABEL arch64
TEXT HELP
Boot the Arch Linux (x86_64) live medium.
It allows you to install Arch Linux or perform system maintenance.
ENDTEXT
MENU LABEL Boot Arch Linux (x86_64)
LINUX boot/x86_64/vmlinuz
INITRD boot/intel_ucode.img,boot/amd_ucode.img,boot/x86_64/archiso.img
APPEND archisobasedir=%INSTALL_DIR% archisolabel=%ARCHISO_LABEL% copytoram

启动时即可将整个 SquashFS 文件复制到内存。如果内存比较小,也可以指定 copytoram_size 来限制 tmpfs 占用内存的最大数量。

同样,也需要修改 efiboot/loader/entries/archiso-x86_64-usb.conf 的启动参数。在 options 行添加

1
options archisobasedir=%INSTALL_DIR% archisolabel=%ARCHISO_LABEL% copytoram

制作

创建工作目录和输出目录

1
mkdir -p work out

最后一步,只需要以 root 权限执行 ./build.sh 就可以啦。

要看具体执行过程的话,加 -v-h 看所有参数。

完成后,即可在 out 目录得到准备好的 ISO 文件。将其 dd 到 USB 闪存盘,大功告成(‘・ω・’)

Ref:

  1. https://wiki.archlinux.org/index.php/Archiso
  2. https://git.archlinux.org/archiso.git/tree/docs/README.bootparams#n53
Li Fanxi's avatar

关注2019维也纳新年音乐会

曾经关注过的那些维也纳新年音乐会:关注维也纳新年音乐会

一年又快要过去了,又快要到了迎接新年、迎接维也纳新年音乐会的时候。

2019年维也纳新年音乐会将由德国指挥家克里斯蒂安·蒂勒曼执棒,这是绰号“大熊”的他首次执棒新年音乐会。他是新年音乐会迎来的第17位指挥家,也是继克莱伯之后的第二位德国指挥家。

CD封面还没出,暂时只能配个指挥的图了:

斯蒂安·蒂勒曼

上半场

01 – Carl Michael Ziehrer – Schönfeld-Marsch; op. 422 – ·勋菲尔德男爵进行曲 *

02 – Josef Strauss – Transactionen; Walzer; op. 184 – 交易圆舞曲 – 1981, 1993

03 – Joseph Hellmesberger jun. – Elfenreigen – 小精灵的舞蹈 – 2007

04 – Johann Strauss II – Express; Polka schnell; op. 311 – 特快列车快速波尔卡 *

05 – Johann Strauss II – Nordseebilder; Walzer; op. 390 – 北海风光圆舞曲 – 1998, 2005

06 – Eduard Strauss – Mit Extrapost; Polka schnell; op. 259 – 特快邮车快速波尔卡 – 2000, 2016

下半场

07 – Johann Strauss II – Der Zigeunerbaron; Overtüre – 吉普赛男爵序曲 – 1987, 1992, 2009

08 – Josef Strauss – Die Tänzerin; Polka francaise; op. 227 – 舞女法兰西波尔卡 *

09 – Johann Strauss II – Künstlerleben; Walzer; op. 316 – 艺术家的生活圆舞曲 – 1989, 1999,  2002, 2006

10 – Johann Strauss II – Die Bajadere; Polka schnell; op. 351 – 印度舞伎快速波尔卡 – 1997, 2005, 2008

11 – Eduard Strauss – Opern-Soiree;  Polka francaise; op. 162 – 歌剧院晚会法兰西波尔卡 *

12-13 – Johann Strauss II – “Ritter Pásmán”, Evas Walzer und Csárdás – 伊娃圆舞曲 * & 查尔达什舞曲(选自轻歌剧《骑士帕斯曼》) – 1967, 1989, 2000, 2011

14 – Johann Strauss II – Ägyptischer Marsch; op. 335 – 埃及进行曲 – 1993, 2014

15 – Joseph Hellmesberger jun. – Entr’acte Valse – 幕间圆舞曲 *

16 – Johann Strauss II – Lob der Frauen; Polka Mazur; op. 315 – 赞美女人玛祖卡波尔卡 – 2003, 2006

17Josef Strauss – Sphärenklänge; Walzer; op. 235 – 天体乐声圆舞曲 – 1954, 1964, 1979, 1980, 1983, 1987, 1992, 2004, 2009, 2013

18 – Johann Strauss II – Im Sturmschritt; Polka schnell; op. 348 – 飞奔快速波尔卡 – 1990, 2004, 2016

加演

19 – – ?

20 – Johann Strauss II – An der schönen blauen Donau, Walzer, op. 314 – 蓝色多瑙河圆舞曲

21 – Johann Strauss I – Radetzky-Marsch, op. 228 – 拉德茨基进行曲

从目前看到的曲目单上,2019年的新年音乐会将演出6位作曲家的20首曲目,可能还会有一首未公布的加演曲目(原因是虽然飞奔快速波尔卡也很适合作为加演曲目,但是按惯例一般不会以圆舞曲作为正式曲目的最后一首,所以推测在飞奔后面应该还有一首快速波尔卡才是加演第一首)。除了施氏家族的四位成员,另外两位作曲家的名字也是新年音乐会爱好者所耳熟能详的:齐雷尔和约瑟夫·赫尔梅斯伯格。2019年是作曲家弗兰兹·冯·苏佩诞辰200周年,苏佩也是新年音乐会老朋友了,他的著名作品《轻骑兵序曲》曾两次入选新年音乐会的曲目单,今年没有选择他的作品纪念他的诞辰可谓是不按套路出牌。

音乐会的上半场,有两首新曲子,《特快列车波尔卡》是其中的一首,上半场另外还有一首《特快邮车》也是火车体裁的。在施特劳斯的时代,正是火车刚刚开始蓬勃发展的时代,所以他们有不少作品是有关火车的。还记得十多年前在深夜在南京站拍摄9列进京直达特快列车过站视频,后期制作时,我就选用了几首施特劳斯的与火车相关的作品作为背景音乐,很是应景。

下半场是经典怀旧时间,除了4首新曲以外,其它的曲子都是在新年音乐会上多次演出的经典曲目。其中我比较期待的是《艺术家的生活圆舞曲》和《查尔达什舞曲》,前者旋律优美,后者节奏奔放,都曾经给我留下深刻的印象。至于《天体乐声》,这个曲子的旋律也是我喜欢的类型,可这个曲子实在是演出次数太多了,经典的演绎也很多,所以反倒是没有太多期待。这就像是加演的最后那两首,刚开始听新年音乐会时,前面的曲目都不重要,就等着听这两首。而现在,这两首倒像是看完电影以后的字幕了——当然,不是说它们不重要,我看电影也是从来都是要把字幕完整看完的,如果没有它们,这就不是一场完整的新年音乐会(2005年新年音乐会是我心中永远的遗憾)。

……

这年头,资讯太发达了。早年想了解新年音乐会的相关信息,中央电视台的专题和直播几乎是唯一的渠道。而现在,就因为我没有在看到曲目单的第一时间就写完这篇文章,没过几天,网上类似体裁的文章已经是铺天盖地,一篇比一篇专业,一篇比一篇挖掘的深入。如果我再把别人写过的东西再抄一遍,意义也不大。收笔了。

最后提供一点相关的链接,有兴趣的朋友可以继续探索:

Phoenix Nemo's avatar

使用 fs.WriteStream 编写超简单的日志流

虽然 console.log 很好用,但是生产环境需要保存日志的时候就比较蛋疼。暴力 fs.appendFile 会消耗大量的 file handler,因此用 writable stream 来复用 file handler 是更好的选择。

大概是个不能再简单的思路了。先创建一个写入流

1
2
3
const fs = require('fs');

let logStream = fs.createWriteStream('./test.log');

这样便创建了一个文件写入口,需要时直接调用 logStream.write 即可写入数据。
接下来编写一个用于记录日志的函数替代 console.log

1
2
3
function logger (message) {
logStream.write(message);
}

至此基本功能就写完啦。但是太简陋了对不对,还是要再加点装饰。

重写 logger 函数,区分 stdoutstderr

1
2
3
4
5
6
7
8
9
10
11
12
let logInfo = fs.createWriteStream('./stdout.log');
let logError = fs.createWriteStream('./stderr.log');

let Logger = {};

Logger.info = (message) => {
logInfo.write('[INFO] ' + message);
}

Logger.error = (message) => {
logError.write('[ERROR] ' + message);
}

感觉还是少了点什么…日期?

1
2
3
Logger.info = (message) => {
logInfo.write(new Date().toISOString() + ' [INFO] ' + message + '\n');
}

嗯嗯。这就像样了。把代码整合起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const fs = require('fs');

let logInfo = fs.createWriteStream('./stdout.log');
let logError = fs.createWiteStream('./stderr.log');

let Logger = {};

Logger.info = (message) => {
logInfo.write(new Date().toISOString() + ' [INFO] ' + message + '\n');
}
Logger.error = (message) => {
logError.write(new Date().toISOString() + ' [ERROR] ' + message + '\n');
}

module.exports = Logger;

需要用时

1
Logger.info('This is an information.');

现在看对应的 stdout.log 文件就有相应内容啦。

1
2
~> tail -f stdout.log
2018-11-18T10:52:57.333Z [INFO] This is an information.

不够刺激?

1
2
3
[...Array(10000)].forEach((item, index) => {
Logger.info('Hello! ' + index);
});
1
2
3
4
5
6
7
8
9
10
11
12
~> tail -f stdout.log
...
2018-11-18T10:58:30.661Z [INFO] Hello! 9990
2018-11-18T10:58:30.661Z [INFO] Hello! 9991
2018-11-18T10:58:30.661Z [INFO] Hello! 9992
2018-11-18T10:58:30.661Z [INFO] Hello! 9993
2018-11-18T10:58:30.661Z [INFO] Hello! 9994
2018-11-18T10:58:30.661Z [INFO] Hello! 9995
2018-11-18T10:58:30.661Z [INFO] Hello! 9996
2018-11-18T10:58:30.661Z [INFO] Hello! 9997
2018-11-18T10:58:30.661Z [INFO] Hello! 9998
2018-11-18T10:58:30.661Z [INFO] Hello! 9999

搞定(┌・ω・)┌超简单的吧。

sunjw's avatar

Windows 10 18282 新桌面壁纸 4K 原版

Windows 10 18282 新桌面壁纸 4K 原版 Windows 10 18282 new wallpaper 4K original version

直接下载链接(Dowload link):http://www.sunjw.us/blog/wp-content/uploads/2018/11/img0_3840x2160.jpg

Phoenix Nemo's avatar

重构 StickerSetBot

关注 Telegraf 有一段时间了。特别是最近 Telegram 上 spammer 猖獗导致 Telegram 对于用户行为限制越来越严格,由此想过写一个简单的 bot 来处理加群请求之类的。

总之原因都是没时间。终于搞定一些事情之后发现之前瞎写的 Telegram 导出贴图 bot 居然备受欢迎…正好 Telegram Bot API 也更新了,来重构吧!

拆分逻辑代码

最头疼的事情首先是当时写这 bot 的时候只顾着考虑各种情况,逻辑像流水一样全部写成一坨。虽然实际不复杂吧但这不是 best practice。于是把每个功能单独拆出来先。

on('command') 的逻辑代码整块移出来作为 handler,然后能够原子化的功能再单独拆分成函数调用。目前的效果虽然还是有不少逻辑层在 handler 里,但是基本达到了比较方便维护的目的。

handler 本来就是拿来写逻辑的啊摔

接下来再清理冗余代码和各种 hard code,加了两个方法让代码看起来更整洁一些。于是就先这样。

迁移框架

好在 Telegraf 和之前用的框架在参数上很多兼容,所以这没有花太多时间。顺便尝试采用了一部分 ES6 的风格,嘛…果然不喜欢。

所以就不要吐槽为什么 ES5 和 ES6 的风格混写了。

之前要一大长串的传参现在只要一个 context 了好方便啊。中间件也好方便啊~

以上。

调试:无尽的 bugfix

并不指望一通大换血之后的代码能一次跑起来…但是没跑起来的原因是我传错了中间件值这不能忍!!为什么一会儿传的是函数本体一会儿传的是函数调用啊摔!!

而且这问题还让我调了两个小时!!!

调通了之后就很舒服了

遇到的坑还有 context 本身不能当 session 用,然而不想再引入 session 中间件于是自己写了个超简陋的内存 session。就是为了多语言支持。因为一觉醒来发现这 bot 语言莫名其妙变中文了(messages 成了全局变量 = =

当然还有 Telegram 自己的坑,比如什么贴纸就是死下载不能然后整个程序就 hang 着了。

一键导出贴纸包

终于!Telegram bot API 添加了 StickerSet 类型。只要有贴纸包名称,就可以获取整个贴纸包的信息。考虑不改变用户习惯的情况下(你哪有什么用户啊可恶)对本身处理贴纸和其他消息的函数做了修改,顺便又拆了俩函数出来(怎么代码越来越多了啊喂!

最后结果就是没有一屏看不到头的函数啦~(你快够

以及加入了用贴纸包链接导出一整组贴纸的功能,算是真正意义上的 StickerSetBot 了。

然后贴纸过多卡死了 Telegram 的 ratelimiting

直接导出单张贴纸

既然功能拆分了那也就方便加更多别的功能啦。比如不新建任务,直接甩过去一张贴纸来获得 PNG 文件~

这只 bot 在这里,源码在这里。欢迎各种玩坏~(记得去发 issue

就酱(,,•﹏•,,)

alswl's avatar

DevOps 和 SRE

最近有一位朋友和我聊职业发展方向问题,聊了不少 DevOps 和 SRE 话题。 我几年前刚接触这两个概念时也常常将之混淆,可惜当时没有人来解答我困惑。 现在这虽然已经极为流行,但是我发现我这位朋友对这两个职位还存在一些误区。 于是我给了一些见解并整理成文章以饕大众。

最常见的误区:

  • DevOps 新概念,好高级哦
  • SRE 是高级版 DevOps
  • 运维可以轻松转身 DevOps 工程师

让我一一给你讲解吧。

image via YouTube

DevOps 和 SRE 定义

DevOps 是字面上 Dev 开发 / Ops 运维两者组合, 严格意义上 DevOps 如下(via DevOps - Wikipedia):

DevOps(Development 和 Operations 的组合词)是一种重视“软件开发人员(Dev) ”和“IT 运维技术人员(Ops)”之间沟通合作的文化、运动或惯例。

SRE 全称是 Site Reliability Engineering,最早是由 Google 提出,并且在其工程实践中发扬光大。 他们还出了一本同名书籍「Site Reliability Engineering」, 让这个理念在互联网工程师圈子里广泛传播。

Google 对 SRE 解释是(via Site Reliability Engineering - Wikipedia):

Site reliability engineering (SRE) is a discipline that incorporates aspects of software engineering and applies that to operations whose goals are to create ultra-scalable and highly reliable software systems.

我将其翻译翻译为中文:

网站稳定性工程师是致力于打造「高扩展、高可用系统」,并将其贯彻为原则的软件工程师。

从定义来看,DevOps 是文化、运动和惯例,而 SRE 是有严格任职要求的职位。 文化是软性定义,文化有更多概念可以捏造,而 SRE 定义精准,就少了想象空间(也可能 SRE 门槛高 😄)。 按 Google 给出的说法是,SRE 工程师实践了 DevOps 文化。这个观点没错,但是国内的 DevOps 逐步独立出 DevOps 工程师, 所以在本文,我着重讨论的是 DevOps 工程师和 SRE 工程师两种职位对比。

两者产生背景和历史

互联网需求催生了 DevOps 。在最传统软件企业中,是只有 Dev 没有 Ops, 那时 Ops 可能还是只是技术支持人员。开发按照瀑布流:需求分析、系统设计、开发、测试、交付、运行, 传统软件发布是一个重量级操作。一旦发布,Dev 几乎不再直接操作。 80 后可能会记得 QQ 每年都会有一个大版本发布吧,QQ 2000 / 2003 / 2004 等等。 此时 Ops 不用和 Dev 直接高频接触,甚至针对一些纯离线业务,压根没有设立 Ops 这个岗位。

互联网浪潮之后,软件由传统意义上桌面软件演变为面向网站、手机应用。 这时候业务核心逻辑,比如交易,社交行为都不在用户桌面完成,而是在服务器后端完成。 这给互联网企业给予了极大操作空间:随时可以改变业务逻辑,这促进了业务快速迭代变更。 但即便这样,Dev 和 Ops 是极其分裂的两个环节。Ops 不关心代码是如何运作的,Dev 不知道代码如何运行在服务器上。

当业界还沉浸在可以每周发布版本喜悦中时,2009 年,Flicker 提出了每天发布 10+ 次概念,大大震撼了业界。 Flicker 提出了几个核心理念:

  • 业务快速发展,需要拥抱变更,小步快跑
  • Ops 目标不是为了网站稳定和快速,而是推动业务快速发展
  • 基于自动化工具提高 Dev / Ops 联接:代码版本管理、监控
  • 高效沟通:IRC / IM Robot(现在那些 ChatBot 套路,10 年前就被 Flicker 玩过了)
  • 信任、透明、高效、互助的沟通文化

原文 SlideShare 在这 10+ Deploys Per Day: Dev and Ops Cooperation at Flickr

真是让人难以想象,今天各种培训公司和一些知名大 V 在呼唤这些 DevOps 理念, 竟然在 2009 年一份幻灯片中就展现淋漓尽致。经典总是不过时,在尘封下闪耀着智慧光芒。 有些人将 DevOps 和运维自动化等同,这是只看到表象。 DevOps 目标是提高业务系统交付速度,并为之提供相关工具、制度和服务。 一些个人或培训机构添油加醋和衍生含义,都是围绕这 DevOps 本质而发散。

接下来聊聊 SRE 历史, SRE 出现要晚一些。在 2003 年时候 Google 的 Ben Treynor 招募了几个软件工程师,这个团队设立目的是帮助 Google 生产环境服务运行更稳定、健壮、可靠。 不同于中小型规模公司,Google 服务于十几亿用户服务,短暂服务不可用会带来致命后果。 因此 Google 走在了时代最前面,SRE 产生了。

这个职位为大规模集群服务,小型团队不需要这样职位设定(可能也招不起真正 SRE 😊)。 Google 在探索若干年之后,SRE 团队开始将自己心得体会写在线上,并在 2016 年将此书出版。

两者的职能不同

DevOps 文化,那么就没有一个具象职能要求。现在不少公司将 DevOps 职能单独抽取出来,称之为 DevOps 工程师。 那让我们看看 DevOps 工程师关心什么:DevOps 文化目的是提交交付速度, DevOps 工程师就自然会关心软件 / 服务的整个生命周期。

一个简单的公式:速度 = 总量 / 时间,添上工程行业术语,即 交付速度 = ((功能特性 * 工程质量) / 交付时间) * 交付风险

功能特性交给产品经理和项目经理管理,DevOps 工程师需要关心剩下几个因素:工程质量 / 交付时间 / 交付风险。 DevOps 工程师职能如下:

  • 管理应用全生命周期(需求、设计、开发、QA、发布、运行)
  • 关注全流程效率提升,挖掘瓶颈点并将其解决
  • 自动化运维平台设计和研发工作(标准化、自动化、平台化)
  • 支持运维系统,包括 虚拟化技术、资源管理技术、监控技术、网络技术

SRE 关键词是「高扩展性」「高可用性」。高扩展性是指当服务用户数量暴增时, 应用系统以及支撑其服务(服务器资源、网络系统、数据库资源)可以在不调整系统结构,不强化机器本身性能 ,仅仅增加实例数量方式进行扩容。高可用性是指,应用架构中任何环节出现不可用时,比如应用服务、网关、数据库 等系统挂掉,整个系统可以在可预见时间内恢复并重新提供服务。当然,既然是「高」可用, 那么这个时间一般期望在分钟级别。SRE 职能可以概括为以下:

  • 为 应用、中间件、基础设施等提供 选型、设计、开发、容量规划、调优、故障处理
  • 为业务系统提供基于可用性、可扩展性考虑决策,参与业务系统设计和实施
  • 定位、处理、管理故障,优化导致故障发生相关部件
  • 提高各部件资源利用率

工作内容不同

职责不同导致两个职位工作内容也不尽相同,我将 DevOps 工程师和 SRE 工程师职能列举如下:

  • DevOps
    • 设定应用生命管理周期制度,扭转流程
    • 开发、管理 开发工程师 /QA 工程师使用 开发平台系统
    • 开发、管理 发布系统
    • 开发、选型、管理 监控、报警系统
    • 开发、管理 权限系统
    • 开发、选型、管理 CMBD
    • 管理变更
    • 管理故障
  • SRE
    • 管理变更
    • 管理故障
    • 制定 SLA 服务标准
    • 开发、选型、管理 各类中间件
    • 开发、管理 分布式监控系统
    • 开发、管理 分布式追踪系统
    • 开发、管理 性能监控、探测系统(dtrace、火焰图)
    • 开发、选型、培训 性能调优工具

很有趣的对比,DevOps 和 SRE 都会关心应用生命周期,特别是生命周期里面中变更和故障。 但是 DevOps 工作内容是主要为开发链路服务,一个 DevOps Team 通常会提供一串工具链, 这其中会包括:开发工具、版本管理工具、CI 持续交付工具、CD 持续发布工具、报警工具、故障处理。 而 SRE Team 则关注更为关注变更、故障、性能、容量相关问题,会涉及具体业务,产出工具链会有: 容量测量工具、Logging 日志工具、Tracing 调用链路跟踪工具、Metrics 性能度量工具、监控报警工具等。

DevOps 和 SRE 关系

DevOps 首先是一种文化,后期逐渐独立成一个职位;SRE 一开始就明确是一个职位; 不少同学把 DevOps 和 SRE 搞混,是被两者表象锁迷惑,看上去这两者都有的工具属性、自动化要求也相似。 甚至有一些开发同学把这类运维工作都统一理解为:服务器 + 工具 + 自动化。这是盲人摸象,管中窥豹。

从技能上来说,两者都需要较强的运维技能。 在职业发展天花板上,DevOps 可能缺乏 SRE 在一些专业领域的技能: 计算机体系结构能力;高吞吐高并发优化能力;可扩展系统设计能力;复杂系统设计能力;业务系统排查能力。 两者都需要软实力,但是 SRE 面临复杂度更高,挑战更大,要求也更高:

  • 分析问题、解决问题能力
  • 对业务系统更了解
  • 具备高并发、高可用系统设计实施经验
  • 对整个系统链路有更全面认识

DevOps 具有普遍意义,现代互联网公司都需要 DevOps,但是并非所有团队对高可用性、高扩展性存在需求,它们不需要 SRE。 DevOps 工程师掌握相关技能之后,也有机会可以发展为 SRE 工程师。 而一位合格 SRE 工程师,在有选择情况下面,我相信不会去转型为 DevOps 工程师。

从专业背景来看,无论是 DevOps 还是 SRE 工程师,都需要研发背景,前者需要开发工具链,后者需要有较强架构设计经验。 如果有运维工程师想转型成为 DevOps 或者 SRE,那么需要补上相关技术知识。 毕竟,不是会搭建一套 Jenkins + Kubernetes 就可以自称为 DevOps / SRE 工程师。

怎么样,有没有解开这几个常见误区呢?希望你看到这里可以豁然开朗,最后附上两个工程师的技能点, 期望有志成为这两种工程师的同学,加油努力。

附录:技能点

DevOps:

  • Operator 技能
    • Linux Basis
      • 基本命令操作
      • Linux FHS(Filesystem Hierarchy Standard 文件系统层次结构标准)
      • Linux 系统(差异、历史、标准、发展)
    • 脚本
      • Bash / Python
    • 基础服务
      • DHCP / NTP / DNS / SSH / iptables / LDAP / CMDB
    • 自动化工具
      • Fabric / Saltstack / Chef / Ansible
    • 基础监控工具
      • Zabbix / Nagios / Cacti
    • 虚拟化
      • KVM 管理 / XEN 管理 / vSphere 管理 / Docker
      • 容器编排 / Mesos / Kubernetes
    • 服务
      • Nginx / F5 / HAProxy / LVS 负载均衡
      • 常见中间件 Operate(启动、关闭、重启、扩容)
  • Dev
    • 语言
      • Python
      • Go(可选)
      • Java(了解部署)
    • 流程和理论
      • Application Life Cycle
      • 12 Factor
      • 微服务概念、部署、生命周期
      • CI 持续集成 / Jenkins / Pipeline / Git Repo Web Hook
      • CD 持续发布系统
    • 基础设施
      • Git Repo / Gitlab / Github
      • Logstash / Flume 日志收集
      • 配置文件管理(应用、中间件等)
      • Nexus / JFrog / Pypi 包依赖管理
      • 面向 开发 / QA 开发环境管理系统
      • 线上权限分配系统
      • 监控报警系统
      • 基于 Fabric / Saltstack / Chef / Ansible 自动化工具开发

SRE:

  • 语言和工程实现
    • 深入理解开发语言(假设是 Java)
      • 业务部门使用开发框架
      • 并发、多线程和锁
      • 资源模型理解:网络、内存、CPU
      • 故障处理能力(分析瓶颈、熟悉相关工具、还原现场、提供方案)
    • 常见业务设计方案和陷阱(比如 Business Modeling,N+1、远程调用、不合理 DB 结构)
    • MySQL / Mongo OLTP 类型查询优化
    • 多种并发模型,以及相关 Scalable 设计
  • 问题定位工具
    • 容量管理
    • Tracing 链路追踪
    • Metrics 度量工具
    • Logging 日志系统
  • 运维架构能力
    • Linux 精通,理解 Linux 负载模型,资源模型
    • 熟悉常规中间件(MySQL Nginx Redis Mongo ZooKeeper 等),能够调优
    • Linux 网络调优,网络 IO 模型以及在语言里面实现
    • 资源编排系统(Mesos / Kubernetes)
  • 理论
    • 容量规划方案
    • 熟悉分布式理论(Paxos / Raft / BigTable / MapReduce / Spanner 等),能够为场景决策合适方案
    • 性能模型(比如 Pxx 理解、Metrics、Dapper)
    • 资源模型(比如 Queuing Theory、负载方案、雪崩问题)
    • 资源编排系统(Mesos / Kurbernetes)

Ref


原文链接: https://blog.alswl.com/2018/09/devops-and-sre/
3a1ff193cee606bd1e2ea554a16353ee
欢迎关注我的微信公众号:窥豹
's avatar

纯软件共享有线键鼠

嗯……我决定暂时忽略我那个已经攒了二十多条 TODO 项的待写博客标题,还是想到啥写啥吧。

话说我现在也算是个用上了机械键盘的正经程序员了嘛,然后就也像其他人一样再也回不去普通键盘的那种手感了。然额我买的这个是有线键盘,只能接在我的主力工作机上用,需要做 iOS 相关开发的时候就不得不用回我那将近十年高龄的 MBP 自带键盘了,总不能每次切换的时候都来回插拔吧,一度令我很是烦恼。

PS. 之前挑选键盘的时候就发现,为啥现在 84 键的键盘这么少呢,明明既有 87 键的键位完备性,又并不比 60% 键盘大多少来着😜,光是能找到一个合适的入门款就已经不错了,更别提有没有蓝牙款的可选了。不过就算有估计我也不会考虑,因为我还是不太喜欢充电或者换电池这种事情😜

前天……唔已经是大前天了,决定再搜一次看看有没有什么方便的解决办法。靠自己只想到了两个搜索方向,一个是 KVM,好歹我以前也是进过机房的人嘛,优点是连显示器都能共享(我的工作机显示器还是挺大的,比 MBP 不知道高到哪里去了呀),缺点是又要连好几根线,我看了看桌面上已然乱七八糟的走线,决定作罢。

另一个就是有线转蓝牙了,然后就在网上一通搜,还真有这种东东,比如这个这个,不过都有点儿小贵呀,都快赶上我这键盘的价格了,而且我也不需要接手机的,犹豫的时候还听到有群友说蓝牙连接其实也不怎么稳定好用来着,于是也作罢了。

这时有群友向我推荐了 synergy,我才知道原来还有通过无线 WIFI 配合软件来实现共享的点子,真是妙啊为啥我之前就想不到呢。哎呀不过这个软件也是要钱的,还是先上 alternativeTo 找找看有没有免费的吧😜。支持跨平台的替代中,先是试了下 ShareMouse,因为有个人免费版可用,不过后来发现这东东限制一台电脑只能有最多一个显示器,而我今年刚好给主力机配了个 Paperlike HD,所以就用不了这个了。

然后就是现在在用的 barrier 啦。虽然一开始看到这东东是开源的还挺有好感的,不过扫了一眼主页发现貌似只能自己编译感觉好麻烦还是有点儿想放弃的来着。不过好歹咱也是个程序猿啊,编译都搞不定的话怎么说得过去,于是就开搞了。

先是 Windows。需要的编译工具有 cmake、Visual Studio(Community 即可)、Qt、Bonjour SDK for Windows 等,还好这些在 Windows 下也都挺好装的(虽然只有 cmake 可以通过 scoop 来装😂),然后按照 ./clean_build.bat 里面的代码创建一个 build_env.bat 文件,来设置下所需的环境变量,就可以执行 ./clean_build.bat 来编译了。嘛反正在我这儿编译还算顺利(除了搞清楚 Qt 要用最新的 5.11.1 才行,还有缺的那个 dns_sd.h 是出自 Bonjour 之外😂),也能直接跑起来,然后我就又去编 macOS 版本的了。

macOS 的依赖准备也差不多,确保你有 Xcode,然后用 Homebrew 装了 cmake、qt 就够了,Bonjour 本就是苹果家的,应该是算在 Xcode 自带里的吧。同样也需要创建个 build_env.sh 文件,不过就不需要设置依赖路径啥的了,只要指定个 Release 就好。不过编完之后的 barrier 命令不能直接运行,要通过 ./build/bundle 目录下的 Barrier.app 来执行才行,把它拖到 Applications 里即可(我一开始没指定编译成 Release,编译出来的 Barrier.app 是运行不了的😂)。

然后就可以跑起来试试啦。Windows 这边指定为服务端,macOS 指定为客户端,Barrier 可以自己通过局域网相互搜索到对方,不过我的 Windows 会被检测出好几个 IP 地址,而对应 Wifi 的那个地址并不是首选的,所以客户端连过来有困难。不过我的 Windows Wifi 地址是固定的,所以只要在客户端手动填下服务端地址就能搞定,就不折腾了。

最后就是服务端的问题了,一直是 starting 状态,从日志看也是要连个啥东西,我猜应该是 Windows Service?然额找了半天也没搞懂该怎么手动安装 service,只好继续去 github 仓库翻翻看有啥提示没。这一翻不要紧,原来 Barrier 仓库的 wiki 是有内容的😂,不只有编译说明(虽然自己摸索也不是很难吧😂(虽然自己摸索出来的 Windows 版不能正常工作吧😂)),还有安装包的下载地址😂😂,当时我就惊了啊,这么重要的东西你为啥不放在首页啊😂。然后我就选择弃疗了,直接下载了 Windows 的安装包搞定,直接跑起来就可以用了😂。

用起来还是挺好的,指定下相对位置后,用鼠标就可以直接在两个屏幕间移来移去啦,然后鼠标在哪个屏幕,键盘焦点就在哪台电脑上,丝般顺滑(好吧鼠标在客户端那边说不上有那么顺滑,毕竟是通过软件和 Wifi 二传手之后的信号😂,不过够用啦),这下不仅能愉快地在两台电脑上都用上我的机械键盘,在切换时也不用再挪椅子啦😂

而且说实话,有时候还是会觉得鼠标比触摸板要更好用一些的,就算是苹果的触摸板(逃

Phoenix Nemo's avatar

Office Service Router 解决方案:Arch Linux in RAM

一直把自己在办公室的 PC 保持开机用于连回办公区、存取数据工作需求。由于最近办公室所在的写字楼要全馆断电检点,所以诞生了构建一个 Service Router 的想法。

思路

运行在内存里对于 Linux 系统来说是完全可能(而且简单)的事情。

最直接的想法就是使用内核 hook 在启动时复制根分区到内存盘然后挂载内存里的数据作为根分区即可。

设备的话,设置 Power on AC 即可通电自启动。

ramroot

作为一只懒卷,这种简单的事情当然先顺手搜索下啦。然后就发现了几乎完美的解决方案——ramroot

ramroot 通过加入内核 hook 然后自动在内存建立 zram 分区,同步根分区数据再启动。还可以在启动时选择是否启动进内存,正好解决了所有的需求。

实现

硬件选择是一台便宜的 Intel NUC,安装两根 4GB LPDDR3 低压内存和一块 120G 2.5 SSD。虽然说起来其实并不需要 SSD(因为数据全部都在内存里,速度比 SSD 更快)但是毕竟日本多震,还是为数据安全着想。毕竟硬盘坏了的话内存系统也无法启动了。

当然如果有集成 32GB eMMC 的小型 PC 的话也是好的选择。

正常安装完 Arch Linux 系统,安装 openssh 和各种必要的服务程序,修改配置文件,然后安装 ramroot 并执行

1
# ramroot enable

此时先别急着重启,先把不需要的包、缓存等文件(/var/cache)删除,保持最小化的根分区。然后再重启。便可看到加载内核 hook 时的提示是否进入内存系统,默认超时后就会自动复制根分区到内存啦。

由于整个系统是运行在内存中的,所以完全没有等待读盘的时间。整个系统的响应速度非常快。限制是内存不够大的话运行一些业务会比较捉襟见肘,而且这样低功耗、低发热的 SoC 处理性能也只能运行一些轻型任务。

下面是一些 IO 性能测试

1
2
3
4
5
6
7
8
9
10
11
12
# ioping -s 1G /
1 GiB <<< . (ext4 /dev/zram0): request=1 time=1.04 s (warmup)
1 GiB <<< . (ext4 /dev/zram0): request=2 time=1.04 s
1 GiB <<< . (ext4 /dev/zram0): request=3 time=1.04 s
1 GiB <<< . (ext4 /dev/zram0): request=4 time=1.04 s
1 GiB <<< . (ext4 /dev/zram0): request=5 time=1.04 s
1 GiB <<< . (ext4 /dev/zram0): request=6 time=1.04 s ^C

--- / (ext4 /dev/zram0) ioping statistics ---
5 requests completed in 5.18 s, 5 GiB read, 0 iops, 988.4 MiB/s
generated 6 requests in 7.20 s, 6 GiB, 0 iops, 853.8 MiB/s
min/avg/max/mdev = 1.04 s / 1.04 s / 1.04 s / 550.7 us
1
2
3
4
5
6
# ioping -RD /

--- / (ext4 /dev/zram0) ioping statistics ---
530.0 k requests completed in 2.49 s, 2.02 GiB read, 212.9 k iops, 831.7 MiB/s
generated 530.0 k requests in 3.00 s, 2.02 GiB, 176.7 k iops, 690.1 MiB/s
min/avg/max/mdev = 3.44 us / 4.70 us / 69.0 us / 1.39 us

可以看到系统根分区在 zram 里,经过压缩因此 IO 带宽受到了 CPU 处理性能的限制。但是 IOPS 依然高得爆表,对比一下 Intel Optane 900P 的 IOPS 性能:

1
2
3
4
# ioping -RD /
--- / (ext4 /dev/nvme0n1p1) ioping statistics ---
163.1 k requests completed in 3.00 s, 56.5 k iops, 220.8 MiB/s
min/avg/max/mdev = 11 us / 17 us / 114 us / 4 us

炒鸡厉害对不对!

不过需要做永久性修改的话还是要下面的方法之一

  • 重新挂载磁盘(虽然并不麻烦)然后手动修改配置文件
  • 重新挂载磁盘然后 rsync zram 到磁盘(方便但是可能会多一些不必要的东西)
  • 重启进入磁盘系统然后运行修改(需要物理接触)

硬件设置

进入系统 BIOS 设置,开启 Power on AC 或设置 Power Failure 后的操作,选择为 Power On (默认一般是 Last State)。

关闭系统、拔出电源,或意外断电后,再接入电源即可自动开机引导系统。因为数据本身就只在内存中,除了运行中的临时更改会丢失,系统和硬盘本体都是安然无恙的。

再也不担心办公室断电检查啦。

大概就是这样。

Phoenix Nemo's avatar

通过 SSH 修正安装有 GPU 的 HPE Proliant 服务器

由于越来越多的渲染、压制等需求,托供货商的关系搞来一台带有独立显卡的 HPE 服务器。经过几番折腾(包括特别奇怪的 LS26-C14 电源线)麻烦了帮忙托管的数据中心的大兄弟好几回,终于算是上架可以开机了。

登入 iLO,安装许可证,启动 iLO Remote Console,打开电源,一切都很顺利。但是 Console 里显示 Early Initialization… 完成后,突然画面一黑,完全没了动静。

以为 iLO 出了 bug,冷重启好几次都是一样的结果。百思不得其解。

再重启一次。仔细观察了一番发现虽然没了画面,但是 POST Code 还是不断变化的,而且 Virtual Media 指示灯不断在闪烁,说明系统仍在正常运行,只是没有视频输出而已。

因此问题定位在视频输出而非系统硬件。既然这台服务器装了显卡,那么很可能是 PCI-e 初始化后视频输出全部交给显卡处理了。搜索了一下 HPE Community,确实有这样的情况存在。解决方案是通过 BIOS 修改显卡设置为默认集成显卡、备选独立显卡。

尝试在设备初始化阶段进入 BIOS,失败。

联系数据中心远程操作的话,可能要等一段时间。

纠结时随便点开 iLO 的管理页面,突然发现了华点:这货居然支持 SSH。

对啦,HPE 的底层系统几乎都是魔改版 Linux,连他们的 SmartArray 都是 Linux 启动一个 Firefox 浏览器来操作的(X

于是正好在网上搜到一篇通过 SSH 修改 BIOS 视频设置的方法。记录如下。

SSH 进入 iLO

确保 SSH 在 iLO 管理页面中已开启,然后使用 SSH 客户端正常连接:

1
ssh Administrator@10.6.254.121

(ssh 用户名是 Administrator 感觉各种违和)

连接到 Virtual Serial Port

命令很简单:vsp

在 iLO 管理页面重启系统,然后等待初始化完成。如果看到按下 F9 进入 BIOS 设置的提示,不要按下它否则会进入 GUI 模式(于是又去独立显卡了就。

看到 ESC + 9 进入 BIOS Setup Utility 时按下键组合,稍等一会儿应该就可以看到提示符 rbsu>

修改视频设置

命令 SHOW CONFIG VIDEO OPTIONS

显示如下

1
2
3
1|Optional Video Primary, Embedded Video Disabled <=
2|Optional Video Primary, Embedded Video Secondary
3|Embedded Video Primary, Optional Video Secondary

即默认关闭了集成显卡,只用独立显卡(不觉得很蠢吗!

于是修改为第三项,默认使用集成显卡,独立显卡作为备用。

1
SET CONFIG VIDEO OPTIONS 3
1
2
3
1|Optional Video Primary, Embedded Video Disabled
2|Optional Video Primary, Embedded Video Secondary
3|Embedded Video Primary, Optional Video Secondary <=

然后敲 EXIT 退出并重启系统。

安装系统和驱动

至此即可通过 iLO Advanced Console 正常安装操作系统。不过需要注意的是进入操作系统后即便安装了对应的显卡驱动,依然默认使用的是集成显卡。以及 RDP 只能使用软解,无法使用独立显卡加速视频输出。这不影响 Blender 或者 Cinema 4D 等直接操作显卡进行计算的程序,但是会影响直接输出视频到桌面的程序。通过 Teamviewer 则可以强制桌面运行在独立显卡上。

顺便吐槽:Blender 把我的工程材质弄丢了…

alswl's avatar

破解三才五格姓名测试

image from Wikipedia 八卦

随着孩子预产期临近,我还有一个重要的任务没有完成:给孩子起一个名字。 这本来是个随性的任务,但是由于上一辈笃信某个算命先生的姓名测试算法,让这个任务难度倍增。 我根据一些古文取了不少名字,但是最后都败在姓名测试上面:得分不高。得分不高老一辈就要有说辞, 我自己就是一个活生生案例,曾用名得分不高,中考被逼换了名字,改头换面重新做人。

我根据韵律取的名字几乎都败在算分数上面,我得琢磨一下其中奥秘,提高取名效率,避免再出现差错。 不少网站都提供姓名测试算命,我且先看看上面的得分,研究一下规律:

每家网站都写得天花乱坠:易经、五格剖象法、五行起名、五格起名、三格数理。 这些都不用 Care,因为它们都是来自同一个算法:「三才五格」。

熊崎健翁的三才五格

我回想先生使用的算命方法,有这么几个特点:

  • 跟我强调使用了「康熙字典」进行计算
  • 给了我一个网址是 .jp 结尾,我当年上网计算之后,的确得到的数字和先生算计的一样,惊为天人

根据这两个线索,我很快挖掘到了 姓名学 - 维基百科,自由的百科全书 。这里面就着重解释了日本人熊崎健翁的「三才五格」算法,

五格分别是天格、人格、地格、外格、总格,三才是天格、人格、地格的总称。 虽然三才五格看着挺像中国易经的概念,但其实是出口转内销的舶来品。 五格的具体解释如下:

  • 天格:是先祖留传下来的,其数理对人影响不大。
  • 地格:又称前运,影响人中年以前的活动力。
  • 人格:又称主运,是整个姓名的中心点,影响人的一生命运。
  • 总格:又称后运,影响人中年以后的命运。
  • 外格:又称变格,影响人的社交能力、智慧等,其数理不用重点去看。

另外熊崎健翁还创造性的将五行引入,把这个模型做的更复杂。 不过评分的影响因子还是只有五格,单个字的五行并没有发挥直接的作用。 也有一些算命先生会刻意将单字的五行做成相生相克连贯起来,这样显得更有学问。

三才五格计算算法

这个算法的流程是这样:

  • 计算每个汉字的笔画数
    • 姓名拆分为姓和名,尤其要注意单姓和复姓
    • 将简体姓和名转换为繁体姓和名
    • 将繁体姓和名映射为康熙字典中的姓和名
    • 将康熙字典中的姓和名的笔画数检出
  • 根据笔画数计算五格(数字)
    • 天格:姓氏笔划再加一数即是天格数(若是复姓,将姓之笔划合计)
    • 人格:将姓氏与第一个名字相加即是人格数(若复姓双名,则姓氏的第二个字笔画加名的第一个字的笔画; 复姓单名则姓氏的第二个字加名的笔画)
    • 地格:将第一个名字与第二个名字相加即是地格数(若是单名,将名字再加一数)
    • 外格:将名字最后一字加一数即是外格数
    • 总格:将姓与名相加即是总格数
  • (可选)根据五格计算五行(这个五行对计算结果没有影响)
    • 尾数 1、2 于五行属木
    • 尾数 3、4 于五行属火
    • 尾数 5、6 于五行属土
    • 尾数 7、8 于五行属金
    • 尾数 9、10 于五行属水
  • 根据笔画数得到解释
    • 计算规则见 总格_百度百科, 其内容是列出 1-81 数字对应的情况,这个规则没有给出具体吉凶
  • 根据解释得到吉凶等级
    • 123cha / 中华起名网 / 美国神婆都是根据这个解释给出一个大概吉凶,他们给吉凶有一些小差异
  • 根据吉凶等级计算总分
    • 每家网站的算法有些差异,我估计算命先生的公式也应该有一些差异
    • 我自己根据对五格的解释,给出一个加权公式,跟几家算命网站进行了加权因子拟合

上述流程中,有两个给算命先生留出的调整空间:

  • 根据解释推出吉凶
  • 根据吉凶计算总分

根据解释推出吉凶,我研读了一下解释,比较倾向中华起名网和美国神婆的吉凶判断。 举例「旱苗逢雨」的解释,123cha 给出「(旱苗逢雨) 万物更新,调顺发达,恢弘泽世,繁荣富贵」吉。 中华起名网和美国神婆给出「(旱苗逢雨):挽回家运的春成育数」和 「(旱苗逢雨)万物更新,调顺发达,恢弘泽世,繁荣富贵」大吉。 从解释的意象来看,我觉得应当作大吉。

第二个调整空间是根据吉凶计算总分。有了五格的吉凶,要给出百分制下面的总分, 这看上去需要一个加权公式。这个公式的设计决定最后得分多少。 我根据五格的定位设计了一份。

常见吉凶等级以及他们对应的得分,这个得分是我自行设计,从 100-0 依次排序:

  • 大吉 100
  • 吉 75
  • *半吉 62.5
  • 平 50
  • 凶 25
  • 大凶 0

由于没有统一算法计算吉凶总分,我根据五格解释,设计了吉凶等级的加权因子:

  • 天格:是先祖留传下来的,其数理对人影响不大。5%
  • 地格:又称前运,影响人中年以前的活动力。20%
  • 人格:又称主运,是整个姓名的中心点,影响人的一生命运。45%
  • 总格:又称后运,影响人中年以后的命运。20%
  • 外格:又称变格,影响人的社交能力、智慧等,其数理不用重点去看。10%

大功告成,我们拿几个姓名过来测算一下这个算法是否合理:

  • 芮成钢总分:100*0.05 + 62.5*0.2 + 100*0.45 + 62.5*0.2 + 100*0.1 = 85
    • 123cha 72
    • 中华算命 92
    • 美国神婆 82
  • 狄小天总分:0*0.05 + 100*0.2 + 75*0.45 + 100*0.2 + 100*0.1 = 83.75
    • 123cha 81
    • 中华算命 91
    • 美国神婆 85
  • 狄二总分:25*0.05 + 25*0.2 + 100*0.45 + 0*0.2 + 25*0.1 = 53.75
    • 123cha 46
    • 中华算命 59
    • 美国神婆 65

通过最后的得分可以看到,三种算法的结果还是比较接近的。

工程实现

光实现了算法还没完,我还要有程序帮我做自动化计算。 名字的输入可以自行设计,用诗经、楚辞、古诗源等古文,然后导入程序计算得分。

为了工程实现,我需要准备如下数据:

  • 简体汉字到繁体汉字的转换
  • 繁体汉字到康熙字典中汉字比划数
  • 根据笔画数计算出吉凶
  • (可选)汉字对应的五行

经过一下午搜索,我终于将需要的数据准备好:

最后

资深算命先生狄大师提醒大家,不管什么测字法都是玄学,玩玩就好,千万不要过于上心, 除非你家里有老人信这个,而你恰好又是一个孝子。

在一切准备就绪之后,我离出去当算命先生之差一个竹竿和墨镜了。 作为工程师,我当然会提供一个开放应用给大家用,请大家耐心等待我的小程序上架。


原文链接: https://blog.alswl.com/2018/07/naming-algorithem/
3a1ff193cee606bd1e2ea554a16353ee
欢迎关注我的微信公众号:窥豹
Li Fanxi's avatar

用NVME提高群晖NAS的性能

群晖的DSM有一个比较鸡肋的功能:SSD Cache,原因如下:

  1. 单条NVME只能做只读Cache,功能有限
  2. 一个SSD Cache只能加速一个存储池(DSM 6.2中的词汇,以前叫存储空间)
  3. 系统分区不在任何存储池里,所以系统分区上的东西都不能加速
  4. NVME只能做Cache,不能用来存数据

DMS的SSD Cache功能是基于Linux的dm-cache来实现的,理论上通过Hack是可以解决上面说的这些问题的,不过得摸透了DSM相关的所有逻辑才能搞,而且对DSM的侵入比较大。所以,有一个想法,直接解决第4条问题,想办法把NVME当成正常的磁盘来存数据。然后就可以按自己的想法,灵活地挑一些需要高速随机IO的数据放在NVME上,其它的数据还是放在磁盘上。

如果问群晖的技术支持可不可以这样做,回答是否定的——非常可以理解,在群晖的设备上,盘位就是钱,白给你增加两个盘位?想都别想。

研究了一番,找到了解决这个问题的办法,虽然不完美,但是可用,并且效果不错。以下内容基于DS918+的硬件,DSM 6.2,系统中所有的磁盘都是Basic模式。由于条件所限,我没有测试RAID或SHR的情况,但原理应该都是一样的,可以参考一下我的思路。DSM的RAID就是标准的Linux RAID,而SHR则是基于LVM搞出来的。

以下步骤请充分理解后自行操作,所有给出的命令仅供参考,切勿依样画葫芦。这里介绍的方法有可能会造成不可逆的数据损失,请自己评估风险。

1. 分区

安装NVME后,系统中会出现/dev/nvme0n*设备,并且在存储管理器中可以看到对应设备。不要在DSM中创建SSD Cache,因为我们准备自己管理这个硬件。

Basic模式下,DSM会把系统中的每块硬盘都分成三个区:

Device      Start         End     Sectors Size Type
/dev/sdd1    2048     4982527     4980480 2.4G Linux RAID
/dev/sdd2 4982528     9176831     4194304   2G Linux RAID
/dev/sdd3 9437184 11720840351 11711403168 5.5T Linux RAID

第一个分区用于存放DSM系统,第二个分区是SWAP,第三个分区是用户可用的存储空间。每块磁盘的前两个分区,分别组成/dev/md0和/dev/md1两个RAID设备,都是RAID 1的模式。

所以,我们也依样画葫芦,把NVME也用相同的方式分区(可以使用系统自带的parted工具),分出与别的磁盘大小一样的前两个分区,类型都设置为RAID。然后剩余的空间分为普通Linux分区,并格式化为你需要的文件系统(ext4或btrfs)。当然,如果你有多块NVME或者你愿意,你也可以把剩余空间分为RAID类型,创建md设备后再格式化。

2. 创建RAID组

分完区后,扩展md0和md1,把NVME的前两个分区,加到这两个RAID组中。

# 如果是2盘位的设备,大概只需要扩展成3就可以了 
$ sudo mdadm --grow --raid-devices=5 /dev/md0 
$ sudo mdadm --grow --raid-devices=5 /dev/md1
$ sudo mdadm --manage /dev/md0 --add /dev/nvme0n1p1
$ sudo mdadm --manage /dev/md1 --add /dev/nvme0n1p2

然后,把第三个数据分区mount到一个你喜欢的位置,比如:

sudo mount /dev/nvme0n1p3 /volume1/homes/admin/nvme

考虑到NVME和磁盘混合组成RAID,可以把所有磁盘(不包括NVME)的对应分区设为writemostly,这样可以让读操作尽量走NVME,提高性能:

sudo echo writemostly | tee /sys/block/md0/md/dev-sdX1/state
sudo echo writemostly | tee /sys/block/md1/md/dev-sdX2/state

这样做完以后,系统分区和SWAP分区都已经可以被NVME加速了。这一步是完全没有风险的,因为即使未来NVME从RAID 1组中意外丢失或损坏,也不会造成任何数据问题。

同时在DSM中,正常访问home/nvme就是访问NVME上的内容,这个目录可以当作普通的存储空间来使用,存放一些需要高速随机IO的数据。

3. 迁移数据

为了达到更好的性能,我们还应该把一些常用的对IO性能敏感的东西迁移到NVME上,包括但不限于:

  1. 系统的PostgreSQL/MariaDB数据库,通常位于/volume1/@database,但也可能分布在多块盘上
  2. 软件包,通常位于/volume1/@appstore,但也可能分布在多块盘上
  3. CloudSync的SQLite数据库,通常位于/volume1/@cloudsync
  4. Docker的镜像和容量数据,通常位于/volume1/@docker
  5. VMM的虚拟磁盘文件,通常位于/volume1/@Repository

迁移的方法很简单,先rsync,然后再mount –bind,比如:

sudo rsync -a /volume1/\@appstore/ /volume1/homes/admin/nvme/\@appstore/
sudo mount --bind /volume1/homes/admin/nvme/\@appstore /volume1/\@appstore

理想情况下,应该先把相关服务停止后(用synoservice命令可以启停服务)再迁移数据,避免迁移过程中有新数据写入造成不一致。为了保证万无一失,建议可以写个迁移脚本,在系统启动过程中所有服务还没有启动前运行一下这个脚本。

这一步是有风险的,因为万一未来某一次mount –bind没有成功或者没有做mount –bind,系统就无法访问到正确的数据了,这对于系统数据库之类的,还是有一定影响的,会造成数据不一致。

4. 重启自动生效

写一个脚本,用于在未来重启时重建RAID和mount相关目录,比如:

# 先把NVME上的数据盘mount起来
mount /dev/nvme0n1p3 /volume1/homes/admin/nvme
# 把SWAP的RAID 1扩容,并把NVME上的分区加进去
# 系统分区不需要,因为会自动完成。
# 但SWAP的RAID每次重启都会重建,所以每次都需要扩容。
mdadm --grow --raid-devices=5 /dev/md1
mdadm --manage /dev/md1 --add /dev/nvme0n1p2
# 把相关的目录mount --bind上去
mount --bind /volume4/homes/admin/nvme/\@appstore /volume4/\@appstore
mount --bind /volume4/homes/admin/nvme/\@cloudsync /volume4/\@cloudsync
mount --bind /volume4/homes/admin/nvme/\@database /volume4/\@database
mount --bind /volume4/homes/admin/nvme/\@docker /volume4/\@docker
mount --bind /volume4/homes/admin/nvme/\@Repository /volume4/\@Repository
# 重新设置RAID writemostly策略
echo writemostly > /sys/block/md0/md/dev-sda1/state
echo writemostly > /sys/block/md0/md/dev-sdb1/state
echo writemostly > /sys/block/md0/md/dev-sdc1/state
echo writemostly > /sys/block/md1/md/dev-sda2/state
echo writemostly > /sys/block/md1/md/dev-sdb2/state
echo writemostly > /sys/block/md1/md/dev-sdc2/state

修改/etc/rc,在SYNOINSTActionPostVolume这一行后面,增加一行对上述脚本的调用。SYNOINSTActionPostVolume执行完后,刚好是所有磁盘都mount好但是没有任何服务启动的时刻,所以这时做mount –bind是最合适的。如果你第三步数据迁移想在重启时做,也是加在这个位置。

这一步是比较不完美的,因为需要修改系统文件,而系统文件有可能会在更新DSM时被覆盖回去,万一被覆盖回去,系统启动后就是一个没有mount –bind的状态了,即使那时再改一遍脚本再重启,DB的一致性可能已经无法保证了。我暂时选用了一个带有一点防御性的做法(同样不是万无一失的):在更新DSM前,把/usr/sbin/reboot改名,这样更新完DSM后系统不会被自动重启,我就可以有机会检查/etc/rc有没有覆盖,如果被覆盖,可以自己改回来以后再重启系统。

5. 其它经验和坑

  • 数据迁移必须用mount –bind,不能用软链,已知有些应用组件在软链的情况下不能正常工作
  • /volume1/@tmp不能迁移到NVME,即使用mount –bind,也会造成Drive等组件不能正常工作
  • 现在的做法,NVME的第二个分区是后加到SWAP分区的RAID里的,所以每次重启都会有个重新同步的过程,IO会打高一会儿,并且完成后DSM会弹一个提示:一致性检查完成。但总体是可以忍的,所以我就没有去深究用什么方法可以在建立SWAP的RAID时就直接把它一起建进去了,因为我还是想尽可以少侵入DSM系统
  • 用户的共享文件夹也可以通过mount –bind迁到NVME上,但是这样做会造成在共享文件夹管理界面上不能正常查看共享文件夹相关信息,所以建议只对共享文件夹里的目录进行mount –bind
  • 如果你只有一条NVME,或者有两条但没有做RAID,那么迁移上去的那些系统文件都是单点(虽然原本在磁盘没有RAID的话是单点),需要留意其中的风险
alswl's avatar

从 SQL Server 到 MySQL(三):愚公移山 - 开源力量

我们用了两章文章 从 SQL Server 到 MySQL(一):异构数据库迁移 / 从 SQL Server 到 MySQL(二):在线迁移,空中换发动机 介绍我们遇到问题和解决方案。 不管是离线全量迁移还是在线无缝迁移, 核心 ETL 工具就是 yugong。

Yugong 是一个成熟工具, 在阿里巴巴去 IOE 行动中起了重要作用, 它与 Otter / Canal 都是阿里中间件团队出品。 它们三者各有分工: Yugong 设计目标是异构数据库迁移; Canal 设计用来解决 MySQL binlog 订阅和消费问题; Otter 则是在 Canal 之上,以准实时标准解决数据库同步问题。 Otter 配备了相对 yugong 更健壮管理工具、分布式协调工具, 从而长期稳定运行。Yugong 设计目标则是一次性迁移工作,偏 Job 类型。 当然 yugong 本身质量不错,长期运行也没问题。 我们有个产线小伙伴使用我们魔改后 yugong, 用来将数据从管理平台同步数据到用户前台,已经稳定跑了半年多了。

yugong 系统结构

这里我不赘述如何使用 yugong,有需求同学直接去 官方文档 查看使用文档。

我直接进入关键环节:解剖 yugong 核心模块。 Yugong 数据流是标准 ETL 流程,分别有 Extractor / Translator / Applier 这三个大类来实现 ETL 过程:

我们依次来看看这三大类具体设计。

Extractor

  • YuGongLifeCycle:Yugong 组件生命周期声明
  • AbstractYuGongLifeCycle:Yugong 组件生命周期一些实现
  • RecordExtractor:基础 Extractor Interface
  • AbstractRecordExtractor:基础 Extractor 虚拟类,做了一部分实现
  • AbstractOracleRecordExtractor:Oracle Extractor 虚拟类,做了一部分 Oracle 相关实现
  • OracleOnceFullRecordExtractor:Oracle 基于特定 SQL 一次性 Extractor
  • OracleFullRecordExtractor:Oracle 全量 Extractor
  • OracleRecRecordExtractor:Oracle 记录 Extractor,用来创建物化视图
  • OracleMaterializedIncRecordExtractor:基于(已有)物化视图 Oracle 增量 Extrator
  • OracleAllRecordExtractor:Oracle 自动化 Extractor,先 Mark 再 Full,再 Inc

Exctractor 从 Source DB 读取数据写入内存, Yugong 官方提供 Extractor 抽象出 AbstractRecordExtractor 类, 其余类都是围绕 Oracle 实现。 另外 Yugong 设计了 YuGongLifeCycle 类实现了组件生命周期管理。

Translator

  • DataTranslator:Translator 基类,为 Row 级别数据处理
  • TableTranslator:Translator 基类,为 Table 级别提供处理(官方代码中没有使用)
  • AbstractDataTranslator:Data Translator 虚拟类,做了部分实现
  • EncodeDataTranslator:转换编码格式 Translator
  • OracleIncreamentDataTranslator:为 Oracle 增量数据准备 Translator,会调整一些数据状态
  • BackTableDataTranslator:Demo,允许在 Translator 中做回写数据操作
  • BillOutDataTranslator:Demo,包含一些阿里业务逻辑 Translator
  • MidBillOutDetailDataTranslator:Demo,包含一些阿里业务逻辑 Translator

Translator 读取内存中 RowData 然后变换, 大部分 Translator 做一些无状态操作,比如编码转换。 另外还有一小部分 Translator 做了业务逻辑操作,比如做一些数据回写。

Applier

  • RecordApplier:基础 Applier Interface
  • AbstractRecordApplier:基础 Applier 虚拟类,做了一部分实现
  • CheckRecordRecordApplier:检查数据一致性 Applier,不做数据写入
  • FullRecordRecordApplier:全量数据 Applier,使用 UPSERT 做数据更新
  • IncreamentRecordApplier:增量 Applier,使用 Oracle 物化视图为数据源
  • AllRecordRecordApplier:自动化 Applier,先使用全量数据 Applier,然后使用增量数据 Applier

Applier 将经过 Translator 处理过的数据写入 Target DB。 Yugong 提供了一致性检查、全量、增量 Applier。 比较特殊是 AllRecordRecordApplier 提供了全套自动化操作。

Others

除了 ETL 三个要素,yugong 还有一些重要类:控制类和工具类。

  • SqlTemplate:提供 CRUD / UPSERT 等操作的基类 SQL 模板
  • OracleSqlTemplate:基于 SqlTemplate 实现的 Oracle SQL 模板
  • RecordDiffer:一致性检查 differ
  • YugongController:应用控制器,控制整个应用数据流向
  • YugongInstance:控制单个迁移任务实例,一张表对应一个 YugongInstance

老战士的问题

说 yugong 有问题会有些标题党,毕竟它是久经考验老战士了。 但对我们来说,开源版本 yugong 还有一些不足:

  • 不支持 SQL Server 读取
  • 不支持 SQL Server 写入(Rollback 需要写入 SQL Server)
  • 不支持 MySQL 读取

除了数据库支持,Yugong 在工程上面倒是也有一些改善空间。 我们最后花费了不少时间,做了工程上改进。

  • 抛弃默认打包方式(基于 maven-assembly-plugin 生成类似 LFS 结构 tar.gz 文件), 改为使用 fat jar 模式打包,仅生成单文件可执行 jar 包
  • 抛弃 ini 配置文件,使用 YAML 配置文件格式(已有老配置仍然使用 ini 文件,YAML 主要管理表结构变更)
  • 改造 Plugin 模式,将 Java 运行时编译改为反射获取 Java 类
  • 拆分 Unit Test / Integration Test,降低重构成本
  • 重构 Oracle 继承结构,使其开放 SQL Server / MySQL 接口
  • 支持 Canal Redis 格式数据作为 MySQL 在线增量数据源

改造之后结构

Extractor

  • AbstractSqlServerExtractor:新增抽象 SqlServer Extractor
  • AbstractMysqlExtractor:新增抽象 MySQL Extractor
  • AbstractFullRecordExtractor:新增抽象 Full 模式 Extractor
  • SqlServerCdcExtractor:新增 SQL Server CDC 增量模式 Extractor
  • MysqlCanalExtractor:新增 MySQL Canal 格式增量消费 Extractor
  • MysqlCanalRedisExtractor:新增 MySQL Canal 格式增量消费 Extractor,使用 Redis 做回溯
  • MysqlFullExtractor:新增 MySQL 全量 Extractor
  • SqlServerFullExtractor:新增 SQL Server 全量 Extractor

在抽象出三个抽象类之后,整体逻辑更为清晰,如果未来要增加新数据库格式支持,也更为简单。

Translator

  • Sha1ShardingTranslator:根据 Sha1 Sharding Translator
  • ModShardingTranslator:根据 Value Mode Sharding Translator
  • RangeShardingTranslator:根据范围 Sharding Translator
  • UserRouterMapShardingTranslator:特定业务使用, 用户分表 Sharding Translator
  • UserRouterMapMobileShardingTranslator:特定业务使用, 用户分表 Sharding Translator
  • ClassLearningNoteInfoShardingTranslator:特定业务使用自定义 Translator
  • ClassLearningIsActiveReverseShardingTranslator:特定业务使用自定义 Translator
  • ColumnFixDataTranslator:调整表结构 Translator
  • NameStyleDataTranslator:调整表字段名 Translator,支持按风格对整个表自动转换
  • CompositeIndexesDataTranslator:解决复合主键下唯一 PK 确定问题的 Translator

新增了一系列 Translator。

Applier

  • SqlServerIncreamentRecordApplier:新增 SQL Server 增量消费 Applier

Applier 结构调整挺小,主要是增加了 SQL Server 的支持。

二次开发心得

如何快速了解一个开源项目?很多同学第一反应就是阅读源码。 看源码固然是有效果,但是性价比太低。 如果项目设计不合理,很快会迷失在代码细节之中。 我的经验是先阅读官方出品的一些 Slide 分享,然后阅读官方核心文档。 Slide 含金量高,在讲述核心中核心。

如果真要去了解细节去阅读源码,那我建议要善用工具, 比如使用 IntelliJ 的 Diagram 功能,抽象出核心类。 还有一些插件比如 SequencePluginReload 方便地生成函数之间调用,实为查看数据流利器。 我在这次开发过程中,也根据生成类图发现了一些问题, 从而在进入 Coding 之前,先对框架继承结构重构。提高了整体开发效率

根据代码风格判断,Yugong 并非是出自一个人之手。这多少会导致代码风格和设计上面不一致。 我自己也常年在业务线里面摸爬滚打,能想象到在快速推进项目中需要糙快猛。 但后人接受开发,多少会有些头疼。 于是我在进入开发之前,引入标准化 CheckStyle,用 Google Style 全局格式化, 使用 Sonar 扫描保证一个代码质量基线。 同时这也是一把双刃剑,格式化项目会导致大量 diff, 这也给我自己埋下了一个苦果,在后期给上游提交 PR 引入无尽问题。

开发过程中我也犯了一些错误。最为头疼是没有在早期考虑到向开源社区贡献, 导致未来向上游合并困难重重,现在还在头疼合并代码中。 另外,由于整体项目时间紧,我贪图实现速度,没有做更详尽单元测试覆盖。 这里没有遵循开源软件的最佳实践。

经过我改造的 Yugong 版本开源地址是:https://github.com/alswl/yugong 。 我也提交了 Pull Request https://github.com/alibaba/yugong/pull/66 , 正在与官方沟通如何将这部分提交并入上游。


原文链接: https://blog.alswl.com/2018/06/sql-server-migration-3/
3a1ff193cee606bd1e2ea554a16353ee
欢迎关注我的微信公众号:窥豹
Phoenix Nemo's avatar

在线扩展 LVM root 分区

才不是没东西写了呢

遇到一个奇葩的原因导致 root 分区被占满的。而且还是奇葩的 CentOS,root 分区是 LVM,Hypervisor 里扩展磁盘后无法直接用 resize2fs。

既然如此就只能暴力重建分区咯。

重建分区

操作前确保操作的分区和之后新建时 Start 保持一致,修改分区表后不至于分区崩坏。

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
~> fidks /dev/sda
Welcome to fdisk (util-linux 2.23.2).

Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.


Command (m for help): p

Disk /dev/sda: 103.1 GB, 103079215104 bytes, 201326592 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk label type: dos
Disk identifier: 0x000a8e23

Device Boot Start End Blocks Id System
/dev/sda1 * 2048 2099199 1048576 83 Linux
/dev/sda2 2099200 50331647 24116224 8e Linux LVM

Command (m for help): d
Partition number (1,2, default 2): 2
Partition 2 is deleted

Command (m for help): n
Partition type:
p primary (1 primary, 0 extended, 3 free)
e extended
Select (default p):
Using default response p
Partition number (2-4, default 2):
First sector (2099200-201326591, default 2099200):
Using default value 2099200
Last sector, +sectors or +size{K,M,G} (2099200-201326591, default 201326591):
Using default value 201326591
Partition 2 of type Linux and of size 95 GiB is set

Command (m for help): w
The partition table has been altered!

Calling ioctl() to re-read partition table.

WARNING: Re-reading the partition table failed with error 16: Device or resource busy.
The kernel still uses the old table. The new table will be used at
the next reboot or after you run partprobe(8) or kpartx(8)
Syncing disks.

~> partprobe

现在就可以看到 /dev/sda2 的大小已经变化了:

1
2
3
4
5
6
7
8
~> lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 96G 0 disk
├─sda1 8:1 0 1G 0 part /boot
└─sda2 8:2 0 95G 0 part
├─centos-root 253:0 0 20.6G 0 lvm /
└─centos-swap 253:1 0 2.4G 0 lvm [SWAP]
sr0 11:0 1 906M 0 rom

扩展 Volume Group

VG 的好处也就是能够灵活扩展分区大小…

1
2
3
~> pvresize /dev/sda2
Physical volume "/dev/sda2" changed
1 physical volume(s) resized / 0 physical volume(s) not resized
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
~> vgdisplay
--- Volume group ---
VG Name centos
System ID
Format lvm2
Metadata Areas 1
Metadata Sequence No 4
VG Access read/write
VG Status resizable
MAX LV 0
Cur LV 2
Open LV 2
Max PV 0
Cur PV 1
Act PV 1
VG Size <95.00 GiB
PE Size 4.00 MiB
Total PE 24319
Alloc PE / Size 5887 / <23.00 GiB
Free PE / Size 18432 / 72.00 GiB
VG UUID TpbtuH-AjTZ-PU3v-UN31-FvfX-kSLv-xLiJG7

至此已经可以看到 Free PE 的部分有多出的 72GB 空间。

扩展 Logic Volume

1
2
3
4
5
6
7
8
9
10
11
12
13
~> lvextend -r -l +100%FREE /dev/centos/root
Size of logical volume centos/root changed from 20.59 GiB (5272 extents) to 92.59 GiB (23704 extents).
Logical volume centos/root successfully resized.
meta-data=/dev/mapper/centos-root isize=512 agcount=4, agsize=1349632 blks
= sectsz=512 attr=2, projid32bit=1
= crc=1 finobt=0 spinodes=0
data = bsize=4096 blocks=5398528, imaxpct=25
= sunit=0 swidth=0 blks
naming =version 2 bsize=4096 ascii-ci=0 ftype=1
log =internal bsize=4096 blocks=2636, version=2
= sectsz=512 sunit=0 blks, lazy-count=1
realtime =none extsz=4096 blocks=0, rtextents=0
data blocks changed from 5398528 to 24272896

确认效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
~> lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 96G 0 disk
├─sda1 8:1 0 1G 0 part /boot
└─sda2 8:2 0 95G 0 part
├─centos-root 253:0 0 92.6G 0 lvm /
└─centos-swap 253:1 0 2.4G 0 lvm [SWAP]
sr0 11:0 1 906M 0 rom

~> df -h
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/centos-root 93G 21G 73G 23% /
devtmpfs 3.9G 0 3.9G 0% /dev
tmpfs 3.9G 8.0K 3.9G 1% /dev/shm
tmpfs 3.9G 8.6M 3.9G 1% /run
tmpfs 3.9G 0 3.9G 0% /sys/fs/cgroup
/dev/sda1 1014M 185M 830M 19% /boot
tmpfs 783M 0 783M 0% /run/user/0

搞定收工(‘・ω・’)

后话

其实虚拟机还用 LVM 的话,直接新增一块虚拟硬盘是最方便的方案。直接 vgextend 一路搞定…

alswl's avatar

从 SQL Server 到 MySQL(二):在线迁移,空中换发动机

(image via https://pixabay.com/en/military-stealth-bomber-refueling-602729/ )

在上篇文章 从 SQL Server 到 MySQL (一):异构数据库迁移 - Log4D 中,我们给大家介绍了从 SQL Server 到 MySQL 异构数据库迁移的基本问题和全量解决方案。 全量方案可以满足一部分场景的需求,但是这个方案仍然是有缺陷的: 迁移过程中需要停机,停机的时长和数据量相关。 对于核心业务来说,停机就意味着损失。 比如用户中心的服务,以它的数据量来使用全量方案,会导致迁移过程中停机若干个小时。 而一旦用户中心停止服务,几乎所有依赖于这个中央服务的系统都会停摆。

能不能做到无缝的在线迁移呢?系统不需要或者只需要极短暂的停机? 作为有追求的技术人,我们一定要想办法解决上面的问题。

在线迁移的原理和流程

针对 Oracle 到 MySQL,市面上已经有比较成熟的解决方案 - alibaba 的 yugong 项目。 在解决 SQL Server 到 MySQL 在线迁移之前,我们先研究一下 yugong 是如何做到 Oracle 的在线迁移。

下图是 yugong 针对 Oracle 到 MySQL 的增量迁移流程:

这其中有四个步骤:

  1. 增量数据收集 (创建 Oracle 表的增量物化视图)
  2. 进行全量复制
  3. 进行增量复制 (可并行进行数据校验)
  4. 原库停写,切到新库

Oracle 物化视图(Materialized View)是 Oracle 提供的一个机制。 一个物化视图就是主库在某一个时间点上的复制,可以理解为是这个时间点上的 Snapshot。 当主库的数据持续更新时,物化视图的更新可以通过独立的批量更新完成,称之为 refreshes。 一批 refreshes 之间的变化,就对应到数据库的内容变化情况。 物化视图经常用来将主库的数据复制到从库,也常常在数据仓库用来缓存复杂查询。

物化视图有多种配置方式,这里比较关心刷新方式和刷新时间。 刷新方式有三种:

  • Complete Refresh:删除所有数据记录重新生成物化视图
  • Fast Refresh:增量刷新
  • Force Refresh:根据条件判断使用 Complete Refresh 和 Fast Refres

刷新机制有两种模式: Refresh-on-commit 和 Refresh-On-Demand。

Oracle 基于物化视图,就可以完成增量数据的获取,从而满足阿里的数据在线迁移。 将这个技术问题泛化一下,想做到在线增量迁移需要有哪些特性? 我们得到如下结论(针对源数据库):

  • 增量变化:支持增量获得增量数据库变化
  • 延迟:获取变化数据这个动作耗时需要尽可能低
  • 幂等一致性:变化数据的消费应当做到幂等,即不管目标数据库已有数据什么状态,都可以无差别消费

回到我们面临的问题上来,SQL Server 是否有这个机制满足这三个特性呢? 答案是肯定的,SQL Server 官方提供了 CDC 功能。

CDC 的工作原理

什么是 CDC? CDC 全称 Change Data Capture,设计目的就是用来解决增量数据的。 它是 SQL Server 2008 新增的特性, 在这之前只能使用 SQl Server 2005 中的 after insert / after delete / after update Trigger 功能来获得数据变化。

CDC 的工作原理如下:

当数据库表发生变化时候,Capture process 会从 transaction log 里面获取数据变化, 然后将这些数据记录到 Change Table 里面。 有了这些数据,用户可以通过特定的 CDC 查询函数将这些变化数据查出来。

CDC 的数据结构和基本使用

CDC 的核心数据就是那些 Change Table 了,这里我们给大家看一下 Change Table 长什么样,可以有个直观的认识。

通过以下的函数打开一张表(fruits)的 CDC 功能。

-- enable cdc for db
sys.sp_cdc_enable_db;
-- enable by table
EXEC sys.sp_cdc_enable_table @source_schema = N'dbo', @source_name = N'fruits', @role_name = NULL;
-- list cdc enabled table
SELECT name, is_cdc_enabled from sys.databases where is_cdc_enabled = 1;

至此 CDC 功能已经开启,如果需要查看哪些表开启了 CDC 功能,可以使用一下 SQL:

-- list cdc enabled table
SELECT name, is_cdc_enabled from sys.databases where is_cdc_enabled = 1;

开启 CDC 会导致产生一张 Change Table 表 cdc.dbo_fruits_CT,这张表的表结构如何呢?

.schema cdc.dbo_fruits_CT
name            default  nullable  type          length  indexed
--------------  -------  --------  ------------  ------  -------
__$end_lsn      null     YES       binary        10      NO
__$operation    null     NO        int           4       NO
__$seqval       null     NO        binary        10      NO
__$start_lsn    null     NO        binary        10      YES
__$update_mask  null     YES       varbinary     128     NO
id              null     YES       int           4       NO
name            null     YES       varchar(255)  255     NO

这张表中以 __ 开头的字段是 CDC 所记录的元数据,idname 是 fruits 表的原始字段。 这意味着 CDC 的表结构和原始表结构是一一对应的。

接下来我们做一些业务操作,让数据库的数据发生一些变化,然后查看 CDC 的 Change Table:

-- 1 step
DECLARE @begin_time datetime, @end_time datetime, @begin_lsn binary(10), @end_lsn binary(10);
-- 2 step
SET @begin_time = '2017-09-11 14:03:00.000';
SET @end_time   = '2017-09-11 14:10:00.000';
-- 3 step
SELECT @begin_lsn = sys.fn_cdc_map_time_to_lsn('smallest greater than', @begin_time);
SELECT @end_lsn = sys.fn_cdc_map_time_to_lsn('largest less than or equal', @end_time);
-- 4 step
SELECT * FROM cdc.fn_cdc_get_all_changes_dbo_fruits(@begin_lsn, @end_lsn, 'all');

这里的操作含义是:

  1. 定义存储过程中需要使用的 4 个变量
  2. begin_time / end_time 是 Human Readable 的字符串格式时间
  3. begin_lsn / end_lsn 是通过 CDC 函数转化过的 Log Sequence Number,代表数据库变更的唯一操作 ID
  4. 根据 begin_lsn / end_lsn 查询到 CDC 变化数据

查询出来的数据如下所示:

__$start_lsn          __$end_lsn  __$seqval             __$operation  __$update_mask  id  name
--------------------  ----------  --------------------  ------------  --------------  --  ------
0000dede0000019f001a  null        0000dede0000019f0018  2             03              1   apple
0000dede000001ad0004  null        0000dede000001ad0003  2             03              2   apple2
0000dede000001ba0003  null        0000dede000001ba0002  3             02              2   apple2
0000dede000001ba0003  null        0000dede000001ba0002  4             02              2   apple3
0000dede000001c10003  null        0000dede000001c10002  2             03              3   apple4
0000dede000001cc0005  null        0000dede000001cc0002  1             03              3   apple4

可以看到 Change Table 已经如实的记录了我们操作内容,注意 __$operation 代表了数据库操作:

  • 1 => 删除
  • 2 => 插入
  • 3 => 更新前数据
  • 4 => 更新后数据

根据查出来的数据,我们可以重现这段时间数据库的操作:

  • 新增了 id 为 1 / 2 的两条数据
  • 更新了 id 为 2 的数据
  • 插入了 id 为 3 的数据
  • 删除了 id 为 3 的数据

CDC 调优

有了 CDC 这个利器,终于意味着我们的方向是没有问题的,我们终于稍稍吁了一口气。 但除了了解原理和使用方式,我们还需要深入了解 CDC 的工作机制,对其进行压测、调优, 了解其极限和边界,否则一旦线上出现不可控的情况,就会对业务带来巨大损失。

我们先看看 CDC 的工作流程,就可以知道有哪些核心参数可以调整:

上图是 CDC Job 的工作流程:

  • 蓝色区域是一次 Log 扫描执行的最大扫描次数:maxscans number(maxscans
  • 蓝色区域同时被最大扫描 transcation 数量控制:maxtrans
  • 浅蓝色区域是扫描间隔时间,单位是秒:pollinginterval

这三个参数平衡着 CDC 的服务器资源消耗、吞吐量和延迟, 根据具体场景,比如大字段,宽表,BLOB 表,可以调整从而达到满足业务需要。 他们的默认值如下:

  • maxscan 默认值 10
  • maxtrans 默认值 500
  • pollinginterval 默认值 5 秒

CDC 压测

掌握了能够调整的核心参数,我们即将对 CDC 进行了多种形式的测试。 在压测之前,我们还需要确定关键的健康指标,这些指标有:

  • 内存:buffer-cache-hit / page-life-expectancy / page-split 等
  • 吞吐:batch-requets / sql-compilations / sql-re-compilations / transactions count
  • 资源消耗:user-connections / processes-blocked / lock-waits / checkpoint-pages
  • 操作系统层面:CPU 利用率、磁盘 IO

出于篇幅考虑,我们无法将所有测试结果贴出来, 这里放一个在并发 30 下面插入一百万数据(随机数据)进行展示:

测试结论是,在默认的 CDC 参数下面:

CDC 的开启/关闭过程中会导致若干个 Process Block, 大流量请求下面(15k TPS)过程会导致约 20 个左右 Process Block。 这个过程中对服务器的 IO / CPU 无明显波动, 开启/关闭瞬间会带来 mssql.sql-statistics.sql-compilations 剧烈波动。 CDC 开启后,在大流量请求下面对 QPS / Page IO 无明显波动, 对服务器的 IO / CPU 也无明显波动, CDC 开启后可以在 16k TPS 下正常工作。

如果对性能不达标,官方有一些简单的优化指南:

  • 调整 maxscan maxtrans pollinginterval
  • 减少在插入后立刻插入
  • 避免大批量写操作
  • 限制需要记录的字段
  • 尽可能关闭 net changes
  • 没任务压力时跑 cleanup
  • 监控 log file 大小和 IO 压力,确保不会写爆磁盘
  • 要设置 filegroup_name
  • 开启 sp_cdc_enable_table 之前设置 filegroup

yugong 的在线迁移机制

OK,截目前位置,我们已经具备了 CDC 这个工具,但是这仅仅提供了一种可能性, 我们还需要一个工具将 CDC 的数据消费出来,并喂到 MySQL 里面去。

好在有 yugong。 Yugong 官方提供了 Oracle 到 MySQL 的封装,并且抽象了 Source / Target / SQL Tempalte 等接口, 我们只要实现相关接口,就可以完成从 SQL Server 消费数据到 MySQL 了。

这里我们不展开,我还会花专门的一篇文章讲如何在 yugong 上面进行开发。 可以提前剧透一下,我们已经将支持 SQL Server 的 yugong 版本开源了。

如何回滚

数据库迁移这样的项目,我们不仅仅要保证单向从 SQL Server 到 MySQL 的写入, 同时要从 MySQL 写入 SQL Server。

这个流程同样考虑增量写入的要素:增量消费,延迟,幂等一致性。

MySQL 的 binlog 可以满足这三个要素,需要注意的是,MySQL binlog 有三种模式, Statement based,Row based 和 Mixed。只有 Row based 才能满足幂等一致性的要求。

确认理论上可行之后,我们一样需要一个工具将 binlog 读取出来,并且将其转化为 SQL Server 可以消费的数据格式,然后写入 SQL Server。

我们目光转到 alibaba 的另外一个项目 Canal。 Canal 是阿里中间件团队提供的 binlog 增量订阅 & 消费组件。 之所以叫组件,是由于 Canal 提供了 Canal-Server 应用和 Canal Client Library, Canal 会模拟成一个 MySQL 实例,作为 Slave 连接到 Master 上面, 然后实时将 binlog 读取出来。 至于 binlog 读出之后想怎么使用,权看用户如何使用。

我们基于 Canal 设计了一个简单的数据流,在 yugong 中增加了这么几个功能:

  • SQL Server 的写入功能
  • 消费 Canal 数据源的功能

Canal Server 中的 binlog 只能做一次性消费, 内部实现是一个 Queue, 为了满足我们可以重复消费数据的能力,我们还额外设计了一个环节,将 Canal 的数据放到 Queue 中,在未来任意时间可以重复消费数据。 我们选择了 Redis 作为这个 Queue,数据流如下。

最佳实践

数据库的迁移在去 Windows 中,是最不容得出错的环节。 应用是无状态的,出现问题可以通过回切较快地回滚。 但数据库的迁移就需要考虑周到,做好资源准备,发布流程, 故障预案处理。

考虑到多个事业部都需要经历这个一个过程,我们项目组将每一个步骤都固化下来, 形成了一个最佳实践。我们的迁移步骤如下,供大家参考:

大阶段 阶段 事项 是否完成 负责人 耗时 开始时间 完成时间 备注
白天 存量数据阶段 创建 MySQL 数据库,准备相关账号资源 DBA
开启 CDC DBA
从 Slave SQLServer dump 一份 snapshot 到 Backup SQL Server DBA
Backup SQL Server 消费数据, ETL 到 MySQL DBA
增量数据阶段 确认 ETL 数据已经消费完成,检查数据总条数 DBA
从 Slave SQLServer 开始消费 CDC 数据,持续写入 MySQL DBA
使用 yugong 检查一天内数据的一致性 DBA
检查不一致的数据,10 分钟之后人工进行检查,确认是 CDC 延迟带来的问题 DBA
检查数据总量条目 DBA
使用 yugong 对抽样表进行全量检查 DBA
凌晨 应用发布阶段 停止 SQL Server 的应用 技术经理
检查没有连接进入 SQL Server DBA
使用 yugong 检查一天内数据的一致性 DBA
检查数据总量条目 DBA
启用基于 MySQL 的应用 运维
测试阶段 测试应用是否正常,回归所有功能 QA
(临时新增)测试 ReadOnly DB 的应用访问情况 QA
完成阶段 接入流量 运维
(可选)回滚阶段 发现问题,直接将应用切回 SQL Server 运维
事后进行数据审计,进行新增数据补偿 DBA
(可选)回滚过程中,使用 Canal 读取 binlog,并使用 Canal Client 重放到 SQL Server DBA

Reference


原文链接: https://blog.alswl.com/2018/05/sql-server-migration-2/
3a1ff193cee606bd1e2ea554a16353ee
欢迎关注我的微信公众号:窥豹
Phoenix Nemo's avatar

在 Linux 服务器配置 LACP 与 VLAN

存储服务器不想放在 OVH 了。所以自己来托管一台机器,顺便折腾下 2x1Gbps 组 LACP Bonding。

前提:服务器需要至少 2 个千兆物理网卡,上联交换机支持 802.3ad。

配置交换机

这里使用的是 Cisco Nexus 3064PQ-10GE 交换机,我们的接口在 Eth1/21-22,port-channel 的配置如下:

1
2
3
4
5
6
7
8
9
# show interface trunk

--------------------------------------------------------------------------------
Port Native Status Port
Vlan Channel
--------------------------------------------------------------------------------

Eth1/21 1 trnk-bndl Po100
Eth1/22 1 trnk-bndl Po100
Po100 1 trunking --
1
2
3
4
5
6
7
8
9
10
show port-channel database
port-channel100
Last membership update is successful
2 ports in total, 2 ports up
First operational port is Ethernet1/21
Age of the port-channel is 0d:00h:02m:16s
Time since last bundle is 0d:00h:02m:04s
Last bundled member is Ethernet1/22
Ports: Ethernet1/21 [active ] [up] *
Ethernet1/22 [active ] [up]

配置服务器

服务器操作系统是 Arch Linux,由于蜜汁问题 netctl 无法启动网卡,就只好用 systemd-networkd 啦。

麻烦一些,但是也还算顺利。与往常一样,折腾服务器网络的时候需要备着 IPMI 以防 connection lost。

内核模块

需要加载 bonding 模块。将模块名写入列表,文件 /etc/modules-load.d/bonding.conf,内容只需要一行:

1
bonding

先别急着加载模块,为了防止模块自动建立一个默认网卡影响后续配置,以及设置 LACP Mode=4 … 等等,先加入一行参数。文件 /etc/modprobe.d/bonding.conf

1
options bonding mode=4 miimon=100 max_bonds=0

然后安装 ifenslave 包,再 modprobe bonding 即可。

bonding 虚拟网卡

首先创建一个虚拟网卡的设备。文件 /etc/systemd/network/bond0.netdev 内容为

1
2
3
4
5
6
7
8
9
[NetDev]
Name=bond0
Kind=bond

[Bond]

Mode=802.3ad
TransmitHashPolicy=layer2+3
LACPTransmitRate=fast
AdSelect=bandwidth

然后在此虚拟网卡上创建网络。这里使用两个物理网卡 eth0eth1 作为 bundle,交换机上的 VLAN id 是 113。文件 /etc/systemd/network/bond0.network 内容为

1
2
3
4
5
6
[Match]
Name=bond0

[Network]

VLAN=vlan113
BindCarrier=eth0 eth1

接下来分别为 eth0eth1 建立网络设置。

  • /etc/systemd/network/eth0.network
1
2
3
4
5
[Match]
Name=eth0

[Network]

Bond=bond0
  • /etc/systemd/network/eth1.network
1
2
3
4
5
[Match]
Name=eth1

[Network]

Bond=bond0

最后是 VLAN 的设置。前面设置了上联 VLAN id 是 113,这里分别建立 VLAN 的虚拟网卡(based on bond0) 并设置网络(IP, etc)。

  • /etc/systemd/network/vlan113.netdev
1
2
3
4
5
6
[NetDev]
Name=vlan113
Kind=vlan

[VLAN]

Id=113
  • /etc/systemd/network/vlan113.network
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[Match]
Name=vlan113

[Network]

VLAN=vlan113

[Address]

Address=10.1.0.100/24

[Route]

Destination=0.0.0.0/0
Gateway=10.1.0.1
DNS=1.1.1.1

[Address]

Address=2600:x:x:x::2/64

[Route]

Gateway=2600:x:x:x::1

多个地址、IPv6 等可以写多个 [Address][Route]

至此就完成啦。开启 systemd-networkd 的自启动:

1
systemctl enable systemd-networkd.service

然后重启网络:

1
systemctl restart systemd-networkd.service

如果配置都没有问题,网络会中断十几秒然后恢复。现在查看网卡列表已经可以看到组合的网卡了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# ip l
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,SLAVE,UP,LOWER_UP> mtu 1500 qdisc mq master bond0 state UP mode DEFAULT group default qlen 1000
link/ether <REDACTED> brd ff:ff:ff:ff:ff:ff
3: eth1: <BROADCAST,MULTICAST,SLAVE,UP,LOWER_UP> mtu 1500 qdisc mq master bond0 state UP mode DEFAULT group default qlen 1000
link/ether <REDACTED> brd ff:ff:ff:ff:ff:ff
4: eth2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether <REDACTED> brd ff:ff:ff:ff:ff:ff
5: eno1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether <REDACTED> brd ff:ff:ff:ff:ff:ff
6: bond0: <BROADCAST,MULTICAST,MASTER,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether <REDACTED> brd ff:ff:ff:ff:ff:ff
7: vlan113@bond0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether <REDACTED> brd ff:ff:ff:ff:ff:ff

ethtool 查看 bond0 的速率显示 2000Mb/s

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# ethtool bond0
Settings for bond0:
Supported ports: [ ]
Supported link modes: Not reported
Supported pause frame use: No
Supports auto-negotiation: No
Supported FEC modes: Not reported
Advertised link modes: Not reported
Advertised pause frame use: No
Advertised auto-negotiation: No
Advertised FEC modes: Not reported
Speed: 2000Mb/s
Duplex: Full
Port: Other
PHYAD: 0
Transceiver: internal
Auto-negotiation: off
Link detected: yes

搞定收工(‘・ω・’)

Reference:

alswl's avatar

如何逃离死海效应

(图片来自 The Dead Sea, Israel | One of the lowest, saltiest and unusu… | Flickr

最近听团队老大的一个分享,介绍公司提倡的工程师的核心价值观,受益良多。 这也让我想起了一篇文章, Bruce F. Webster 在 2008 年写了一篇文章「The Wetware Crisis: the Dead Sea effect」, 翻译过来是「死海效应」。

Bruce 在文章中阐述了一个概念:一个团队可能陷入一种反模式,称之为死海效应。 死海是位于约旦的一个高盐分水域,由于水分被蒸发,这里的盐度是正常海水的 8 倍。 将水分比喻为高质量人才,盐分比喻为低质量人才。 水分容易被蒸发,而补入不足,盐分不容易蒸发日积月累,进而导致整个团队的人才质量劣化。

借用经济学里面的一个概念:劣币驱逐良币。

形成

为什么会形成这样的状况呢?

在一个正常运作的团队中,整个人才系统应该是循环的。有新的成员加入,也有老的成员离开。 只要市场上人才质量不发生剧烈的波动,团队内人才质量应该是平稳的。 但死海效应中不这样,好的人才不断流失,低质量人才被留存,这个团队素质越来越差。 形成这种状况的原因其实挺简单:环境恶化,高质量人才有更强的实力,可以自由地选择满足自己期望的团队。 而低质量人才没有更多选择空间,从而只能在当前的环境下继续生存。

死海效应导致的恶劣后果是显而易见的。 现代组织中人是生产第一要素。 如果人才质量持续降低,那么整个团队会缺乏战斗力,商业上得不到发展,这个团队将面临瓦解的风险。

什么样的团队容易滋生死海效应,我总结了几个点:无压、不透明、人情。

第一个点是无压,当一个公司的业务发展进入瓶颈期,战略上面没有远见, 抑或是已经占据了市场中的有利地形,温饱不愁,那么就容易失去压力。 我对商业的理解有限,就拿技术团队来做比喻: 当一个技术团队给业务提供的支撑足够并且业务发展平稳, 那么技术团队有两个选择:维持平稳的状态;给自己提出更高的技术要求。 前者是舒适的,后者长期带来回报,但是短期辛苦并且有风险。 前者的团队空间有限,挑战难度低,更容易产生死海效应。

第二个滋生死海效应的点是不透明。这种不透明体现在多个方面: 战略上是否向全体员工透明?业绩结果是否向全体员工透明?团队和个人绩效结果是否透明?评价标准是否透明? 不透明会会导致团队的不公平。 一线员工看不到未来发展的方向和业绩,无法感受到个体被组织所尊重。 也许有些人尸位素餐,却能获得高收益,自然无法让团队的高贡献者认可。 Google 有一系列方法,包括 OKR 透明化,TGIF(Thank God It’s Friday) 全体沟通会,有效的解决这个问题。

第三个点是人情。大家在一起工作,时间一长或多或少会产生羁绊。 中国社会又特别讲究人情来往,今天你帮我一些忙,那我就得记在心上,未来有机会要还给你。 这当然是一种人和人之间沟通的一种黏合剂和缓冲,但是也是职业化程度低的表现。 企业的存在是有使命和目的,营造一种大家庭的氛围一定不在其使命中。 小团队早期可以家族化管理,但是一旦走上正轨,一定要规范化管理。 人情其实是对标准和制度的破坏,而企业的正常运转,恰恰最是需要对标准和制度的遵守。 讲究人情,也会对职责和评价产生破坏,这又会导致不公平,死海效应也会应运而生。 这点我是非常佩服阿里,它有一个出名的职务轮换制度,管理层不能长期担任同一职位,需要定期轮换。 这样就能保证关键人才在企业内部是流动的,不会锁定在特定岗位。 也就避免了长期锁定带来的人情账户、内部小团体。

如何逃离

那么如何来解决「死海效应」呢?有这么些方法:追求卓越、制度和透明、人才标准和流动。

我以前和一个候选人沟通,他的一句话让我印象深刻:公司业务高速发展时,就不会存在复杂的管理问题。 这是一种理想情况。 但业务发展需要时间,进入瓶颈期之后,想继续保持团队活力,改怎么办?追求卓越。 效率能否再提高一些?自动化的程度是否能够再提高?能不能向一流团队 FLAG 看齐? 这样就开阔了团队和个人的发展空间。 一旦有了追求卓越的信念,即便业务发展稍缓,仍然能够抵御死海效应。

高质量人才离开往往是觉得受了委屈才离开,那么公平的制度和透明的实施就尤其重要。 保证不由某个个体的意志力转移,而是像机器一样可以运转,像数学一样可以被计算。 这个需要自上而下的贯彻,否则实施起来非常困难。 举一个例子,如果团队大部分成员会在月末调整绩效目标而获得更高的评分。 那么坚持不调整的人可能获得较低评分,这不是个体能够影响的局面。 没有自上而下的推动,制度和透明就只能是一阵清风。

最后是人才标准和流动。人才流动并非是一个坏事,如同人的生理循环,吸收有益物质,排除有害物质。 这个循环必须正向运行,设定合理的准入标准和淘汰标准,高质量输入,低质量淘汰。 末尾淘汰制听起来残酷,其实在广泛实施,比如阿里的 3.25 的考核,腾讯的年末淘汰一人。 对于大部分非家族公司来说,如果真的出现害群之马,难道不淘汰么? 淘汰应该制度化,让所有人看到并且重视,而不是担心会造成集体不安就隐性操作, 这反而更容易产生猜测和担忧。

最后

一旦进入死海效应的恶性循环,我认为乱世用重典为佳,小打小闹不能形成大影响。 关键时刻就必须搞运动,做动员,大张旗鼓。 当然这需要高层有足够的决心和意志力。如果管理层计划偏安一隅,没有宏伟愿景, 那么十几年的企业寿命也能为股东赚回足够的钱,死海效应是存在即合理的现象。 低质量的人才管理成本也低,人才死海效应和业务劣化恶性循环, 最后进入企业衰老期,天道轮回,有生必有死,看开就好。


原文链接: https://blog.alswl.com/2018/04/death-sea-effect/
3a1ff193cee606bd1e2ea554a16353ee
欢迎关注我的微信公众号:窥豹
's avatar

cmder 颜色配置

工作需要,现在在用 Windows 10 作为主力开发系统,然后根据以往使用 Linux 和 macOS 的经验,一个好用的终端还是必不可少的,然后就找到了 cmder,一个非常好用的 Windows 终端环境。

PS. 其实倒也不全是因为工作需要啦,现在的 Windows 10 也已经挺好用的了,现在老了也懒得折腾 Linux 桌面了,苹果的电脑还是那么的贵,当然最重要的是 Windows 上游戏多啊😂

跟 msys2 这种在 Windows 系统中模拟 Linux 环境的工具不同,cmder 的定位是 Windows 自带 cmd 窗口的替代品。现在有很多命令行工具,如 git、node.js 等,都专门针对 Windows 系统进行了优化,而这样的优化版本是需要在 Windows 自己的 cmd.exe 环境下才能正常运行的,这时一个像 cmder 这样的 cmd.exe 增强工具就很有必要了。至于在 msys2 中通过 pacman 安装的版本,特别是 node.js,实际使用中总会有这样那样的问题,大概还是 unix 版在 Windows 下的水土不服吧。

PS. 有了好用的终端,当然还少不了好用的软件包管理器啦,特别是对 Windows 这种并不原生对命令行特别友好的系统来说,能有个管理器帮忙安装和升级命令行工具的话会方便很多。我自己选用的是 scoop,虽然没有 Chocoletey 那么流行和那么多的软件包数量,但胜在可以方便的定制安装位置(Chocolatey 要定制貌似得用收费版才行),对我来说已经够用了。

emmm 进入正题……之前没在 Windows 下搞过 node.js 开发的时候还不知道,原来 Windows 的 cmd.exe 也是支持彩色输出的,而且颜色还挺多😂。这时遇到的一个问题就是,在 cmder 下用 node.js 时总会有些输出的前景色跟背景色一样导致有些字符显示不出来了😂:

从上图可以看出(其实我也是过了一段时间才注意到的😂,因为记得我一开始遇到这个问题的时候,还没有这个显眼的背影色来着,也不记得当时用的是啥配色了😂),画红圈的地方就有字符没能正常显示出来。

经过摸索,只要按照下图中的配置就可以把这个颜色问题给修正过来了:

下面是最终效果图:

嘛,虽然原来的背景色没了,不过至少能看了不是😂

Phoenix Nemo's avatar

迁移 DokuWiki 到 BookStackApp

Wiki 这么反人类的语法是怎么存在这么久的???????

总之受不了 Wiki 语法的可维护性了。什么?这玩意儿有维护性???

以及万年不更新的各种插件。系统升级后 PHP 7 不兼容,一看还是 swiftmail 的问题。生气。

正好有需求要整合一套知识库平台,搜索了一下 Confluence 的 alternative,发现了 BookStackAppPhabricator

前者适合个人或开源社区使用,后者则是一整套企业协作解决方案。对于我的需求来讲,BookStackApp 就足够啦。

页面数据

DokuWiki 并不使用数据库,因此没有一个通用的中间件来实现数据格式转换。而 DokuWiki 的语法非常奇葩——比如,它的一级标题是 ====== 这样 ======,六级标题才是 = 这样 =,正好和一般的 Wikitext 倒置。图片、内链等的表达方式也相当愚蠢,这些问题使我在思考迁移方案的第一个小时内就放弃了直接从源码转移的途径。

顺便,还有另外一个问题——本来为了使 Wiki 易于编写,这 DokuWiki 还安装了 Markdown 插件。因此部分页面中混杂着 Markdown 语法,更增加了源码处理的复杂度。

综合来看,最通用的数据格式,就是最终渲染出来的 XHTML 了。

图片

DokuWiki 的图片存储策略也是非常的奇特。由于它没有数据库,因此为了保持图片与页面的对应,它将图片存储在每个页面同样的路径下,并通过执行 PHP 的方式获取(扶额。

更甚者!!!

外链的图片,也是通过 /lib/exe/fetch.php 带参数来获取!!

我 的 天 哪。

因此既然在页面数据的考量中决定了使用最终渲染输出的 XHTML 来处理数据格式,图片也需要特殊的下载和归档技巧。这将需要使用 sanitize-html 提供的 transformer 方法来实现。

逻辑实现

一开始尝试了一些 Site Exporter 插件,但遗憾的是并没有什么真正能派上用场。甚至一些暴力递归下载所有页面和资源的脚本的表现也非常糟糕。

但是根据 DokuWiki 的官方 Tips,它可以将文章内容单纯导出 XHTML,只需要加上 ?do=export_xhtmlbody 参数即可。这就方便了,因为这样只需要一个完整的页面列表就可以了。随便找一个可以输出子命名空间的插件,新建一个页面用于从根命名空间展开就 OK 啦。

请求这个列表页面的 XHTML body 输出,使用 cheerio 遍历所有的 a 标签,就获得了所有要导出的页面地址。分别再去请求这些页面的 XHTML body 输出,做如下处理:

  1. 跟踪所有的 img 标签,下载图片文件并按预定义的路径规则和文件名归档。
  2. sanitize-html 清除所有不必要的标签、样式、id 和 class。
  3. sanitize-html 按预定义的路径规则更新所有 aimg 标签属性。

看代码

后来发现 DokuWiki 的性能不足以支撑异步请求的速度,额外加上了 sleep 模块来控制请求频率(扶额。

脚本执行完后,将图片目录移动到 BookStackApp 的对应位置,便可以直接读取所有的 HTML 文件来导入数据啦。

用了这么久,才发现原来还有比 raw HTML 更难以维护的数据格式啊…(望天。

alswl's avatar

从 SQL Server 到 MySQL(一):异构数据库迁移

背景

沪江成立于 2001 年,作为较早期的教育学习网站, 当时技术选型范围并不大: Java 的版本是 1.2,C# 尚未诞生,MySQL 还没有被 Sun 收购, 版本号是 3.23。 工程师们选择了当时最合适的微软体系,并在日后的岁月里, 逐步从 ASP 过度到 .net,数据库也跟随 SQL Server 进行版本升级。

十几年过去了,技术社区已经发生了天翻地覆的变化。 沪江的技术栈还基本在 .net 体系上,这给业务持续发展带来了一些限制。 人才招聘、社区生态、架构优化、成本风险方面都面临挑战。 集团经过慎重考虑,发起了大规模的去 Windows 化项目。 这其中包含两个重点子项目:开发语言从 C# 迁移到 Java, 数据库从 SQL Server 迁移到 MySQL。

本系列文章就是向大家介绍, 从 SQL Server 迁移到 MySQL 所面临的问题和我们的解决方案。

迁移方案的基本流程

设计迁移方案需要考量以下几个指标:

  • 迁移前后的数据一致性
  • 业务停机时间
  • 迁移项目是否对业务代码有侵入
  • 需要提供额外的功能:表结构重构、字段调整

经过仔细调研,在平衡复杂性和业务方需求后, 迁移方案设计为两种:停机数据迁移和在线数据迁移。 如果业务场景允许数小时的停机,那么使用停机迁移方案, 复杂度低,数据损失风险低。 如果业务场景不允许长时间停机,或者迁移数据量过大, 无法在几个小时内迁移完成,那么就需要使用在线迁移方案了。

数据库停机迁移的流程:

停机迁移逻辑比较简单,使用 ETL(Extract Translate Load) 工具从 Source 写入 Target,然后进行一致性校验,最后确认应用运行 OK, 将 Source 表名改掉进行备份。

在线迁移的流程:

在线迁移的方案稍微复杂一些,流程上有准备全量数据,然后实时同步增量数据, 在数据同步跟上(延迟秒级别)之后,进行短暂停机(Hang 住,确保没有流量), 就可以使用新的应用配置,并使用新的数据库。

需要解决的问题

从 SQL Server 迁移到 MySQL,核心是完成异构数据库的迁移。

基于两种数据迁移方案,我们需要解决以下问题:

  • 两个数据库的数据结构是否可以一一对应?出现不一致如何处理?
  • MySQL 的使用方式和 SQL Server 使用方式是否一致?有哪些地方需要注意?
  • 如何确保迁移前后的数据一致性?
  • 在迁移中,如何支持数据结构调整?
  • 如何保证业务不停情况下面,实现在线迁移?
  • 数据迁移后如果发现业务异常需要回滚,如何处理新产生的数据?

为了解决以上的问题,我们需要引入一整套解决方案,包含以下部分:

  • 指导文档 A:SQL Server 转换 MySQL 的数据类型对应表
  • 指导文档 B:MySQL 的使用方式以及注意点
  • 支持表结构变更,从 SQL Server 到 MySQL 的 ETL 工具
  • 支持 SQL Server 到 MySQL 的在线 ETL 工具
  • 一致性校验工具
  • 一个回滚工具

让我们一一来解决这些问题。

SQL Server 到 MySQL 指导文档

非常幸运的是,MySQL 官方早就准备了一份如何其他数据库迁移到 MySQL 的白皮书。 MySQL :: Guide to Migrating from Microsoft SQL Server to MySQL 里提供了详尽的 SQL Server 到 MySQL 的对应方案。 包含了:

  • SQL Server to MySQL - Datatypes 数据类型对应表
  • SQL Server to MySQL - Predicates 逻辑算子对应表
  • SQL Server to MySQL – Operators and Date Functions 函数对应表
  • T-SQL Conversion Suggestions 存储过程转换建议

需要额外处理的数据类型:

SQL Server MySQL
IDENTITY AUTO_INCREMENT
NTEXT, NATIONAL TEXT TEXT CHARACTER SET UTF8
SMALLDATETIME DATETIME
MONEY DECIMAL(19,4)
SMALL MONEY DECIMAL(10,4)
UNIQUEIDENTIFIER BINARY(16)
SYSNAME CHAR(256)

在实际进行中,还额外遇到了一个用来解决树形结构存储的字段类型 Hierarchyid。这个场景需要额外进行业务调整。

我们在内部做了针对 MySQL 知识的摸底排查工作, 并进行了若干次的 MySQL 使用技巧培训, 将工程师对 MySQL 的认知拉到一根统一的线。

关于存储过程使用,我们和业务方也达成了一致:所有 SQL Server 存储过程使用业务代码进行重构,不能在 MySQL 中使用存储过程。 原因是存储过程增加了业务和 DB 的耦合,会让维护成本变得极高。 另外 MySQL 的存储过程功能和性能都较弱,无法大规模使用。

最后我们提供了一个 MySQL 开发规范文档,借数据库迁移的机会, 将之前相对混乱的表结构设计做了统一了约束(部分有业务绑定的设计, 在考虑成本之后没有做调整)。

ETL 工具

ETL 的全称是 Extract Translate Load(读取、转换、载入), 数据库迁移最核心过程就是 ETL 过程。 如果将 ETL 过程简化,去掉 Translate 过程, 就退化为一个简单的数据导入导出工具。 我们可以先看一下市面上常见的导入导出工具, 了解他们的原理和特性,方便我们选型。

MySQL 同构数据库数据迁移工具:

异构数据库迁移工具:

看上去异构数据库迁移工具和方案很多,但是经过我们调研,其中不少是为老派的传统行业服务的。 比如 Kettle / Ispirerer,他们关注的特性,不能满足互联网公司对性能、迁移耗时的要求。 简单筛选后,以下几个工具进入我们候选列表(为了做特性对比,加入几个同构数据库迁移工具):

工具名称 热数据备份保证一致性 batch 操作 支持异构数据库 断点续接 开源 开发语言 GUI
mysqldump V 使用 single-transaction X X X V C X
pt-table-sync V 使用 transaction 或 lock table 的 FTWRL V X V V Pell X
DataX X V V X V Java X
yugong X V V V V Java X
DB2DB X V V X X .net V
MySQL Workbench X ? V X V C++ V

由于异构数据库迁移,真正能够进入我们选型的只有 DataX / yugong / DB2DB / MySQL Workbench。 经过综合考虑,我们最终选用了三种方案, DB2DB 提供小数据量、简单模式的停机模式支持, 足以应付小数据量的停机迁移,开发工程师可以自助完成。 DataX 为大数据量的停机模式提供服务, 使用 JSON 进行配置,通过修改查询 SQL,可以完成一部分结构调整工程。 yugong 的强大可定制性也为在线迁移提供了基础, 我们在官方开源版本的基础之上,增加了以下额外功能:

  • 支持 SQL Server 作为 Source 和 Target
  • 支持 MySQL 作为 Source
  • 支持 SQL Server 增量更新
  • 支持使用 YAML 作为配置格式
  • 调整 yugong 为 fat jar 模式运行
  • 支持表名、字段名大小写格式变化,驼峰和下划线自由转换
  • 支持表名、字段名细粒度自定义
  • 支持复合主键迁移
  • 支持迁移过程中完成 Range / Time / Mod / Hash 分表
  • 支持新增、删除字段

关于 yugong 的二次开发,我们也积累了一些经验,这个我们下篇文章会来分享。

一致性校验工具

在 ETL 之后,需要有一个流程来确认数据迁移前后是否一致。 虽然理论上不会有差异,但是如果中间有程序异常, 或者数据库在迁移过程中发生操作,数据就会不一致。

业界有没有类似的工具呢? 有,Percona 提供了 pt-table-checksum 这样的工具, 这个工具设计从 master 使用 checksum 来和 slave 进行数据对比。 这个设计场景是为 MySQL 主从同步设计, 显然无法完成从 SQL Server 到 MySQL 的一致性校验。 尽管如此,它的一些技术设计特性也值得参考:

  • 一次检查一张表
  • 每次检查表,将表数据拆分为多个 trunk 进行检查
  • 使用 REPLACE...SELECT 查询,避免大表查询的长时间带来的不一致性
  • 每个 trunk 的查询预期时间是 0.5s
  • 动态调整 trunk 大小,使用指数级增长控制大小
  • 查询超时时间 1s / 并发量 25
  • 支持故障后断点恢复
  • 在数据库内部维护 src / diff,meta 信息
  • 通过 Master 提供的信息自动连接上 slave
  • 必须 Schema 结构一致

我们选择 yugong 作为 ETL 工具的一大原因也是因为它提供了多种模式。 支持 CHECK / FULL / INC / AUTO 四种模式。 其中 CHECK 模式就是将 yugong 作为数据一致性检查工具使用。 yugong 工作原理是通过 JDBC 根据主键范围变化,将数据取出进行批量对比。

这个模式会遇到一点点小问题,如果数据库表没有主键,将无法进行顺序对比。 其实不同数据库有自己的逻辑主键,Oracle 有 rowid, SQL Server 有 physloc。这种方案可以解决无主键进行比对的问题。

如何回滚

我们需要考虑一个场景,在数据库迁移成功之后业务已经运行了几个小时, 但是遇到了一些 Critical 级别的问题,必须回滚到迁移之前状态。 这时候如何保证这段时间内的数据更新到老的数据库里面去?

最朴素的做法是,在业务层面植入 DAO 层的打点, 将 SQL 操作记录下来到老数据库进行重放。 这种方式虽然直观,但是要侵入业务系统,直接被我们否决了。 其实这种方式是 binlog statement based 模式, 理论上我们可以直接从 MySQL 的 binlog 里面获取数据变更记录。 以 row based 方式重放到 SQL Server。

这时候又涉及到逆向 ETL 过程, 因为很可能 Translate 过程中,做了表结构重构。 我们的解决方法是,使用 Canal 对 MySQL binlog 进行解析, 然后将解析之后的数据作为数据源, 将其中的变更重放到 SQL Server。

由于回滚的过程也是 ETL,基于 yugong, 我们继续定制了 SQL Server 的写入功能, 这个模式类似于在线迁移,只不过方向是从 MySQL 到 SQL Server。

其他实践

我们在迁移之前做了大量压测工作, 并针对每个迁移的 DB 进行线上环境一致的全真演练。 我们构建了和生产环境机器配置一样, 数据量一样的测试环境,并要求每个系统在上线之前都进行若干次演练。 演练之前准备详尽的操作手册和事故处理方案。 演练准出的标准是:能够在单次演练中不出任何意外,时间在估计范围内。 通过演练我们保证了整个操作时间可控,减少操作时候的风险。

为了让数据库的状态更为直观的展现出来, 我们对 MySQL / SQL Server 添加了细致的 Metrics 监控。 在测试和迁移过程中,可以便利地看到数据库的响应情况。

为了方便 DBA 快速 Review SQL。 我们提供了一些工具,直接将代码库中的 SQL 拎出来, 可以方便地进行 SQL Review。 再配合其他 SQL Review 工具, 比如 Meituan-Dianping/SQLAdvisor, 可以实现一部分自动化,提高 DBA 效率,避免线上出现明显的 Slow SQL。

最后

基于这几种方案我们打了一套组合拳。经过将近一年的使用, 进行了 28 个通宵,迁移了 42 个系统, 完成了包括用户、订单、支付、电商、学习、社群、内容和工具的迁移。 迁移的数据总规模接近百亿,所有迁移项目均一次成功。 迁移过程中积累了丰富的实战经验,保障了业务快速向前发展。

下一篇:从 SQL Server 到 MySQL(二):在线迁移,空中换发动机 - Log4D


原文链接: https://blog.alswl.com/2018/03/sql-server-migration-1/
3a1ff193cee606bd1e2ea554a16353ee
欢迎关注我的微信公众号:窥豹
Phoenix Nemo's avatar

制作 Arch Linux 系统模板镜像

阿里云镜像制作踩坑记。

此文章主要记录按照阿里云 Customized Linux 制作 VPC 镜像的过程。一些部分也可用作制作其他平台镜像的参考。

当然记录的原因主要是 Arch 上的 cloud-init 打死无法在阿里云上修改 root 密码,就很气。

建立虚拟机

因为要制作 Customized Linux,所以第一步无法在阿里云平台上使用公共镜像制作。本机启动一个 Virtual Box,新建虚拟机,虚拟磁盘选择 RAW/IMG 格式即可。

按照一般步骤安装 Arch Linux,需要整个磁盘仅有一个分区。虽然很多平台支持多分区的镜像文件,但是莫名在这里踩了坑所以。

(另外吐槽:vps2arch 居然不帮我把 base-devel 装全了?!)

系统配置

安装一些必需的包。

1
# pacman -S qemu-guest-ga openssh

启用服务。

1
2
3
# systemctl enable qemu-ga
# systemctl enable sshd
# systemctl enable systemd-networkd

网络配置

哪个魂淡跟我讲 VPC 是 DHCP?装着 cloud-init 的 Arch 就可以自动设置内网 IP,这个没装的就 GG。

修改文件 /etc/systemd/network/default.network

1
2
3
4
5
[Match]
Name=en*

[Network]

DHCP=ipv4

总之先这样放着。

定制脚本

根据阿里云的文档,cloud init 不生效的时候需要用约定好的配置文件和脚本完成各种兼容动作。

新建目录 /aliyun_custom_image

新建文件 /usr/bin/aliyun-custom-os,写入内容

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#!/bin/bash

os_conf_dir=/aliyun_custom_image
os_conf_file=${os_conf_dir}/os.conf

load_os_conf() {
if [[ -f $os_conf_file ]]; then
. $os_conf_file
echo $password
return 0
else
return 1
fi
}

cleanup() {
# ensure $os_conf_file is deleted, to avoid repeating config system
rm $os_conf_file >& /dev/null
# ensure $os_conf_dir is exitst
mkdir -p $os_conf_dir
}

config_password() {
if [[ -n $password ]]; then
password=$(echo $password | base64 -d)
if [[ $? == 0 && -n $password ]]; then
echo "root:$password"
echo "root:$password" | chpasswd
fi
fi
}
config_hostname() {
if [[ -n $hostname ]]; then
echo "$hostname" > /etc/hostname
hostnamectl set-hostname $hostname
fi
}
config_network() {
if [[ -n $eth0_ip_addr ]]; then
config_interface
systemctl restart systemd-networkd
fi
}
config_interface() {
mask2cdr $eth0_netmask
cat << EOF > /etc/systemd/network/default.network
# Generated by Aliyun Custom OS helper
# DO NOT EDIT THIS FILE! IT WILL BE OVERWRITTEN

[Match]
Name=$(ip link | awk -F: '$0 !~ "lo|vir|wl|^[^0-9]"{print $2a;getline}' | sed -e 's/^[[:space:]]*//')

[Network]
Address=$eth0_ip_addr/$netmask
Gateway=$eth0_gateway

[Link]
MACAddress=$eth0_mac_address

[Address]
Address=$eth0_ip_addr/$netmask
EOF
echo "nameserver 1.1.1.1" > /etc/resolv.conf
for ns in $dns_nameserver
do
echo "nameserver $ns" >> /etc/resolv.conf
done
}

mask2cdr() {
# Assumes there's no "255." after a non-255 byte in the mask
local x=${1##*255.}
set -- 0^^^128^192^224^240^248^252^254^ $(( (${#1} - ${#x})*2 )) ${x%%.*}
x=${1%%$3*}
netmask=$(( $2 + (${#x}/4) ))
}

if load_os_conf ; then
config_password
config_hostname
config_network
cleanup
else
echo "not load $os_conf_file"
fi

赋予执行权限

1
# chmod +x /usr/bin/aliyun-custom-os

新建 systemd unit 文件 /usr/lib/systemd/system/aliyun-custom-os.service 写入内容

1
2
3
4
5
6
7
8
9
10
11
12
[Unit]
Description=Aliyun Custom OS Helper Script

[Service]

Type=oneshot
ExecStart=/usr/bin/aliyun-custom-os
TimeoutSec=30
StandardInput=tty
RemainAfterExit=yes

[Install]

WantedBy=multi-user.target

然后启用这个服务

1
systemctl enable aliyun-custom-os

挂载镜像

正常 shutdown 虚拟机,然后拿到镜像文件的路径。例如 ~/vm/archlinux.img

接下来需要将此镜像挂载到宿主机系统中修改、清理文件。首先确定镜像文件中的分区位置:

1
2
$ file ~/vm/archlinux.img
archlinux.img: x86 boot sector; partition 1: ID=0x83, active, starthead 32, startsector 2048, 41938944 sectors, code offset 0x63

得知 startsector2048

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ fdisk -l ~/vm/archlinux.img
You must set cylinders.
You can do this from the extra functions menu.

Disk archlinux.img: 0 MB, 0 bytes
255 heads, 63 sectors/track, 0 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x91d8e293

Device Boot Start End Blocks Id System
archlinux.img1 * 1 2611 20969472 83 Linux
Partition 1 has different physical/logical endings:
phys=(1023, 254, 63) logical=(2610, 180, 2)

得知 sectorsize512

使用 mount 命令带 offset 参数挂载镜像中的分区:

1
2
$ sudo mkdir -p /mnt/img
$ sudo mount -t ext4 -o loop,offset=$((2048*512)) /path/to/archlinux.img /mnt/img/ # 更改 -t auto 或者其他此分区使用的文件系统格式

就可以 cd /mnt/img 看到镜像里的 rootfs 啦。

清理/检查文件

要删除的:

1
2
3
4
5
# rm /root/.bash_history # _(:з」∠)_
# rm /etc/ssh/ssh_host_* # 强制每次部署的时候重新生成密钥对
# rm -r /var/log/* # 清理不需要的日志
# rm -r /var/cache/* # 清理缓存
# rm /etc/resolv.conf.bak # 避免恢复成制作时的 DNS

要检查的:

/etc/hosts - 我不知道为什么,第一次的时候把这个文件留空了(:з」∠)

/etc/resolv.conf - 鉴于总是有人喜欢手动修改这个文件,所以直接把它写成静态文件好了。内容例如

1
2
nameserver 8.8.8.8
nameserver 8.8.4.4

/etc/ssh/sshd_config 中是否允许 root 密码登陆。

准备镜像

退出 /mnt/img 目录,然后卸载镜像

1
# umount /mnt/img

(可选)使用 qemu-img 转换镜像格式到 VHD,减少镜像文件大小。特别是对国内的小水管上传(心疼

1
$ qemu-img convert -f raw -O vpc archlinux.img archlinux.vhd

上传镜像

在相同的 region 创建一个 OSS bucket,然后创建一个 RAM 子用户赋予 OSS 写权限并创建 Access Key,使用 OSSBrowser 上传准备好的 VHD 文件。

上传完毕后,在 ECS 标签下的镜像标签即可导入镜像。如果是第一次操作,需要给 ECS 授权访问 OSS。在导入的页面提示中提供了授权的链接。镜像内容配置如下:

  • OSS Object 地址:镜像文件在 OSS 中的 URL
  • Image 名称:archlinux-2018.1-x86_64 … 等符合要求即可
  • 操作系统:Linux
  • 系统盘大小:40GB
  • 系统架构:x86_64
  • 系统平台:Customized Linux
  • 镜像格式:VHD(如果是 img 就选 RAW)
  • 镜像描述:随便写啦。

确定后应该就会开始制作镜像了。

测试

因为没有做经典实例的兼容,这个镜像只能用于 VPC 的实例。总体而言,cloud-init 本来兼容的 Arch 却无法更改 root 密码(其他的倒是没问题),所以才选择了用一个 dirty 的方案来实现。

不知道应该说阿里云的工程师对自定义镜像的考虑周到还是对不同发行版的考虑欠妥…?

最后庆幸倒腾来去上传了好多遍 20G 的文件,日本运营商家宽带宽对等真的是帮了大忙,不然一个镜像制作不知道要到什么时候 > > (斜眼看国内三大运营商

参考:

alswl's avatar

从 2017 到 2018

(2017 年 2 月摄于瑞虹月亮湾)

我有两年没公开年终总结了,原因很简单:年终结果无法让自己满意, 生活持续呈线性发展。那今年为什么又要将总结发出来呢? 并非是我的 2017 过得如何充实、有成就感,而是出于两个目的。 第一是我认识到 OKR 需要平和对待,我目前对自己的生活是缺乏完全掌控力的, 我无法既渴求爆炸性的增长,又期望在这一过程中低风险,我需要接受这种现状。 第二是曝光自己的目标,让回顾和计划透明化。 从社会心理学的角度上来看,公开的承诺有助于个体更努力地驱动目标的完成。

2017

2017 年我将个人生活纳入了 OKR 管理的范围,在这之前是琐碎的 Checklist 管理方法。 OKR 管理的优势在于年初可以关注大方向,保持全年的路线连贯性。 同时 OKR 可以量化的追踪每事项的进展,促进执行。

我在 2017 年的 OKR 大致如下:

  • 阅读
    • 精读书籍
    • 粗读书籍
  • 写作
    • 写博客
  • 健康
    • 游泳
    • 跑步
    • 素食
  • 习惯
    • 冥想
    • 英语
    • 计划性
  • 技术
    • 操作系统理论
    • 新语言学习
    • 分布式系统
    • 大数据和人工智能
  • 创业准备
    • 行业思考
    • 思维模型
    • 人脉认识
  • 工作
    • 普通任务的完成
    • 突破型任务的完成
  • 家庭生活
    • 家庭旅行(国内/国外)
    • 下一代
    • 上海话
  • 财务
    • 投资入门
    • 投资学习学习

我就不将 Key Result 一一罗列出来了,加权平均之后,年度最终的得分是 0.40895。 我不觉得自己的目标设置得过高,过低的分数的原因是执行力不够。

人的一身充满许多轻易就能够改变自己人生轨迹的事件,比如高考、恋爱,工作变动。 在 2017 年,我面临最重大事情就是我离开堆糖,在沪江开始了一段新的职业生涯。 不像那些足够理性的人,离开堆糖我非常不舍,过程中充满挣扎,最后还是做了决定。 到沪江之后,整个环境发生了变化,我从一百人不到的精英团队进入一个几千人、 具有许多历史包袱的大团队,一度压力很大。 在经历了冲突、沟通、推动这些状态之后,我也逐渐适应了。 此时又出现了新的焦虑:如何在一个相对迟缓的环境推动有效的变化。 这是一个大课题,也是我 2018 年想尝试解决的问题。

17 年工作内容,主要围绕这么几块内容:将这个集团的 SQL Server 迁移到 MySQL 上。 核心需要解决的是将大规模异构数据进行离线/在线迁移,目标是缩短停机时间。 在迁移过程中还要提供一些数据重构功能,以便数据结构进行调整。 这个问题和当年阿里巴巴的去 IOE 有些相似,他们是 Oracle 到 MySQL,而我是从 SQL Server 到 MySQL。 我投入不少时间,好在结果挺不错,项目前后持续了半年时间,没有发生一例问题。 现在已经可以脱离我独立运作了。 除此之外我还在集团引入了 Metrics 系统;在集团开展一个每周的技术分享活动:沪江技术之夜。

2017 全年看书有 48 本,是我近年来读书较多的一年。读书速度加快的主要原因是: 我主动追求更快的读书速度,提升索引归纳能力。 为此反复学习了「如何阅读一本书」/「如何高效学习」等方法。

今年推荐阅读的几本书是:

  • 「讲理 : 作文四书之一」 - 王鼎钧
    • 教你如何写论说文,让每个作者能挥洒自如表达自己思想和态度。 作者有一套「作文四书」,感谢余晟老师赠书。
  • 「人类简史」 - 尤瓦尔·赫拉利
    • 开脑洞的人类历史学研究,作者妙笔生花,将复杂事情以形象和有趣的方法呈现给大家。
  • 「腾讯传 : 中国互联网公司进化论」- 吴晓波
    • 中国互联网企业发家史,虽然大环境在不断变化,但当时决策的逻辑还有相当参考价值。
  • 「解读中国经济 - 林毅夫」
    • 作为高参,林毅夫对中国当代经济环境理解深刻。 书中讲述了从 78 年改革开放到现在的制度变迁。

17 写了 7 篇文章,低于预期。虽然在我的 inbox 里面存了不少草稿, 但觉得自己的逻辑还不流畅,修辞也不够优雅,还不好意思拿出来给大家看。 文字驾驭能力差这个问题我也意识到了,恰好边上有位写作高手,推荐了一些相关书籍。 期望接下来通过学习写作得到改善。

2017 年去了一趟香港,不幸的是遭遇身体不适,在中环地铁晕厥,导致澳门行也取消。 患难见真情,我老婆对我也是真感情。除此之外,还和夫人去了一趟九华山。

17 年还有一件幸运的事情,上海牌照拍了 17 个月之后终于中了,具备了买车的资格。 唯一遗憾的是,同时中了两张牌照,但是没有这么大的需求,无奈放弃一张, 还被罚款 1000 元。

17 年让我产生幸福感的几个物件推荐:

2018

2018 年我将迎来自己的三十而立。

人这种动物,总是在周期性的时间点上比平时更容易思考。 我花了几个周末,加上好几个洗澡的时间思考我接下来的目标是什么。 1 年的目标?5 年的目标?

我一度没有答案,尤其是在被罗胖等内容创业者贩卖焦虑之后。 我倒是不担心自己像中兴那位 35 岁的同行,在各种压力之下逼得跳楼。 我的压力和焦虑主要是来自我自己未来想象空间太小。 剩下的时间越少,可以调整的变量范围就越小,随着时间巨轮碾过, 不管未来发展是线性还是非线性,都在忙忙碌碌中逐步变成现实。

有时候也挺恨自己不具备天马行空的想象力和愿景。 我思考的结果是,技术发展速度虽然快,但是核心改变是有限的。 我目前没有足够的想法来做上层的事,那就切入基础领域,花时间补足短板。 在这个思路指导下,新年的 OKR 对基础领域的投入提高了比例:

Direction Objective Key Result
掌握面向未来的技能 初步掌握机器学习的基本技能 学完 Coursera Machine Learning
学完「集体编程智慧」
掌握区块链知识 读一本区块链的书
掌握基础的技能 数学 看完「数学之美」
看完「什么是数学」
看完「如何解题」
写一篇关于数学的文章
弄清楚概率论的研究对象、范畴、方法论
提升英文水平 扇贝背完 TOFEL 词汇
扇贝背完 GAE 词汇
写 2 篇英文博客
经济管理 读完「经济学原理」
读完吴晓波的一系列书
读 3 本关于投资的书
通过投资获得 X 元收益
提升系统化思考能力 读 5 本相关书籍
学完 Coursera Model Thinking 课程
团队管理 阅读团队管理相关 5 本书籍
产出 3 篇相关文章
XXX
提升专业技能 Scala 语言深入 学完 Coursera Scala 课程
读完「Akka Cookbook」
读完「深入理解 Scala」
分布式系统深入 学习 MIT 课程
研究 TiDB,产出文章
写一篇 CAP 文章
计算机语言和基础 读完「Java Concurrency in Practice」
读完「Go 语言编程」
读完「算法新解」
读完「SICP」
提升家庭生活质量 旅行 出国旅行 1 次
出江浙沪旅行 2 次
父母 回父母家 10 次
小孩 生 1 个
买 1 辆车
养成习惯 运动 全年跑步或游泳 36 次
计划和复盘 全年做早晨计划 264 次
全年做习惯追踪 365 次
每周做 Review
提升个人 PR 结实外部朋友 参会 4 次
面向 100+ 人的外部做 1 次分享
外部代码贡献 参与 1 个开源项目,提交核心作用代码
提升写作能力 全年写作 12 篇
参加开智的课程

从目标执行的可行性上来说,年度 OKR 往往是允许甚至是推荐在执行过程中修改的。 季度的 OKR 在确定之后不允许进行修改。毕竟计划赶不上变化,但是又不能朝令夕改。 希望我今年在大方向保持稳定,每个季度能够执行贯彻到位,将时间价值最大化。

2017,混乱和秩序相交。2018,进入冰山之下。


原文链接: https://blog.alswl.com/2018/01/2017-2018/
3a1ff193cee606bd1e2ea554a16353ee
欢迎关注我的微信公众号:窥豹
sunjw's avatar

网站迁移

好久好久不写博客了。今天完成了网站迁移,从一个不知名的 host 转到了阿里云云虚拟主机。考虑到实际上这个网站主要的流量是访问 JSToolNpp,来源大多在国外,再加上使用了个 .us 的域名,所以选择了海外云。迁移到是挺顺利,各种 FTP 一顿操作就好了,WordPress 则是直接导入的数据库。

Anyway, it works!

's avatar

vue 集成 iframe

书接上回:最近不是在用 vue 开发 H5 App 嘛(再后来又加上了 react-native 😂,这个以后有机会说),我们有个接口返回的是 HTML 页面,在原来的原生 App 里面自然是用个 WebView 控件来展示啦,但我们现在整个应用都是 H5 了,HTML 里面嵌另一个 HTML,那第一反应自然就是 iframe 啰。

PS. H5 里面是嵌不了原生控件的对吧?react-native 是可以的,有时确实是要方便一些的……吧

PSS. 除了 iframe 之外呢,最近好像还有一个比较新的标准是 web components,也是用来做内嵌的吧,但对浏览器的版本要求应该比较高(虽然也是有 polyfill 的啦),记得我当时好像也是试了一下的,但是没成功 😂,也不记得是啥问题了,而且 iframe 也能满足要求啦,所以就没弄这个了。回头有机会再研究下吧。

如果只是简单的页面展示的话,直接通过 <iframe> 控件的 :src 属性绑定页面 URL 就完事啦。

但我们这是个 App 嘛,页面里面也是有一些可交互元素的,比如点击跳转到详情页之类的,这时就要绑定 <iframe>@load 事件(vue 写法,对应于 JS 接口里面的 onload 事件),示例代码如下:

要点:

  • 第 15 行:在 @load 事件回调方法 onIframeLoad 中,iframe 控件本身可以从 event.target 中得到,不需要用 vue 的 ref 之类的。
  • 第 16 行:要从 iframe 页面中访问外部提供的接口,可以通过发送 message 事件来实现,那么在外部的 HTML 环境中就要通过 window.addEventListener() 来设置对该事件的监听函数。这里的 window 对象就是 iframe 外部 vue 所在的浏览器对象。
  • 第 17 行:从外部访问 iframe 本身的 window 对象就是通过 iframe.contentWindow 来得到,而调用该对象的 eval 方法就是从外部对 iframe 进行操作的一般方法了。该方法接受一个字符串参数,可以传入一段完整的 JS 代码,这时用 JS 新特性 template literals 来传这个参数就会很舒服了,既支持多行,又支持内嵌表达式,可以传点儿参数进去。
  • 第 18 行:耶,服务端返回回来的页面自带 jQuery zepto,这让我们的字符串小脚本好写很多啦。不过要注意:由于这段脚本是要直接在 iframe 所在的浏览器环境(WebView)里面跑的,而不是像外面的 vue 会先经过 babel 的编译(因为用了 .vue 格式嘛),所以最好不要用那些个比较新的语法,比如这里就没有用 () => {} 的匿名函数写法(因为……我也懒得查啦,而且手机的 WebView 兼容性嘛,你懂的 😏)。
  • 第 19 行:在 iframe 环境中,window.parent 就是其外部环境的 window 对象啦,调用其 postMessage 方法就可以往外边发 message 事件了,所传参数会最终变成该事件的 data 字段。
  • 第 30 行:最终接收到 iframe 传出来的 message 事件,然后就……该干嘛干嘛啰。

OK,以上就是本次的 vue 集成 iframe 并实现互操作的方法总结啦。

Li Fanxi's avatar

2018新年好

2017年,我做了这些事:

– 网络生活

写了2篇博客,太懒了,除了开年的新年好和年末的新年音乐会两大作业文,2017年其它的内容居然什么都没写。其实有好几篇都在草稿箱里,只是都没有最终完成。博客空间总访问量31646 PageView(Google Analytics数据),数字下跌接近50%。跟前两年一样,依然是在吃老本,2010年和2013年写的几篇旧文章撑起了半边天。饭否发消息49条,其中包括照片16张。

calibre修了两个微不足道的豆瓣元数据获取的Bug,给Seastar修了一个微不足道的Bug。

继续维持Linux Kernel Patch Statistics网站的运行,处理各种用户反馈过来的数据问题。几次想重构这个网站,方案也想了好几版,但是还是没有着手去做。最初的思路是想写个Flink的任务,流式去处理新的Patch信息,然后更新统计结果。后来仔细想了想,其实不用这么复杂,就目前的数据规模,直接把原始往数据库里一塞,然后正常查询就足够了。同时数据精准度也还有待进一步提高,但是这个主要是人肉工作,有不小的难度。期望2018年能把这个事情落地了。

– 几个IT产品

Amazon Echo Dot:对智能音箱的第一次尝试,总体感受是低于期望值。最重要的原因是它并不面对中国市场,所以很多功能并不适合在中国使用。最明显的就是在音乐播放这个功能上,无法识别中文歌名就是一个硬伤,播放自有音乐也非常困难。在智能家居方面,跟Home Assistant可以有一定程度上的集成,但是在国内依然用处不大。所以目前已几乎闲置。

天猫精灵:公测时第一批买到的,支持中文就是它最大的优点,智能家居的功能目前在国内也比Echo要稍稍接地气一点。从公测到现在有快半年时间了,能够看到它的明显进步的。目前在家里还是经常使用它的音乐、提醒、天气时间、智能家居等功能的。不过在有魔盒、魔盘等产品在先,我个人是不看好这类天猫XX的硬件产品的未来的。

SONY MDR-1000X蓝牙降噪耳机:在上下班的大巴上使用,降噪效果还是不错的,可以把音量保持在比较低的程度。不过这个产品有时会让我的耳朵产生胀痛的不适感。但是我其实一直没想明白,降噪的原理是把噪音声波的相位颠倒一下播放,那是不是就等同于一直有跟噪音音量相同的声音给灌到我耳朵里了?音量虽然开低了,但这个降噪的声音本身会不会损伤耳朵呢?

Brother PT-1230PC标签机:用几次就会吃灰的东西,所以买了个廉价的全新二手机器。接电脑使用是一件很不方便的事情,好处是可以完全自定义打印的内容。每次打印都会浪费很长一段标签纸,所以最好一批多打印几个才环保。然而它没有自动半切的功能,所以打印完以后还要手工把标签一段段的剪开(即使一次只打一个,也得手工剪掉前面浪费的那一段),这才是这个机器最不方便的地方。淘宝上的廉价耗材完全堪用,除了标签薄一点、表面光泽度稍差,在家用环境下应该都没有问题。

佳能CP910照片打印机:也是一个大部分时间吃灰的东西,所以也是买了个廉价的全新二手。现在偶而会有临时打印照片的需求,家附近的照相馆的打印的价格坑爹不说,主要是打印效果惨不忍睹,而线下或在线冲印服务则时效性不够。总体来说,对这个产品是满意的,打印的品质稍逊于冲印的品质,锐度和饱和度稍差。但是可以自己把屏幕调到跟打印出来的效果一致,这样就不用担心PS得好好的照片打出来不合心意了。

Synology DS918+:又换了个NAS,去年买的916+才用了一年多就把它换掉了。原因是新机器比旧机器便宜,买新卖旧,损失不大,而新机器可以支持NVME的SSD Cache,这样就不用浪费宝贵的硬盘槽位来做SSD Cache了。然而现实却并不美好,Synology的SSD Cache只能对一个存储空间生效,并且不会对系统文件分区进行加速,而且只用一条SSD时,只能进行只读Cache,所以除了在使用VMM时有那么一点点小作用以外,其它时候几乎都感受不到SSD带来的加速作用。

玩客云:迅雷出的“NAS”,背后的风起云涌的故事我就不讲了,反正我原价买了两个,卖了一个留了一个,就等于白拿了一个还有得赚。跟群晖比,这个东西真是连NAS都算不上,而且从安全性、可靠性等角度来说,我根本不敢用它。但是我非常喜欢它的思路,这才是普通人可以用的NAS:插上硬盘就能用,不用太担心内网、外网、端口映射的问题(群晖的QuickConnect?只能呵呵它一下了),没有文件、文件夹的概念,放进去的就是照片、视频、文档这样的数据,直接自动归类放好、视频文件自动下载元信息,需要的话可以手工再添加标签来分类。这样的产品用来做手机存储的扩展和备份,非常方便。

博朗耳温枪IRT6520:2016年买了一个,2017年又买了一个,必须专门拿出来黑它一下。山寨做工就不说了,操作繁琐也不说了,重点是太太太容易坏了,而且几乎没有质保。2016年我是亚马逊闪购买的,无质保。2017在京东超市自营买的,坏了依然需要自己跟代理商扯皮。一气之下直接联系了京东客服投诉,京东售后自己给兜底了,换了个新的。现在每次用还是提心吊胆的,不知道什么时候就又会测温不准了。

荣耀V9手机:三年内买的第6个荣耀手机,现在半家人都在用荣耀手机。华为的产品不良心,但还算省心。荣耀虽然硬件各种缩水,但是日常用用还算够。V9的屏幕色彩调得有点夸张,乍一看还挺惊艳的,但是仔细看还是掩盖不了它的低端屏幕的本质。NFC当公交卡很好用,指纹解锁我也一直觉得国产手机全都可以秒杀同时期的iPhone。

戴森V8 absolute吸尘器:跟只有它价格十分之一不到的某型号的小狗吸尘器来比较,它工作时的表现确实是非常不错的。不过它的塑料件的质感和做工,真是对不起它的价格。另外,因为所有的主要部件(电机、电池)的重量都在手上,所以使用起来还是有点费劲的,尤其是使用短小的吸头清理小地方的时候。

– 出行

量子号邮轮,出行前只要多看看网站评价中的差评,就可以把自己的心理预期降低到一个合理的程度,然后就可以玩得很开心。邮轮旅行的陆地行程大都是坑爹的,正确的姿势就是充分享受在船上的休闲时光。

成都,对于不能吃辣又本来就不是吃货的我,成都算不上是一个“来了就不想走”的城市。大熊猫基地比想象的要有意思一些。

南京/江苏大剧院,江苏省终于有了一个说得过去的音乐厅。祖宾‧梅塔和以色列爱乐的演出,虽然比不上维也纳爱乐乐团,但也足够惊艳。期待2018年能有值得让我再专门跑一趟的演出。

千岛湖,一个除了休闲啥也干不了的地方。

展望2018年:

还是说点工作,2017年在工作上实现了从Java回到C++的转型,如果说C++ 14语言和纯异步编程带来的挑战还不是个大问题的话,真正的挑战存在于从简单业务代码开发转型为去做高性能底层系统的设计和实现。尤其是设计阶段需要参考很多现有的系统和论文,去理解它们的设计意图,去理解它们的实现机制,并在这个基础上找到适合自己的方案,在之前没有太多积累的前提下,非常难。

可以说,2017年的工作压力是相当的大,也是我自己第一次在工作中真正开始怀疑自己分析、解决问题的能力。不过在经历了系统的几次迭代后,似乎还是找到了一点点感觉,期待2018能更好的迎接新的挑战。

Phoenix Nemo's avatar

自托管的在线协作翻译平台 Weblate

起因:Transifex 这货闭源一段时间后突然开始抢钱了。

正巧一堆开源项目需要一个在线协作的翻译平台,于是测试了几个比较知名的开源程序。一遍折腾下来,发现 Weblate 可以最大化满足要求。顺便提一句,Weblate 也是有 hosted 付费服务的,但是在预算内的源字符串等限制依旧太多,所以选择使用他们的源码来搭建一套。

以及:我讨厌 Docker。

Weblate 文档 提供了非常全面的从起步到上手到各种高级用法的指南,因此这里不多赘述安装的过程。只记录少许踩过的坑。

这套程序看似简单,但实际上是基于 Django、使用了一大堆组件的复杂程序。如果想保持 system clean,最好(最快)的办法还是使用 Docker。

准备

小型实例只需要一台虚拟机即可。但是即便只托管几个项目,它依旧会吃掉 2 个 CPU 核心和 4GB 内存,和曾经开源版的 Transifex 有得一拼 大概也解释了为何这类服务都死贵

如果是托管在公网上的实例,则推荐使用 HTTPS。Weblate 的 Docker compose 提供了 HTTPS 支持,稍后会提到。

安装 Git, Docker 和 docker compose,在一些软件仓库中一般是 docker-cedocker-compose,其他软件均不需要手动安装。

搭建

首先克隆 docker compose 配置到本地

1
2
git clone https://github.com/WeblateOrg/docker.git weblate-docker
cd weblate-docker

为了直接开始使用 HTTPS,现在需要先建立域名解析记录,将要使用的域名(例如 weblate.example.com)指向服务器 IP。然后在该目录下创建配置文件 docker-compose-https.override.yml 内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
version: '2'
services:
weblate:
environment:
- WEBLATE_DEBUG=0
- WEBLATE_EMAIL_HOST=smtp.gmail.com
- WEBLATE_EMAIL_HOST_USER=noreply@weblate.org
- WEBLATE_EMAIL_HOST_PASSWORD=system.email.password.here
- WEBLATE_ALLOWED_HOSTS=weblate.example.com
- WEBLATE_SERVER_EMAIL=noreply@weblate.org
- DEFAULT_FROM_EMAIL=noreply@weblate.org
- WEBLATE_REGISTRATION_OPEN=0
- WEBLATE_TIME_ZONE=UTC
- WEBLATE_OFFLOAD_INDEXING=1
- WEBLATE_REQUIRE_LOGIN=1
- WEBLATE_ADMIN_NAME=Weblate Admin
- WEBLATE_ADMIN_EMAIL=admin@weblate.org
- WEBLATE_ADMIN_PASSWORD=your+initial+password
https-portal:

environment:
DOMAINS: 'weblate.example.com -> http://weblate'

这份配置文件指定了:

  • 关闭 Django DEBUG mode (即生产模式)
  • 系统外发邮件服务器 smtp.gmail.com
  • 系统外发邮件用户名 noreply@weblate.org
  • 系统外发邮件密码 system.email.password.here
  • 允许使用的域名 weblate.example.com,如果有多个域名,使用逗号隔开
  • 系统外发邮件地址 noreply@weblate.org
  • 关闭注册通道,用户必须管理员手动添加
  • 设置系统时间为 UTC
  • 打开后台索引,降低运行时的负载
  • 执行任何操作前要求登陆
  • 默认管理员名称是 Weblate Admin
  • 默认管理员邮箱地址是 admin@weblate.org
  • 默认管理员密码是 your+initial+password

然后在 https-portal 容器中指定了要使用 SSL 的域名 weblate.example.com 和后端指向的容器 http://weblate

在当前目录中执行

1
docker-compose -f docker-compose-https.yml -f docker-compose-https.override.yml up

会顺序拉取、启动 4 个 docker 容器,分别是:

  • https-portal
  • weblate
  • postgresql
  • memcached

第一次启动需要一些时间拉取镜像并导入初始数据。全部完成后,访问 weblate.example.com 应该可以看到一个 HTTPS 的 Weblate 实例运行,使用之前定义的默认管理员邮箱地址和密码即可登入。

此时转回终端,按一次 ^C 等待四个容器正确关闭,然后编辑 docker-compose-https.override.yml,删除以下配置

  • WEBLATE_ADMIN_NAME
  • WEBLATE_ADMIN_EMAIL
  • WEBLATE_ADMIN_PASSWORD

否则,如果更改了默认管理员的信息(如用户名等)下次启动会再次创建管理员帐号,并使用相同的邮箱,导致默认管理员无法使用邮箱登陆(报错返回 2 个用户信息)。解决办法是使用用户名…(摔

集成配置

再次运行 docker-compose -f docker-compose-https.yml -f docker-compose-https.override.yml up 后可以很快启动所有需要的程序。此时登入 Weblate 实例,指向 /admin/ssh/ 点击创建 SSH 密钥。

在对单一 repo 提交的情况下,此 SSH Key 可作为 GitHub deploy key,但是如果需要多个不同 repo 提交时,有两种方法:

  • 创建一个 GitHub 用户,然后将此 SSH Key 添加到此用户下,再给此用户所有必要的写权限
  • 使用 Access Token 作为 HTTPS 密码访问必要的 repo

浏览器指向 /admin/trans/project/ 新建一个 Project。这个 Project 不仅指一个项目,也可以作为一个 Organization 的存在,更精确的解释是一个软件集,例如 KDE 套件可以包含一大堆的组件。

指向 /admin/trans/subproject/ 这里才是可以添加要翻译的项目的地方。如果对应的 repo 添加了公钥,这里可以直接使用 SSH 方式的 push URL。

File mask 填写所有语言文件相对 repo root 的路径,使用 * 代替语言代号。如果是 Monolingual language file,例如 key 是 user.info.comment_posted 这样而非原本即可阅读的文本,则 Monolingual base language fileBase file for new translations 均为源语言文件相对 repo root 的路径,这样即可正确识别源语言的字符串不至于让别人拿着 comment_posted 这样的 key 来猜意思

持续集成

提交翻译后,Weblate 会在后台完成索引并提交必要的更改。当然也会一不小心刷了别人的屏…

在 repo 的 settings -> integration 中可以添加 Weblate 作为集成,每次有新的提交即可触发 Weblate 更新源语言文件。

结论

我很开心可以省下每年数千美元来用一个非常卡的在线协作翻译平台

当然,我依旧讨厌 Docker。

alswl's avatar

工作和热情

最近和一位老朋友吃饭,他说他最近比较苦恼: 「开始有职业危机了,担心自己失去对工作的热情,似乎离油腻的中年人又进了一步」。 作为一名互联网工程师,我深知这个行业技术日新月异, 如果对工作都失去了兴趣,会将自己置于跟不上时代发展、自身得不到提升的危险境地; 从个人生活质量来看,工作占据了一天 1/3 ~ 2/3 的时间, 失去热情的工作会成为人生的桎梏,不是驾驭工作,而是被工作所奴役, 这会进而影响一个人的身心健康,得个抑郁症稀疏平常。

如何保持对工作的热情,当自己陷入困境时候如何重新调动工作热情? 这是职场人需要深思的问题。

工作带来什么?

从因到果,让我们先聊聊工作给我们带来什么?

对于不是含着金钥匙出生的群众来说,物质上的回报是重要并且第一位的。 如果温饱的需求都没有被满足,就没必要讨论精神世界的热情了。 大部分刚出校园的职场新人可能处于这个状态,一人吃饱全家不愁。 从马斯洛需求层次理论说起: 当「生理需求」、「安全需求」被满足之后,就需要「社交需求」、「尊重需求」 和「自我实现的需求」。 将这些需求映射到工作中,那就是成了「有志同道合的人一起工作」, 「工作能干出成绩,获得认可」, 「有挑战,干出成就感」。

我有过一段经历:一个优秀工程师突然申请离职,和他沟通离职原因时, 他的反馈是「乏了,觉得工作没劲,甚至怀疑开发工程师是否是自己想要的工作, 想要回家休息一段时间」。 这个小伙子其实很优秀很优秀,FullStack,好奇心强烈,休息了一段时间,去了豆瓣厂。 我们后来分析,可能有这么几个原因导致他对这份工作失去了兴趣:

  • 他被分配到的工作内容不够有挑战,业务开发在一定程度上面有重复性
  • 他所做的几个项目,没有被充分发挥价值,没有获得足够的认同感
  • 公司的业务没有出现迅猛爆发,缺乏业务上的成就感

由于这个案例的存在,后期我们也做了一些调整,避免再次发生这样的优秀人才流失悲剧。 当然,这些调整都是从组织的视角出发。但并非所有组织都有这样自省能力。 那么作为个体,有哪些方法可以挽回逝去的热情呢?

放大已有成绩的影响力

将已有的工作成绩影响力放大,这是最不耗力气的一种办法。

我们工作中,应当是取得了一些成绩的,这些成绩是否已经最大化发挥作用了? 还有没有价值可以深挖?

扩大作用的一种做法就是写作。 将工作内容中有创新、创意的地方,落实为文章进行发表。 不少公司会有和技术相关的宣传平台,比如沪江就有一个「沪江技术学院」公众号。 就比较适合发布公司内的一些技术创新、工程实践。 如果公司没有提供这样的平台,也可以使用自己的公众号或者博客, 唯一要注意的就是敏感信息不要泄露。

除了使用写文章的方式,还有其他一些思路可以参考:做内部/外部分享; 将成果开源出来。做分享可以给自己带来不少成就感, 内部的分享可以更快速的对自己的成果进行宣传,也能通过这种形式培养信任感。 做开源是一个更大的话题,不少团队有这样的成功经验。比如饿了么的 Element, 我司也开源了 Guice 这样的系统。 这都是在日常工作中的工程实施总结形成的开源项目。

有同学会问:自己觉得自己的工作亮点太少,缺乏对外部分享的价值。 其实这种担忧大可不必,只要是有价值的输出,总是能找到适合的输出对象, 如果对行业老司机没有帮助,那么对新人是否有帮助呢? 对上下游的 parterner 是否有帮助么? 前两天还看到一个阿里的开源项目 alibaba/java-dns-cache-manipulator, 你能说这个很复杂么?但仍然可以给特定场景的人带来帮助。

加大深度广度,追求极致

假如工作内容是千篇一律,相信不管是谁都无法长期容忍。 这时候就要考虑一下突破自己固有的职责边界。

第一种路径是突破工作内容所需要的深度, 将工作关注的问题能够刨根问底。在平凡的工作机会上面做出彩。 牛顿在发现力学三大定律,发明微积分后,还去英国皇家铸币厂工作。 我相信作为物理学家、数学家,他对主持铸币这个工作和他过往的工作无关, 但他仍然投入了极大精力和热情去处理事务,并且变革了从银本位到金本位。

第二种方式是扩大自己的涉猎面,尝试探索新鲜事务。 在日常工作中,思考一下自己的上游、下游是否有遇到一些瓶颈,可否给予他们帮助? 支撑自己日常工作的基础设施是否足够高效、健壮,能否在上面出一些力量? 更有甚者,一些技术同学可以向前台走,去从技术的视角推动业务的变化。 也有一些同学可以往后走,往 BI 或者 AI 发展。 改变自己的职能变动太大,但是适当扩展自己涉猎面可以增强自己综合素质, 为未来提供更多可能性。

最后一个路径是以更极致的标准来要求自己。 有些朋友可能会说道:在工程实践中,我们不是不推崇过度设计么? 的确,我们要避免矫枉过正,但如果现在要求达到 60 分,我们做 120 分可能是过度设计, 我并不觉得追求 80 分有什么过分。 以超出需求者期望的姿态做事,技术上面追求极致,业务上追求卓越, 过程可能比较艰辛,但是获得的成就感也会更大。

积极主动去摸索自己的瓶颈,不要被自己的现状所禁锢。 掌握自己固有知识,挖掘上下游知识,这是保持热情的重要方式。

寻找志同道合的朋友

一个人是孤独的,鲁滨逊尚且需要星期五相伴, 何况我们这些生活在高度复杂社会里的人。 当一个人对方向产生迷茫,对处境有困惑,对问题感到棘手时候, 陪伴在周围的朋友是极其重要的。

我在前东家曾经有过迷茫的时间段,但是遇到了相当多可以学习的朋友。 我们交流对现状的看法,探索解决问题的思路,畅谈未来发展的可能性。 这个过程中,发挥出了 1+1>2 的效应,互相激励,互相促进。

如果在团队内找不到合适的伙伴, 那么参加外部活动,加入本地社区就是另外一种寻找朋友的方式。 在豆瓣同城、Meetup 等网络社区中,都可以找到大量同行业的朋友。 我自己曾经深度参与的组织有 CPyUG(Python 社区) / GDG(Google 开发者社区) / LUG(Linux 用户组)。

写作也是一种高效的社交,建立自己的知识输出平台,有利于结实更多同行业人才。 输出自己的经验的同时,还能进行二次思考,并且形成自己的个人品牌。

用脚投票

如果经过各种尝试,发现的确无法恢复自己对工作的热情,那么也可能是环境的问题。 给自己一些时间思考,思考清楚原因。 离开对个体和团队来说未必是一件好事。 长期消沉没有战斗力的人,对于团队其实是团队毒药,会将气氛带差。 在一个没有热情的环境中工作,也是浪费个人的时间。「逝者如斯,不舍昼夜」, 一个人的生命何其短暂,不要浪费在没热情的事情上。

如果离开当前的团队,那么在下次选择时候,一定要慎重考虑之前遇到的问题, 避免悲剧重复发生。

最后

一个人很难持续保持极高热情,原因是你的工作内容始终会变得熟悉和重复。 这时候即进入一个舒适区,选择适应环境还是挣扎走出来, 这取决于个人的追求。 努力调整自己,通过学习、体验、Review 的方式,来帮助自己成长。 当到达瓶颈期的时候,想办法从自身、外部寻找帮助,重新进入非舒适区,才能让自己持续成长。

祝你有热情地工作。


原文链接: https://blog.alswl.com/2017/12/enthusiasm/
3a1ff193cee606bd1e2ea554a16353ee
欢迎关注我的微信公众号:窥豹
's avatar

quasar 开发中的跨域问题

最近在用 vue 三件套 + quasar 开发 hybrid app,遇到了一个跨域问题,简单记录一下。

Phoenix Nemo's avatar

使用 Blender 渲染 Minecraft 3D 效果图

突发奇想渲染 Minecraft 3D 效果图,首先用 Chunky 尝试了一下发现效果虽好但:

  • 人物动作过于限制
  • 渲染太!慢!了!

然而并买不起 Cinema 4D,所以来尝试一下 Blender 啦~

准备工作

  • 比较好的显卡。我的家用游戏机是 NVIDIA GTX 1080Ti,Cycles Render 可以 offload 掉绝大部分 CPU 的压力。
  • 安装 Blender
  • 下载 jmc2obj
  • 下载 MCPrep
  • 准备地图、材质、玩家皮肤等资源。

导出地图文件到 obj

启动 jmc2obj,在最上方选择地图存档位置并单击 load。在 UI 里选择要导出的地图部分,点击 Export。在左侧的选项中依次:

  1. Map Scale = 1.0
  2. Center 选中,否则可能会出现在距离地图很远的地方
  3. Texture Export
    • Pre-scale textures - 如果是原版材质,建议设置为 4x。如果是高清材质,按需要选择即可。
    • 不勾选 Export alpha channel in separate file(s)
    • 不勾选 Export all textures in a single file
    • 选择从 Minecraft 安装里导出默认材质,或自行选择一个额外的材质包。
    • 然后选择材质的导出位置。建议在目标目录中新建一个 textures 目录,然后导出到此目录中。此目录里面会出现一个 tex 目录,包含所有的材质文件。

材质导出进度完成后,开始导出地图文件。在右侧的选项中依次:

  1. 取消所有的选项勾选
  2. 勾选 Create a separate object for each material
  3. 可选勾选:
    • Render Entities
    • Occulude different adjacent materials
    • Optimize mesh
    • Do not allow duplicate vertexes
  4. 其余选项均保持非勾选状态
  5. 点击 Export 导出到之前创建的 textures同级目录
  6. 可能会遇到 banner 找不到材质的问题,忽略继续。导出后还不能使用,需要一个简单而 ugly 的 hack - 再导出一遍覆盖之前的 obj 和 mtl 文件。

导出后的工作目录如下:

.obj 文件是地图数据,.mtl 则是刚才导出材质的材质索引,指向 textures/tex 的相对路径,因此这些文件的相对位置不能改变。

安装 MCPrep

启动 Blender,在 File -> User Preferences -> Add-ons 里,选择 Install Add-on from File...

然后点击下载好的 MCPrep 的 .zip 文件即可。

导入 blender

启动 Blender,先删掉默认的 object 和 lamp,然后依次选择 File -> Import -> Wavefront (.obj)

选择刚才生成的 .obj 文件导入。

(大概会卡一会儿… 喝杯茶先)

导入完成后,首先需要设置材质。选中所有 object (快捷键 A,如果被反选则再摁一次即可),左侧标签页切换到 MCPrep,然后点击 Prep Materials。

进入 Walk Navigation 模式(或者其他熟悉的移动模式),Add -> lamp -> Sun 然后将添加的 Sun 光源移动到地图的合适位置(比如一个边角)。

然后移动 Viewport 到合适的位置,将默认的 Camera 对齐到当前视角。快捷键 Ctrl+Alt+0

设置背景

在 Blender 的上方菜单栏中,有 Blender Render 和 Cycles Render 两个选项。

如果使用 Blender Render,则需要在 Sun 的属性里设置 Sky & Atmosphere 并设置 Ray Shadow 以使光源和阴影正确对应。

如果使用 Cycles Render,则在 World 属性中设置 background 为 Sky Texture。

当然也可以使用自己的天空图像或其他材质。

渲染

在渲染属性中设置使用 CPU 或者 GPU 渲染图像,并可以设置分辨率等。

修改好设置,在 Blender 上方菜单栏中点击 Render -> Render Image 即可开始渲染啦~

玩家和实体

我相信熟练使用 C4D/Maya/Blender 的玩家们不需要看这篇教程… 所以玩家实体的高级用法不多讲。

需要的材料是一份玩家或者其他实体的 Rig,在 PlanetMinecraft 或者 MinecraftForum 上有很多。贴上皮肤就可以用啦。

在 player rig 中调整好 pose,保存。在要渲染的世界工程里,选择 File -> Append,然后选择刚才的玩家 pose 的 blender 文件,进入后选择 Scene 并 append。

选择 Scene 的原因是有不少 rig 并不是一个单一 object,如果是单一 object,则可以直接导入 object。

在右上方的 object 列表中选择刚才 append 的 scene,定位玩家实体并全部选中 -> Ctrl+C 复制 -> 返回渲染世界 -> Ctrl+V 粘贴。

选择粘贴进来的玩家实体,然后调整到合适的位置。

成果

这大概是我第一次玩 blender。

总之以下是成果啦。

比如某玩家的 pose 效果图:

比 chunky 的动作自然多了吧~

最后是完成的效果:

Happy Rendering~

Li Fanxi's avatar

关注2018维也纳新年音乐会

曾经关注过的那些维也纳新年音乐会:关注维也纳新年音乐会

在2018年的新年音乐会上,我们将迎来一张熟悉的老面孔,他就是意大利指挥家里卡尔多‧穆蒂。穆蒂1993年第一次登上新年音乐会的指挥台,后来又指挥了1997年、2000年和2004年的新年音乐会,2018年他将第5次登上新年音乐会的指挥台。

实话说,刚开始的时候我并不是非常喜欢他,1997年我是第二次看新年音乐会的直播,看到穆蒂时,我不自觉地把他与前一年的指挥洛林‧马泽尔进行了比较,相比马泽尔生动的演出,穆蒂的演出显得要严肃一些,与新年的节日气氛有点格格不入。2000年没有看直播,看录播时就没了新年的气氛,加上开场那平淡如水的《大河圆舞曲》,这一年的音乐会也没有给我留下深刻的印象。倒是2004年的那一次,穆蒂选择的那些让人耳目一新的曲目,让我改变了对他的印象。但是那段时间有传闻说穆蒂已经表态以后不会再上新年音乐会,便只能放弃了对他的期待。所以,当年初时得知穆蒂会指挥2018年新年音乐会时,颇是激动了一番。穆蒂此次登台,也破解了传说中的“天体乐声大魔咒”——在新年音乐会历史上,波斯科夫斯基、卡拉扬、克莱伯这几位大师在演完这个曲子以后就都没有再上过维也纳新年音乐会的指挥台了,而穆蒂恰巧在2004年时选择过这个曲目。

2018维也纳新年音乐会CD封面

来看曲目单:

  • 01 Johann Strauss II – Einzugsmarsch aus ‘Der Zigeunerbaron’ – 入城式进行曲,选自《吉普赛男爵》
  • 02 Josef Strauss II – Wiener Fresken; Walzer; op. 249 – 维也纳壁画圆舞曲 *
  • 03 Johann Strauss II – Brautschau-Polka; op. 417 – 相亲波尔卡(选自《吉普赛男爵》) *
  • 04 Johann Strauss II – Leichtes Blut; Polka schnell; op. 319 – 轻如鸿毛快速波尔卡
  • 05 Johann Strauss I – Marien-Walzer; op. 212 – 玛丽安圆舞曲 *
  • 06 Johann Strauss I – Wilhelm-Tell-Galopp; op. 29b – 威廉退尔加洛普 *

 

  • 07 Franz von Suppé – Boccaccio; Ouvertüre – 薄伽丘序曲 *
  • 08 Johann Strauss II – Myrtenblüten; op. 395 – 桃金娘花冠圆舞曲 *
  • 09 Alphons Czibulka – Stephanie-Gavotte; op. 312 – 史蒂芬妮加沃特舞曲 *
  • 10 Johann Strauss II – Freikugeln; Polka schnell; op. 326 – 魔弹快速波尔卡
  • 11 Johann Strauss II – Geschichten aus dem Wienerwald; Walzer; op. 325 – 维也纳森林的故事圆舞曲
  • 12 Johann Strauss II – Fest-Marsch; op. 452 – 庆典进行曲
  • 13 Johann Strauss II – Stadt und Land; Polka Mazur; op. 322 – 城市与乡村玛祖卡波尔卡
  • 14 Johann Strauss II – Maskenball-Quadrille; op. 272 – 假面舞会四对舞
  • 15 Johann Strauss II – Rosen aus dem Süden; Walzer; op. 388 – 南国玫瑰圆舞曲
  • 16 Josef Strauss – Eingesendet; Polka schnell; op. 240 – 读者来信快速波尔卡
  • 17 ?
  • 18 Johann Strauss II – An der schönen blauen Donau, Walzer, op. 314 – 蓝色多瑙河圆舞曲
  • 19 Johann Strauss I – Radetzky-Marsch, op. 228 – 拉德茨基进行曲

这次的新年音乐会选择了19个曲子,应该是一个比较合适的数字,不至于让穆蒂像2004年时那样赶场子。除了目前未知的第一个加演曲目,穆蒂选择了7首从未在新年音乐会上演出过的全新的曲目,其中还包括阿尔冯斯·齐布尔卡(Alphons Czibulka)的一首加沃特舞曲,他是一位奥地利的军人、作曲家、钢琴家、指挥家。

开场的入城式进行曲是新年音乐会的常客,看到这个标题,我的耳边就响起了它的旋律,好像新一年的音乐会就这样开始了,非常期待。

接下来就是两个新的曲子,不过相亲波尔卡是选自大家耳熟能详的轻歌剧《吉普赛男爵》,所以里面会有一些熟悉的旋律出现。

轻如鸿毛快速波尔卡在新年音乐会上演出过4次,最近的一次是2003年哈农库特指挥的,这个曲子我印象非常深刻,因为它的旋律一点也不“轻如鸿毛”,尤其是在被誉为“节拍器”的哈农库特演绎下,更是激烈无比,不知道穆蒂会给我们带来如何的感受。

音乐会上半场以老约翰的两首新曲子收尾,而下半场则以苏佩、约翰和齐布尔卡的三个新曲子开场,新曲子如此集中的出现,一定会成为本场音乐会最为吸引人的部分。

新曲子都演完以后,音乐会就进入了怀旧与经典的篇章。魔弹快速波尔卡,曾经在阿巴多指挥的1991年新年音乐会和巴伦博伊姆指挥的2009年新年音乐会中出现过,是一首适合加入“噱头”的曲子,不过这次把它放在下半场还未到高潮的时段,很可能就不会有什么特别的了。

旋律优美的维也纳森林圆舞曲则是新年音乐会的常客,已故的洛林‧马泽尔就多次选择了这个曲目,并自己拿起小提琴,演奏其中的独奏部分。这个曲子中还选用了一种奥地利的民间乐器——齐特尔琴,音色很特别。

庆典进行曲虽然只在1996年作为开场曲在新年音乐会上亮相过一次,它却是一首我熟悉得不能再熟悉的曲子,因为这是我第一次看新年音乐会时听到的第一个曲子。有着这样的情怀加成,这首曲子成为2018年新年音乐会中我最期待的曲子之一。

乡村和城市以及假面舞会,也都曾经在新年音乐会上出现过,却不是常客。其中假面舞会只在阿巴多指挥的1988年新年音乐会上出现过。阿巴多指挥过两次新年音乐会,选曲风格都很特别,也带来了不少争议,2018年是阿巴多诞辰85周年,不知道是不是这个原因,穆蒂才特别选择了两首阿巴多指挥过曲子。

南国玫瑰和读者来信,这两个曲子我都很熟悉,在新年音乐会上也出现过数次,其中读者来信穆蒂曾经在1997年选择过。穆蒂在16个正式演出曲目中,只炒了自己指挥过的一首冷饭,为他点个大大的赞。

十四年未见,非常期待穆蒂的再次亮相。

小莞's avatar

服务性能监控:USE 方法(The USE Method)

本文首发在沪江技术学院公众号,小莞翻译,我做了校对。 由于微信公众号的封闭性,我担心未来文章不容易被发现。 为了避免沧海遗珠,特意转到这里。

英文原文:The USE Method


USE 方法是一种能分析任何系统性能的方法论。 我们可以根据能帮助系统分析的结构化清单,来迅速的定位资源的瓶颈和错误所在。 它通常会先以列出问题为开始,然后再寻找适合的指标,而不是给你制定一些固定的指标, 然后让你按部就班的执行下去。

本页左侧下方,是我列出的,根据不同的操作系统(Linux、 Solaris 等) 衍生的 USE 方法列表。(译者注:可以参考原文链接)

我列出了为不同的操作系统而衍生的 USE 方法列表供大家参考, 你们可以根据你的环境来为你的站点服务,选择适合的附加监控指标。

通过这个工具,可以很方便的筛选出适合不同的系统的建议 metrics:USE Method: Rosetta Stone of Performance Checklists

Intro(Introduction)

如果你遇到一个很严重的性能问题升级的时候,并且你不能确定它是否由服务导致的, 这时候你该怎么办?

我们都说万事开头难。所以我开发出了 USE 方法,来帮助大家,如何去快速的解决常见的性能问题,而同时又不容易忽略重要的地方。

USE 方法在设计之初就定位了简洁、明了、完整、快速的特性, 就好像一本航天手册的紧急事项列表那样。 (译者注:航天手册,介绍包括不限于飞机的各种特性、指标、性能等, 用于帮助飞行学员学习驾驶飞机,或者是帮助那些希望提高他们的飞行潜能和航空知识的人了解的更全面)。

USE 方法已经在不同的企业、课堂(作为学习工具)以及最近的云计算等场景中,被成功应用了无数次。

USE 方法基于 3+1 模型(三种指标类型+一种策略),来切入一个复杂的系统。我发现它仅仅发挥了 5% 的力量,就解决了大概 80% 的服务器问题,并且正如我将证明的,它除了服务器以外,也同样适应于各种系统。

它应当被理解为一种工具,一种很大的方法工具箱里面的工具。不过,它目前仍然还有很多问题类型以待解决,还需要点其他方法和更多的时间。

Summary

USE 方法可以概括为:检查所有的资源的利用率,饱和度,和错误信息。

我们期望大家能尽早使用 USE 方法去做性能检查,或者是用它确定系统的瓶颈。

名词定义:

  • 资源: 服务器功能性的物理组成硬件(CPU, 硬盘, 总线)
  • 利用率: 资源执行某工作的平均时间
  • 饱和:衡量资源超载工作的程度,往往会被塞入队列
  • 错误: 错误事件的数量

分析软件资源,或者是软件的强制性限制(资源控制)也是很有用的,同时要关注哪些指标是处于正常的可接受范围之内的。这些指标通常用以下术语表示:

  • 利用率: 以一个时间段内的百分比来表示,例如:一个硬盘以 90% 的利用率运行
  • 饱和度: 一个队列的长度,例如:CPUs 平均的运行时队列长度是4
  • 错误(数): 可度量的数量,例如:这个网络接口有 50 次(超时?)

我们应该要调查那些错误,因为它们会降低系统的性能,并且当故障模型处于可回复模式的时候,它可能不会立刻被发现。

这包括了那些失败和重试等操作,以及那些来自无效设备池的失效设备。

低利用率是否意味着未饱和?

即使在很长一段时间内利用率很低,一个爆发增长的高利用率,也会导致饱和 and 性能问题,这点要理解起来可能有违三观!

我举个例子,有一位客户遇到的问题,即使他们的监控工具显示 CPU 使用率从来没有超出过 80% ,但是 CPU 饱和度依然有问题(延迟)监控工具报告了 5 分钟的平均值,而其中,CPU利用率曾在数秒内高达 100% 。

资源列表

下面来看如何开始使用。

准备工作时, 你需要一个资源列表来按步就班的去做。 下面是一个服务器的通用列表:

  • CPUs: sockets, cores, hardware threads (virtual CPUs)
  • 内存: 容量
  • 网络接口
  • 存储设备: I/O, 容量
  • 控制器: 存储, 网卡
  • 通道: CPUs, memory, I/O

有些组件分两种类型的资源:存储设备是服务请求资源(I / O) 以及容量资源(population), 两种类型都可能成为系统瓶颈。 请求资源可以定义为队列系统,可以将请求先存入排队然后再消化请求。

有些物理组件已被省略,例如硬件缓存(例如,MMU TLB / TSB,CPU)。

USE 方法对于在高利用率或高饱和度下,遭受性能退化、导致瓶颈的资源最有效,在高利用率下缓存可以提高性能。

在使用 USE 方法排除系统的瓶颈问题之后 ,你可以检查缓存利用率和其他的性能属性。

如果你不确认要不要监控某一个资源时,不要犹豫,监控它,然后你就能看到那些指标工作的有多么的棒。

功能模块示意图

另外一种迭代资源的方法,是找到或者绘制一张系统的功能模块示意图。

这些显示了模块关系的图,在你查找数据流的瓶颈的时候是非常有用的,这里有一张Sun Fire V480 Guide(page 82)的例图:

我喜欢这些图表,尽管制作出它很难。 不过,由硬件工程师来画这张图是最适合的-他们最善于做这类事。如果不信的话你可以自己试试。

在确定各种总线的利用率的同时,为每个总线的功能图表,注释好它的最大带宽。这样我们就能在进行单次测量之前,得到能将系统瓶颈识别出来的图表。

Interconnects

CPU,内存和I / O interconnects 往往被忽略。 幸运的是,它们并不会频繁地成为系统的瓶颈。 不幸的是,如果它们真的频繁的成为瓶颈,我们能做的很少(也许你可以升级主板,或减少 load:例如,"zero copy"项目减轻内存总线 load)。

使用 USE 方法,至少你会意识到你没有考虑过的内容:interconnect 性能。 有关使用 USE 方法确定的互连问题的示例,请参阅分析 Analyzing the HyperTransport。

Metrics

给定资源列表,识别指标类型:利用率,饱和度和错误指标。这里有几个示例。看下面的 table,思考下每个资源和指标类型,metric 列是一些通用的 Unix/Linux 的术语提示(你可以描述的更具体些):


resource type metric CPU utilization CPU utilization (either per-CPU or a system-wide average) CPU saturation run-queue length or scheduler latency(aka Memory capacity utilization available free memory (system-wide) Memory capacity saturation anonymous paging or thread swapping (maybe "page scanning" too) Network interface utilization RX/TX throughput / max bandwidth Storage device I/O utilization device busy percent Storage device I/O saturation wait queue length Storage device I/O errors device errors ("soft", "hard", ...)


这些指标是每段间隔或者计数的平均值,作为你的自定义清单,要包括使用的监控软件,以及要查看的统计信息。如果是不可用的指标,可以打个问号。最后,你会完成一个完事的、简单、易读的 metrics 清单.

Harder Metrics

再来看几个硬件指标的组合


resource type metric CPU errors eg, correctable CPU cache ECC events or faulted CPUs (if the OS+HW supports that) Memory capacity errors Network saturation Storage controller utilization CPU interconnect utilization Memory interconnect saturation I/O interconnect utilization


这些依赖于操作系统的指标一般会更难测量些, 而我通常要用自己写的软件去收集这些指标。

重复所有的组合,并附上获取每个指标的说明,你会完成一个大概有30项指标的列表,其中有些是不能被测量的,还有些是难以测量的。

幸运的是,最常见的问题往往是简单的(例如,CPU 饱和度,内存容量饱和度,网络接口利用率,磁盘利用率),这类问题往往第一时间就能被检查出来。

本文的顶部,pic-1中的 example checklists 可作为参考。

In Practice

读取系统的所有组合指标,是非常耗时的,特别是当你开始使用总线和 interconnect 指标的情况下。

现在我们可以稍微解放下了,USE 方法可以让你了解你没有检查的部分,你可以只有关注其中几项的时间例如:CPUs, 内存容量, 存储容易, 存储设备 I/O, 网络接口等。通过 USE 方法,那些以前未知的未知指标现在变成了已知的未知指标(我理解为,以前我们不知道有哪些指标会有什么样的数据,现在起码能知道我们应该要关注哪些指标)。

如果将来定位一个性能问题的根本原因,对你的公司至关重要的时候,你至少已经有一个明确的、经过验证的列表,来辅助你进行更彻底的分析,请完成适合你自己的 USE 方法,有备无患。

希望随着时间的推移,易于检查的指标能得以增长,因为被添加到系统的 metrics 越多,会使 USE 方法将更容易(发挥它的力量)。 性能监视软件也可以帮上忙,添加 USE 方法向导to do the work for you(do what work? )。

Software Resources

有些软件资源可以用类似的方式去分析。 这通常适用于软件的较小组件,而不是整个应用程序。 例如:

  • 互斥锁(mutex locks):利用率可以定义为锁等待耗时;饱和率定义为等待这把锁的线程个数。
  • 线程池:利用率可以定义为线程工作的时长;饱和率是等待线程池分配的请求数量。
  • 进程/线程 容量:系统是有进程或线程的上限的,它的实际使用情况被定义为利用率;等待数量定义为饱和度;错误即是(资源)分配失败的情况(比如无法 fork)。 (译注:fork 是一个现有进程,通过调用 fork 函数创建一个新进程的过程)
  • 文件描述符容量(file descriptor capacity):和上述类似,但是把资源替换成文件描述符。

如果这几个指标很管用就一直用,要不然软件问题会被遗留给其他方法了(例如,延迟,后文会提到其他方法:other methodologies )。

Suggested Interpretations

USE 方法帮助你定位要使用哪些指标。 在学习了如何从操作系统中读取到这些指标后,你的下一步工作就是诠释它们的值。对于有的人来说, 这些诠释可能是很清晰的(因为他们可能很早就学习过,或者是做过笔记)。而其他并不那么明了的人,可能取决于系统负载的要求或期望 。

下面是一些解释指标类型的通用建议:

  • Utilization: 利用率通常象征瓶颈(检查饱和度可以进一步确认)。高利用率可能开始导致若干问题:
  • 对利用率进行长期观察时(几秒或几分钟),通常来说 70% 的利用率会掩盖掉瞬时的 100% 利用率。
  • 某些系统资源,比如硬盘,就算是高优先级请求来了,也不会在操作进行中被中断。当他们的利用率到 70% 时候,队列系统中的等待已经非常频繁和明显。而 CPU 则不一样,它能在大部分情况下被中断。
  • Saturation:任何非 0 的饱和度都可能是问题。它们通常是队列中排队的时间或排队的长度。
  • Errors:只要有一条错误,就值得去检查,特别是当错误持续发生从而导致性能降低时候。

要说明负面情况很容易:利用率低,不饱和,没有错误。 这比听起来更有用 - 缩小调查范围可以快速定位问题区域。

Cloud Computing

在云计算环境中,软件资源控制可能是为了限制 使用共享计算服务的 tenants 的流量。在 Joyent 公司,我们主要使用操作系统虚拟化(SmartOS),它强加了内存限制, CPU 限制和存储I / O限制。 所有这些资源限制,都可以使用USE Method进行检查,类似于检查物理资源。

例如,在我们的环境中,"内存容量利用率"可以是 tenants 的内存使用率 vs 它的内存上限 。即使传统的 Unix 页面扫描程序可能处于空闲状态,也可以通过匿名页面活动看到"内存容量饱和度"。

Strategy

下面是用流程图 的方式画了 USE 方法的示意图。 请注意,错误检查优先于利用率和饱和度检查(因为通常错误更快的表现出来,并更容易解释)。

USE 方法定位到的问题,可能是系统瓶颈。 不幸的是,系统可能会遇到多个性能问题,因此您发现的第一个可能的问题最终却不是个问题。 发现的每个问题都可以用方法持续的挖掘,然后继续使用 USE 方法对更多资源进行反复排查。

进一步分析的策略包括工作量特征和 drill-down 分析。 完成这些后,你应该有依据据能判断,纠正措施是要调整应用的负载或调整资源本身。

Apollo

(译者注:Apollo 这一段我们可以不太关注,它主要是讲 USE 方法,与阿波罗登月计划相关的系统设计的一些渊源)

我之前有提到过,USE 方法可以被应用到除服务器之外。为了找到一个有趣的例子, 我想到了一个我没有完全不了解的系统,并且不知道从哪里开始:阿波罗月球模块指导系统。 USE 方法提供了一个简单的流程来尝试第一步是寻找一个资源列表,或者更理想的话,找到一个功能模块图表。我在 【Lunar Module - LM10 Through LM14 Familiarization Manual】中发现了以下内容:

这些组件中的一部分可能未表现出利用率或饱和度特性。在迭代后, 就可以重新绘制只包含相关组件的图表(还可以包括:"可擦除存储"部分的内存,"核心区域"和 "vac区域 "寄存器)。

我将从阿波罗主脑(AGC)本身开始。 对于每个指标,我浏览了各种 LM 文档,看看哪些是合理的(有意义的):

  • AGC utilization: This could be defined as the number of CPU cycles doing jobs (not the "DUMMY JOB") divided by the clock rate (2.048 MHz). This metric appears to have been well understood at the time.
  • AGC saturation: This could be defined as the number of jobs in the "core set area", which are seven sets of registers to store program state. These allow a job to be suspended (by the "EXECUTIVE" program - what we\'d call a "kernel" these days) if an interrupt for a higher priority job arrives. Once exhausted, this moves from a saturation state to an error state, and the AGC reports a 1202 "EXECUTIVE OVERFLOW-NO CORE SETS" alarm.
  • AGC errors: Many alarms are defined. Apart from 1202, there is also a 1203 alarm "WAITLIST OVERFLOW-TOO MANY TASKS", which is a performance issue of a different type: too many timed tasks are being processed before returning to normal job scheduling. As with 1202, it could be useful to define a saturation metric that was the length of the WAITLIST, so that saturation can be measured before the overflow and error occurs.

其中的一些细节,可能对于太空爱好者来说是非常熟悉的:在阿波罗 11 号降落的时候发生的著名的 1201("NO VAC AREAS")和 1202 警报。("VAC"是向量加速器的缩写, 用于处理 vector quantities 作业的额外存储; 我觉得 wikipadia 上将 "向量"描述为"空"可能是错误的)。

鉴于阿波罗 11 号的 1201 警报,可以继续使用其他方法分析,如工作负载表征。 工作负载很多可以在功能图中看到,大多数工作负载是通过中断来生效的。 包括用于跟踪命令模块的会合雷达,即使 LM 正在下降,该模块也仍然在执行中断 AGC(阿波罗主脑)的任务。 这是发现非必要工作的一个例子(或低优先级的工作; 雷达的一些更新可能是可取的,因此 LM AGC可以立即计算出中止路径)。

作为一个更深的例子,我将把会合雷达当作资源去检查. 错误最容易识别。 有三种信号类型: "DATA NO GOOD", "NO TRACK", and "SHAFT- AND TRUNNION-AXIS ERROR"。

在有某一小段时间里,我不知道能从哪里开始使用这个方法, 去寻找和研究具体的指标。

Other Methodologies

虽然 USE 方法可能会发现 80% 的服务器问题,但基于延迟的方法(例如Method R)可以找到所有的问题。 不过,如果你不熟悉软件内部结构,Method R 就有可能需要花费更多时间。 它们可能更适合已经熟悉它的数据库管理员或应用程序开发人员。

而 USE 方法的职责和专长包括操作系统(OS)和硬件,它更适合初级或高级系统管理员,当需要快速检查系统健康时,也可以由其他人员使用。

Tools Method

以下介绍一个基于工具的方法流程(我称它作"工具方法"),与 USE 方法作比较:

  1. 列出可用的性能工具(可以选择性安装或购买其他的)。
  2. 列出每个工具提供的有用的指标
  3. 列出每个工具可能的解释规则

按照这个方法做完后,将得到一个符合标准的清单,它告诉我们要运行的工具,要关注的指标以及如何解释它们。 虽然这相当有效,但有一个问题,它完全依赖于可用(或已知的)的,可以提供系统的不完整视图的工具。 用户也不知道他们得到的是一张不完整的视图 - 所以问题将仍然存在。

而如果使用 USE 方法,不同的是, USE 方法将通过迭代系统资源的方式,来创建一个完整的待确认问题列表,然后搜索工具来回答这些问题。这样构建了一张更完整的视图,未知的部分被记录下来,它们的存在被感知(这一句我理解成前文中提到的:未知 的未知变为已知的未知)。 基于 USE ,同样可以开发一个清单类似于工具方法(Tool-Method),显示要运行的工具(可用的位置),要关注的指标以及如何解释它。

另一个问题是,工具方法在遍历大量的工具时,将会使寻找瓶颈的任务性能得到分散。而 USE 方法提供了一种策略,即使是超多的可用工具和指标,也能有效地查找瓶颈和错误。

Conclusion

USE 方法是一个简单的,能执行完整的系统健康检查的策略,它可以识别常见的系统瓶颈和错误。它可以在调查的早期部署并快速定位问题范围,如果需要的话,还可以进一步通过其他方法进行更详细的研究。

我在这个篇幅上,解释了 USE 方法并且提供了通用的指标案例,请参阅左侧导航面板中对应操作系统的示例清单, 其建议了应用 USE 方法的工具和指标。另请参阅基于线程的补充方法,TSA Method。

Acknowledgments

  • 感谢 Cary Millsap and Jeff Holt (2003) 在"优化 Oracle 性能"一文中提到的 Method R 方法 (以及其他方法), 使我有了灵感,我应该要把这个方法论写出来。
  • 感谢 Sun Microsystems 的组织,包括 PAE 和 ISV, 他们将 USE 方法(那时还没命名)应用于他们的存储设备系列,绘制了标注指标和总线速度的 ASCII 功能块图表 - 这些都比您想象的要困难(我们应该早些时候询问硬件团队的帮助)。
  • 感谢我的学生们,多年前我授予他们这个方法论,谢谢他们提供给我的使用反馈。
  • 感谢 Virtual AGC 项目组(The Virtual AGC project),读他们的站点 ibiblio.org 上的文档库,就象是一种娱乐. 尤其是 LMA790-2 "Lunar Module LM-10 Through LM-14 Vehicle Familiarization Manual" ( 48 页有功能模块图表), 以及 "阿波罗指导和月球导航模块入门学习指南", 都很好的解释了执行程序和它的流程图 (These docs are 109 and 9 Mbytes in size.)
  • 感谢 Deirdré Straughan 编辑和提供反馈,这提高了我的认知。
  • 文章顶部的图片,是来自于波音 707 手册,1969 出版。它不是完整的,点击查看完整的版本(译注:为方便阅读,就是下面这张:)

Updates

USE Method updates:(略)

  • It was published in ACMQ as Thinking Methodically about Performance (2012).
  • It was also published in Communications of the ACM as Thinking Methodically about Performance (2013).
  • I presented it in the FISL13 talk The USE Method (2012).
  • I spoke about it at Oaktable World 2012: video, PDF.
  • I included it in the USENIX LISA `12 talk Performance Analysis Methodology.
  • It is covered in my book on Systems Performance, published by Prentice Hall (2013).

More updates (Apr 2014):

  • LuceraHQ are implementing USE Method metrics on SmartOS for performance monitoring of their high performance financial cloud.
  • LuceraHQ 正在 SmartOS 上,为他们高性能金融云的性能监测,实施 USE 方法指标
  • I spoke about the USE Method for OS X at MacIT 2014 (slides)。

原文链接: https://blog.alswl.com/2017/11/use-method/
3a1ff193cee606bd1e2ea554a16353ee
欢迎关注我的微信公众号:窥豹
罗晟, alswl's avatar

Stack Overflow 的 HTTPS 化:漫漫长路的终点


今天,我们默认在 Stack Overflow 上部署了 HTTPS。目前所有的流量都将跳转到 https:// 上。与此同时,Google 链接也会在接下去的几周内更改。启用的过程本身只是举手之劳,但在此之前我们却花了好几年的时间。到目前为止,HTTPS 在我们所有的 Q&A 网站上都默认启用了。

在过去的两个月里,我们在 Stack Exchange 全网维持发布 HTTPS。Stack Overflow 是最后,也是迄今最大的的一个站点。这对我们来说是一个巨大里程碑,但决不意味着是终点。后文会提到,我们仍有很多需要做的事情。但现在我们总算能看得见终点了,耶!

友情提示:这篇文章讲述的是一个漫长的旅程。非常漫长。你可能已经注意到你的滚动条现在非常小。我们遇到的问题并不是只在 Stack Exchange/Overflow 才有,但这些问题的组合还挺罕见。我在文章中会讲到我们的一些尝试、折腾、错误、成功,也会包括一些开源项目——希望这些细节对你们有所帮助。由于它们的关系错综复杂,我难以用时间顺序来组织这篇文章,所以我会将文章拆解成架构、应用层、错误等几个主题。

首先,我们要提一下为什么我们的处境相对独特:

  • 我们有几百个域名(大量站点及服务)
  • 我们允许用户提交、嵌入内容(比如帖子中的图片和 YouTube 视频)
  • 我们仅有一个数据中心(造成单源的延时)
  • 我们有广告(及广告网络)
  • 我们用 websockets,任何时刻的活跃数都不少于 50 万个(连接数问题)
  • 我们会被 DDoSed 攻击(代理问题)
  • 我们有不少站点及应用还是通过 HTTP API 通信的(代理问题)
  • 我们热衷于性能(好像有点太过了)

由于这篇文章实在太长,我在这里先列出链接:

开篇

我们早在 2013 年就开始考虑在 Stack Overflow 上部署 HTTPS 了。是的,现在是 2017 年。所以,究竟是什么拖了我们四年?这个问题的答案放在任何一个 IT 项目上都适用:依赖和优先级。老实说,Stack Overflow 在信息安全性上的要求并不像别家那么高。我们不是银行,也不是医院,我们也不涉及信用卡支付,甚至于我们每个季度都会通过 HTTP 和 BT 种子的方式发布我们大部分的数据库。这意味着,从安全的角度来看,这件事情的紧急程度不像它在其他领域里那么高。而从依赖上来说,我们的复杂度比别人要高,在部署 HTTPS 时会在几大领域里踩坑,这些问题的组合是比较特殊的。后文中会看到,有一些域名的问题还是一直存在的。

容易踩坑的几个领域包括:

  • 用户内容(用户可以上传图片或者指定 URL)
  • 广告网络(合同及支持)
  • 单数据中心托管(延迟)
  • 不同层级下的几百个域名(证书)

那我们究竟是为什么需要 HTTPS 呢?因为数据并不是唯一需要安全性的东西。我们的用户中有操作员、开发者、还有各个级别的公司员工。我们希望他们到我们站点的通信是安全的。我们希望每一个用户的浏览历史是安全的。某些用户暗地里喜欢 monad 却又害怕被人发现。同时,Google 会提升 HTTPS 站点的搜索排名(虽然我们不知道能提升多少)。

哦,还有性能。我们热爱性能。我热爱性能。你热爱性能。我的狗热爱性能。让我给你一个性能的拥抱。很好。谢谢。你闻起来很香。

懒人包

很多人喜欢情人包,所以我们来一场快速问答(我们喜欢问答!):

  • 问:你们支持什么协议?
  • 问:你们支持 SSL v2 或者 v3 吗?
  • 问:你们支持哪些加密套件?
  • 问:Fastly 回源走的是 HTTPS 吗?
    • 答:是。如果到 CDN 的请求是 HTTPS,回源的请求也是 HTTPS。
  • 问:你们支持前向安全性吗?
    • 答:是。
  • 问:你们支持 HSTS 吗?
    • 答:支持。我们正在 Q&A 站点中逐步支持。一旦完成的话我们就会将其移至节点上。
  • 问:你们支持 HPKP 吗?
    • 答:不支持,应该也不会支持。
  • 问:你们支持 SNI 吗?
    • 答:不支持。出于 HTTP/2 性能考虑,我们使用是一个合并的通配符证书(详见后文)。
  • 问:你们的证书是哪来的?
    • 答:我们用的是 DigiCert,他们很棒。
  • 问:你们支持 IE 6 吗?
    • 答:这次之后终于不再支持了。IE 6 默认不支持 TLS(尽管你可以启用 1.0 的支持),而我们则不支持 SSL。当我们 301 跳转就绪的时候大部分 IE 6 用户就不能访问 Stack Overflow 了。一旦我们弃用 TLS 1.0,所有 IE 6 用户都不行了。
  • 问:你们负载均衡器用的什么?
  • 问:使用 HTTPS 的动机是什么?

证书

让我们先聊聊证书,因为这是最容易被误解的部分。不少朋友跟我说,他安装了 HTTPS 证书,因此他们已经完成 HTTPS 准备了。呵呵,麻烦你看一眼右侧那个小小的滚动条,这篇文章才刚刚开始,你觉得真的这么简单么?我有这个必要告诉你们一点人生的经验 :没这么容易的。

一个最常见的问题是:「为何不直接用 Let’s Encrypt?」

答案是:这个方案不适合我们。 Let’s Encrypt 的确是一个伟大的产品,我希望他们能够长期服务于大家。当你只有一个或少数几个域名时,它是非常出色的选择。但是很可惜,我们 Stack Exchange 有数百个站点,而 Let’s Encrypt 并不支持通配域名配置。这导致 Let’s Encrypt 无法满足我们的需求。要这么做,我们就不得不在每上一个新的 Q&A 站点的时候都部署一个(或两个)证书。这样会增加我们部署的复杂性,并且我们要么放弃不支持 SNI 的客户端(大约占 2% 的流量)要么提供超多的 IP——而我们目前没这么多的 IP。

我们之所以想控制证书,还有另外一个原因是我们想在本地负载均衡器以及 CDN / 代理提供商那边使用完成相同的证书。如果不做到这个,我们无法顺畅地做从代理那里做失效备援(failover)。支持 HTTP 公钥固定(HPKP)的客户端会报认证失败。虽然我们仍在评估是否使用 HPKP,但是如果有一天要用的话我们得提前做好准备。

很多朋友在看见我们的主证书时候会吓得目瞪口呆,因为它包含了我们的主域名和通配符子域名。它看上去长成这样:

为什么这么做?老实说,是我们让 DigiCert 替我们做的。这么做会导致每次发生变化的时候都需要手动合并证书,了 我们为什么要忍受这么麻烦的事呢?首先,我们期望能够尽可能让更多用户使用我们产品。这里面包括了那些还不支持 SNI 的用户(比如在我们项目启动的时候 Android 2.3 势头正猛)。另外,也包括 HTTP/2 与一些现实问题——我们过会儿会谈到这一块。

Meta 子域(meta.*.stackexcange.com

Stack Exchage 的一个设计理念是,针对每个 Q&A 站点,我们都有一个地方供讨论。我们称之为 “second place”。比如 meta.gaming.stackexchange.com 用来讨论 gaming.stackexchange.com。这个有什么特别之处呢?好吧,并没有,除了域名:这是一个 4 级域名。

我之前已经说过这个问题,但后来怎么样了呢?具体来说,现在面临的问题是 *.stackexchange.com 包含 gaming.stackexchange.com(及几百个其它站点),但它并不包含 meta.gaming.stackexchange.comRFC 6125 (第 6.4.3 节) 写道:

客户端 不应该 尝试匹配一个通配符在中间的域名(比如,不要匹配 bar.*.example.net

这意味着我们无法使用 meta.*.stackexchange.com,那怎么办呢?

  • 方案一:部署 SAN 证书(多域名证书)
    • 我们需要准备 3 个证书和 3 个 IP(每张证书支持域名上限是 100),并且会把新站上线复杂化(虽然这个机制已经改了)
    • 我们要在 CDN/代理层上部署三个自定义证书
    • 我们要给 meta.* 这种形式的域名配置额外的 DNS 词条
      • 根据 DNS 规则,我们必须给每个这样的站点配置一条 DNS,无法批量配置,从而提高了新站上线和维护代理的成本
  • 方案二:将所有域名迁移到 *.meta.stackexchange.com
    • 我们会有一次痛苦的迁移过程,但这是一次性的,并且未来维护证书成本较低
    • 我们需要部署一个全局登录系统(详情见此
    • 这个方案仍然不解决 HSTS 预加载下面的 includeSubDomains 问题(详情见此
  • 方案三:啥都不做,放弃
    • 这个方案最简单,然而这是假方案

我们部署了 全局登录系统,然后将子 meta 域名用 301 重定向到新地址,比如 gaming.meta.stackexchange.com。做完这个之后我们才意识到,因为这些域名曾经存在过,所以对于 HSTS 预加载来说是个很大的问题。这件事情还在进行中,我会在文章最后面讨论这个问题。这类问题对于 meta.pt.stackoverflow.com 等站点也存在,不过还好我们只有四个非英语版本的 Stack Overflow,所以问题没有被扩大。

对了,这个方案本身还存在另一个问题。由于将 cookies 移动到顶级目录,然后依赖于子域名对其的继承,我们必须调整一些其他域名。比如,在我们新系统中,我们使用 SendGrid 来发送邮件(进行中)。我们从 stackoverflow.email 这个域名发邮件,邮件内容里的链接域名是 sg-links.stackoverflow.email(使用 CNAME 管理),这样你的浏览器就不会将敏感的 cookie 发出去。如果这个域名是 links.stackoverflow.com,那么你的浏览器会将你在这个域名下的 cookie 发送出去。 我们有不少虽然使用我们的域名,但并不属于我们自己的服务。这些子域名都需要从我们受信的域名下移走,否则我们就会把你们的 cookie 发给非我们自有的服务器上。如果因为这种错误而导致 cookie 数据泄露,这将是件很丢人的事情。

我们有试过通过代理的方式来访问我们的 Hubspot CRM 网站,在传输过程中可以将 cookies 移除掉。但是很不幸 Hubspot 使用 Akamai,它会判定我们的 HAProxy 实例是机器人,并将其封掉。头三次的时候还挺有意思的……当然这也说明这个方式真的不管用。我们后来再也没试过了。

你是否好奇为什么 Stack Overflow 的博客地址是  https://stackoverflow.blog/?没错,这也是出于安全目的。我们把博客搭在一个外部服务上,这样市场部门和其他团队能够更便利地使用。正因为这样,我们不能把它放在有 cookie 的域名下面。

上面的方案会牵涉到子域名,引出 HSTS 预加载 和 includeSubDomains 命令问题,我们一会来谈这块内容。

性能:HTTP/2

很久之前,大家都认为 HTTPS 更慢。在那时候也确实是这样。但是时代在变化,我们说 HTTPS 的时候不再是单纯的 HTTPS,而是基于 HTTPS 的 HTTP/2。虽然 HTTP/2 不要求加密,但事实上却是加密的。主流浏览器都要求 HTTP/2 提供加密连接来启用其大部分特性。你可以来说 spec 或者规定上不是这么说的,但浏览器才是你要面对的现实。我诚挚地期望这个协议直接改名叫做 HTTPS/2,这样也能给大家省点时间。各浏览器厂商,你们听见了吗?

HTTP/2 有很多功能上的增强,特别是在用户请求之前可以主动推送资源这点。这里我就不展开了,Ilya Grigorik 已经写了一篇非常不错的文章。我这里简单罗列一下主要优点:

咦?怎么没提到证书呢?

一个很少人知道的特性是,你可以推送内容到不同的域名,只要满足以下的条件:

  1. 这两个域名需要解析到同一个 IP 上
  2. 这两个域名需要使用同一张 TLS 证书(看到没!)

让我们看一下我们当前 DNS 配置:

λ dig stackoverflow.com +noall +answer
; <<>> DiG 9.10.2-P3 <<>> stackoverflow.com +noall +answer
;; global options: +cmd
stackoverflow.com.      201     IN      A       151.101.1.69
stackoverflow.com.      201     IN      A       151.101.65.69
stackoverflow.com.      201     IN      A       151.101.129.69
stackoverflow.com.      201     IN      A       151.101.193.69

λ dig cdn.sstatic.net +noall +answer
; <<>> DiG 9.10.2-P3 <<>> cdn.sstatic.net +noall +answer
;; global options: +cmd
cdn.sstatic.net.        724     IN      A       151.101.193.69
cdn.sstatic.net.        724     IN      A       151.101.1.69
cdn.sstatic.net.        724     IN      A       151.101.65.69
cdn.sstatic.net.        724     IN      A       151.101.129.69

嘿,这些 IP 都是一致的,并且他们也拥有相同的证书!这意味着你可以直接使用 HTTP/2 的服务器推送功能,而无需影响 HTTP/1.1 用户。 HTTP/2 有推送的同时,HTTP/1.1 也有了域名共享(通过 sstatic.net)。我们暂未部署服务器推送功能,但一切都尽在掌握之中。

HTTPS 是我们实现性能目标的一个手段。可以这么说,我们的主要目标是性能,而非站点安全性。我们想要安全性,但光是安全性不足以让我们花那么多精力来在全网部署 HTTPS。当我们把所有因素都考虑在一起的时候,我们可以评估出要完成这件事情需要付出的巨大的时间和精力。在 2013 年,HTTP/2 还没有扮演那么重要的角色。而现在形势变了,对其的支持也多了,最终这成为了我们花时间调研 HTTPS 的催化剂。

值得注意的是 HTTP/2 标准在我们项目进展时还在持续发生变化。它从 SPDY 演化为 HTTP/2,从 NPN 演化为 ALPN。我们这里不会过多涉及到这部分细节,因为我们并没有为其做太多贡献。我们观望并从中获准,但整个互联网却在推进其向前发展。如果你感兴趣,可以看看 Cloudflare 是怎么讲述其演变的

HAProxy:支持 HTTPS

我们最早在 2013 年开始在 HAProxy 中使用 HTTPS。为什么是 HAProxy 呢?这是历史原因,我们已经在使用它了,而它在 2013 年 的 1.5 开发版中支持了 HTTPS,并在 2014 年发布了正式版。曾经有段时间,我们把 Nginx 放置在 HAProxy 之前(详情看这里)。但是简单些总是更好,我们总是想着要避免在链路、部署和其他问题上的复杂问题。

我不会探讨太多细节,因为也没什么好说的。HAProxy 在 1.5 之后使用 OpenSSL 支持 HTTPS,配置文件也是清晰易懂的。我们的配置方式如下:

  • 跑在 4 个进程上
    • 1 个用来做 HTTP/前端处理
    • 2-4 个用来处理 HTTPS 通讯
  • HTTPS 前端使用 socket 抽象命名空间来连接至 HTTP 后端,这样可以极大减少资源消耗
  • 每一个前端或者每一「层」都监听了 :433 端口(我们有主、二级、websockets 及开发环境)
  • 当请求进来的时候,我们在请求头上加入一些数据(也会移除掉一些你们发送过来的),再将其转发给 web 层
  • 我们使用  Mozilla 提供的加密套件。注意,这和我们 CDN 用的不是同样的套件。

HAProxy 比较简单,这是我们使用一个 SSL 证书来支持 :433 端口的第一步。事后看来,这也只是一小步。

这里是上面描述情况下的架构图,我们马上来说前面的那块云是怎么回事:

CDN/代理层:通过 Cloudflare 和 Fastly 优化延迟

我对 Stack Overflow 架构的效率一直很自豪。我们很厉害吧?仅用一个数据中心和几个服务器就撑起了一个大型网站。不过这次不一样了。尽管效率这件事情很好,但是在延迟上就成了个问题。我们不需要那么多服务器,我们也不需要多地扩展(不过我们有一个灾备节点)。这一次,这就成为了问题。由于光速,我们(暂时)无法解决延迟这个基础性问题。我们听说有人已经在处理这个问题了,不过他们造的时间机器好像有点问题。

让我们用数字来理解延迟。赤道长度是 40000 公里(光绕地球一圈的最坏情况)。光速在真空中是 299,792,458 米/秒。很多人用这个数字,但光纤并不是真空的。实际上光纤有 30-31% 损耗,所以我们的这个数字是:(40,075,000 m) / (299,792,458 m/s * .70) = 0.191s,也就是说最坏情况下绕地球一圈是 191ms,对吧?不对。这假设的是一条理想路径,而实际上两个网络节点的之间几乎不可能是直线。中间还有路由器、交换机、缓存、处理器队列等各种各样的延迟。累加起来的延迟相当可观。

这些和 Stack Overflow 有什么关系呢?云主机的优势出来了。如果你用一家云供应商,你访问到的就是相对较近的服务器。但对我们来说不是这样,你离服务部署在纽约或丹佛(主备模式)越远,延迟就越高。而使用 HTTPS,在协商连接的时候需要一个额外的往返。这还是最好的情况(使用 0-RTT 优化 TLS 1.3)。Ilya Grigorik 的 这个总结 讲的很好。

来说 Cloudflare 和 Fastly。HTTPS 并不是闭门造车的一个项目,你看下去就会知道,我们还有好几个项目在并行。在搭建一个靠近用户的 HTTPS 终端(以降低往返时间)时,我们主要考虑的是:

  • 终端 HTTPS 支持
  • DDoS 防护
  • CDN 功能
  • 与直连等同或更优的性能

优化代理层的准备:客户端性能测试

开始正式启用终端链路加速之前,我们需要有性能测试报告。我们在浏览器搭好了一整套覆盖全链路性能数据的测试。 浏览器里可以通过 JavaScript 从 window.performance 取性能耗时。打开你浏览器的审查器,你可以亲手试一下。我们希望这个过程透明,所以从第一天开始就把详细信息放在了 teststackoverflow.com 上。这上面并没有敏感信息,只有一些由页面直接载入的 URI 和资源,以及它们的耗时。每一张记录下来的页面大概长这样:

我们目前对 5% 的流量做性能监控。这个过程没有那么复杂,但是我们需要做的事情包括: 1. 把耗时转成 JSON 2. 页面加载后上传性能测试数据 3. 将性能测试上传给我们后台服务器 4. 在 SQL Server 中使用 clustered columnstore 存储数据 5. 使用 Bosun (具体是 BosunReporter.NET) 汇集数据

最终的结果是我们有了一份来自于全球真实用户的很好的实时汇总。这些数据可供我们分析、监控、报警,以及用于评估变化。它大概长这样:

幸好,我们有持续的流量来获取数据以供我们决策使用,目前的量级是 50 亿,并且还在增长中。这些数据概览如下:

OK,我们已经把基础工作准备好了,是时候来测试 CDN/代理层供应商了。

Cloudflare

我们评估了很多 CDN/DDoS 防护层供应商。最终选择了 Cloudflare,主要是考虑到他们的基础设施、快速响应、还有他们承诺的 Railgun。那么我们如何测试使用了 Cloudfalre 之后用户的真实效果?是否需要部署服务来获取用户数据?答案是不需要!

Stack Overflow 的数据量非常大:月 PV 过十亿。记得我们上面讲的客户端耗时纪录吗?我们每天都有几百万的访问了,所以不是直接可以问他们吗?我们是可以这么做,只需要在页面中嵌入 <iframe> 就行了。Cloudflare 已经是我们 cdn.sstatic.net(我们共用的无 cookie 的静态内容域)的托管商了。但是这是通过一条CNAME DNS 纪录来做的,我们把 DNS 指向他们的 DNS。所以要用 Cloudflare 来当代理服务的话,我们需要他们指向我们的 DNS。所以我们先需要测试他们 DNS 的性能。

实际上,要测试性能我们需要把二级域名给他们,而不是 something.stackoverflow.com,因为这样可能会有不一致的胶水记录而导致多次查询。明确一下,一级域名 (TLDs)指的是 .com.net.org.dance.duck.fail.gripe.here.horse.ing.kim.lol.ninja.pink.red.vodka. 和 .wtf。 注意,这些域名尾缀都是,我可没开玩笑。 二级域名 (SLDs) 就多了一级,比如 stackoverflow.comsuperuser.com 等等。我们需要测的就是这些域名的行为及表现。因此,我们就有了 teststackoverflow.com,通过这个新域名,我们在全球范围内测试 DNS 性能。对一部分比例的用户,通过嵌一个 <iframe>(在测试中开关),我们可以轻松地获取用户访问 DNS 的相关数据。

注意,测试过程最少需要 24 小时。在各个时区,互联网的表现会随着用户作息或者 Netflix 的使用情况等发生变化。所以要测试一个国家,需要完整的一天数据。最好是在工作日(而不要半天落在周六)。我们知道会有各种意外情况。互联网的性能并不是稳定的,我们要通过数据来证明这一点。

我们最初的假设是,多增加了的一个节点会带来额外的延时,我们会因此损失一部分页面加载性能。但是 DNS 性能上的增加其实弥补了这一块。比起我们只有一个数据中心来说,Cloudflare 的 DNS 服务器部署在离用户更近的地方,这一块性能要好得多得多。我希望我们能有空来放出这一块的数据,只不过这一块需要很多处理(以及托管),而我现在也没有足够多的时间。

接下来,我们开始将 teststackoverflow.com 放在 Cloudflare 的代理上做链路加速,同样也是放在 <iframe> 中。我们发现美国和加拿大的服务由于多余的节点而变慢,但是世界其他地方都是持平或者更好。这满足我们的期望。我们开始使用 Cloudflare 的网络对接我们的服务。期间发生了一些 DDos 的攻击,不过这是另外的事了。那么,为什么我们接受在美国和加拿大地区慢一点呢?因为每个页面加载需要的时间仅为 200-300ms,哪怕慢一点也还是飞快。当时我们认为 Railgun 可以将这些损耗弥补回来。

这些测试完成之后,我们为了预防 DDos 工作,做了一些其他工作。我们接入了额外的 ISP 服务商以供我们的 CDN/代理层对接。毕竟如果能绕过攻击的话,我们没必要在代理层做防护。现在每个机房都有 4 个 ISP 服务商(译者注:相当于电信、联通、移动、教育网),两组路由器,他们之间使用 BGP协议。我们还额外添置了两组负载均衡器专门用于处理 CDN/代理层的流量。

Cloudflare: Railgun

与此配套,我们启用了两组 Railgun。Railgun 的原理是在 Cloudflare 那边,使用 memcached 匹配 URL 进行缓存数据。当 Railgun 启用的时候,每个页面(有一个大小阈值)都会被缓存下来。那么在下一次请求时候,如果在这个 URL 在 Cloudflare 节点上和我们这里都缓存的话,我们仍然会问 web 服务器最新的数据。但是我们不需要传输完整的数据,只需要把传输和上次请求的差异数据传给 Cloudflure。他们把这个差异运用于他们的缓存上,然后再发回给客户端。这时候, gzip 压缩 的操作也从 Stack Overflow 的 9 台 Web Server 转移到了一个 Railgun 服务上,这台服务器得是 CPU 密集型的——我指出这点是因为,这项服务需要评估、购买,并且部署在我们这边。

举个例子,想象一下,两个用户打开同一个问题的页面。从浏览效果来看,他们的页面技术上长得几乎一样,仅仅有细微的差别。如果我们大部分的传输内容只是一个 diff 的话,这将是一个巨大的性能提升。

总而言之,Railgun 通过减少大量数据传输的方式提高性能。当它顺利工作的时候确实是这样。除此之外,还有一个额外的优点:请求不会重置连接。由于 TCP 慢启动,当连接环境较为复杂时候,可能导致连接被限流。而 Railgun 始终以固定的连接数连接到 Cloudflare 的终端,对用户请求采用了多路复用,从而其不会受慢启动影响。小的 diff 也减少了慢启动的开销。

很可惜,我们由于种种原因我们在使用 Railgun 过程中一直遇到问题。据我所知,我们拥有当时最大的 Railgun 部署规模,这把 Railgun 逼到了极限。尽管我们花了一年追踪各种问题,最终还是不得不放弃了。这种状况不仅没有给我们省钱,还耗费了更多的精力。现在几年过去了。如果你正在评估使用 Railgun,你最好看最新的版本,他们一直在做优化。我也建议你自己做决定是否使用 Railgun。

Fastly

我们最近才迁到 Fastly,因为我们在讲 CDN/代理层,我也会顺带一提。由于很多技术工作在 Cloudflare 那边已经完成,所以迁移本身并没有什么值得说的。大家会更感兴趣的是:为什么迁移?毕竟 Cloudflare 在各方面是不错的:丰富的数据中心、稳定的带宽价格、包含 DNS 服务。答案是:它不再是我们最佳的选择了。Flastly 提供了一些我们更为看中的特性:灵活的终端节点控制能力、配置快速分发、自动配置分发。并不是说 Cloudflare 不行,只是它不再适合 Stack Overflow 了。

事实胜于雄辩:如果我不认可 Cloudflare,我的私人博客不可能选择它,嘿,就是这个博客,你现在正在阅读的。

Fastly 吸引我们的主要功能是提供了 Varnish 和 VCL。这提供了高度的终端可定制性。有些功能吧,Cloudfalre 无法快速提供(因为他们是通用化的,会影响所有用户),在 Fastly 我们可以自己做。这是这两家架构上的差异,这种「代码级别高可配置」对于我们很适用。同时,我们也很喜欢他们在沟通、基础设施的开放性。

我来展示一个 VCL 好用在哪里的例子。最近我们遇到 .NET 4.6.2 的一个超恶心 bug,它会导致 max-age 有超过 2000 年的缓存时间。快速解决方法是在终端节点上有需要的时候去覆盖掉这个头部,当我写这篇文章的时候,这个 VCL 配置是这样的:

sub vcl_fetch {
  if (beresp.http.Cache-Control) {
      if (req.url.path ~ "^/users/flair/") {
          set beresp.http.Cache-Control = "public, max-age=180";
      } else {
          set beresp.http.Cache-Control = "private";
      }
  }

这将给用户能力展示页 3 分钟的缓存时间(数据量还好),其余页面都不设置。这是一个为解决紧急时间的非常便于部署的全局性解决方案。 我们很开心现在有能力在终端做一些事情。我们的 Jason Harvey 负责 VCL 配置,并写了一些自动化推送的功能。我们基于一个 Go 的开源库 fastlyctl 做了开发。

另一个 Fastly 的特点是可以使用我们自己的证书,Cloudflare 虽然也有这个服务,但是费用太高。如我上文提到的,我们现在已经具备使用 HTTP/2 推送的能力。但是,Fastly 就不支持 DNS,这个在 Cloudflare 那里是支持的。现在我们需要自己解决 DNS 的问题了。可能最有意思的就是这些来回的折腾吧?

全局 DNS

当我们从 Cloudflare 迁移到 Fastly 时候,我们必须评估并部署一个新的 DNS 供应商。这里有篇 Mark Henderson 写的 文章 。鉴于此,我们必须管理:

  • 我们自己的 DNS 服务器(备用)
  • Name.com 的服务器(为了那些不需要 HTTPS 的跳转服务)
  • Cloudflare DNS
  • Route 53 DNS
  • Google DNS
  • Azure DNS
  • 其他一些(测试时候使用)

这个本身就是另一个项目了。为了高效管理,我们开发了 DNSControl。这现在已经是开源项目了托管在 GiHub 上,使用 Go 语言编写。 简而言之,每当我们推送 JavaScript 的配置到 git,它都会马上在全球范围里面部署好 DNS 配置。这里有一个简单的例子,我们拿 askubuntu.com 做示范:

D('askubuntu.com', REG_NAMECOM,
    DnsProvider(R53,2),
    DnsProvider(GOOGLECLOUD,2),
    SPF,
    TXT('@', 'google-site-verification=PgJFv7ljJQmUa7wupnJgoim3Lx22fbQzyhES7-Q9cv8'), // webmasters
    A('@', ADDRESS24, FASTLY_ON),
    CNAME('www', '@'),
    CNAME('chat', 'chat.stackexchange.com.'),
    A('meta', ADDRESS24, FASTLY_ON),
END)

太棒了,接下来我们就可以使用客户端响应测试工具来测试啦!上面提到的工具可以实时告诉我们真实部署情况,而不是模拟数据。但是我们还需要测试所有部分都正常。

测试

客户端响应测试的追踪可以方便我们做性能测试,但这个并不适合用来做配置测试。客户端响应测试非常适合展现结果,但是配置有时候并没有界面,所以我们开发了 httpUnit (后来知道这个项目重名了 )。这也是一个使用 Go 语言的开源项目。以 teststackoverflow.com 举例,使用的配置如下:

[[plan]]
    label = "teststackoverflow_com"
    url = "http://teststackoverflow.com"
    ips = ["28i"]
    text = "<title>Test Stack Overflow Domain</title>"
    tags = ["so"]
[[plan]]
    label = "tls_teststackoverflow_com"
    url = "https://teststackoverflow.com"
    ips = ["28"]
    text = "<title>Test Stack Overflow Domain</title>"
    tags = ["so"]

每次我们更新一下防火墙、证书、绑定、跳转时都有必要测一下。我们必须保证我们的修改不会影响用户访问(先在预发布环境进行部署)。 httpUnit 就是我们来做集成测试的工具。

我们还有一个开发的内部工具(由亲爱的 Tom Limoncelli 开发),用来管理我们负载均衡上面的 VIP 地址 。我们先在一个备用负载均衡上面测试完成,然后将所有流量切过去,让之前的主负载均衡保持一个稳定状态。如果期间发生任何问题,我们可以轻易回滚。如果一切顺利,我们就把这个变更应用到那台负载均衡上。这个工具叫做 keepctl(keepalived control 的简称),时间允许的话很快就会整理开源出来。

应用层准备

上面提到的只是架构方面的工作。这通常是由 Stack Overflow 的几名网站可靠性工程师组成的团队完成的。而应用层也有很多需要完成的工作。这个列表会很长,先让我拿点咖啡和零食再慢慢说。

很重要的一点是,Stack Overflow 与 Stack Exchange 的架构 Q&A 采用了多租户技术。这意味着如果你访问 stackoverflow.com 或者 superuser.com 又或者 bicycles.stackexchange.com,你返回到的其实是同一台服务器上的同一个 w3wp.exe 进程。我们通过浏览器发送的 Host 请求头来改变请求的上下文。为了更好地理解我们下文中提到的一些概念,你需要知道我们代码中的 Current.Site 其实指的是 请求 中的站点。Current.Site.Url() 和 Current.Site.Paths.FaviconUrl 也是基于同样的概念。

换一句话说:我们的 Q&A 全站都是跑在同一个服务器上的同一个进程,而用户对此没有感知。我们在九台服务器上每一台跑一个进程,只是为了发布版本和冗余的问题。

全局登录

整个项目中有一些看起来可以独立出来(事实上也是),不过也同属于整个大 HTTPS 迁移中的一部分。登录就是其中一个项目。我首先来说说这个,因为这比别它变化都要早上线。

在 Stack Overflow(及 Stack Exchange)的头五六年里,你登录的是一个个的独立网站。比如,stackoverflow.comstackexchange.com 以及 gaming.stackexchange.com 都有它们自己的 cookies。值得注意的是:meta.gaming.stackexchange.com 的登录 cookie 是从 gaming.stackexchange.com 带过来的。这些是我们上面讨论证书时提到的 meta 站点。他们的登录信息是相关联的,你只能通过父站点登录。在技术上说并没有什么特别的,但考虑到用户体验就很糟糕了。你必须一个一个站登录。我们用「全局认证」的方法来「修复」了这个问题,方法是在页面上放一个 <iframe>,内面访问一下 stackauth.com。如果用户在别处登录过的话,它也会在这个站点上登录,至少会去试试。这个体验还行,但是会有弹出框问你是否点击重载以登录,这样就又不是太好。我们可以做得更好的。对了,你也可以去问问 Kevin Montrose 关于移动 Safari 的匿名模式,你会震惊的。

于是我们有了「通用登录」。为什么用「通用」这个名字?因为我们已经用过「全局」了。我们就是如此单纯。所幸 cookies 也很单纯的东西。父域名里的 cookie(如 stackexchange.com)在你的浏览器里被带到所有子域名里去(如 gaming.stackexchange.com)。如果我们只二级域名的话,其实我们的域名并不多:

是的,我们有一些域名是跳转到上面的列表中的,比如 askdifferent.com。但是这些只是跳转而已,它们没有 cookies 也无需登录。

这里有很多细节的后端工作我没有提(归功于 Geoff Dalgas 和 Adam Lear),但大体思路就是,当你登录的时候,我们把这些域名都写入一个 cookie。我们是通过第三方的 cookie 和随机数来做的。当你登录其中任意一个网站的时候,我们在页面上都会放 6 个 <img> 标签来往其它域名写入 cookie,本质上就完成了登录工作。这并不能在 所有情况 下都适用(尤其是移动 Safari 简直是要命了),但和之前比起来那是好得多了。

客户端的代码不复杂,基本上长这样:

$.post('/users/login/universal/request', function (data, text, req) {
    $.each(data, function (arrayId, group) {
        var url = '//' + group.Host + '/users/login/universal.gif?authToken=' + 
            encodeURIComponent(group.Token) + '&nonce=' + encodeURIComponent(group.Nonce);
        $(function ( ) { $('#footer').append('<img style="display:none" src="' + url + '"></img>'); });
    });
}, 'json');

但是要做到这点,我们必须上升到账号级别的认证(之前是用户级别)、改变读取 cookie 的方式、改变这些 meta 站的登录工作方式,同时还要将这一新的变动整合到其它应用中。比如说,Careers(现在拆成了 Talent 和 Jobs)用的是另一份代码库。我们需要让这些应用读取相应的 cookies,然后通过 API 调用 Q&A 应用来获取账户。我们部署了一个 NuGet 库来减少重复代码。底线是:你在一个地方登录,就在所有域名都登录。不弹框,不重载页面。

技术的层面上看,我们不用再关心 *.*.stackexchange.com 是什么了,只要它们是 stackexchange.com 下就行。这看起来和 HTTPS 没有关系,但这让我们可以把 meta.gaming.stackexchange.com 变成 gaming.meta.stackexchange.com 而不影响用户。

本地 HTTPS 开发

要想做得更好的话,本地环境应该尽量与开发和生产环境保持一致。幸好我们用的是 IIS,这件事情还简单的。我们使用一个工具来设置开发者环境,这个工具的名字叫「本地开发设置」——单纯吧?它可以安装工具(Visual Studio、git、SSMS 等)、服务(SQL Server、Redis、Elasticsearch)、仓库、数据库、网站以及一些其它东西。做好了基本的工具设置之后,我们要做的只是添加 SSL/TLS 证书。主要的思路如下:

Websites = @(
    @{
        Directory = "StackOverflow";
        Site = "local.mse.com";
        Aliases = "discuss.local.area51.lse.com", "local.sstatic.net";
        Databases = "Sites.Database", "Local.StackExchange.Meta", "Local.Area51", "Local.Area51.Meta";
        Certificate = $true;
    },
    @{
        Directory = "StackExchange.Website";
        Site = "local.lse.com";
        Databases = "Sites.Database", "Local.StackExchange", "Local.StackExchange.Meta", "Local.Area51.Meta";
        Certificate = $true;
    }
)

我把使用到的代码放在了一个 gist 上:Register-Websites.psm1。我们通过 host 头来设置网站(通过别名添加),如果直连的话就给它一个证书(嗯,现在应该把这个行为默认改为 $true 了),然后允许 AppPool 账号来访问数据库,于是我们本地也在使用 https:// 开发了。嗯,我知道我们应该把这个设置过程开源出来,不过我们仍需去掉一些专有的业务。会有这么一天的。

为什么这件事情很重要? 在此之前,我们从 /content 加载静态内容,而不是从另一个域名。这很方便,但也隐藏了类似于跨域请求(CORS)的问题。在同一个域名下用同一个协议能正常加载的资源,换到开发或者生产环境下就有可能出错。「在我这里是好的。」

当我们使用和生产环境中同样协议以及同样架构的 CDN 还有域名设置时,我们就可以在开发机器上找出并修复更多的问题。比如,你是否知道,从 https:// 跳转到 http:// 时,浏览器是不会发送 referer 的?这是一个安全上的问题,referer 头中可能带有以明文传输的敏感信息。

「Nick 你就扯吧,我们能拿到从 Google 拿到 referer 啊!」确实。但是这是因为他们主动选择这一行为。如果你看一下 Google 的搜索页面,你可以看到这样的 <meta> 指令:

<meta content="origin" id="mref" name="referrer">

这也就是为什么你可以取到 referer。

好的,我们已经设置好了,现在该做些什么呢?

混合内容:来自于你们

混合内容是个筐,什么都能往里装。我们这些年下来积累了哪些混合内容呢?不幸的是,有很多。这个列表里我们必须处理的用户提交内容:

上面的每一个都带有自己独有的问题,我仅仅会覆盖一下值得一提的部分。注意:我谈论的每一个解决方案都必须扩展到我们这个架构下的几百个站点和数据库上。

在上面的所有情况中(除了代码片段),要消除混合内容的第一步工作就是:你必须先消除的混合内容。否则,这个清理过程将会无穷无尽。要做到这一点,我们开始全网强制仅允许内嵌 https:// 图片。一旦这个完成之后,我们就可以开始清理了。

对于问题、答案以及其他帖子形式中,我们需要具体问题具体分析。我们先来搞定 90% 以上的情况:stack.imgur.com。在我来之前 Stack Overflow 就已经有自己托管的 Imgur 实例了。你在编辑器中上传的图片就会传到那里去。绝大部分的帖子都是用的这种方法,而他们几年前就为我们添加了 HTTPS 支持。所以这个就是一个很直接的查找替换(我们称为帖子 markdown 重处理)。

然后我们通过通过 Elasticsearch 对所有内容的索引来找出所有剩下的文件。我说的我们其实指的是 Samo。他在这里处理了大量的混合内容工作。当我们看到大部分的域名其实已经支持 HTTPS 了之后,我们决定:

  1. 对于每个 <img> 的源地址都尝试替换成 https://。如果能正常工作则替换帖子中的链接
  2. 如果源地址不支持 https://,将其转一个链接

当然,并没有那么顺利。我们发现用于匹配 URL 的正则表达式其实已经坏了好几年了,并且没有人发现……所以我们修复了正则,重新做了索引。

有人问我们:「为什么不做个代理呢?」呃,从法律和道德上来说,代理对我们的内容来说是个灰色地带。比如,我们 photo.stackexchange.com 上的摄像师会明确声明不用 Imgur 以保留他们的权利。我们充分理解。如果我们开始代理并缓存全图,这在法律上有点问题。我们后来发现在几百万张内嵌图片中,只有几千张即不支持 https:// 也没有 404 失效的。这个比例(低于 1%)不足于让我们去搭一个代理。

我们确实研究过搭一个代理相关的问题。费用有多少?需要多少存储?我们的带宽足够吗?我们有了一个大体上的估算,当然有点答案也不是很确定。比如我们是否要用 Fastly,还是直接走运营商?哪一种比较快?哪一种比较便宜?哪一种可以扩展?这个足够写另一篇博客了,如果你有具体问题的话可以在评论里提出,我会尽力回答。

所幸,在这个过程中,为了解决几个问题,balpha 更改了用 HTML5 嵌入 YouTube 的方式。我们也就顺便强制了一下 YouTube 的 https:// 嵌入。

剩下的几个内容领域的事情差不多:先阻止新的混合内容进来,再替换掉老的。这需要我们在下面几个领域进行更改:

  • 帖子
  • 个人资料
  • 开发故事
  • 帮助中心
  • 职场
  • 公司业务

声明:JavaScript 片段的问题仍然没有解决。这个有点难度的原因是:

  1. 资源有可能不以 https:// 的方式存在(比如一个库)
  2. 由于这个是 JavaScript,你可以自己构建出任意的 URL。这里我们就无力检查了。
    • 如果你有更好的方式来处理这个问题,请告诉我们。我们在可用性与安全性上不可兼得。

混合内容:来自我们

并不是处理完用户提交的内容就解决问题了。我们自己还是有不少 http:// 的地方需要处理。这些更改本身没什么特别的,但是这至少能解答「为什么花了那么长时间?」这个问题:

  • 广告服务(Calculon)
  • 广告服务(Adzerk)
  • 标签赞助商
  • JavaScript 假定
  • Area 51(这代码库也太老了)
  • 分析跟踪器(Quantcast, GA)
  • 每个站点引用的 JavaScript(社区插件)
  • /jobs 下的所有东西(这其实是个代理)
  • 用户能力
  • ……还有代码中所有出现 http:// 的地方

JavaScript 和链接比较令人痛苦,所以我在这里稍微提一下。

JavaScript 是一个不少人遗忘的角落,但这显然不能被无视。我们不少地方将主机域名传递给 JavaScript 时假定它是 http:// ,同时也有不少地方写死了 meta 站里的 meta. 前缀。很多,真的很多,救命。还好现在已经不这样了,我们现在用服务器渲染出一个站点,然后在页面顶部放入相应的选择:

StackExchange.init({
  "locale":"en",
  "stackAuthUrl":"https://stackauth.com",
  "site":{
    "name":"Stack Overflow"
    "childUrl":"https://meta.stackoverflow.com",
    "protocol":"http"
  },
  "user":{
    "gravatar":"<div class=\"gravatar-wrapper-32\"><img src=\"https://i.stack.imgur.com/nGCYr.jpg\"></div>",
    "profileUrl":"https://stackoverflow.com/users/13249/nick-craver"
  }
});

这几年来我们在代码里也用到了很多静态链接。比如,在页尾,在页脚,在帮助区域……到处都是。对每一个来说,解决方式都不复杂:把它们改成 <site>.Url("/path") 的形式就好了。不过要找出这些链接有点意思,因为你不能直接搜 "http://"。感谢 W3C 的丰功伟绩:

<svg xmlns="http://www.w3.org/2000/svg"...

是的,这些是标识符,是不能改的。所以我希望 Visual Studio 在查找文件框中增加一个「排除文件类型」的选项。Visual Studio 你听见了吗?VS Code 前段时间就加了这个功能。我这要求不过分。

这件事情很枯燥,就是在代码中找出一千个链接然后替换而已(包括注释、许可链接等)。但这就是人生,我们必须要做。把这些链接改成 .Url() 的形式之后,一旦站点支持 HTTPS 的时候,我们就可以让链接动态切换过去。比如我们得等到 meta.*.stackexchange.com 搬迁完成之后再进行切换。插播一下我们数据中心的密码是「煎饼馃子」拼音全称,应该没有人会读到这里吧,所以在这里存密码很安全。当站点迁完之后,.Url() 仍会正常工作,然后用 .Url() 来渲染默认为 HTTPS 的站点也会继续工作。这将静态链接变成了动态。

另一件重要的事情:这让我们的开发和本地环境都能正常工作,而不仅仅是链到生产环境上。这件事情虽然枯燥,但还是值得去做的。对了,因为我们的规范网址(canonical)也通过 .Url() 来做了,所以一旦用户开始用上 HTTPS,Google 也可以感知到。

一旦一个站点迁到 HTTPS 之后,我们会让爬虫来更新站点链接。我们把这个叫修正「Google 果汁」,同时这也可以让用户不再碰到 301。

跳转(301)

当你把站点移动到 HTTPS 之后,为了和 Google 配合,你有两件重要的事情要做:

  • 更新规范网址,比如 <link rel="canonical" href="https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454" />
  • 把 http:// 链接通过 301 跳转至 https://

这个不复杂,也不是浩大的工程,但这非常非常重要。Stack Overflow 大部分的流量都是从 Google 搜索结果中过来的,所以我们得保证这个不产生负面影响。这个是我们的生计,如果我们因此丢了流量那我真是要失业了。还记得那些 .internal 的 API 调用吗?对,我们同样不能把所有东西都进行跳转。所以我们在处理跳转的时候需要一定的逻辑(比如我们也不能跳转 POST 请求,因为浏览器处理得不好),当然这个处理还是比较直接的。这里是实际上用到的代码:

public static void PerformHttpsRedirects()
{
    var https = Settings.HTTPS;
    // If we're on HTTPS, never redirect back
    if (Request.IsSecureConnection) return;

    // Not HTTPS-by-default? Abort.
    if (!https.IsDefault) return;
    // Not supposed to redirect anyone yet? Abort.
    if (https.RedirectFor == SiteSettings.RedirectAudience.NoOne) return;
    // Don't redirect .internal or any other direct connection
    // ...as this would break direct HOSTS to webserver as well
    if (RequestIPIsInternal()) return;

    // Only redirect GET/HEAD during the transition - we'll 301 and HSTS everything in Fastly later
    if (string.Equals(Request.HttpMethod, "GET", StringComparison.InvariantCultureIgnoreCase)
        || string.Equals(Request.HttpMethod, "HEAD", StringComparison.InvariantCultureIgnoreCase))
    {
        // Only redirect if we're redirecting everyone, or a crawler (if we're a crawler)
        if (https.RedirectFor == SiteSettings.RedirectAudience.Everyone
            || (https.RedirectFor == SiteSettings.RedirectAudience.Crawlers && Current.IsSearchEngine))
        {
            var resp = Context.InnerHttpContext.Response;
            // 301 when we're really sure (302 is the default)
            if (https.RedirectVia301)
            {
                resp.RedirectPermanent(Site.Url(Request.Url.PathAndQuery), false);
            }
            else
            {
                resp.Redirect(Site.Url(Request.Url.PathAndQuery), false);
            }
            Context.InnerHttpContext.ApplicationInstance.CompleteRequest();
        }
    }
}

注意我们并不是默认就跳 301(有一个 .RedirectVia301 设置),因为我们做一些会产生永久影响的事情之前必须仔细测试。我们会晚一点来讨论 HSTS 以及后续影响。

Websockets

这一块会过得快一点。Websocket 不难,从某种角度来说,这是我们做过的最简单的事情。我们用 websockets 来处理实时的用户影响力变化、收件箱通知、新问的问题、新增加的答案等等。这也就说基本上每开一个 Stack Overflow 的页面,我们都会有一个对应的 websocket 连接连到我们的负载均衡器上。

所以怎么改呢?其实很简单:安装一个证书,监听 :443 端口,然后用 wss://qa.sockets.stackexchange.com 来代替 ws:// 。后者其实早就做完了(我们用了一个专有的证书,但是这不重要)。从 ws://wss:// 只是配置一下的问题。一开始我们还用 ws:// 作为 wss:// 的备份方案,不过后来就变成仅用 wss:// 了。这么做有两个原因:

  1. 不用的话在 https:// 下面会有混合内容警告
  2. 可以支持更多用户。因为很多老的代理不能很好地处理 websockets。如果使用加密流量,大多数代理就只是透传而不会弄乱流量。对移动用户来说尤其是这样。

最大的问题就是:「我们能处理了这个负载吗?」我们全网处理了不少并发 websocket,在我写这估的时候我们有超过 600000 个并发的连接。这个是我们 HAProxy 的仪表盘在 Opserver 中的界面:

不管是在终端、抽象命名空间套接字还是前端来说都有很多连接。由于启用了 TLS 会话恢复,HAProxy 本身的负载也很重。要让用户下一次重新连接更快,第一次协商之后用户会拿到一个令牌,下一次会把这个令牌发送过来。如果我们的内存足够并且没有超时,我们会恢复上次的会话而不是再开一个。这个操作可以节省 CPU,对用户来说有性能提升,但会用到到更多内存。这个多因 key 大小而异(2048,4096 或是更多?)我们现在用的是 4096 位的 key。在开了 600000 个 websocket 的情况下,我们只用掉了负载均衡器 64GB 内存里的 19GB。这里面 12GB 是 HAProxy 在用,大多数为 TLS 会话缓存。所以结果来说还不错,如果我们不得不买内存的话,这也会是整个 HTTPS 迁移中最便宜的东西。

未知

我猜现在可能是我们来谈论一些未知问题的时候。有些问题是在我们尝试之前无法真正知道的:

  • Google Analytics 里的流量表现怎么样?(我们会失去 referer 吗?)
  • Google Webmasters 的转换是否平滑?(301 生效吗?规范域名呢?要多长时间?)
  • Google 搜索分析会怎么工作(我们会在搜索分析中看到 https:// 吗?)
  • 我们搜索排名会下降吗?(最恐怖的)

有很多人都谈过他们转化成 https:// 的心得,但对我们却有点不一样。我们不是一个站点。我们是多个域名下的多个站点。我们不知道 Google 会怎么对待我们的网络。它会知道 stackoverflow.comsuperuser.com 有关联吗?不知道。我们也不能指望 Google 来告诉我们这些。

所以我们就做测试。在我们全网发布 中,我们测试了几个域名:

对,这些是 Samo 和我会了仔细讨论出来的结果,花了有三分钟那么久吧。Meta 是因为这是我们最重要的反馈网站。Security 站上有很多专家可能会注意到相关的问题,特别是 HTTPS 方面。最后一个,Super User,我们需要知道搜索对我们内容的影响。比起 meta 和 security 来说法,Super User 的流量要大得多。最重要的是,它有来自 Google 的原生流量。

我们一直在观察并评估搜索的影响,所以 Super User 上了之后其他网站过了很久才跟上。到目前为止我们能说的是:基本上没影响。搜索、结果、点击还有排名的周变化都在正常范围内。我们公司依赖于这个流量,这对我们真的很重要。所幸,没有什么值得我们担心的点,我们可以继续发布。

错误

如果不提到我们搞砸的部分,这篇文章就还不够好。错误永远是个选择。让我们来总结一下这一路让我们后悔的事情:

错误:相对协议 URL

如果你的一个资源有一个 URL 的话,一般来说你会看到一些 http://example.com 或者 https://example.com 之类的东西,包括我们图片的路径等等。另一个选项就是你可以使用 //example.com。这被称为相对协议 URL。我们很早之前就在图片、JavaScript、CSS 等中这么用了(我们自有的资源,不是指用户提交)。几年后,我们发现这不是一个好主意,至少对我们来说不是。相对协议链接中的「相对」是对于页面而言。当你在 http://stackoverflow.com 时,//example.com 指的是 http://example.com;如果你在 https://stackoverflow.com 时,就和 https://example.com 等同。那么这个有什么问题呢?

问题在于,图片 URL 不仅是用在页面中,它们还用在邮件、API 还有移动应用中。当我们理了一下路径结构然后在到处都使用图片路径时我们发现不对了。虽然这个变化极大降低了代码冗余,并且简化了很多东西,结果却是我们在邮件中使用了相对 URL。绝大多数邮件客户端都不能处理相对协议 URL 的图片。因为它们不知道是什么协议。Email 不是 http:// 也不是 https://。只有你在浏览器里查看邮件,有可能是预期的效果。

那该怎么办?我们把所有的地方都换成了 https://。我把我们所有的路径代码统一到两个变量上:CDN 根路径,和对应特定站点的文件夹。例如 Stack Overflow 的样式表在 https://cdn.sstatic.net/Sites/stackoverflow/all.css 上(当然我们有缓存中断器),换成本地就是 https://local.sstatic.net/Sites/stackoverflow/all.css。你能看出其中的共同点。通过拼接路径,逻辑简单了不少。则 通过强制 https://,用户还可以在整站切换之前就享受 HTTP/2 的好处,因为所有静态资源都已经就位。都用 https:// 也表示我们可以在页面、邮件、移动还有 API 上使用同一个属性。这种统一也意味着我们有一个固定的地方来处理所有路径——我们到处都有缓存中断器。

注意:如果你像我们一样中断缓存,比如 https://cdn.sstatic.net/Sites/stackoverflow/all.css?v=070eac3e8cf4,请不要用构建号。我们的缓存中断使用的是文件的校验值,也就是说只有当文件真正变化的时候你才会下载一个新的文件。用构建号的话可能会稍微简单点,但同时也会对你的费用还有性能有所损伤。

能做这个当然很好,可我们为什么不从一开始就做呢?因为 HTTPS 在那个时候性能还不行。用户通过 https:// 访问会比 http://慢很多。举一个大一点的例子:我们上个月在 sstatic.net 上收到了四百万个请求,总共有 94TB。如果 HTTPS 性能不好的话,这里累积下来的延迟就很可观了。不过因为我们上了 HTTP/2,以及设置好 CDN/代理层,性能的问题已经好很多了。对于用户来说更快了,对我们来说则更简单,何乐不为呢!

错误:API 及 .internal

当我们把代理架起来开始测试的时候发现了什么?我们忘了一件很重要的事,准确地说,我忘了一件很重要的事。我们在内部 API 里大量地使用了 HTTP。当然这个是正常工作的,只是它们变得更慢、更复杂、也更容易出问题了。

比方说一个内部 API 需要访问 stackoverflow.com/some-internal-route,之前,节点是这些:

  • 原始 app
  • 网关/防火墙(暴露给公网)
  • 本地负载均衡器
  • 目标 web 服务器

这是因为我们是可以解析 stackoverflow.com 的,解析出来的 IP 就是我们的负载均衡器。当有代理的情况下,为了让用户能访问到最近的节点,他们访问到的是不同的 IP 和目标点。他们的 DNS 解析出来的 IP 是 CDN/代理层 (Fastly)。糟了,这意识着我们现在的路径是这样的:

  • 原始 app
  • 网关/防火墙(暴露给公网)
  • 我们的外部路由器
  • 运营商(多节点)
  • 代理(Cloudflare/Fastly)
  • 运营商(到我们的代理路)
  • 我们的外部路由器
  • 本地负载均衡器
  • 目标 web 服务器

嗯,这个看起来更糟了。为了实现一个从 A 调用一下 B,我们多了很多不必要的依赖,同时性能也下降了。我不是说我们的代理很慢,只是原本只需要 1ms 就可以连到我们数据中心……好吧,我们的代理很慢。

我们内部讨论了多次如何用最简单的方法解决这个问题。我们可以把请求改成 internal.stackoverflow.com,但是这会产生可观的修改(也许也会产生冲突)。我们也创建一个 DNS 来专门解析内部地址(但这样会产生通配符继承的问题)。我们也可以在内部把 stackoverflow.com 解析成不同的地址(这被称为水平分割 DNS),但是这一来不好调试,二来在多数据中心的场景下不知道该到哪一个。

最终,我们在所有暴露给外部 DNS 的域名后面都加了一个 .internal 后续。比如,在我们的网络中,stackoverflow.com.internal 会解析到我们的负载均衡器后面(DMZ)的一个内部子网内。我们这么做有几个原因:

  • 我们可以在内部的 DNS 服务器里覆盖且包含一个顶级域名服务器(活动目录)
  • 当请求从 HAProxy 传到 web 应用中时,我们可以把 .internalHost 头中移除(应用层无感知)
  • 如果我们需要内部到 DMZ 的 SSL,我们可以用一个类似的通配符组合
  • 客户端 API 的代码很简单(如果在域名列表中就加一个 .internal

我们客户端的 API 代码是大部分是由 Marc Gravell 写的一个 StackExchange.Network 的 NuGet 库。对于每一个要访问的 URL,我们都用静态的方法调用(所以也就只有通用的获取方法那几个地方)。如果存在的话就会返回一个「内部化」URL,否则保持不变。这意味着一次简单的 NuGet 更新就可以把这个逻辑变化部署到所有应用上。这个调用挺简单的:

# uri = SubstituteInternalUrl(uri);

这里是 stackoverflow.com DNS 行为的一个例子:

  • Fastly:151.101.193.69, 151.101.129.69, 151.101.65.69, 151.101.1.69
  • 直连(外部路由):198.252.206.16
  • 内部:10.7.3.16

记得我们之前提到的 dnscontrol 吗?我们可以用这个快速同步。归功于 JavaScript 的配置/定义,我们可以简单地共享、简化代码。我们匹配所有所有子网和所有数据中心中的所有 IP 的最后一个字节,所以用几个变量,所有 AD 和外部的 DNS 条目都对齐了。这也意味着我们的 HAProxy 配置更简单了,基本上就是这样:

stacklb::external::frontend_normal { 't1_http-in':
  section_name    => 'http-in',
  maxconn         => $t1_http_in_maxconn,
  inputs          => {
    "${external_ip_base}.16:80"  => [ 'name stackexchange' ],
    "${external_ip_base}.17:80"  => [ 'name careers' ],
    "${external_ip_base}.18:80"  => [ 'name openid' ],
    "${external_ip_base}.24:80"  => [ 'name misc' ],

综上,API 路径更快了,也更可靠了:

  • 原始 app
  • 本地负载均衡器(DMZ)
  • 目标 web 服务器

我们解决了几个问题,还剩下几百个等着我们。

错误:301 缓存

在从 http:// 301 跳到 https:// 时有一点我们没有意识的是,Fastly 缓存了我们的返回值。在 Fastly 中,默认的缓存键并不考虑协议。我个人不同意这个行为,因为在源站默认启用 301 跳转会导致无限循环。这个问题是这样造成的:

  1. 用户访问 http:// 上的一个网络
  2. 通过 301 跳转到了 https://
  3. Fastly 缓存了这个跳转
  4. 任意一个用户(包括 #1 中的那个)以 https:// 访问同一个页面
  5. Fastly 返回一个跳至 https:// 的 301,尽量你已经在这个页面上了

这就是为什么我们会有无限循环。要解决这个问题,我们得关掉 301,清掉 Fastly 缓存,然后开始调查。Fastly 建议我们在 vary 中加入 Fastly-SSL,像这样:

sub vcl_fetch {
  set beresp.http.Vary = if(beresp.http.Vary, beresp.http.Vary ",", "") "Fastly-SSL";

在我看来,这应该是默认行为。

错误:帮助中心的小插曲

记得我们必须修复的帮助文档吗?帮助文档都是按语言区分,只有极少数是按站点来分,所以本来它们是可以共享的。为了不产生大量重复代码及存储结构,我们做了一点小小的处理。我们把实际上的帖子对象(和问题、答案一样)存在了 meta.stackexchange.com 或者是这篇帖子关联的站点中。我们把生成的 HelpPost 存在中心的 Sites 数据库里,其实也就是生成的 HTML。在处理混合内容的时候,我们也处理了单个站里的帖子,简单吧!

当原始的帖子修复后,我们只需要为每个站点去再生成 HTML 然后填充回去就行了。但是这个时候我犯了个错误。回填的时候拿的是当前站点(调用回填的那个站点),而不是原始站。这导致 meta.stackexchange.com 里的 12345 帖子被 stackoverflow.com 里的 12345 帖子所替代。有的时候是答案、有的时候是问题,有的时候有一个 tag wiki。这也导致了一些很有意思的帮助文档。这里有一些相应的后果

我只能说,还好修复的过程挺简单的:

再一次将数据填充回去就能修复了。不过怎么说,这个当时算是在公共场合闹了个笑话。抱歉。

开源

这里有我们在这个过程中产出的项目,帮助我们改进了 HTTPS 部署的工作,希望有一天这些能拯救世界吧:

下一步

我们的工作并没有做完。接下去还有一此要做的:

  • 我们要修复我们聊天域名下的混合内容,如 chat.stackoverflow.com,这里有用户嵌入的图片等
  • 如果可能的话,我们把所有适用的域名加进 Chrome HSTS 预加载列表
  • 我们要评估 HPKP 以及我们是否想部署(这个很危险,目前我们倾向于不部署)
  • 我们需要把聊天移到 https://
  • 我们需要把所有的 cookies 迁移成安全模式
  • 我们在等能支持 HTTP/2 的 HAProxy 1.8(大概在九月出来)
  • 我们需要利用 HTTP/2 的推送(我会在六月与 Fastly 讨论这件事情——他们还现在不支持跨域名推送)
  • 我们需要把 301 行为从 CDN/代理移出以达到更好的性能(需要按站点发布)

HSTS 预加载

HSTS 指的是「HTTP 严格传输安全」。OWASP 在这里有一篇很好的总结。这个概念其实很简单:

  • 当你访问 https:// 页面的时候,我们给你发一个这样的头部:Strict-Transport-Security: max-age=31536000
  • 在这个时间内(秒),你的浏览器只会通过 https:// 访问这个域名

哪怕你是点击一个 http:// 的链接,你的浏览器也会直接跳到 https://。哪怕你有可能已经设置了一个 http:// 的跳转,但你的浏览器不会访问,它会直接访问 SSL/TLS。这也避免了用户访问不安全的 http:// 而遭到劫持。比如它可以把你劫持到一个 https://stack<长得很像o但实际是个圈的unicode>verflow.com 上,那个站点甚至有可能部好了 SSL/TLS 证书。只有不访问这个站点才是安全的。

但这需要我们至少访问一次站点,然后才能有这个头部,对吧?对。所以我们有 HSTS 预加载,这是一个域名列表,随着所有主流浏览器分发且由它们预加载。也就是说它们在第一次访问的时候就会跳到 https:// 去,所以永远不会有任何 http:// 通信。

很赞吧!所以要怎么才能上这个列表呢?这里是要求:

  1. 要有一个有效的证书
  2. 如果你监听 80 端口的话,HTTP 应该跳到同一个主机的 HTTPS 上
  3. 所有子域名都要支持 HTTPS
  4. 特别是如果有 DNS 纪录的话,www 子域名要支持 HTTPS
  5. 主域名的 HSTS 头必要满足如下条件:
  6. max-aget 至少得是十八周(10886400 秒)
  7. 必须有 includeSubDomains 指令
  8. 必须指定 preload 指令
  9. 如果你要跳转到 HTTPS 站点上,跳转也必须有 HSTS 头部(而不仅仅是跳过去的那个页面)

这听起来还行吧?我们所有的活跃域名都支持 HTTPS 并且有有效的证书了。不对,我们还有一个问题。记得我们有一个 meta.gaming.stackexchange.com 吧,虽然它跳到 gaming.meta.stackexchange.com,但这个跳转本身并没有有效证书。

以 meta 为例,如果我们在 HSTS 头里加入 includeSubDomains 指令,那么网上所有指向旧域名的链接都会踩坑。它们本该跳到一个 http:/// 站点上(现在是这样的),一旦改了就会变成一个非法证书错误。昨天我们看了一下流量日志,每天仍有 8 万次访问的是通过 301 跳到 meta 子域上的。这里有很多是爬虫,但还是有不少人为的流量是从博客或者收藏夹过来的……而有些爬虫真的很蠢,从来不根据 301 来更新他们的信息。嗯,你还在看这篇文章?我自己写着写着都已经睡着 3 次了。

我们该怎么办呢?我们是否要启用 SAN 证书,加入几百个域名,然后调整我们的基础架构使得 301 跳转也严格遵守 HTTPS 呢?如果要通过 Fastly 来做的话就会提升我们的成本(需要更多 IP、证书等等)。Let’s Encrypt 倒是真的能帮上点忙。获取证书的成本比较低,如果你不考虑设置及维护的人力成本的话(因为我们由于上文所述内容并没有在使用它).

还有一块是上古遗留问题:我们内部的域名是 ds.stackexchange.com。为什么是 ds.?我不确定。我猜可能是我们不知道怎么拼 data center 这个词。这意味着 includeSubDomains 会自动包含所有内部终端。虽然我们大部分都已经上了 https:// ,但是如果什么都走 HTTPS 会导致一些问题,也会带来一定延时。不是说我们不想在内部也用 https://,只不过这是一个整体的项目(大部分是证书分发和维护,还有多级证书),我们不想增加耦合。那为什么不改一下内部域名呢?主要还是时间问题,这一动迁需要大量的时间和协调。

目前,我们将 HSTS 的 max-age 设为两年,并且不包括 includeSubDomains。除非迫不得以,我不会从代码里移除这个设定,因为它太危险了。一旦我们把所有 Q&A 站点的 HSTS 时间都设置好之后,我们会和 Google 聊一下是不是能在不加 includeSubDomains 的情况下把我们加进 HSTS 列表中,至少我们会试试看。你可以看到,虽然很罕见,但目前的这份列表中还是出现了这种情况的。希望从加强 Stack Overflow 安全性的角度,他们能同意这一点。

聊天

为了尽快启用 安全 cookie(仅在 HTTPS 下发送),我们会将聊天(chat.stackoverflow.com、[chat.stackexchange.com及 chat.meta.stackexchange.com)跳转至 https://。 正如我们的通用登录所做的那样,聊天会依赖于二级域名下的 cookie。如果 cookie 仅在 https:// 下发送,你就只能在 https:// 下登录。

这一块有待斟酌,但其实在有混合内容的情况下将聊天迁至 https:// 是一件好事。我们的网络更加安全了,而我们也可以处理实时聊天中的混合内容。希望这个能在接下去的一两周之内实施,这在我的计划之中。

今天

不管怎么说,这就是我们今天到达的地步,也是我们过去四年中一直在做的事情。确实有很多更高优先级的事情阻挡了 HTTPS 的脚步——这也远远不是我们唯一在做的事情。但这就是生活。做这件事情的人们还在很多你们看不见的地方努力着,而涉及到的人也远不止我所提到的这些。在这篇文章中我只提到了一些花了我们很多时间的、比较复杂的话题(否则就会太长了),但是这一路上不管是 Stack Overflow 内部还是外部都有很多人帮助过我们。

我知道你们会有很多的疑问、顾虑、报怨、建议等等。我们非常欢迎这些内容。本周我们会关注底下的评论、我们的 meta 站、Reddit、Hacker News 以及 Twitter,并尽可能地回答/帮助你们。感谢阅读,能全文读下的来真是太棒了。(比心)


原文链接: https://blog.alswl.com/2017/09/https-on-stack-overflow/
3a1ff193cee606bd1e2ea554a16353ee
欢迎关注我的微信公众号:窥豹
alswl's avatar

一个关于 nolock 的故事:深入理解数据库隔离级别

加入沪江不久,我就被扔到一个将集团 SQL Sever 的数据库迁移到 MySQL 的项目里, 同时伴随进行的还有 .net 系统迁移到 Java 系统。 在这个过程中我发现了一个很有趣的现象:历史遗留的 .net 项目中, 几乎所有的 SQL 中都会使用一个关键字:nolock。 这让我很困惑,nolock 的字面意思是对当前技术不使用锁技术,为什么要这样用呢?

我找了一个范例如下:

SELECT [id] 
FROM   [dbo].[foos] WITH(nolock) 
WHERE  aField = 42 
       AND bField = 1 

作为横向支持工程师,开发工程师会问我:「数据库即将从 SQL Server 迁移到 MySQL,我们编码中还需要使用 nolock 么? MySQL 里面对应的写法是什么?」。 我并没有 SQL Server 的生产环境使用经验,一时间无法回答。 于是课后做相关知识学习,这里就是这次学习的一点成果。

这个问题将被拆解成三个小问题进行回答:

  • nolock 是什么?
  • 为什么会需要在每个 Query 语句使用 nolock
  • MySQL 的对应写法是什么?

让我们一个一个来看。

第一个问题:nolock 是什么?

nolock 是 SQL Server 的一个关键字,这类关键字官方将其称之为 Hints。 Hints 的设计目的是为了能够让 SQL 语句在运行时,动态修改查询优化器的行为。 在语法上,Hints 以 WITH 开头。除了 WITH(nolock), 还有 TABLOCK / INDEX / ROWLOCK 等常见的 Hints。

让我们仔细看看 MSDN 文档上的解释:

nolock 的作用等同于 READUNCOMMITTED

READUNCOMMITTED 这是一种 RDBMS 隔离级别。 使用 nolock 这个关键词,可以将当前查询语句隔离级别调整为 READ UNCOMMITTED

计算机基础好的同学,应该对 READUNCOMMITTED 这个关键词还有印象。 而基础不扎实的同学,也许只是觉得这个关键词眼熟,但是讲不清楚这是什么。 如果阅读这句话完全没有理解困难,那恭喜你,你可以直接跳到下一节了。 其他朋友就跟随我继续探索一下 RDMBS 的世界,复习一下隔离级别相关的知识。

隔离级别

SQL 92 定义了四个隔离级别 (Isolation (database systems) - Wikipedia), 其隔离程度由高到低是:

  • 可序列化(Serializable)
  • 可重复读(Repeatable reads)
  • 提交读(Read committed)
  • 未提交读(Read uncommitted)

单单将这几个技术名词简单地罗列出来并没有什么意义,还有这几个问题需要搞清楚:

  • 隔离级别解决什么问题?
  • 为什么存在多种隔离级别?
  • 我们所谓的隔离级别从高到低,是什么含义,如何逐层降低的?

首先是「隔离级别解决什么问题?」, 用通俗的语言描述就是:加一个针对数据资源的锁,从而保证数据操作过程中的一致性。

这是最简单的实现方式,过于粗暴的隔离性将大幅降低性能, 多种隔离级别就是是为了取得两者的平衡。

接下来我们来回答第二个问题「为什么存在多种粒度的隔离级别?」 这其实是一个需求和性能逐步平衡的过程,

我们逐层递进,将隔离级别由低到高逐层面临进行分析。

Read Uncommitted

Read Uncommitted 这个隔离级别是最低粒度的隔离级别, 如同它的名字一般,它允许在操作过程中不会锁,从而让当前事务读取到其他事务的数据。

如上图所示,在 Transaction 2 查询时候,Transaction 1 未提交的数据就已经对外暴露。 如果 Transaction 1 最后 Rollback 了,那么 Transaction 读取的数据就是错误的。

「读到了其他事务修改了但是未提交的数据」即是脏读

Read Committed

想要避免脏读,最简单的方式就是在事务更新操作上加一把写锁, 其他事务需要读取数据时候,需要等待这把写锁释放。

如上图所示,Transaction 1 在写操作时候,对数据 A 加了写锁, 那么 Transaction 2 想要读取 A,就必须等待这把锁释放。 这样就避免当前事务读取其他事务的未提交数据。

但是除了脏读,一致性的要求还需要「可重复读」,即 「在一个事务内,多次读取的特定数据都必须是一致的 (即便在这过程中该数据被其他事务修改)」。

上图就是没能保证「可重复度」,Transaction 2 第一次读取到了数据 A, 然后 Transaction 1 对数据 A 更新到 A',那么当 Tranction 2 再次读取 A 时候, 它本来期望读到 A,但是却读到了 A',这和它的预期不相符了。 解决这个问题,就需要提升隔离级别到「Repeatable Read」。

Repeatable Read

这个名字非常容易理解,即保障在一个事务内重复读取时, 始终能够读取到相同的内容。来看图:

如上所示,当 Transation 2 读取 A 时候,会同时加上一把 Read Lock, 这把锁会阻止 Transaction 1 将 A 更新为 A',Transaction 1 要么选择等待, 要么就选择结束。

当我们将隔离级别升到这里是,似乎已经完美无缺了。 不管是写入还是读取,我们都可以保证数据的一致性不被破坏。 但是其实还有漏洞:新增数据的一致性!

上述的三个隔离级别,都是对特定的一行数据进行加锁, 那假如将要更新的数据还没有写入数据库,如何进行加锁呢? 比如自增表的新键,或者现有数据内的空缺 Key?

如图所示,在上述操作中,Transaction 2 查询了一个范围 Range 之后,Transaction 1 在这个范围内插入了一条新的数据。此时 Transaction 2 再次进行范围查询时候, 会发现查询到的 Range 和上次已经不一样了,多了一个 newA。

这就是最高隔离级别才能解决的「幻影读」: 当两个完全相同的查询语句执行得到不同的结果集, 这常常在范围查询中出现。

Serializable

从字面意思看,该隔离级别需要将被操作的数据加锁加一把锁。 任何读写操作都需要先获得这把锁才能进行。如果操作中带 WHERE 条件, 还需要将 WHERE 条件相关的范围全部加锁。

如图所示,在 Transaction 2 操作过程中,会对 Range 进行加锁, 此时其他事务无法操作其中的数据,只能等待或者放弃。

DB 的默认隔离级别

现在我们已经理解了隔离级别,那么「SQL Server 默认使用的隔离级别是什么呢?」 根据 Customizing Transaction Isolation Level 这个文档描述,SQL Server 默认隔离级别是 READ COMMITTED。

MySQL InnoDB 的默认隔离级别可以在 MySQL :: MySQL 5.7 Reference Manual :: 14.5.2.1 Transaction Isolation Levels 查询到,是 Read-Repeatable。

隔离级别并没有最好之说,越高隔离级别会导致性能降低。 隔离级别的设定需要考虑业务场景。

第二个问题:为什么要使用 nolock?

我们已经知道 nolock 的作用是动态调整隔离级别。 那为什么在 SQL Server 的 Query 操作中,需要启用 nolock 呢? 我问了几个工程师,他们都语焉不详,或者是很泛泛地说:禁用读写锁,可以提升查询性能。

此时我产生了困惑:「那么此时的数据一致性就不需要考虑了么? 我们的数据库,已经到了需要禁用锁的程度来进行优化了么?」 我于是自己去探索,想知道为何广泛使用 nolock 会成为一个「最佳实践」?

由于时代久远,我只能追述到一些相关信息,比如 Top 10 SQL Server Integration Services Best Practices | SQL Server Customer Advisory Team 中提到 「Use the NOLOCK or TABLOCK hints to remove locking overhead.」 但这个是针对于 SSIS 查询器,并不是针对业务内部使用。 反而能找到一大堆的文档,在反对使用 nolock 这个关键字。

继续追查下去,还从蛛丝马迹中寻找到一个使用 nolock 的理由, SQL Server 默认是 Read Committed, 更新操作会产生排它锁,会 block 这个资源的查询操作, 已插入但未提交的数据主键也会产生一个共享锁, 而此时则会 block 这张表的全表查询和 Insert 操作。 为了避免 Insert 被 Block,就会推荐使用 nolock

为了验证这是原因,我做一些 nolock 测试。

nolock 测试

检查当前 SQL Server 隔离级别,确认隔离级别是默认的 Read Committed:

SELECT CASE transaction_isolation_level
       WHEN 0
         THEN 'Unspecified'
       WHEN 1
         THEN 'ReadUncommitted'
       WHEN 2
         THEN 'ReadCommitted'
       WHEN 3
         THEN 'Repeatable'
       WHEN 4
         THEN 'Serializable'
       WHEN 5
         THEN 'Snapshot' END AS TRANSACTION_ISOLATION_LEVEL
FROM sys.dm_exec_sessions
WHERE session_id = @@SPID

-- ReadCommitted

创建表,初始化数据:

CREATE TABLE foos (
  id    BIGINT    NOT NULL,
  value NCHAR(10) NULL,
  CONSTRAINT pk PRIMARY KEY clustered (id)
);
INSERT INTO foos (id, value) VALUES (1, '1'), (2, '2');

在 Transaction 1 中发起 Update 操作(INSERT / DELETE 同理),但是并不做 Commit 提交:

BEGIN TRANSACTION;
INSERT INTO foos (id, value) VALUES (3, '3');

开启一个新的 Session,发起全表查询和新增 PK 查询操作:

SELECT * FROM foos;
SELECT * FROM foos WHERE id = 4;

不出所料,此时查询果然会被 Block 住。

MVCC

并发控制的手段有这些:封锁、时间戳、乐观并发控制、悲观并发控制。 SQL Server 在 2005 后,引入了 MVCC(多版本控制)。 如果最终数据是一致,会允许数据写入,否则其他事务会被阻止写入。 那么 MVCC 引入是否可以解决 Insert 数据的锁问题? 同样,我做了以下测试:

查询 SQL Server 使用启用 MVCC ALLOW_SNAPSHOT_ISOLATION:

SELECT name, snapshot_isolation_state FROM sys.databases;

使用 T-SQL 启用测试表的 SNAPSHOT_ISOLATION:

ALTER DATABASE HJ_Test3D SET ALLOW_SNAPSHOT_ISOLATION ON;

接着重复上面里面的 Insert 试验,依然被 Block 住。 看来 MVCC 并不能解决 Insert 锁的问题。

SQL Server 2005 之后还需要使用 nolock 么?

从官方文档和上文测试可以看到,在 Insert 时候,由于排它锁的存在, 会导致 SELECT ALL 以及 SELECT 新插入数据的相关信息被锁住。 在这两种情景下面是需要使用 nolock 的。

除此之外,有这么几类场景可以使用 nolock

  • 在 SSIS 查询器中进行数据分析,不需要精准数据
  • 历史数据进行查询,没有数据更新操作,也不会产生脏数据

我们需要思考一下,性能和数据一致性上的权衡上, 我们是否愿意放弃数据一致性而为了提高一丝丝性能? 以及我们有多少场景,会频繁使用 SELECT ALL 操作而没有查询条件?

微软官方在 2008 的特性列表里面,明确地指出 nolock 特性未来会在某个版本被废除:

Specifying NOLOCK or READUNCOMMITTED in the FROM clause of an UPDATE or DELETE statement.

而改为推荐:

Remove the NOLOCK or READUNCOMMITTED table hints from the FROM clause.

事实上,我听过不少团队会禁止在生产环境使用不带 WHERE 条件的 SQL。 那在这种模式下,产生相关的问题的几率也就更小了。 如果有很高的并发需求,那需要考虑一下是否需要其他优化策略:比如使用主从分离、 Snapshot 导出、流式分析等技术。

第三个问题:MySQL 的对应写法是什么?

终于轮到 MySQL 的讨论了。MySQL,InnoDB 天生支持 MVCC, 并且支持 innodb_autoinc_lock_mode AUTO_INCREMENT Handling in InnoDB。 这样可以避免 Insert 操作锁住全局 Select 操作。 只有在同时 Insert 时候,才会被 Block 住。

innodb_autoinc_lock_mode 支持几种模式:

  • innodb_autoinc_lock_mode = 0 (“traditional” lock mode)
    • 涉及auto-increment列的插入语句加的表级AUTO-INC锁,只有插入执行结束后才会释放锁
  • innodb_autoinc_lock_mode = 1 (“consecutive” lock mode)
    • 可以事先确定插入行数的语句,分配连续的确定的 auto-increment 值
    • 对于插入行数不确定的插入语句,仍加表锁
    • 这种模式下,事务回滚,auto-increment 值不会回滚,换句话说,自增列内容会不连续
  • innodb_autoinc_lock_mode = 2 (“interleaved” lock mode)
    • 同一时刻多条 SQL 语句产生交错的 auto-increment 值

这里也做了相应的测试。首先检查数据库隔离级别和 innodb_autoinc_lock_mode 模式:

SELECT @@global.tx_isolation, @@session.tx_isolation, @@tx_isolation;
SHOW variables LIKE 'innodb_autoinc_lock_mode';

检查后发现都是 Repeatable Read,innodb_autoinc_lock_mode 模式是 1。 然后创建测试表:

CREATE TABLE `foos` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8;

在 Transaction 1 中 Insert 数据:

START TRANSACTION;
INSERT INTO foos (name) VALUES ("a");

在 Transaction 2 中 Select 数据,可以正常查询:

SELECT * FROM   foos;

在 Transaction 2 中 Insert 数据,会被 Block 住:

START TRANSACTION;
INSERT INTO foos (name) VALUES ("a");

这个测试可以证明 MySQL 可以在 innodb_autoinc_lock_mode=1 下, Insert 同时 Query 不会被 Block, 但是在另外一个事务中 Insert 会被 Block。 结论是,由于 innodb_autoinc_lock_mode 的存在,MySQL 中可以不需要使用 nolock 关键词进行查询。

回顾一下

本文着重去回答这么几个问题:

  • 为什么要用 noloc
  • 为什么要改变隔离级别?
  • 为什么 MySQL 不需要做类似的事情?

虽然只凑足了三个 「为什么」 的排比, 但是聪明的读者仍然会发现,我是使用了著名的 五个为什么 方法思考问题。 通过使用这个方法,我们最后不但打破了老旧的最佳实践,还了解了本质原理, 并找到了新的最佳实践。

希望读者朋友在遇到困难时候,多问几个为什么,多抱着打破砂锅问到底的精神, 这样才能让每个困难成为我们成长的垫脚石。

相关资料


原文链接: https://blog.alswl.com/2017/09/sql-server-nolock/
3a1ff193cee606bd1e2ea554a16353ee
欢迎关注我的微信公众号:窥豹
jaseywang's avatar

由 rc_mksid 引起 pppd 奔溃的一个 bug

最近手贱想把手头的几台玩具机器统一下标准,其中一个标准是将 kernel.pid_max 增加到了 512000,结果就在当天的凌晨,一台跑着 pptp 的 VPS 崩溃了: Jun 20 22:53:09 jaseywang vps pptpd: ======= Backtrace: ========= Jun 20 22:53:09 jaseywang vps pptpd: /lib64/libc.so.6(__fortify_fail+0x37)[0x7fdba6416047] Jun 20 22:53:09 jaseywang vps pptpd: /lib64/libc.so.6(+0x10d200)[0x7fdba6414200] Jun 20 22:53:09 jaseywang vps pptpd: /lib64/libc.so.6(+0x10c709)[0x7fdba6413709] Jun 20 … Continue reading
alswl's avatar

当我们在聊监控,我们在聊什么?

最近在团队中给大家做了一个分享,泛泛地聊了一些有关「监控」的话题。 其实做分享对分享者的作用往往大于参与者。 这是一次将自己知识的梳理的过程,于是我将这次分享整理成这篇文章。

目的 🎯

我们先来聊聊,什么是「监控」,以及我们期望通过「监控」完成哪些目的?

传统意义上的监控,是指:

通过一些手段和工具,关注运行中的硬件、软件、用户体验的关键数据,将其暴露出来。 当关键数据出现异常时候发出警告,进行人工或者自动的响应。

我们平时看到的最常见的监控系统,比如 Zabbix,提供了丰富的模板, 可以监控服务器的 Load / CPU Usage / Alive 这些常规指标。 并在出现问题时候,对其进行报警通知。 随后运维工程师们会上线进行应急操作,case by case 的处理故障。

我将上面的使用目的归纳为:

  • 故障发生时提供数据报警
  • 提供历史数据以供分析

故事到这里似乎可以结束了,可监控真的是这么简单的么? 当然没,随着时代的进步,用户对服务提出了更为严苛的要求, 同时我们也有能力进一步控制平均故障修复时间 (MTBF), 上述描述的做法已经不能满足我们了。

现在让我们切换一下视角,从传统的 OPS 的视角切换到 SRE (Site Reliability Engineering)的视角。 当我们在关注网站整体的可用性时,我们会发现: 故障警报处理当然很重要,但是我们根本上想减少甚至避免 MTBF。 我们有两种手段: 一种是去除单点故障,让问题自然发生,但是不对线上造成影响; 另一种是在问题出现的早期就发现并进行及时修复。 前者是高可用范畴,后者就是我们今天关注的「监控」了。

监控的目的是要将灾难消灭在襁褓里;在灾难即将出现或者发生问题时,
给大家展示直接的原因

那为了达成这两个目标,我们需要回到问题的本质,重新思考两个问题:

  1. 监控哪些对象?
  2. 如何识别故障?

对象 🐘🐘

我们说的监控对象,一般指的都是某个资源, 资源即持有某种其他方需要的某些属性的载体,包括硬件、软件。 除了资源这种类型,还有一种常见的监控对象是「体验」,即终端用户的访问感受, 这块内容我们暂时略去。

让我们来先看一下常见的资源:

  • 硬件
    • 服务器
    • 网络设备
  • 软件
    • Application
    • Infrastructure

这个分类是粗粒度的描述,为了落地地描述监控对象对象的健康状况, 我们还要进一步细化。以「服务器」为例,我们可以将其监控的内容细化为以下监控项:

  • CPU
  • Memory
  • Network interface
  • Storage devices
  • Controllers

如何评估这些监控项的健康状况?我们使用 SLI(Service Level Indicator)。 比如可用性就是一个最容易理解的 SLI。 这里我将资源归为两类,面向用户提供服务的资源和面向存储的资源, 以下是针对这两类资源的常见 SLI:

  • User-facing Service
    • Availability
    • Latency
    • Throughput
  • Storage System
    • Latency
    • Throughput
    • durability

基于 SLI 建立的数字关键指标,称之为 Service Level Objective。 SLO 往往是一组数字范围,比如 CPU 负载的 SLO 可以设置为 0.0-6.0(针对 8 核 CPU)。 不同的资源、不同的业务场景,会有不一样的 SLO 设计。

看到这里,我们已经聊了要监控哪些指标,那么接下来我们聊聊如何用量化的思想, 帮助指标更易于识别、分析和决策。

量化的思想 🔢

刚开始担任线上救火队成员时候,当有个系统出现问题时候,我经常听到这样的描述: 网站挂了、页面打不开了,CPU 出问题了,内存爆了,线程池炸了等等。 这样的表述虽然没错,但带来的可用价值太少,信息熵太低。 这样的说辞多了,就给人产生一种不靠谱,不科学的感觉。

那怎样才能成为科学的描述? 古希腊哲学家在思考宇宙的时候,提出了一种心智能力, 从而打开了科学的窗子,这就是 Reasonable,中文名叫理智,这成为了自然科学的基石。 使用 Reasonable 探讨意味着探讨要深入问题的本质,不停留在表象,挖掘出真正有价值的内容。

但是光有 Reasonable 还不够,B站粉丝建了一个微博,每天会检查 今天B站炸了吗, 他只能告诉我们炸没炸,不能给工程师带来实际的用处。 在科学的发展历史上,我们可以发现在亚里士多德的著作里没有任何数据公式。 他对现象只有描述,只是定性分析,通过描述性状来阐述定理。 这个定性的研究方式到了伽利略那里才出现了突破。 这里我们可以引入第二个关键词是 Quantifier,量化。 伽利略率先使用定量分析的方法,并将其运用到动力学和天文学,从而开创了近代科学。

如果我们以定量的方式来描述网站挂没挂,就会变成:网站的响应耗时在 30s,基本无法使用。 描述线程池出问题,就会变成:active 线程数量是 200,已经到达 maxCount 数量,无法进行分配。 你看,通过这样的描述,我们一下子就能发现问题出在哪里。

USE 💡

现在我们已经了解了「监控哪些对象?」,以及尝试用「量化」这个法宝来「识别故障」。 那有没有一些最佳实践帮助大家高效的识别故障呢?这里我推荐 Brend Gregg 大神的 USE 方法。 Brend Gregg 是 Netflix 的首席 SRE,著有 Systems Performance Book, 目前已经出版中文版 性能之巅:洞悉系统、企业与云计算

USE 分别是三个单词的首字母缩写:

  • Utilization:使用率,CPU running percent,硬盘的 IO
  • Saturation:饱和度,一般偏存储型资源,内存使用,硬盘使用
  • Error:错误数

我们可以为每个资源找到各自的 USE 度量指标,具体的 Check List 清单可以参考 USE Method: Rosetta Stone of Performance Checklists

这里举个例子,前段时间在设计 MySQL HA 方案时候,同时关注了 MySQL 的监控方案, 那么针对 MySQL,我们要做哪些监控呢?下面是使用 USE 方法设计出来的 SLI:

  • Business
    • Questions:语句计总,Throughput
    • Slow_queries:慢查询计总,Error
    • Com_select:查询语句计总,Throughput
    • Com_insert:插入语句计总,Throughput
    • Com_update:更新语句计总,Throughput
  • Threads & Connections
    • Threads_connected:当前连接数,Utilization
    • Threads_running:当前使用中连接数,Utilization
    • Aborted_connects:尝试连接失败数,Error
    • Connection_errors_max_connections:由于连接数超标从而失败的连接数,Error
  • Buffer
    • Innodb_buffer_pool_pages_total:内存使用页数,Utilization
    • Innodb_buffer_pool_read_requests:读请求数计总,Utilization

完 🏁

如果你对我上面描述的还意犹未尽,建议你可以看 Effective Monitoring and Alerting。 虽然本书没有中文版,但是关于监控、报警的原理解析很到位,值得一看。 另外还有一本 SRE: Google运维解密, 里面有不少篇幅在讲「SLA」,也是和监控、报警息息相关的。

这次讲了一些概念性的内容,期望对大家有帮助,下一次我再分享一篇文章,聊聊 Metrics。


原文链接: https://blog.alswl.com/2017/06/monitoring-introducing/
3a1ff193cee606bd1e2ea554a16353ee
欢迎关注我的微信公众号:窥豹
Phoenix Nemo's avatar

NetFLOW / sFLOW 流量报告:FastNetMon + InfluxDB + Grafana

最近稍微有点时间折腾了下 Cisco 的三层交换,尝试搭建了一套数据中心用的流量统计/监控/报告系统。过程不是很复杂,但是也只算利用了一套高级软件组合的一点点功能。之后打算继续研究更多的功能实现,不过也要看有没有时间了…

准备工作

首先确认出口路由设备支持 netflow/sflow 的对应版本。一般 Cisco 的路由器或者三层交换都是支持的。

然后准备一个常见的 Linux 系统,虚拟机或者物理机都可以。

出口路由设备能够连通到该 Linux 系统,并且 flow collector 设置到该 Linux 系统的 IP 地址和对应端口。

FastNetMon

安装 fastnetmon,只需要一条简单的脚本命令。

然后将所有要监控的网段加入 /etc/networks_list。一行一个,例如:

1
2
3
10.1.0.0/16
192.168.254.0/24
8.8.0.0/16

按照安装文档打开两个终端,分别启动主进程和客户端

1
/opt/fastnetmon/fastnetmon
1
/opt/fastnetmon/fastnetmon_client

如果没有问题,应该在客户端上可以看到收到的 flow 数据。

先关闭 fastnetmon 进程,修改配置文件打开 Graphite 支持:

1
2
3
4
graphite = on
graphite_host = 127.0.0.1
graphite_port = 2003
graphite_prefix = fastnetmon

=== 2018-07-26 更新 ===

如果有比较新的发行版(内核 >= 3.6)可以开启 AF_PACKET,安装并启动 irqbalance 来获得更好的抓包性能。

InfluxDB

安装 InfluxDB,官方提供了各种包管理器的安装方式。

配置文件一般位于 /etc/influxdb/influxdb.conf,需要根据环境做安全相关设置(侦听地址、端口、鉴权、etc)并打开 Graphite Simulation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[[graphite]]
enabled = true
bind-address = "127.0.0.1:2003"
database = "flow_dc1"
protocol = "tcp"
consistency-level = "one"
name-separator = "."

# batch-size / batch-timeout requires InfluxDB >= 0.9.3
batch-size = 5000 # will flush if this many points get buffered
batch-timeout = "1s" # will flush at least this often even if we haven't hit buffer limit

templates = [
"fastnetmon.hosts.* app.measurement.cidr.direction.function.resource",
"fastnetmon.networks.* app.measurement.cidr.direction.resource",
"fastnetmon.total.* app.measurement.direction.resource"
]

顺序重启 InfluxDB 和 fastnetmon。检查 flow 数据是否记录到 InfluxDB:

1
2
3
4
5
6
7
8
9
10
11
$ influx
Connected to http://localhost:8086 version 1.2.4
InfluxDB shell version: 1.2.4
> use flow_dc1
Using database flow_dc1
> select mean(value) from networks where direction = 'incoming' and resource = 'bps' group by *
name: networks
tags: app=fastnetmon, cidr=10_1_0_0_24, direction=incoming, resource=bps
time mean
---- ----
0 4735.049632696411

Grafana

Grafana 是一款非常强大且易用的数据可视化工具。安装 Grafana 然后修改配置文件的必要部分,配置文件一般位于 /etc/grafana/grafana.ini

完成后重启 Grafana,将浏览器指向 Grafana 的 HTTP 服务器地址即可看到登录界面。如果内部使用的话,建议关闭匿名访问和注册功能。

使用默认的 admin / admin 登录,按照引导完成配置、添加数据源(Data source),数据源即是 InfluxDB 的 HTTP API 地址。如果 Grafana 中限制了数据源白名单,需要将 InfluxDB 的 HTTP API 地址和端口加到白名单里。

添加面板、Graph,在 Graph 编辑模式里写入类似这样的查询语句:

1
SELECT mean("value") FROM "networks" WHERE "direction" = 'incoming' AND "resource" = 'bps' AND "cidr" =~ /^10_1_0_0_16/ AND $timeFilter GROUP BY time($interval) fill(previous)

即可看到有图表出现。根据需求完善查询语句和图表配置即可简单实现各种可视化效果。例如流量和数据包的实时报告:

总结

通过配合 FastNetMon,InfluxDB 和 Grafana 即可快速实现一套基于 NetFLOW / sFLOW 的流量统计报告系统。但是 FastNetMon 的功能远不止流量统计,Grafana 也有大量插件和灵活的用法可以满足更多需求。如果配置合理,此方案也可适用于 40Gbps+ 接入的中型数据中心且成本低廉。以及——

  1. InfluxDB 真的很快!
  2. Grafana 的图表真的很省资源!
  3. Chronograph 卡死了我的浏览器!(i7-7700K / Chrome)

以及一大早手工修好了 K812 的耳机线,省掉了 2 万日元的线材费用非常开心

alswl's avatar

XSS 攻击的处理

这是一年前写的项目笔记,一直在我的待办事项里等待做总结,今天偶然翻到,就整理成文章发出来。 谨以此文怀念 乌云


事情缘由

春节前的某一天,收到一封来自乌云(国内知名白帽子团队)的邮件, 告知我厂网站上出现一例 XSS 漏洞。 因为以前对 XSS 输入做过防御,还以为是某个前端 DOM 上的 XSS 漏洞, 后来仔细一看,不妙,是个影响甚大的存储型 XSS 漏洞。

这里简单科普一下 XSS 跨网站脚本 -维基百科,自由的百科全书 中介绍到:

跨网站脚本(Cross-site scripting,通常简称为XSS或跨站脚本或跨站脚本攻击)是一种网站应用程序的安全漏洞攻击,是代码注入的一种。 它允许恶意用户将代码注入到网页上,其他用户在观看网页时就会受到影响。这类攻击通常包含了HTML以及用户端脚本语言。

XSS 攻击可以分成两种,反射性 XSS / 存储型 XSS。前者是需要用户触发的 XSS, 针对当前用户的攻击行为。而后者存储型 XSS 则更为严重,一旦攻击代码被保存, 所有访问被攻击的页面,都会触发用户被攻击行为。

这次爆出的问题就是最严重的存储型 XSS,意味着每个访问到有问题页面的用户都会中招。 时间紧迫,问题必须被解决。

XSS 实现手段

在解决问题之前,需要对这个问题有必要的基础认识。 我们先看看 XSS 攻击是如何工作的,以及攻击者的目的是什么。

XSS 的原理是通过构造特殊的数据,并通过传递参数或者保存表单数据的方式, 让这些构建的数据破坏 DOM 结构,从而让自己预先构造数据中的 JS 脚本被执行。

检查存储型 XSS 漏洞的方法,可以在对应的 input field 里放入一些构造的数据,如果保存后可以被执行,就说明存在 XSS 漏洞。

常见的检测方法(来自 跨网站脚本 - 维基百科,自由的百科全书

><script>alert(document.cookie)</script>
='><script>alert(document.cookie)</script>
"><script>alert(document.cookie)</script>
<script>alert(document.cookie)</script>
<script>alert (vulnerable)</script>
%3Cscript%3Ealert('XSS')%3C/script%3E
<script>alert('XSS')</script>
<img src="javascript:alert('XSS')">
<img src="http://xxx.com/yyy.png" onerror="alert('XSS')">
(这个仅限IE有效)

攻击者通过 XSS 可以窃取用户的相关信息,如果用户是管理员,那么影响更大。 通过这些身份信息,攻击者可以进一步篡改信息或者进行诈骗,后果不堪设想。 PS:一个有效粗暴的方式,是将对公、对内系统的域名分离,对内部系统进行物理级别隔离。

我厂历史上的处理方案

XSS 问题又来已久,咱厂子开了这么久,历史上如何防御的呢? 答案是用了两个策略:第一个是使用 OWASP 提供的库进行内容过滤,第二个是在存储数据时,存储了转义后的数据。

在技术上处理 XSS,有两种态度可以选择:第一种是前置过滤,即将用户所有数据都进行转义, 在输出时候在前端(模板渲染)层面直接输出。 第二种是用户输入的数据不经过转义就直接存储起来,前端在使用时候保证对数据进行转义。

我厂历史上使用的方案的前者,优点是在于前端不需要在每个地方转义, 避免某个地方忘记了转义,从而导致漏洞。缺点则是在输出内容到非 Web 客户端时候,比如 APP,需要进行额外的数据处理过程, 否则 HTMLEncode 的内容,在 APP 上面无法正确输出。

这个处理方案是稳妥的,那么为什么最近又暴露出问题了? 排查之后发现,原来最近有若干个服务迁移到了一个新系统, 而新系统在安全上面没有全局处理,所以爆出了漏洞。

本次处理方案

知道了原因,那么可以快速解决问题了。在这次处理过程中,我们讨论了在当前移动平台增长迅速,Web 平台增长缓慢的大势下,能否直接存储用户原始数据? 而且由于规范制定不严格,目前系统内有些地方存储转码后数据,有些地方存储转码前数据。 导致在一些特殊的字符(颜文字)处理上不一致,从而导致在处理 br / < 这类特殊字符时,表现不同。

由于 DB 中有部分数据转义处理,部分数据原文存储,所以先处理输出后敏感信息,在模板层面启用全局 encode。 将有危险的数据转移为在 HTML 文本。

PS:现代 Web 框架的模板渲染引擎,一般会默认开启 HTMLEncode,而 Freemarker 居然在 2.3.24-rc01 才支持,现在都没有发布,唉……

处理方案:

  • 开启全局 HTML 输出 Encode,有一个 Default HTML-escape using Freemarker 方案,可以默认开启 Html Encode,在这个处理方案中,需要注意有些地方真的需要输出原始 html,需要 noescape 特殊处理
  • 检查所有前端操作,禁止字符串拼接,使用框架支持的模板进行渲染,拖小菊的福,新系统在这块工作完成度一直比较好
  • 将 OWASP 方案强制开启

其他 Tips

OWASP 有一个很长的 列表,教导如何避免 XSS,里面提到了:

  • 为何以及如何进行「积极防御」,对立面是仅仅输出时候转义内容本身
  • 几条对抗 XSS 的规则
    • 尽量不在特定地方输出不可信变量:script / comment / attribute / tag / style, 因为逃脱 HTMl 规则的字符串太多了。
    • 将不可信变量输出到 div / body / attribute / javascript tag / style 之前,对 & < > " ' / 进行转义
    • 将不可信变量输出 URL 参数之前,进行 URLEncode
    • 使用合适的 HTML 过滤库进行过滤
    • 预防 DOM-based XSS,见 DOM based XSS Prevention Cheat Sheet
    • 开启 HTTPOnly cookie,让浏览器接触不到 cookie

最后送上一个 XSS 攻击工具 http://webxss.net/,知己知彼,百战不殆。


原文链接: https://blog.alswl.com/2017/05/xss/
3a1ff193cee606bd1e2ea554a16353ee
欢迎关注我的微信公众号:窥豹
Phoenix Nemo's avatar

不作死就不会死系列,TFTP 修复变砖的 Nighthawk X6

由于之前买的 AC87U 经常被 roommate 抱怨掉线(风评表示这货 5G 有问题,然而我连着 5G 毛事儿没有,隔壁用 2.4G 却一直掉线)…

新购入的路由器是 Netgear Nighthawk X6 R8000。

由于之前的 Security Advisory,所以到手第一件事就是配上网络更新固件啦。更新挺慢的于是点点点完事儿撸猫去了。过了一会儿回来一看怎么还没网络?得,砖了…

讲道理,Netgear 也算大厂了,这种 online update 干了不知道多少回,第一次遇到这都能变砖的(扶额。

现象就是电源橙色灯亮后一会儿变成白色灯闪烁,且网络服务没有启动。尝试过 factory reset 无效,官方提供的 TFTP 强刷工具也无效(刷不进…

解决方案反而是意想不到的简单。总之大概记录下修复的过程。

  1. 官方网站下载适用的新版固件并解压,应该得到一个 .chk 文件
  2. 关闭路由器电源等待 10 秒,网线插 LAN 口开机。
  3. 检查是否获得了正确的 IP。如果没有,可能 DHCP 服务没起来。手动设置一个正确的 IP 吧。然后能 ping 通路由器 IP 即可。
  4. 电源灯开始闪烁的时候,执行命令 tftp -i [router ip] put [path/to/firmware.chk]。例如 tftp -i 192.168.1.1 put ./R8000-V1.0.3.36_1.1.25.chk
  5. 等一会儿路由器自动重启,搞定。

配置都没丢…然后我依旧没有搞定 OCN 要怎么连 IPv6… 说好的 IPv6 PPPoE 呢…

Phoenix Nemo's avatar

Minecraft 服务器资源控制策略:AI 抑制而非数量限制

Minecraft 的 lag 问题已经司空见惯,各种控制资源消耗和卡顿的插件也层出不穷。但是它们几乎都非常用力地在一个点上:控制实体数量。

这并不无道理,因为 Minecraft 中最消耗资源的部分就是实体。但是暴力控制实体数量会导致刷怪塔无法正常工作、掉落物清理速度过快等问题,在生存服务器中可能引发玩家的强烈不满。

所以,喵窝开发组从另一个角度做出了一些尝试。

启发

生物实体的数量巨大,主要集中的地区显然不是野外的自然刷怪区,而是玩家聚集的刷怪场、村民工程、动物养殖场等。如果不限制生物的数量和密度同时降低资源消耗,那么只能从生物实体的特性入手了。

Minecraft 最近的版本中引用了 NoAI 的 NBT Tag,带有此标签的生物将不会进行 AI 计算。换句话说,除了占用服务器内存中的一点数据,几乎不会对这个生物实体有任何其他的 CPU 算力消耗。

也就是说,实体消耗的算力资源,绝大部分都是 AI 计算的消耗。

方案

抓上一票人做了一些测试,结果证实生物失去 AI 后大幅降低了 CPU 的算力消耗。这是个 positive 的信号,但是接下来的测试则遇到了问题。

对于养殖场,等生物数量变化不大(或者说只是定期来清理并重新养殖一次)的设施,生物失去 AI 的影响很小,只有在重新繁殖时需要恢复 AI。但是刷怪塔则因为生物没有 AI,同时也被强制不受重力影响而几乎无法使用,即便同时设置 NoGravityfalse 也无效。

开发组中 @Librazy 提到了 Spigot 的一个参数 nerf-spawner-mobs,开启时刷怪笼生成的生物将不会拥有 AI,但是会被外界影响(例如水流和火球等)而移动。这个选项是全局的,因此不需要开启,只需要反射 spigot 中设置该功能的方法即可。

于是整个方案的流程便是当服务器卡顿时抑制生物密集区的生物 AI 从而降低资源占用,同时最大程度上保证玩家对生物的需求。「服务器卡顿」的考量以服务器 TPS 而非实体数量为准,当服务器 TPS 高于一定值时即认为服务器没有超负荷,不会有任何操作,最大程度上利用硬件的性能。

实现

插件主要由开发组的 @Cylin@Librazy 编写,源代码以 MIT 协议发布在 GitHub 上。

插件每隔一段时间扫描服务器的 TPS 确认运行状况,如果 TPS 低于阈值则触发 AI 控制,TPS 高于一定值且持续一段时间即认为服务器已恢复正常运行状态,自动恢复被抑制的实体 AI 减少对生存体验的影响。

实现过程中额外添加了一些额外可能被生存服务器用到的功能:

  • per-world 控制,如果玩家需要建造以仇恨为基础的小黑塔,可以关闭对末地的控制。
  • 实体总量和单区块实体密度在 AI 抑制时纳入考虑,更加精准抑制资源消耗较高的区块。

测试

yasui 插件在 毛玉線圈物語 服务器中应用测试。由于近期玩家数量爆炸式增长(日常在线 5 人到 ~30 人甚至 50 人),各种实体控制插件均告无效。yasui 插件应用后被证实数次发挥作用,没有任何实体数量限制的前提下将服务器 TPS 稳定在 19 以上,服务器实体承载数量从 ~2500 提到至接近 5000,并且还有继续提高的可能(数次触发中最高一次单世界实体记录是 4808,其他世界中仍有大约 2000 实体未被计入)。

吐槽:你们贼能刷

jaseywang's avatar

全网统一账户实践

分享下目前我们全网的账号管理体系。

整体的账户管理思路是分而治之。主要分为下面三类账户:
1. 办公网账户,也就是大家熟悉的域账户。对于办公网账户,全网用户一人一账户,在 OpenLDAP 的基础上做了一些开发,这是进入公司内部的大门,所有新入职的员工都会分配一个该账号,不管是在办公室连接 Wi-Fi 还是在家连接 anyconnect VPN,访问 confluence/jira 等基础办公设置,都需要通过此账户进行登录认证。
2. 生产网账户,主要用来访问线上、线下机器资源。工程师访问线上生产、测试机器,登录线下自助机树莓派(raspberry) 均需要通过此账户认证,整个 user/group 的分配、HBAC/sudo 的控制、密码/公钥的管理均在 freeIPA/IDM 上实现,freeIPA 是 RedHat 支持的一整套集成安全信息管理解决方案系统,又称 IDM。
3. 数据库账户,这块比较小众,简单带过。
下面会针对上面两大块分别介绍。

办公网账户

该账号与所有人息息相关,从入职第一天起到你的 lastday,都需要登录该账户才可以访问办公资源,包括常见的 Wi-Fi/Gitlab/Jira/Confluence/Jenkins/Cisco Anyconnect VPN/Zabbix/Grafana/跳板机(intermediate host) 以及自己开发的各种内部系统。

这里面需要先简单描述下目前我们的多网分离的架构,多网是指办公网(包含国际线路)、生产网、测试网、专线网(到医院 HIS/LIS 等信息系统)、OOB(带外管理网),这五张大网是处于「部分」分离的状态的,比如,办公网到生产网/测试网/专线网/OOB 是完全分离的状态,除非通过跳板机(后面全部以 ih 代指)登录之后再访问;生产网跟测试网也是几乎分离的,除了极个别的公共服务。以办公网登录生产、测试网为例,在进入用户验证这步之前,会先通过 IP <-> MAC <-> 用户身份的一一映射绑定,先通过最基本 IP:PORT 的 ACL 方式来控制住大部分的请求。所以在你能通过网页打开 git,通过 ssh 协议 pull/push git 仓库的时候,说明已经通过 TCP/IP 层面的验证机制以及用户级别的验证机制,具体如何实现的会在后面的博客中说明。

默认情况下,OpenLDAP 支持的 schema 非常有限;另外不同应用接入方式都不大一样,需要逐一尝试。

比如 LDAP 跟 Anyconnect 的对接,官方提供的 schema 仅仅基本可用,离生产还有一段距离,默认只支持添加一组 IP/Netmask,以及一条 IP 层面的 ACL,为此需要自行扩展维护一套 cisco.ldif 文件,这个是我们自行维护的一个 schema 文件,根据我们业务的情况,新增了一组 IP/netmask 以及若干的 ACL。下面是一个普通用户的数据文件示例:

从 slapcat 导出的字段数据可以看到,为了实现 Wi-Fi 账号跟 OpenLDAP 的共享,加入了 samba 相关的 schema,具体的可以看这个文件

再比如,对于 anyconnect/ih 的认证,需要增加两步验证机制,这里引入 OTP(privacyIDEA) 以及 Radius 作为跟 OpenLDAP 的桥梁例,具体的交互图可以看这里:

最终实现了用户的 CRUD 在 OpenLDAP 层面控制。用户的两步验证,下图可以看到,privacyIDEA 支持十余种的 token,包括 hOTP/mOTP/sms/email/Yubikey 等等:

privacyIDEA 不仅仅支持我们使用的 ldapresolver,sql/passwd/scim 的 resolver 都有很好的支持。目前我们使用 Google Authenticator 是基于事件的 HOTP(RFC4226):

用来管理 OpenLDAP 的工具有不少,包括我们使用的经典的 phpldapadmin,除此之外,fusiondirectoryweb2ldap 都是不错的选择。同时为了支持用户的自助修改、重置密码,定期修改用户密码,我们引入了 LTB,一个非常神奇的 LDAP 工具箱集合,目前仅支持通过邮件找回密码的链接来重置密码:

除了自助机服务,LTB 还支持 LDAP 性能、用户使用数据的监控等若干非常实用的脚本。
这么重要的基础服务稳定性肯定是需要保障的,通过 syncprov 模块实现双 master 的写的高可用,通过多个 slave 同步 master,前端挂 Haproxy 实现读的高可用:

对用户来说,他们看到的是下面这个样子:

PACKT 出版的 Mastering OpenLDAP 是本通俗易懂的书,浏览一遍之后应该能轻松应对上面的内容。 

生产网账户

这里的生产网泛指包含线上生产测试服务器线下自助机树莓派等在内的所有 *nix 系统。所有需要登录如上机器的用户都需要获得该账户的权限之后,才能访问基于 HBAC 机制的主机以及颗粒度更细致的用户执行权限。

整个系统基于 freeIPA/IDM 实现,默认情况下,每台机器有一个专门用来运行进程(我们所有的 JVM 进程都跑在 vm 上,不会出现混跑的状态)的 worker 用户,所有的相关的 bin/, log/, lib/ 等目录文件都通过 Jenkins 统一打包到 ~/worker/ 下面,实现跟系统文件的隔离。/home/worker/ 的权限如下:

worker/ 下面所有的目录文件权限都如上所示。这样,只要用户在 bm_be_sre 这个组下面,就能实现 worker/ 下面也即 JVM 相关服务的读取权限:

下图可以看到,appCenter 这个服务是跑在 worker 下面的:

类似的,如果想实现 worker/ 下面的写操作,比如执行 bin/ 下面的脚本,将用户加到 30002(sre) 这个组即可,这里完全可以各自的需求自定义。所有的 sudo/su 相关权限设置全部在 IPA 的 Policy 操作即可,比如下面这个赋予的是 sre 这个组的用户可以执行 /bin/su – worker、/bin/su worker 这两个单独的比较危险的命令以及 sre_allow 命令行组里面基础命令(/bin/chmod, /bin/chown, /bin/cp 等等):

目前单台 8G/4core 的 IPA 支撑着 1000+ 的线上生产测试的物理虚拟机以及线下 100+ 的树莓派,应付起来绰绰有余,CPU 平均 1% 利用率、disk IO 平均 10%。

对我们的用户来说,在经过办公网/VPN 的 IP ACL 过滤之后,通过办公网账号结合 OTP 登录线上的任意一台跳板机(ih),然后通过 ih 再登录线上的机器,跟办公网账户类似的是,需要定期修改密码。对有权限看到的用户来说,他们看到的是下面这样

以上的两块账户体系算是把当年在阿里的基础核心账户体系复制了一遍,对最终用户来说,体验相对来说会更好些,当然我们没法也暂时没必要实现类似阿里郎那种监控每台入网设备所有操作的系统。

需要注意的是,在 IPA 上新增的用户,或者新增 sudo 权限,由于 sssd 的缓存,不会立即生效,可以通过 sss_cache 或者删除缓存文件可以让规则立即生效,这部分可以以类似 hook 的方式触发,每次修改用户权限的时候自动触发该操作。

freeIPA 结尾的 A 表示 audit,实际情况是目前还不支持 audit 功能,所以这块功能我们通过 snoopy 结合 rsyslog 方式进行收集审计

对于办公网账户,目前通过 python-ldap 实现了多个层次的自助以及服务的打通,freeIPA 还未进行这块的开发工作,最终的效果类比阿里内外,一个 portal 实现不同用户的自助服务。

数据库账户

第三块,或者说非常小众的一块账户是 DB 的账户,这里面主要涉及 MySQL 以及 NoSQL(Redis/ElasticSearch) 的账户划分管理审计。

MySQL 的账户管理我们通过 saltstack 的 mysql_user 进行管理,我们用的saltstack 是 2015.8.7,对 MySQL 5.7 的支持有不少 bug,2016 开头的版本对此都修复了,嫌麻烦的不妨直接把 salt 给升级了或者做 diff 给 2015.8.7 打 patch,主要原因是 5.7 之前存储用户表的密码字段是 password 而 5.7 之后变成了 authentication_string。

目前所有的 MySQL 账户用户通过 git 管理而密码通过 salt pillar 管理,审计则使用了 Percona 的 Audit Log Plugin 通过 rsyslog 打到中心服务器收集分析。

对于 Redis/ES 来说,由于上面存储的数据为非敏感数据,所有的都通过 IP 层面的 ACL 进行控制访问。

jaseywang's avatar

一张复杂网络的生长过程

虽然不是网络出身,但是对于我们全网架构的「生长」过程还是比较了解的,分几个重要的时间点讲讲里面有意思的事情。

15 年下旬

公司业务还没起飞,全网架构简单,应付一天 1k+ 的订单了(有效挂号单)绰绰有余,机房跟第三方的卡管机构通过一根 10M 的 SDH 专线打通,所有到院方的请求先经过第三方的卡管机构,由其转发再进行业务层面的处理,实际的带宽峰值不到 500kbps。

16 年初

正式开始批量接入三甲医院,同时跟医院数据打通的方式也由原先的经过第三方卡管机构的非直连方式变成了直连方式,所有接入的院区通过两根双活的专线跟我们机房打通,两根专线的意义很重大,一旦一根专线被挖掘机挖断,可以在 1s 之内切换到另外一根,上层业务通过重试机制可以做到几乎无感知。新闻上报道的某某地区由于光纤被挖掘机挖断造成网络中断的问题在后来专线数量增加的情况下时有发生,但是到目前为止对于这种双主的架构并无影响,考虑到成本等因素,暂时不会做 N + 2。

为了控制 IP 地址的分配以及部分安全性的考虑,跟院方接入全部采用双向 nat 的方式,双向 nat 的问题之前的博客有过介绍,只不过之前是通过 iptables 实现,本次通过专业的路由设备实现。经过双向的 IP 地址转后,我们对院方的地址全部隐藏,另外由于院内自助机的 IP 全部由我们自行分配,通过双向 nat 之后,我们有更大的自主权。下面这张图演示了 IP 地址转换的过程:

与此同时,为了快速实现新接入医院的稳定上线,保守采用了双防火墙双路由器的策略,通过 BGP/BFD 协议实现单条线路失败的快速收敛,总成本控制在 20K 左右:

医院的数据通过一根 2M 的专线,一根 10M 的专线汇聚到我们的核心机房,核心机房通过主流的三层结构互联。

对于办公网来说,为了实现访问 google 等国际联路,我们通过在办公网的路由器上设立 GRE 隧道的方式连接到国内的专线服务商,再由该服务商进行国际流量的转发,由于跨了国内的公网环境,会出现偶尔的延时增高以及中断的问题。

16 年中

随着接入院区的爆发式增长,成本成了很大的一个问题,原先所有院区接入同一个核心机房的方式不仅成本无法得到控制,单点也成为了一个迫在眉睫的问题。原先三层的简单架构也无法满足日后的水平横向扩展。为了解决上面的问题,花了大量时间跟北京具有运营商资质的公司进行了大量的商务沟通,实现了专线、带宽资源的大幅度下降。在保证稳定性的前提下,2M 的专线做到了平均 450RMB/m,10M 的 1500RMB/m,50M 的 4000RMB/m,100M 的 6000RMB,同时引入了阶梯价格的策略,量大会更加具有竞争力,当然三大运营商的价格会比鹏博士、天地祥云的高些,毕竟稳定性也高的多(这个后来验证确实如此)。最终跟北京三大运营商以及几家准运营商达成了友好的合作,实现了双方的共赢。上面的问题谈妥了,接下来自然引入了下面几个核心思想:
1. 链路类型,优先选用 SDH/MSTP 协议的,G.703 次之。
2. 运营商,成本跟稳定性下的权衡,优先选用电信/电信通,联通/天地祥云次之。
3. 专线接口类型,优先选用双绞线电口,光纤单模次之,方便排查问题,同时默认接入千兆全双工,百兆全双工作为备选。
4. 建立中立的中转机房,所有院区的专线先通过中转机房再进入核心机房,实现了跨区域专线的成本以及稳定性的大幅提升。
5. 在上条的基础上,全网引入骨干网 msr(metropolitan small router)、核心网 csr(core small router)、汇聚网 csw (core switch),专线 dll(dedicated leased line),边界路由器 edr(edge router),边界交换机 esw(edge switch)。优先选择中低端廉价设备,比如 msr 使用 Cisco2921/K9,csw 使用 WS-C3750X-24T-F,esw 使用 WS-2960X-24TS-L 等型号,通过 HSRP/BFD 等协议实现分而治之。

着重说下最后的两点,对于第 4 点,其架构图如下:

可以看到,这一阶段引入了 msr/csw 等角色,将运营商的专线接入诸如鹏博士、天地祥云等中立机房的进行中转,最终汇聚到核心的机房。每一台中转机房的 msr 承载 20 条左右的专线,通过上方的 csw 进行线性扩展,目前每条专线的带宽在 2~10M 左右,所以不会给机器造成很大的压力。

对于第 5 点,在核心骨干汇聚分成之后,一方面是实现了多网分离,即生产网、测试网、OOB 网,办公网以及到院方的专线网的分离,极大的方便了 tcp/ip 层面的访问控制,在这个阶段,所有对人的访问控制(人访问线上线下)全部通过在网络设备上加白名单的方式实现,算是实现了基础的访问控制,虽然比较繁琐。另外一方面全网由之前的树根结构扩展到了树根枝干末梢叶子的网状结构,扩展性以及冗余性都有了很大的提高,下面这张图是相对比较成型的网络拓扑:

这个阶段,除了院内、机房的大幅度改造之外,办公网尤其是国际网络也做了优化,从原先的 GRE 隧道方式一步到位成了专线直连方式,办公网通过专线直接到服务商节点,节点直接到第一线的香港节点,同时,为了避免所有流量都走国际网络,我们在内网通过 DNS view 的方式,将主要被墙的域名解析丢给 Google DNS,其余则解析丢给国内 DNS,DNS 层面解析结束之后,TCP/HTTP 的流量则通过在路由层面通过不同的 ACL 规则实现国内国际流量的划分。下图是日常访问 Google 的 ICMP 延时:

16 年底

为了进一步控制成本,响应国产化的号召,原先一家院区 4 台设备(2 台防火墙、2 台路由器)的架构,在保证稳定性不变的情况下,优化成了由 2 台带路由功能的防火墙(USG 6306)组成的专线网络:

在同样秒级切换的情况下,成本降低到了原先的一半不到。同时,考虑到后续接入非三甲院区的特殊性,目前正在调研舍弃大型物理硬件,通过 4G 网卡配套 VPN 的方式来进行网络层面的直连。

在优化成本以及性能的基础上,还进行了下面几点的优化:
1. 通过 PowerDNS 的 API 实现了上千条内网的 DNS 正反向解析,这对于使用 mtr 细颗粒度的排查定位问题提供了很大的帮助
2. QoS 的优化,优先走生产上的业务流量,其次生产上的非核心(日志、监控等),在 msr 上通过 CoPP 实现
3. 全网设备的配置备份问题,所有设备的配置每日自动备份到内网的 gitlab 上
4. 为了实现生产网访问被墙的资源,在办公网搞了两套 tinyproxy,生产的机器通过 http_proxy,访问前端的 Haproxy 实现了网络资源的自由访问
5. 办公网无线的 LDAP 认证,这块在全网统一账户实践有所涉及

从 15 年底到 16 年底的一年多时间,通过不断的演变,一个相对比较完善有很强冗余性的大网基本建设完成,该架构支撑目前每天的 100k 订单(有没有发现一年期间增长了多少倍),40k 的有效挂号量没有任何瓶颈。

在建设初期遇到不少跟院方通信的网络中断问题,起初做贼心虚以为是自身的问题,后来通过监控不断的完善,实现了分钟级别的问题发现与诊断。

下面列一些有趣的问题:
最常见的,某院区由于专线被挖断,单条专线中断,平均一个月一次。院方机房全部或者部分掉电,导致所有专线中断;院方机房制冷设备故障,导致专线设备罢工死机。院方线下自助机网络设备故障,导致该区域自助机全部脱网,类似的事情不一而足。再比如我们的 RPC 服务访问某院区 HIS,凡是下一跳为 10.222.0.45 均能够访问终点 IP,凡是下一跳为 10.222.0.41 则无法访问同样的的终点 IP,经过抓包发现由于院方的两台核心路由故障导致。最喜剧的性的事,某院区偷懒将我们跟 HIS 直连的 IP 划分到他们的核心网络,由于 arp-proxy 的问题导致全院 HIS 宕机数个小时。当然医院 HIS 宕机其实是件很频繁的事,之前仅仅因为处在一个封闭落后并且无知的环境,很多时候重启应用重启机器问题就算是绕过去了,现在一下子暴露在了一支「正规军」面前,一下子露馅了。

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
欢迎关注我的微信公众号:窥豹
Li Fanxi's avatar

2017新年好

2016年,我做了这些事:

– 写了8篇博客

博客空间总访问量61198 PageView(Google Analytics数据),比前一年稍有下降,但是实际的访问量应该还没有这么多,因为发现Google Analytics结果中出现了相当数量的Spam数据,暂时还没研究怎么能去过滤掉。跟去年一样,首页、在Linux下使用“360随身WiFi 2”calibre常见问题为Raspberry Pi 2编译内核模块这几个页面的PV占去了总PV的50%多。饭否发消息85条,包括照片17张。

– 自由软件相关

接手了一个网站:Linux Kernel Patch Statistics。这个网站的内容是按人、公司、国家等维度的指标去统计各Linux内核版本中Patch的数量。我很偶然地看到有人在LKML中吐槽说这个网站的域名过期了。这个网站的作者是我以前的同事,于是我联系他提醒他,没想到他表示说不打算继续维护这个网站了。我觉得就这么放弃一个在社区有一定影响力的网站有点可惜,所以在征得他同意的前提下,把这个网站接了过来,并且还请朋友帮忙把过期的域名给抢注了回来。

网站恢复运行的不到一个月时间中,我已经收到各种数据订正、添加功能和Bug报告的邮件,看来这个网站的的价值比我想象的还大一些。不过这个网站的后台代码确实是经久失修,所以目前数据统计的精准度非常糟糕(因为根据邮件地址把数据按公司、国别来归类,这里面的映射关系绝大部分是需要人肉来维护的,一旦没有及时维护,归类为Unknown的数据就会越来越多,也就失去了统计的意义),而且每天一次全量数据产出过程需要占用大量的CPU、IO和内存资源。所以后面需要优先先维护一下基础数据,保证统计数据质量,然后再考虑下整体的重构问题。

calibre贡献了一点点代码,改写了一下从Amazon获取书籍元信息的插件,使之可以支持中国亚马逊网站。给HBaseFlink的代码/文档各修正了一处Typo,其实只是为了实践一下给这两个项目Contribute的流程,不过后来由于工作内容的变化,没有再深入关注过这两个项目。给C++异步框架Seastar修正了一处Bug。train-graph合并了一些其它人贡献的代码和数据,发布了一个3.0版本。

– 几个IT产品

群晖DS916+ NAS:淘汰了原来用的DS214play,主要是出于盘位和性能的考虑。不过新的机器的性能依然很让人捉鸡。不过出于对DSM系统版权的尊重,我还是没有选择自己买机器组黑群晖的方案。我的群晖系统的评价依然是:轻度使用很不错,重度使用时细节缺失很多、问题也很多。但是市面上已经找不出更好的了。

Macbook Air:公司提供的工作电脑,我的第二台苹果设备(N年前得到过一个iPod Nano)。这样的电脑用来做开发机实在是性能捉鸡,尤其是为了编译Linux程序再启一个Docker的情况下。公认的优点就不说了,缺点就是有些Windows能做的事情它还是不能做,而有些Linux能做的事情它也不能做。对于我这种已经被Linux虐了十年的人来说,不能做Windows能做的事是很容易接受的,但现在不能做的事的又变多了,所以还有点不爽,于是有了下面的Dell 7040m。

Dell 7040m微型台式机:为了更有效的开发Linux C++程序,买了这个微型台式机当工作机。配置成i7 6700T的CPU,16G内存,SM951的SSD,装Arch Linux。实际用下来整体能满意,但是就编译大型C++程序来说,单核性能仍然不是非常出色。另有同事买了相似配置的Intel的Skull Canoyo,也是差不多的体验。我也知道我的应用场景下应该买个标准台式机才能配置更好性能的CPU,但是谁让我这个机器的外型的毒呢?

华硕AC68U路由器:其实去年买的AC66U完全够用了,不过还是因为一次特价剁手了更高端一点的AC68U。整体使用体验与AC66U相仿。不过从外观来说,我反而还更喜欢AC66U一点,AC66U给人的感觉是做工精致、用料实足。AC68U其实也一样,但给我的感觉却是:傻大笨粗。

Raspberry Pi 3:没啥说的,我是树莓派的脑残粉,出一个买一个。相对2来说,主要就是64位、内置蓝牙和Wi-Fi,性能稍有提高,别的就没有了。树莓派是吃灰神器是名不虚传的,这个现在已经吃灰。还买了一个Sense HAT传感器模块,做了一个贪吃蛇游戏后也吃灰了。有了3以后,我用以前吃灰的2和Camera Module做了一个网络摄像头,配合群晖做监控,效果勉强凑合。

Pebble 2:本来是在Kickstarter预定了Pebble Time 2,但因为正在用的Pebble花屏越来越严重,等不及就先收了一个Pebble 2,没想到次日Pebble就宣布被fitbit收购了(我觉得与其说是收购,不如就当是破产了更合适),Time 2也没有机会再问世了。Pebble 2的使用体验与Pebble高度一致,我很满意,只可惜这已经是绝唱了。希望在它坏掉以前,能有更出色的产品出现。

二手Kindle Paperwhite 2:跟以前用的Paperwhite比,差别并不大,只不过因为Paperwhite被老妈重度使用中,所以自己重买一个。没买3是因为性价比,毕竟我也不是重度使用。而且看书这个单一需求来说,我并不觉得Paperwhite 1/2/3/Kindle Voyage有多大的差别。

二手Intel Compute Stick:我需要一台常开的Windows机器来满足把NAS上的照片上传到 Google Photos的需求,这个东西很符合我的要求,功耗不到5W,直接由路由器USB口供电就可以了。性能对于我来说也完全够用,有了它不但解决了Google Photos上传的问题,甚至我的电脑上已经不再需要安装Windows虚拟机了,偶而需要用Windows的时候,直接远程桌面连上去用就可以了。

– 出行

南京*3、合肥、西安。对南京的感情依然不变、合肥真不是一个旅游城市、第二次去陕西省历史博物馆已找不回第一次去时惊艳的感觉。

展望2017年:

谈点工作,在用Java写了4年业务代码后,2016年,我终于又回归了技术开发。在短暂地用了一段时间Scala后,还回归到了C++开发。说是“回归”,其实还是更大的挑战,因为需要用C++ 14来写一些高性能的分布式程序,对于我来说也仍然是一个全新的课题。期待2017年可以在这个方面能有所收获。

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
欢迎关注我的微信公众号:窥豹
Li Fanxi's avatar

关注2017维也纳新年音乐会

曾经关注过的那些维也纳新年音乐会:关注维也纳新年音乐会,2017年将是我第22次收看维也纳新年音乐会的直播。

2017维也纳新年音乐会CD封面

  • 01 Franz Lehár – Nechledil Marsch aus der Operette Wiener Frauen – 内西雷迪尔进行曲(选自喜歌剧《维也纳的妇女》)
  • 02 Èmile Waldteufel – Les Patineurs; Walzer; op. 183 – 溜冰者圆舞曲
  • 03 Johann Strauss II – ‘s gibt nur Kaiserstadt, ‘s gibt nur Wien!; Polka; op. 291 – 只有帝国之都,只有维也纳(皇城)波尔卡 – 1997
  • 04 Josef Strauss – Winterlust; Polka schnell; op. 121 – 冬趣快速波尔卡 – 2005
  • 05 Johann Strauss II – Mephistos Hollenrufe; Walzer; op. 101 – 梅菲斯特的地狱圆舞曲 – 1995
  • 06 Johann Strauss II – So angstlich sind wir nicht!, op. 413 – 我们决不畏惧波尔卡 – 2009
  • 07 Franz von Suppé – Ouvertüre zu Pique Dame – 黑桃皇后序曲
  • 08 Carl Michael Ziehrer – Hereinspaziert!; Walzer; op. 518 – 闲庭信步圆舞曲 – 1979
  • 09 Otto Nicolai – Die lustigen Weiber von Windsor, Moon Choir – 月升小合唱(选自轻歌剧《愉快的温沙妇人》)
  • 10 Johann Strauss II – Pepita-Polka; op. 138 – 细花纹方格波尔卡
  • 11 Johann Strauss II – Rotunde-Quadrille; op. 360 – 圆形大厅四对舞
  • 12 Johann Strauss II – Die Extravaganten; Walzer; op. 205 – 奢靡圆舞曲
  • 13 Johann Strauss I – Indianer-Galopp; op. 111 – 印度人加洛普 – 2004
  • 14 Josef Strauss – Die Nasswalderin; Polka mazur; op. 267 – 纳斯瓦尔德的女孩玛祖卡波尔卡 – 1996
  • 15 Johann Strauss II – Auf zum Tanze!; Polka schnell; op. 436 – 跳舞吧快速波尔卡
  • 16 Johann Strauss II – Tausend und eine Nacht; Walzer; op. 346 – 一千零一夜圆舞曲 – 1992, 2005
  • 17 Johann Strauss II – Tik-Tak; Polka schnell; op. 365 – 提塔快速波尔卡 – 1979, 2002, 2012
  • 18 Eduard Strauss ? – ?
  • 19 Johann Strauss II – An der schönen blauen Donau, Walzer, op. 314 – 蓝色多瑙河圆舞
  • 20 Johann Strauss I – Radetzky-Marsch, op. 228 – 拉德茨基进行曲

2017年维也纳新年音乐会即将迎来首次登上该音乐盛事指挥台的指挥家古斯塔沃·杜达梅尔。做为当代最为杰出的指挥家之一,80后的杜达梅尔也将成为有史以来新年音乐会上最年轻的指挥。

年轻的指挥家为这传统古老的音乐会带来了新的气息,这次音乐会正式演出的17个曲目中,有8首是第一次在新年音乐会上与乐迷见面,剩下的9首中也有7首是只在新年乐会上演出过一次的“冷门”曲目。加演的第一个曲目目前还没有公布,如果“路边社”消息属实,加演的是爱德华的一首快速波尔卡,那么这次音乐会的曲目单中就将史无前例地出现九位作曲家的名字,新年音乐会在演绎施特劳斯家族音乐的传统之上,越来越多的融入了更多其他作曲家的作品。

维也纳音乐之友协会合唱团将第一次加入新年音乐会的演出行列,在下半场与爱乐乐团一同演绎“月升小合唱”。

中央电视台从1987年开始转播维也纳新年音乐会,到现在已经有30年。我从1996年开始收看新年音乐会,到现在已经超过了20年。经历了刚开始的陌生和新奇以及中间的狂热,新年音乐会如今已经成为了一种习惯。我用上面的文字列举完了音乐会曲目单的各项“技术参数”以后,惊讶地发现,对于每一个具体的曲目,我竟然写不出任何文字再去深入的点评它们。那些曾经演出过的曲目,在脑海中的印象似乎也变得越来越模糊。不过我也不打算像以前那样把曲目单上的曲子都找出来复习+预习一遍了,我相信在新年到来的时候,乐团的演绎会让我回忆起那些曾经熟悉旋律,那种与老朋友相见的感觉想必是非常美好的。

期待新年音乐会,也期待新的一年。

Li Fanxi's avatar

十年(中)

这文章已经快烂尾了,努努力,先补个中段出来。

TL;DR

其实我已经不太想写后面的内容了,写这篇文章时我已经仔细地想过我所希望在文章中表达出来的内容。总结到最后,我觉得这十年中让我自己受益最大的一点就是:突破自我,走出舒适区,积极、主动地多走一步。

对于我自己来说,即使到现在,我也常常很难主动去打破自己的舒适区,积极地多走一步。但是回首过去的十年,但凡我多走了一步,无论是主动还被迫,最终都得到了超越预期的结果。

这里说的多走一步,还不仅仅局限于自己的事。在跟别人合作的时候或者打交道时,多走一步,或者帮助别人多走一步,都是非常有益的。

2009年

产品维护的工作比想象中的更难做,尤其是换到了一个全新的产品线后,对于一个完全不了解的产品,需要同时去给客户解决多个平台上N个版本产品中出现的各种疑难杂症,实在是一件非常有挑战的事情。

有两个Case印象深刻:

  1. 有个Case我完全无从下手,只好开始“忽悠”,说了自己的一些怀疑点,让核心技术支持部门协助客户去收集更多的日志,趁收集日志的时间我就可以在后台更进一步地分析问题。有一天突然就发现,美国开发团队以前开发这个产品的一位Principal Engineer(首席工程师?反正级别很高,是个传奇人物)跳进了这个Case。他不但详细的分析了问题的原因,给出解决思路和方案,还“顺手”把代码改好、打好补丁包。后来我向他致谢的时候,他说:这个产品就像是我的孩子,我有责任把它照顾好。
  2. 有位客户发现产品界面上的CPU占用率曲线在产品连续运行了两个月左右后就会显示成持续占用100%,但实际CPU占用率并没有这么高。我从前台Java Script一直到后台程序及内核代码全部分析了一遍以后终于发现,是因为程序中用32位无符号整形数来表示开机后到当前时间的毫秒数,所以每过49.7天,这个整形数就会溢出,造成问题。我修掉了这个问题,并且定制了一个Linux内核,可以在启动时指定uptime的初始值用于快速重现这个Bug,把Patch和这个测试用内核一起提交给QA。QA后来表示非常感激,因为如果没有这个测试用内核,要Mock出一个很大的uptime值是一件很麻烦的事情。

这两个Case说的都是Ownership,说的都是怎么在尽职的前提下再多走一步。在一个人人都很优秀的环境下,“尽职”只能算是一个基本要求,想要有额外的收获,你必须有主动的额外的付出。

有了前两年“脱宅”的积累,2009年全年的每个休息日我基本上都在南京城里逛来逛去,与南京这座城市的感情也越发深厚。尤其是在经历了一些事情后,让我对人的情感有了更为深入的理解,这对我后面的生活产生了很多积极的影响。很多事情,即使不能多走一步,也应该多想一点。多想一点,也许就能让一个原本优秀的人变得更为卓越。

除了没事逛来逛去,我也把很多很多时间放在了南京图书馆。这一年看了很多闲书,包括铁路、艺术、建筑、地理、文学、数学、摄影、设计等领域。现在回想起来,真是怀念这段时间,可以肆无忌惮地把大块时间花在“不务正业”的事情上。看了这么多闲书,并不可能帮助我在这些领域中有所建树,但是对于我来说,人生的乐趣就在于不断地学习、不断地探索未知。有时候,时间就是用来浪费的。

2010年

我的第三位Manager是一个爱憎分明的人,我有幸成为他比较喜欢的下属之一,所以他给了我充分自主发挥的空间,也为我争取了很多很多的机会,甚至为了帮我争取一个去美国参加会议的机会,“强迫”另一位Manager退掉了去美国的机票,把参会的名额留给我。当然,更重要的是他在工作中给我创造的机会和对我的各种想法的重视和支持。那时公司也很鼓励微创新,我的各种微创新在他的帮助下都得以在团队内或产品中落地生根,这给了我极大的自信,也帮助我在部门内找到了属于我的位置。

机遇和挑战总是并存的,在这个开发团队,我负责了这个产品两个大版本中最重要的两个大Feature,从前期方案调研到最后开发实现,统统要搞定。还是得益于公司完善的流程和管理, 这两个版本软件做完以后,我完整了实践了两遍软件从需求到发布的完整流程,从中学到了很多的东西。同时,这两个Feature也涉及到很多跟美国开发团队以及国外一些第三方公司的打交道的事,所以又进一步提高了我跟老外们打交道的能力。这一段经历,不但让我在技术上有所收获,更重要的是让我学到了做事的方法,很多事情并不会自动朝着你想象的方向去发展,怎么才能推动事情往前迈步,既需要努力,也需要很多的技巧。其中最基本的技巧就是要合理计划、分解任务制定小的里程碑、踏实实践

这一年的业余时间花了很多在自由软件上,更多的参与到一些简单自由软件项目中去。再次提起Ownership一词,在自由软件世界里,你可以既是软件用户,也是软件的主人。当你以软件主人的心态去使用自由软件时,才会真正理解自由软件背后的理念,也才能从中收获更多的价值。

 

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.

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 域名。当然一般就是看起来会比较厉害而已…

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:

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 个依赖包!!!

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

===== 2018/9/28 更新 =====

Telegram bot API 更新了(早就)

于是这只 bot 可以一键导出一组贴纸了。详情

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

pyresttest:

  • 哈哈哈,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
欢迎关注我的微信公众号:窥豹
Li Fanxi's avatar

十年(上)

上周末是参加工作十周年,回南京与同一年进入公司的老同事们聚了聚,很开心,也有颇多感想。

对于我在Trend Micro的第一份工作,一直以来,我是心存感激的。从学校走向社会,从稚嫩变得慢慢成熟,回头去看,有很多人、很多事都值得感激。

总结一下过去的十年,顺便熬一锅心灵鸡汤。

2006年

离开学校,走进公司。趋势科技应聘经历里提到的那位在一面时给了我很多自信的面试官成了我的第一位Manager。放到现在,也许我不会说事无巨细、无微不至的Manager是一位足够Professional的Manager,但是作为职场新人,这样的Manager给了我足够的帮助。他对我的信任、给我提供的机会和帮助,都为我的成长提供了一个良好的环境。后来,我做过新人的Mentor、也带过小项目,践行了很多从他身上学到的东西。

公司提供的培训,帮助我理解了Development Process、Design Documents、Technical Writing、Internationalize & Localization、User Centered Design、Writing Secure Code相关的知识,并在短时间内都在工作中得到了实践,这些经验一直受用至今。虽然互联网公司并不适合照搬传统软件公司的开发流程,但是很多方法思路依然值得借鉴。

技术方面,虽然1999年我就装过玩过Linux,但一直到工作前对Linux都还处于基本不懂的状态。工作的前半年,我学习了Linux下的各种开发工具,从无到有独立完成了产品中一个GTK程序的开发,学会了修改了内核模块的代码和内核调试的基本工具。后面五年中用到的各种知识和技能中的一大半都是在这半年中积累起来的。学东西最快的途径就是看书加实践,学而不思则罔,学而不实践则很难真正把书上的东西变成自己的。

我参与开发的第一个产品ServerProtect for Linux 3.0(图片来自网络)

公司让大家考了托业(TOEIC),并开始上英语课,还推行了一段时间英文会议。于是原先只出现在邮件交流中的各种“洋泾浜”英语,开始出现在日常的会议中,挂在了每个人的嘴边。这些并不完美的英语,却真正的帮助到了我,从这以后,基本上我就摆脱了“哑巴英语”的困境 。

英语课上,老师推荐了一部电影《Dead Poet Society》,在这部电影里,我看到了自己的影子,这部电影让我改变了很多。以Carpe diem的理由,我开始努力摆脱曾经自己给自己定下的条条框框,去尝试更多的“不可能”。

2007年

年初,我的Mentor也许是因为偷懒,让我帮他去上原本属于他的Effective Presentation培训。没有想到,短短一天培训中学到的一些基础的演讲技能、对“紧张”的正确认识以及对演讲前充分准备的重要性的认识,就帮助我从一个不敢在公开场合发言的人,变成了一个不太惧怕公开演讲的人。培训导师通过理论和实践帮助我建立了自信。

为了开源产品中的内核模块,我深入理解了GNU工程及自由软件哲学,研究了各种自由软件/开源许可证,并开始积极参与到自由软件社区中。

为了实践Customer Insight,生平第一次出差、第一次坐飞机。我一直以为那时自己做的产品其实没有太多实用价值,当看到自己的产品在客户的环境中真正发挥着作用时,自豪感优然而生。

因为“Carpe diem”,这一年,我的行为举止发生了很多变化。发展了不少的小众爱好:暴走火车地铁无线电,并因此结交了几位挚友。自己去参加CSDN的技术大会,开始关注Web 2.0。通过开发“豆饭”,开始与互联网开发结下不解之缘。通过参与UCDChina的活动,与本地互联网公司的设计师们建立了良好的关系。我的社交圈子在这一年中,扩大了很多,并且很多时候都是我自己主动出击,这在以前,我是绝对做不到的。

2008年

由于参与开发的产品已经在前一年完成并发布,工作角色从产品开发转为产品维护。这段日子是我的工作经历中最为灰暗的日子。首先,产品维护需要在有限的时间内解决客户的问题,我在学会了“沟通技巧”和“做事方法”的同时,放弃了对技术精益求精的追求。为了降低Resolution Time,我学会了忽悠,而不是去寻找Root Cause。其次,新的Manager强调学习做事方法高于技术追求,他让我在各种挫折中自己去学会在职场中生存下来的技能,但是各种挫折不断地打击着我刚刚积累起来的一点点自信心。

然而,这种种不如意,却只是成长的代价。我这一年的技术积累没有前两年那么多,但依靠那些被逼迫着学到的职场技能,加上各种机缘巧合,反而倒奠定了我在团队中的地位。然而“捷径”只是加速达到某些目标的一种手段,真正起作用的,依然是踏踏实实的每一步。

公司开始强调文化建设,难听的说法叫“洗脑”。也许是那个时候还比较Simple和Naive,这次文化建设的培训让我受益非浅。直到现在,我依然认同那时被灌输的思想:你需要让你个人变得更加优秀,才能发挥出更大的价值。

参加了公司的Home Building Project,这是公司与一个公益组织Gawad Kalinga合作进行的公益项目,去菲律宾盖房子(这个翻译其实不对,这是Home Building,不是House Building,盖房子只是一种行为,目的是帮助那里的人们拥有更美好的家)。第一次走出国门、第一次与来自不同国家和地区的同事合作、第一次与这么多小朋友近距离接触、第一次与生活在贫穷与自然灾害面前的人们面对面交流。过去了这么多年,当初的震撼已经慢慢的淡去,但留在内心深处的东西,也许会影响一个人的一生。

Home Building Project的合影

那时公司组织各种活动很多,加上那段时间我自己也参与了不少线下的活动,从中自己积累和总结了很多组织线下活动的技能。3月底自告奋勇为当时的Unix-Center网站成立一周年组织了南京地区的线下活动,活动虽然规模不大,但很成功,甚至还吸引了上海、无锡等周边城市的自由软件爱好者。这次活动也为后来成立Nanjing Linux User Group埋下了种子。

Unix-Center周年庆

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
欢迎关注我的微信公众号:窥豹
Li Fanxi's avatar

Synology PhotoStation性能优化

本文的内容已经过时,6.7.0以上的Photo Station已经没有严重的性能问题了。

我折腾过不少家用NAS方案,包括最早的WD My Book World Edition,后来的Joggler,再到后来的Raspberry Pi,这些方案除了性能存在一些问题以外,最大的缺点就是易用性存在问题,不但非“专业”人士用起来存在困难,就连我自己也对土法泡制的照片管理功能感到不满。直到两年前入了群晖(Synology)的家用NAS DS214play,事情才变得安逸起来。

平心而论,Synology的系统虽然功能强大,体验也还不错,但细节上做得其实真是挺糙的。最近就发现了它的照片管理软件PhotoStation出现了严重的性能问题,在照片库里只存了8万余张照片的情况下,打开首页要花费的时间已经超过了20秒,每打开一个文件夹都需要等待10秒以上,几乎不可用了。

以下分析以DSM 6.0为例,PhotoStation版本为6.4-3166。PhotoStation的安装路径为/volume1/@appstore/PhotoStation。

Synology的DSM系统,后台使用PostgreSQL数据库,前端是PHP页面。简单推理一下就可以知道,PhotoStation的性能瓶颈主要是在对照片索引数据库的访问上。性能调优的第一步就是先要找到哪些SQL查询占用了太多的时间。打开PostgreSQL记录SQL查询的开关,并查看所有SQL执行情况:

$ sudo su postgres
$ vi ~/postgresql.conf
log_statement = 'all'
$ psql photo postgres
# SELECT pg_reload_conf();
# \q
$ exit
$ sudo tail -f /var/log/postgresql.log

通过查看SQL执行记录,很容易发现几个明显的慢查询:

1.

SELECT COUNT(*) as total FROM photo_image; 
SELECT COUNT(*) as total FROM video;

2.

SELECT COUNT(logid), MAX(logid) FROM photo_log;

3.

SELECT * FROM (
    SELECT path AS filename, timetaken AS takendate, 
        create_time AS createdate, 'photo' AS type
    FROM photo_image
    WHERE path LIKE '/volume1/photo/%'
        AND path NOT LIKE '/volume1/photo/%/%' AND disabled = 'f'
    UNION
    SELECT path AS filename, mdate AS takendate,
        date AS createdate, 'video' AS type
    FROM video
    WHERE path LIKE '/volume1/photo/%'
        AND path NOT LIKE '/volume1/photo/%/%'
        AND disabled = 'f'
    ) AS totalCount; 

4.

SELECT path, resolutionx, resolutiony, version FROM (
    SELECT path, resolutionx, resolutiony, version,
        create_time, privilege_shareid, disabled
    FROM photo_image WHERE privilege_shareid IN (
        SELECT shareid FROM photo_share WHERE ref_shareid = (
            SELECT shareid FROM photo_share WHERE sharename = '2016'))
    AND disabled = 'f'
    UNION ALL
    SELECT path, resolutionx, resolutiony, 0 AS version, 
        date AS create_time, privilege_shareid, disabled
    FROM video WHERE privilege_shareid IN (
        SELECT shareid FROM photo_share WHERE ref_shareid = (
            SELECT shareid FROM photo_share WHERE sharename = '2016'))
    AND disabled = 'f') temp
ORDER BY create_time DESC LIMIT 1; 

优化的思路很简单,由于PhotoStation在正常情况下访问数据库所需要的读性能是远远大于写性能的,所以就通过牺牲写性能来逐一击破上面这些慢查询:

1. 程序的目的就是想知道系统中有多少张照片和多少个视频(而且其实并不需要精确知道,差不多就行),可惜对于PostgreSQL来说,由于它采用MVCC来解决并发问题,SELECT COUNT(*)是一个需要进行全表扫描的慢操作。解决方案就是用另外用一张表来存这两个表的总记录条数,并在原表上添加触发器来更新记录数。

CREATE TABLE photo_count (table_oid Oid PRIMARY KEY, count int);
ALTER TABLE photo_count OWNER TO "PhotoStation";
CREATE FUNCTION count_increment() RETURNS TRIGGER AS $_$
BEGIN
  UPDATE photo_count SET count = count + 1 WHERE table_oid = TG_RELID;
  RETURN NEW;
END $_$ LANGUAGE 'plpgsql';
CREATE FUNCTION count_decrement() RETURNS TRIGGER AS $_$
BEGIN
  UPDATE photo_count SET count = count - 1  WHERE table_oid = TG_RELID;
  RETURN NEW;
END $_$ LANGUAGE 'plpgsql';
CREATE TRIGGER photo_image_increment_trig AFTER INSERT ON photo_image 
  FOR EACH ROW EXECUTE PROCEDURE count_increment();
CREATE TRIGGER photo_image_decrement_trig AFTER DELETE ON photo_image 
  FOR EACH ROW EXECUTE PROCEDURE count_decrement();
CREATE TRIGGER video_increment_trig AFTER INSERT ON video 
  FOR EACH ROW EXECUTE PROCEDURE count_increment();
CREATE TRIGGER video_decrement_trig AFTER DELETE ON video 
  FOR EACH ROW EXECUTE PROCEDURE count_decrement();
INSERT INTO photo_count VALUES 
  ('photo_image'::regclass, (SELECT COUNT(*) FROM photo_count));
INSERT INTO photo_count VALUES 
  ('video'::regclass, (SELECT COUNT(*) FROM video));

然后在PHP程序中修改需要统计表数记录数的逻辑,在这里可以看到似乎同一个Session中只会查一次,但即使就是这一次,也已经慢得让人不开心了:

diff --git a/photo/include/file.php b/photo/include/file.php
index 541c5cb..7caa5de 100755
--- a/photo/include/file.php
+++ b/photo/include/file.php
@@ -536,8 +536,11 @@ class File {
 		if ($key && isset($_SESSION[SYNOPHOTO_ADMIN_USER][$key])) {
 			return $_SESSION[SYNOPHOTO_ADMIN_USER][$key];
 		}
-		$query = "SELECT count(*) as total FROM $table";
-
+		if ('photo_image' == $table || 'video' == $table) {
+			$query = "SELECT count as total FROM photo_count where table_oid='$table'::regclass";
+		} else {
+		    $query = "SELECT count(*) as total FROM $table";
+		}
 		$result = PHOTO_DB_Query($query);
 		if (!$result) {
 			// db query fail, won't update session value

2. 其实可以用跟前一个问题类似的方法去解决。但是这个其实只是一个没太多用处的操作日志表,所以我用更为简单粗暴的方法去解决这个问题:减少在数据库中保留日志的条数。直接修改PHP程序:

diff --git a/photo/include/log.php b/photo/include/log.php
index 1c982af..56385db 100644
--- a/photo/include/log.php
+++ b/photo/include/log.php
@@ -1,8 +1,8 @@
 <?php
 
 class PhotoLog {
-	const LIMIT = 100000;
-	const PURGECOUNT = 10000;
+	const LIMIT = 1000;
+	const PURGECOUNT = 100;
 	public static $SupportFormat = array("html", "csv");
 
 	public static function Debug($msg)

3. 第三个问题主要体现在对照片路径的处理上,为了选出位于某个路径下(不含子目录)的照片,程序采用了path LIKE ‘/path/%’ AND path NOT LIKE ‘/path/%/%’这样的查询条件。其实PostgreSQL在一定程度上是可以利用path字段上的索引来很好的优化这个查询的,但是实际运行中发现(通过在PostgreSQL的客户端中用explain和explain analyze分析查询)在某些情况下索引会失效,造成非常差的查询性能。解决方案还是用写性能来换读性能,先在表上加一个dirname字段并建立索引,按path算好文件所在的目录名写入dirname,然后把查询条件改为对dirname的查询,避免使用通配符和LIKE运算即可:

ALTER TABLE photo_image ADD COLUMN dirname TEXT NOT NULL DEFAULT '';
UPDATE photo_image 
    SET dirname = LEFT(path,LENGTH(path)-STRPOS(REVERSE(path),'/')+1);
ALTER TABLE video ADD COLUMN dirname TEXT NOT NULL DEFAULT '';
UPDATE video SET dirname = LEFT(path,LENGTH(path)-STRPOS(REVERSE(path),'/')+1);
CREATE INDEX dirname_index ON photo_image USING btree(dirname);
CREATE INDEX dirname_index ON video USING btree(dirname);
CREATE OR REPLACE FUNCTION set_dirname()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
BEGIN
  NEW.dirname := LEFT(NEW.path,LENGTH(NEW.path)-STRPOS(REVERSE(NEW.path),'/')+1);
  RETURN NEW;
END $function$
CREATE TRIGGER set_dirname_trigger 
BEFORE INSERT OR UPDATE ON photo_image 
FOR EACH ROW 
  EXECUTE PROCEDURE set_dirname();
CREATE TRIGGER set_dirname_trigger 
BEFORE INSERT OR UPDATE ON video 
FOR EACH ROW 
  EXECUTE PROCEDURE set_dirname();

同时修改PHP程序:

diff --git a/photo/include/photo/synophoto_csPhotoDB.php b/photo/include/photo/synophoto_csPhotoDB.php
index ac8f932..43e58ee 100755
--- a/photo/include/photo/synophoto_csPhotoDB.php
+++ b/photo/include/photo/synophoto_csPhotoDB.php
@@ -1607,10 +1607,8 @@ class csSYNOPhotoDB {
 		} else {
 			$albumRealPath = self::EscapeLikeParam(SYNOPHOTO_SERVICE_REAL_DIR_PATH."{$albumName}/");
 		}
-		$cond = "path LIKE ? {$this->escapeStr} AND path NOT LIKE ? {$this->escapeStr} AND disabled = 'f' ";
-		array_push($pathSqlParam, "{$albumRealPath}%");
-		array_push($pathSqlParam, "{$albumRealPath}%/%");
-
+		$cond = "dirname = ? {$this->escapeStr} AND disabled = 'f' ";
+		array_push($pathSqlParam, "{$albumRealPath}");
 		if (!$removePhoto) {
 			$photoQuery = "SELECT path as filename, timetaken as takendate, create_time as createdate, 'photo' as type
 FROM photo_image WHERE $cond";
 			$sqlParam = array_merge($sqlParam, $pathSqlParam);

4. 这个查询只是为了查询一个目录及其所有子目录中最新一个照片或视频,用其缩略图来作为目录的封面图片。群晖的工程师自己也知道这个查询很慢,所以还在程序中加了个逻辑,当照片视频数量大于200000时,放弃按日期排序,直接随机选一张。然而,这个查询实际上是可以简单优化的,明明不需要把所有的照片视频UNION到一起后再找出最新的一个,可以直接分别找出最新的照片和最新的视频,然后再到这两个中去取一个相对更新的就可以了。直接修改PHP代码实现:

diff --git a/photo/include/photo/synophoto_csPhotoAlbum.php b/photo/include/photo/synophoto_csPhotoAlbum.php
index ca128f0..f0e57e7 100755
--- a/photo/include/photo/synophoto_csPhotoAlbum.php
+++ b/photo/include/photo/synophoto_csPhotoAlbum.php
@@ -145,9 +145,11 @@ class csSYNOPhotoAlbum {
 		$cond .= " AND disabled = 'f'";
 
 		$table = "(" .
-				"SELECT path, resolutionx, resolutiony, version, create_time, privilege_shareid, disabled FROM pho
to_image WHERE $cond " .
+				"(SELECT path, resolutionx, resolutiony, version, create_time, privilege_shareid, disabled FRO
M photo_image WHERE $cond " .
+				"ORDER BY create_time DESC LIMIT 1)" .
 				"UNION ALL " .
-				"SELECT path, resolutionx, resolutiony, 0 as version, date as create_time, privilege_shareid, disa
bled FROM video WHERE $cond " .
+				"(SELECT path, resolutionx, resolutiony, 0 as version, date as create_time, privilege_shareid,
 disabled FROM video WHERE $cond " .
+				"ORDER BY create_time DESC LIMIT 1)" .
 		") temp";
 
 		// this may cost lots of time, so it won't sort by create_time if the total count exceeds the threshold (200,000)

做完以上优化,我的PhotoStation已经基本可以做到点进文件夹秒开了,至少我自己已经比较满意了。声明一下,其实我并不太懂数据库相关理论和技术,以上“优化”只能说是在我自己的实验中起到了优化的效果,也许其中一些并不太科学,希望这篇文章能起到抛砖引玉的作用。

重新安装PhotoStation或升级DSM系统会造成我们对程序所作的修改丢失,所以在修改完成后,务必做好备份。

另外,适时对PostgreSQL数据库进行VACUUM操作似乎可以起到提高访问性能的目的,尤其是在做过大量照片更新后。

Li Fanxi's avatar

一个网络访问故障的排查

故障描述:

一个Python程序,在我的Macbook Air上使用Gearman库访问Gearman服务器时始终报错:
Found no valid connections in list:
[<GearmanConnection 111.111.111.111:80 connected=True>]

问题排查过程:

  1. 我的另一台电脑的Linux系统下运行这个程序是正常的,所以证明Gearman服务器本身是好的,程序也是对的。
  2. 别人的Macbook Air/Macbook Pro上运行这个程序也是正常的,系统中的Python版本是一致的,证明这个程序在Mac OS X下并不存在兼容性问题。
  3. 111.111.111.111:80这个是一个从开发环境访问生产环境服务器的虚拟IP/端口,把Gearman服务安装到开发环境网络中222.222.222.222:4730,程序运行正常。
  4. 由于访问开发环境正常、访问生产环境异常,所以怀疑是网络原因。使用ping/traceroute命令访问111.111.111.111,都正常。用telnet 111.111.111.111 80尝试TCP连接,也正常。
  5. 在无线/有线之间切换网络物理连接方式,测试结果一样:本机与目标服务器之间网络通畅,TCP连接正常,但Gearman连接无法正常建立。并且同处于同一个网络中的其它电脑上运行一样程序都访问正常。
  6. 怀疑Gearman的Python库在某些特殊情况下有Bug,在里面加了很多print来打印日志,发现客户端在与服务器建立完连接后,在真正要通信时,连接又变成了断开状态。但由于不太了解Gearman库的实现细节,再要进一步Debug,存在一定困难,暂时放弃这个思路。
  7. 为了排查Gearman连接断开的原因,用Wireshark进行抓包。抓包的结果非常令人惊讶:在程序运行的整个过程中,没有抓到任何客户端与服务器之间通信的数据包。但是程序打印出来的日志却明明显示出连接是先建立再断开的。
  8. 至此,问题已经快要查清楚了:本机有个程序劫持了本机发起的网络通信,实施中间人攻击,造成Gearman连接异常。
  9. 用Python写socket程序尝试向不同目标发起各种TCP连接(后来意识到其实用telnet就可以了),同时用Wireshark抓包,发现本机发起的所有目标为80端口的连接都会被劫持。劫持的效果是如果通过80端口进行HTTP通信,劫持的程序就会充当一个代理服务器的功能,正常完成通信过程。但是一开始出问题的程序是在80端口跑Gearman的通信协议,所以这个劫持程序无法正确处理,造成了通信异常。
  10. 最后就是要找出是哪个程序实施了劫持,用了一个很土的方法:下载一个大文件,同时用netstat -na查出与服务器80端口通信的本地端口号(谁叫Mac OS X的netstat命令没有-p参数直接看是哪个进程的连接呢?),然后用sudo lsof -i :<local port>命令查出这个本地端口号的使用者。

真相大白:

Cisco AnyConnect Secure Mobility Client中自带了一个进程名为acwebsecagent的Web安全模块,这个安全模块不管VPN是否在使用,都会劫持本机所有的发往80端口的通信,具体它做了什么好事坏事就不得而知了。

找到了问题所在,网上搜一搜就能查到很多吐槽这个Web Security模块的贴子了,它会随着AnyConnect默认安装到你的电脑上(我的电脑系统是IT预装的,不然我装AnyConnect时肯定会手工把这个勾勾去掉)。解决问题的方法也很简单 ,一行命令卸载它:
sudo /opt/cisco/anyconnect/bin/websecurity_uninstall.sh

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。

's avatar

Firefox 插件:TooManyTabs

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

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

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

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

以上

Li Fanxi's avatar

最好的简谱编辑排版软件JP-Word

本文是一则硬广告。

上大学时跟同学一起办过一份音乐小报,名为《白桦林》,虽然只办了一期就夭折了,不过在这期小报上试图排版朴树那首《白桦林》的简谱(附带吉他和弦)的经历让人难以忘怀。我那时坚信自己用Word排版复杂版面的水平已经到了出神入化的程度,但还是被这个简谱排版工作打败了。Word虽然是“所见即所得”,但是那排版好的谱子打印出来时,总是会有那么些对不齐或者走样。那时学校打印店打印一页A4纸的价格是1.5元,不舍得反复打印试验,最终还是通过剪刀胶水才把这个排版任务搞定的。

《白桦林》小报第三版。由于Word版本和字体的原因,现在看到的跟当年排出来的效果有差别。

上大学时,经常玩一个叫HappyEO的电脑键盘模拟电子琴的软件——强势插入,再打个广告,HappyEO是用电脑键盘(也可以接MIDI键盘)模拟电子琴的最好的软件,没有之一。因为对这个软件的喜爱,我跟HappyEO的作者有着较多的交流,一路见证着HappyEO电子琴和后来的iDreamPiano模拟钢琴等软件的诞生。2003年时,他告诉我他做了一个简谱排版软件,叫JP-Word

实话说,1.0的JP-Word一点也不诱人,粗糙的界面、鸡肋的图片输出和打印功能、封闭专有的文件格式实在不能让我提起太大的兴趣。加上对于我来说,平时几乎没有简谱排版的需求,所以对这个软件并没有太高的热情。接下来两三年时间,JP-Word慢慢升级到了3.0,功能丰富了一些,但没有什么本质的变化。

一下过了十年,JP-Word的作者突然告诉我,JP-Word出了4.0的新版本。这个脱胎换骨的新版本,着实可以用“惊艳”来形容。

JP-Word 4.0界面预览

不像五线谱打谱有自由的Lilypond,还有Overture、Encore等强大的商业软件。市面上简谱音乐软件并不多,优秀打谱软件更是少之又少。之所以我敢在文章标题上把JP-Word说成是“最好的”,个人认为主要有以下几点:

  • 独创的的“切换音符时值组合”功能。常见音符的时值组合不用一个个手工调整,音符输入后按几下空格键就自动组合好了。
  • 强大的歌词编辑功能。支持歌词自动对位,谱打完,歌词一打上去就自动对齐到它该去的地方。
  • 支持矢量PDF的输出。而且排版结果不管是布局还是字体,都非常美观,放到专业环境下用也毫无压力。
  • 开放的JPW-ABC格式。一改以前用专有格式的方式,新版本用了开放的纯文本来描述乐谱,为二次开发提供了便利。

JP-Word简谱编辑软件不是完全免费的,免费的版本只提供了部分主要的功能集,但折腾党可以通过手工写JPW-ABC的方式实现几乎全部收费版本的功能。

缺点也是有的,比如目前不支持直接播放乐谱和MIDI导出。不过Thanks to开放的JPW-ABC格式,完全可以写个简单的脚本,把JPW-ABC文件转换为MIDI文件。我正在尝试写个简单Python脚本,把JPW-ABC转换为Lilypond格式,然后借助Lilypond,可以直接生成对应的五线谱和MIDI文件。有兴趣可以关注我的Github项目jpw2lilypond,不过目前上面还只有一个很垃圾的Prototype(对象建模建错了,虽然基本功能在,但已经没办法往下写了),而且现在每天的时间都不够用,开发进度一定不会很快。

JP-Word 4.0的功能介绍与使用说明:http://www.happyeo.com/intro_jpw.htm

下载JP-Word 4.0免费版的地址:http://www.happyeo.com/downloads_jpw.htm

注册JP-Word的方法:http://www.happyeo.com/register_jpw4.htm

Li Fanxi's avatar

实践个人网站迁移HTTPS与HTTP/2

赶个时髦,把自己的博客进行了全站HTTPS改造,并升级支持了HTTP/2,总结在此,当作备忘。

很惭愧,虽然曾经做过几年Web安全产品,其实我自己并没有非常深入的去理解和思考Web安全更多内在的东西,所以可能文中的部分描述并不完全准确。很多内容参考了Jerry Qu的博客上的内容,都以参考文献的方式列在文章中,我这里只写结论,技术细节可以参考他的原文。

动因

HTTPS改造的好处当然是更安全。虽然对于一个博客网站来说,“安全”似乎并不是一个非常重要的因素,但是以国内现实的情况来说,使用HTTPS提供网站服务有一个好处就是可以避免网络运营商篡改网页内容(比如插入弹出广告)——其实吧,HTTPS以后,Chrome浏览器地址栏显示的绿色小锁才是吸引我迁移的真正原因,挺好看的。

HTTP/2从协议层面消除了传统HTTP的一些不足和缺陷,对我来说,直接的好处就是可以大幅度提高网页载入的速度。有关HTTP/2的前世今生,可以参考以下文章[1]。

HTTPS改造

证书

HTTPS改造的一个基本要素就是证书,在传统上有很多认证机构(CA)可以收费签发证书,比如大名鼎鼎的Verisign。现在也有很多公司可以提供免费或者廉价的证书,比如有名的StartSSL,以及最近很火的Let’s Encrypt

我先是尝试了StartSSL的免费证书,但是它只能签发有效期一年的免费证书,每年都得手动去更新证书是一件很让人头痛的事情。所以后来选定了使用Let’s Encrypt,虽然Let’s Encrypt的证书有效期只有三个月,但是可以方便的通过脚本来实现自动更新。

使用Let’s Encrypt的证书有两种方式,一种是使用他的提供的工具脚本,另一种是使用ACME协议。我目前使用的是ACME协议方式,参考[2]。如果用Let’s Encrypt的工具,参考[3]。我个人比较喜欢ACME协议方式,因为很轻量级,Let’s Encrypt自己的工具太过“全家桶”了,不够简洁明了。

主要的步骤如下:

  1. 生成一个帐号私钥
    $ openssl genrsa 4096 > account.key
  2. 生成一个域名私钥
    $ openssl genrsa 4096 > domain.key
  3. 生成证书签名请求CSR文件,通常至少包含祼域名和带www主机名的两个域名。
    $ openssl req -new -sha256 -key domain.key -subj "/"
        -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf
        "[SAN]\nsubjectAltName=DNS:yoursite.com,DNS:www.yoursite.com")) >
        domain.csr
  4. 在自己网站上配置一个可以从外部访问的目录,用来完成challenge。Let’s Encrypt会生成一个文件,你把它放在这个目录里,然后Let’s Encrypt如果能访问到这个文件,就证明了这个域名是属于你的。nginx配置类似于如下,配在80端口的Server里面:
    location ^~ /.well-known/acme-challenge/
    {
        alias /home/xxx/www/challenges/;
        try_files $uri =404;
    }
  5. 下载acme_tiny脚本,并运行,里面用到了帐号私钥(account.key)、域名私钥(domain.key)、CSR文件(domain.csr)和ACME challenge的路径,生成签发的证书(signed.crt)。
    $ wget https://raw.githubusercontent.com/diafygi/acme-tiny/master/acme_tiny.py
    $ python acme_tiny.py --account-key ./account.key --csr ./domain.csr
        --acme-dir /home/xxx/www/challenges/ > ./signed.crt
  6. 最后合并Let’s Encrypt的中间证书和我们自己的证书:
    $ wget -O - https://letsencrypt.org/certs/lets-encrypt-x1-cross-signed.pem
        > intermediate.pem
    $ cat signed.crt intermediate.pem > chained.pem

Web服务器

我使用nginx作为Web服务器,启用HTTPS服务,只需要在原来的HTTP服务上加几行配置就可以了:

listen 443 ssl;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:
  EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:
  EECDH+3DES:RSA+3DES:!MD5;
ssl_prefer_server_ciphers on;
ssl_certificate /home/user/path/to/your/chained.pem;
ssl_certificate_key /home/user/path/to/your/domain.key;
ssl_session_timeout 5m;

其中需要用到你自己网站的私钥domain.key,还有Let’s Encrypt给你签发并合并了中间证书的证书文件chained.pem。

SSL协议版本与加密算法

我上面的配置用的是CloudFlare推荐的配置,详细的讨论可以参考[4]。我这么配基本上放弃了对Windows XP + IE6的支持,但可以让Qualys SSL Labs给出的评分提高到A级。

WordPress

我的博客是用WordPress构建的,WordPress在很多地方都会把带协议的网站URL给存下来,造成无法透明的把HTTP改成HTTPS。暴力解决方法就是去它的数据库做全文替换[5]。

先把WordPress设置->常规中的两个URL设置改为https,然后去WordPress的数据库对已有的文章进行全局字符串替换:

UPDATE `<wordpress_prefix>_posts` SET post_content=
    (REPLACE (post_content, 'http://[domain name]',
    'https://[domain name]'));

其它页面嵌入内容

如果在HTTPS的页面上嵌入了非HTTPS的内容,比如跨站的HTTP图片,浏览器上的绿色小锁就会变成灰色了。如果是跨站的CSS或者JavaScript,在现代浏览器上可能会直接被禁止加载[6]。

解决的方案就是把src的协议去掉,直接写成//domain.com/path/to/image这样的形式,可以兼容HTTP和HTTPS。

但是有些源站根本就不支持HTTPS,或者虽然提供了HTTPS服务,但证书不合法,这么做就行不通了。比如我的页面右侧的饭否的图片,虽然饭否有HTTPS服务,但证书过期了,直接嵌入就会有问题。

我的解决方案也是简单粗暴的,直接在nginx里为这些网站相关URL做一个反向代理。比如,为了解决饭否图片的问题,我在我的nginx里加了以下配置,然后把饭否图片的域名换成我自己的域名:

location /u {
    proxy_pass http://b.fanfou.com;
    proxy_set_header Host b.fanfou.com;
    proxy_redirect off;
}

自动重定向HTTP请求

至此,HTTPS改造已经准备好了,重启nginx后就可以用HTTPS协议来访问网站,检查是否工作正常。主要检查证书是不是正常,另外还有看看有没有混杂非HTTPS资源造成页面加载不正常的。Chrome的Developer Tools可以帮助你排查这些问题。

如果一切正常,就可以考虑自动重定向所有的HTTP请求了,301跳转通常是最理想的方式。在nginx的80端口http服务器配置中添加以下的内容:

location / {
    return 301 https://$server_name$request_uri;
}

其它要考虑的问题

SNI:如果在nginx用server_name实现了单主机的多虚拟站点,那就会出现一个IP地址上对应多个域名的情况,这时服务器和客户端都需要支持SNI,才能在HTTPS的情况下正常工作。较新版本的nginx版本服务器都是支持SNI的,但IE6之类的老旧浏览器不支持。所以如果放弃老旧浏览器支持的话,SNI不是个问题。否则就只能保证在同一个IP上只启用一个域名的HTTPS网站,才能确保客户访问无障碍。

HSTS:虽然启用了HTTPS,但是用户在访问时如果没有显式输入https协议,现在的浏览器默认还是会先选用http协议,再由服务器进行301/302跳转,这时就有可能被劫持。目前的解决方案是在自己的网站上输出HSTS头,并把自己的域名加入HSTS Preload List列表里[6]。我没有启用HSTS,因为一但启用没办法撤销,万一以后不能提供HTTPS服务了,想降级为HTTP都没有机会了。

其它:对于个人小网站来说,也许前面的讨论基本够用。但对稍大的网站来说,要考虑的问题还有更多。比如:CDN支持、各子域名证书的管理模式、SHA1不安全证书与老版本浏览器兼容性、大量非HTTPS外部资源的处理、特殊客户端不支持302引流等等。

HTTP/2改造

新版的nginx已经内置了对HTTP/2协议的支持,所以完成HTTPS改造后,启用HTTP/2支持是一件相对来说比较简单的事情。

在nginx里HTTPS的listen配置中加入http2即可,当然不要忘记重新reload nginx:

listen 443 ssl http2;

在Chrome中可以安装一个HTTP/2 and SPDY indicator插件,这时打开HTTP/2网站时,地址栏右侧就会出现一个蓝色的闪电标记,证明这个网站已经支持了HTTP/2协议。

新版的curl也可以用来帮助检查HTTP/2是否工作正常,在curl网站URL时,加上–http2参数即可。别忘了,必须是用HTTPS协议才能支持HTTP/2哦。

说得这么轻巧,事实上很多系统中的nginx都不是可以支持HTTP/2的新版本,所以还需要手动编译新版的nginx。

nginx -V可以列出当前nginx编译时使用的configure参数,可以作为重新编译时的参考,在它列出的参数的基础上加上–with-http_v2_module参数,就可以启用HTTP/2功能了。

参考文献

[1] 凯文叔叔的网志 – HTTP/2

[2] Let’s Encrypt,免费好用的 HTTPS 证书

[3] Let’s Encrypt SSL证书配置

[4] 关于启用 HTTPS 的一些经验分享(二)

[5] 迁移 WordPress 博客至 HTTPS

[6] 关于启用 HTTPS 的一些经验分享(一)

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.

Li Fanxi's avatar

2016新年好

2015年,我做了这些事:

– 写了3篇博客

博客空间总访问量66949 PageView(Google Analytics数据),出乎意料地比前一年上涨14.7%,但还是没有达到2013年时的水平。首页、在Linux下使用“360随身WiFi 2”calibre常见问题为Raspberry Pi 2编译内核模块这几个页面的PV占总PV的50%,2015年新写的文章中有一篇能排进前50% PV,也算欣慰。饭否发消息118条,包括照片19张。

– 自由软件相关

Richard Stallman的传记《Free as in Freedom》第一版中文版翻译工作在经历了各种坎坷和挫折后终于完成了,中文版正式命名为《若为自由故——自由软件之父理查德·斯托曼传》。这本书是与GNU中文翻译小组协调人邓楠共同翻译完成的,我负责部分的翻译质量我个人并不十分满意,一方面因为英文水平的原因,一方面也是因为后期做得确实太过仓促。

图书出版后,出版社投入了很多资源进行推广,包括通过哲思社区邀请到Stallman本人来中国演讲并签售,不过总体效果还是差强人意。计划出版第二版的事情,目前也因为各种原因暂时搁浅。出乎意料的事情倒是第一版的Kindle版本很顺利的上架了,虽然Stallman对此也许不太高兴,因为他说过,Kindle是Malware

值得一提的是,这本书是国内少有的依据GFDL许可证出版的图书,我很用力地推动出版社编辑去完成了这件几乎不可能的事情。当然,因为众所可以理解的原因,这件事做得非常低调,但是如果你仔细看这本书的版权页,除了能看到GFDL的声明外,还能看到一些很微妙的变化。

– 几个IT产品

Myo手势控制手环:其实是2014年买的,只不过一直跳票,直到2015年才到货。到手一天就解毒了,直接放到闲鱼上去转让给别人了。这真的只是个实验品,要实现它的官方Demo上那些很炫酷的功能,还有万里长征要走。

Raspberry Pi 2:跟一代相比,性能提升了很多。很多人批判树莓派系列产品的性能、性价比、实用性,我觉得都用力用错了方向。这东西原本的定位就是个电脑学习机,它的优势在于丰富的资源和强大的社区。如果需要更低的价格、更好的性能,完全可以选择其它的嵌入式设备。

联想newifi路由器:在最低69元的成交价格上,买到一个802.11AC的路由器,并且还能刷成华硕固件或者OpenWrt固件,而且运行还非常稳定,很值了。虽然它只有百兆的LAN口,虽然它5G信号很弱。当然,它的原生固件实在不忍吐槽。

华硕AC-66U路由器:曾经的高端路由器,今年最低打折到了不到500元,买回来几乎没有太折腾就扔到角落让它勤勤恳恳的工作去了。这才是一个合格路由器应该具有素质!当然,实际上我还是刷了一次机,改了很多脚本,为了可以顺畅使用Google搜索,还为了实现电信、移动双线接入和策略路由。

华为荣耀4X(二手,两台)/5X/7手机:今年年内居然买了4台华为手机,除了7以外还都是最低端的机器。实际使用体验还算不错,第一次发现Android手机不root也可以用的好好的。当然,低端就是低端,屏幕色彩很差,外放音质也很差,做工/手感也比较一般,不过日常使用并无大碍,电池续航能力也不错。

– 旅游

2015年0出游,铁路运转里程0。

– 其它

还有一件很重要的事情,不放在博客上说。

展望2016年:

2016年是挺多事的周年纪念的,比如初中毕业20周年、研究生毕业暨工作10周年等等,年纪越大越喜欢怀念过去。当然,怀念是为了总结,为了前进。

Li Fanxi's avatar

关注2016维也纳新年音乐会

曾经关注过的那些维也纳新年音乐会:关注维也纳新年音乐会,2016年将是我第21次收看维也纳新年音乐会的直播。

2016年维也纳新年音乐会将由拉脱维亚指挥家马里斯·杨松斯(Mariss Jansons)执棒,他2006年第一次登上维也纳新年音乐会的指挥台,史无前例的在一场新年音乐会中演绎了23首乐曲。而他在2012年的再次登台,又打破了这个纪录,达到了24首。

2016年新年音乐会曲目的单的正式消息来得特别晚,直到上周才在维也纳爱乐乐团的官居网上放出。总共21首。其实一个月前就有小道消息放出了这个曲目单,不过在这个谣言满天飞的时代,我还是等正式消息再来写这篇文章。

2016维也纳新年音乐会CD封面

上半场:

  • 01 – Robert Stolz – UNO Marsch – 国际联合进行曲
  • 02 – Johann Strauss II – Schatzwalzer; op. 418 – 珍宝圆舞曲 (2003, 2009)
  • 03 – Johann Strauss II – Violetta-Polka française; op. 404 – 维奥列塔法兰西波尔卡
  • 04 – Johann Strauss II – Vergnügungszug; Polka schnell; op. 281 – 游览车快速波尔卡 (1970, 1982, 1987, 1992, 2001, 2005)
  • 05 – Carl Michael Ziehrer – Weana Madl’n; Walzer; op. 288 – 维也纳的少女圆舞曲
  • 06 – Eduard Strauss – Mit Extrapost; Polka schnell; op. 259 – 特快邮车快速波尔卡 (2000)

下半场:

  • 07 – Johann Strauss II – Ouverture zu Eine Nacht in Venedig – 威尼斯之夜序曲 (1994, 2001, 2009)
  • 08 – Eduard Strauss – Außer Rand und Band-Polka schnell; op.168 – 激动万分快速波尔卡
  • 09 – Josef Strauss – Sphärenklänge; Walzer; op. 235 – 天体乐声圆舞曲 (1954, 1964, 1979, 1980, 1983, 1987, 1992, 2004, 2009, 2013)
  • 10 – Johann Strauss II – Sängerlust-Polka française; op. 328 – 快乐的歌手法兰西波尔卡(维也纳童声合唱团)
  • 11 – Josef Strauss – Auf Ferienreisen; Polka; op. 133 – 假期旅行波尔卡(维也纳童声合唱团) (1988, 1995)
  • 12 – Johann Strauss II – Fürstin Ninetta – Entr’acte zwischen 2. und 3. Akt 轻歌剧《侯爵夫人尼奈塔》第三幕间奏曲
  • 13 – Emil Waldteufel – Valse-España; op. 236 – 西班牙圆舞曲
  • 14 – Joseph Hellmesberger/Vater – Ball-Szene 舞会场景
  • 15 – Johann Strauss I – Seufzer-Galopp; op. 9 – 叹息加洛普 (1991)
  • 16 – Josef Strauss – Die Libelle; Polka Mazur; op. 204 – 蜻蜓玛祖卡波尔卡 (1954, 1983, 1989, 2000, 2002, 2008)
  • 17 – Johann Strauss II – Kaiser Walzer, op.437 – 皇帝圆舞曲 (1975, 1982, 1987, 1991, 1996, 2003, 2008)
  • 18 – Johann Strauss II – Auf der Jagd; Polka schnell; op. 373 – 在猎场上快速波尔卡 (1954, 1979, 1988, 1993, 2005, 2010)

加演:

  • 19 – Johann Strauss II – Im Sturmschritt; Polka schnell; op. 348 – 飞奔快速波尔卡 (1990, 2004)
  • 20 – Johann Strauss II – An der schönen blauen Donau, Walzer, op. 314 – 蓝色多瑙河圆舞曲
  • 21 – Johann Strauss I – Radetzky-Marsch, op. 228 – 拉德茨基进行曲

每次看新一年的曲目单时,总会在脑中冒出两种想法:“又来”、“这是什么鬼”。这恰恰就是对每届新年音乐会不变的期待:期待看到经典曲目的全新演绎,也期待在这个舞台上见到更多新的作曲家、新的作品。

2016年新年音乐会引入了三位新的作曲家的作品:罗伯特·施托尔茨的《联合国进行曲》、埃米尔·瓦尔德退费尔的《西班牙圆舞曲》以及老约瑟夫·赫尔梅斯伯格的《维也纳的舞会场景》。约瑟夫·赫尔梅斯伯格的舞曲作品在历年的新年音乐会上已经多次出现,而他父亲老约瑟夫·赫尔梅斯伯格的作品还是第一次出现,不知道会带来何种耳目一新的感觉。除此之外,非施氏家族的曲目还选择了齐莱尔的《维也纳的少女圆舞曲》,这也是一个首次在新年音乐会上亮相的节目。

2016年是爱德华·施特劳斯逝世100周年,指挥和乐团特别选择了两首爱德华的波尔卡舞曲,纪念这位施特劳斯家族最小的成员。不过群众呼声很大希望能听到的爱德华的圆舞曲作品依然没有露面。

指挥杨松斯在2012年时请来了维也纳童声合唱团的小朋友们,这个拥有500多年历史的合唱团的小歌唱家们,在2016年的元旦将又一次用他们的天籁之声征服全世界的听众。

2016年的曲目中有几首耳熟能详的“又来”曲目,比如:《游览车》、《天体乐声》、《蜻蜓》、《皇帝》和《在猎场上》,当然这些就算是新年音乐会的经典作品了。看到名字与旋律一样优美的《天体乐声圆舞曲》,又不禁让我想起所谓“天体乐声大魔咒”了:在新年音乐会历史上,波斯科夫斯基、卡拉扬、克莱伯、穆蒂这几位大师在演完这个曲子以后就都没有再上过维也纳新年音乐会的指挥台了。只有马泽尔和巴伦博伊姆破解了这个“魔咒”,但是他们有一个共同点,就是在首次登台新年音乐会时就指挥演奏了这个曲目。扬松斯是我很喜欢的指挥家之一,我可真心希望他不要被这“魔咒”所困。

2016年是我收看维也纳新年音乐会直播的20周年,对1996年洛林·马泽尔在新年音乐会上第一次用中文“新年好”向全世界问好的场面仍然记忆尤新。十周年的2006年前后,我在QQ的“施特劳斯之声”群和一些论坛认识了Duckula、苏大米、JosefKitty、king_zhd、定定、蓝色多瑙河等很多有相同爱好的朋友,并且花了很多时间通过各种渠道尽可能收集了历年新年音乐会所有录音录像资料,估计是那时在网上可以找到的整理得最完整的一套录音资料了。2015年是维也纳新年音乐会创办的75周年纪念,两个月前SONY为此出版了一套维也纳新年音乐会75周年曲目全集CD, 收录所有在新年音乐会上演出过的曲目,还特地补录了所有早年没有录音时演出过的曲目。拿到这套CD,回想起以前一首首曲子的寻找的日子,颇为唏嘘。

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.

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.

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 啦之类的危险命令哟,可别说我没提醒你哟 -_-

's avatar

Keepalived 实现双机热备并对关键进程进行监控

三连更有木有!(对不起我网文看多了,而且中间其实断过两天的,我不会告诉你其实是我把这事儿给忘了的)

另,这篇照例是工作需要。


使用 Keepalived 实现多机热备(无负载均衡)时,除了对网口状态的监控外,一般还要对系统关键进程(如 Web 服务器的话就是 nginx 或者 httpd 之类的)进行监控,这时就要用到 vrrp_script 配置。网上能找到的 vrrp_script 示例都使用了 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 模式进行网络编程时,比较固定的一个模式是这样的:

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。

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 对象中取得协商出来的算法。

      '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 版,刚刚满足我的手机阅读需要。

      '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?

      's avatar

      [翻译] 用 Ruby 写编译器之六:匿名函数 lambda

      原文链接:http://www.hokstad.com/writing-a-compiler-in-ruby-bottom-up-step-6.html


      既然上次已经提到说,我们其实是在从 Lisp、Scheme 还有类似的其他语言中借鉴各种要实现的功能(我没想过要把这个项目做成原创的⋯⋯或者至少也要等到以后再说吧),那么现在也是时候实现一些更加强大的功能了。

      那就来做延迟求值以及匿名函数吧

      Lambda ,又名匿名函数,可以像普通的数值或者字符串类型那样被当作函数参数来到处传递,也可以在需要的时候才调用(当然不调也可以)。同时,外层函数(也就是定义匿名函数的函数)作为它们的运行环境,在其中定义的局部变量可以被这些匿名函数所访问。这就形成了[一个闭包](http://en.wikipedia.org/wiki/Closure_(computer_science))。我们这次**并不是**要实现完整的闭包功能,只是开头的一小步而已,完整的实现要等到再后面了。

      's avatar

      [翻译] 用 Ruby 写编译器之五:整数常量,以及 if 语句

      原文链接:http://www.hokstad.com/writing-a-compiler-in-ruby-bottom-up-step-5.html


      上次我承诺会发布的更快一些,不过还是失败了⋯⋯作为补偿,这章的内容将会是原计划中的第 5,6,7 章内容的合并,因为这三章确实都很短。闲话少叙:

      处理数字常量

      到目前为止,我们只处理了一些实现所必须的数字常量,也就是当一个外部函数的返回值是数字的情况,而且没有做任何形式的类型检查。

      's avatar

      [翻译] 用 Ruby 写编译器之四:自定义函数,以及运行时支持

      原文链接:http://www.hokstad.com/writing-a-compiler-in-ruby-bottom-up-step-4.html


      抱歉,又拖了很长时间。要忙的事情实在很多。正如上一篇文章末尾提到的那样,这次要讲的是自定义函数,以及一个简单的“运行时库”。

      自定义函数

      一门编程语言如果连函数和方法都没有的话,那也就不能算是一门语言了。而且,实践表明,一门面向对象语言中的所有特性都可以通过过程式的语言要素来实现:一个方法也只不过是以一个对象为额外参数的函数而已。因此,增加对函数的支持就是实现一门语言的核心所在。

      's avatar

      [翻译] 用 Ruby 写编译器之三:语句序列,以及子表达式

      原文链接:http://www.hokstad.com/writing-a-compiler-in-ruby-bottom-up—step-3.html


      我本来是想要早点发表的,可是我这周又不行了 – 虽然整理一篇旧文只需要半个小时。不管怎样,这是第三章,而且我会在末尾大概列一下之后的大纲。由于我会试着把一些小的步骤组合成更有内容的章节(下面就有个这样的例子),因此原来的 30 篇文章已经被我给减到了 20 篇左右(当然,这只是我已经完成了的,后面还有新的呢)。

      via these people and places