Nanjing Linux User Group

September 18, 2016

李凡希

十年(中)

这文章已经快烂尾了,努努力,先补个中段出来。

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一词,在自由软件世界里,你可以既是软件用户,也是软件的主人。当你以软件主人的心态去使用自由软件时,才会真正理解自由软件背后的理念,也才能从中收获更多的价值。

 

by Li Fanxi at September 18, 2016 03:16 PM

September 09, 2016

王宇希

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.

by jaseywang at September 09, 2016 12:33 PM

September 07, 2016

Phoenix Nemo

搭建一套权威 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):

Glue Records

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

Glue Records

收尾

全部完成之后就是一个完整功能的 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 域名。当然一般就是看起来会比较厉害而已…

September 07, 2016 02:11 PM

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

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

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

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

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

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

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

原帖在这里

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

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

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

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

最后献丑一张w

Kira( > ◡╹)~ ◡╹)~" /> ◡╹)~" />

September 07, 2016 01:48 PM

Sierra 4G LTE EM7345 with Arch Linux

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

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

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

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

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

启用 modemmanager 服务

1
systemctl enable modemmanager.service

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

1
MODULES="... cdc_mbim"

重新生成内核镜像

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

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

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

Sierra EM7345 4G LTE Speedtest on China Unicom

参考文档:

September 07, 2016 01:48 PM

ThinkPad X1 Carbon 2015 与久违的 Arch Linux

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

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

X1 Carbon 2015 20BSCTO1WW

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

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

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

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

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

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

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

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

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

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

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

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

Desktop

KInfoCenter

最后的一点牢骚。

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

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

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

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

September 07, 2016 01:48 PM

August 26, 2016

王宇希

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:

by jaseywang at August 26, 2016 01:20 PM

August 22, 2016

Phoenix Nemo

想要导出 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 个依赖包!!!

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

August 22, 2016 07:15 PM

August 14, 2016

alswl

API 集成测试实践

abao.png

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

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

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

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

特性需求

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

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

挑选出来的选型和评价

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

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

搭建 demo,进行试用

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

supertest:

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

pyresettest:

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

hippie-swagger:

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

robotframework:

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

使用感觉

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

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

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

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

感谢项目贡献者:

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

参考文档


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

3a1ff193cee606bd1e2ea554a16353ee

by alswl at August 14, 2016 11:08 AM