羞耻的事

我有一个爱好,就是去看别人的自我介绍。自我介绍存在于很多地方,比如个人网站的 About 页面,知乎的个人主页等等。粗略归类,大致分为三种。

第一种,非常详细地讲述自己愿意展现出来的方方面面。从学校,到职业,到爱好,看完之后,对这个人便有基本了解。

第二种,简短介绍。比如“高中狗”,“药丸党”,“喜欢音乐的码农”等等,展现自己的一两个方面。另有一种标签式的介绍,形如“A/B/C/D”。也有放一个链接的,比如我在知乎的介绍就是本站的网址。

第三种,没有介绍。有的人没有什么可介绍的,或者不想介绍,因此不写。此种情况也见于不少大师的个人网站——他们太有名了,以至于不需要介绍。

我们不是大师,因此需要自我介绍来让别人快速了解自己。而当需要深入了解一个人时,自我介绍就远远不够了。博客里的文章,知乎的回答,豆瓣的影评书评,Bangumi 的吐槽,这些内容为我们深入了解一个人提供了可能性。然而常见的情况却是,很多人除了个人介绍,就没了。这便是我认为羞耻的一件事:个人介绍提供的内容多于其它内容。

大概有人不理解为什么光有个人介绍在我看来是不好的,我来讲一下背后的逻辑:在我看来,既然一个人提供了自我介绍,那么就表明这个人有被互联网上其它人了解的意愿。看了个人介绍之后,如果我希望进一步了解这个人,我必然会寻找他留下的更多信息。即使这些信息因为领域不同我不能看懂,但它们的存在会让我尊重这个人。如果没有,站在他的角度,我会觉得羞耻。就好像你在家门口挂了个牌子,写者几个大字“欢迎来做客”,结果屋子里却没有家具,客人来了连坐的地方都找不到。

每个写下自我介绍的人,我敢保证,都渴望被互联网上的其它人了解。当我看到一个人给自己贴上各种标签 A/B/C/D/E/F/G 的时候,我深刻地理解,这个人希望能有同样属于 A or B or C or D or E or F or G 的同类来发现自己,同时自己也能确认对方的存在,要是能认识就更好了。但就我个人而言,我真的不会因为一个人写上了几个字就对这个人感兴趣,除非标签太抢眼比如 G 家工程师或者头像很好看并且是本人。我也非常怀疑,除我之外的其它人会如此,尤其当这个标签还不是非常小众的时候。摆明了渴望被了解,却因为提供不了更多信息而无法被了解,实在是太羞耻了。

当你无法提供内容或无意被了解的时候,最好是不写自我介绍。我尊重所有不写自我介绍的人。

最后说一种让我看到之后立刻失去兴趣的介绍:MBTI 性格测试结果的四个字母。这种介绍给我的感觉是:他是个无趣的人,除了性格,啥都没有。

博客性能优化:续

去年写过一篇文章《博客性能优化》。 网站搬迁之后,需要重新优化一下。下面的测试结果都来自 webpagetest

优化之前

对字体开启压缩
按照 FONT MIME TYPES IN NGINX 这篇文章里说的,在 Nginx 的 mime.types 文件中加入下面几行:

font/ttf                      ttf;
font/opentype                 otf;
application/font-woff         woff;
application/vnd.ms-fontobject eot;

nginx.conf 的 gzip 类型中添加

gzip_types [...] font/ttf font/opentype application/vnd.ms-fontobject image/svg+xml;

除了 woff 文件,其它的字体都可以压缩。woff 本身就是压缩过的。

让客户端缓存静态文件
之前静态文件的配置很简单:

location /static {
    alias /home/laike9m/static;
}

完全没有客户端缓存。现在加上了:

location /static {
    alias /home/laike9m/static;
    etag on; 
    expires max;
    add_header Pragma public;
    add_header Cache-Control "public";
    access_log off;
}

/media 作同样配置。
特别地,我希望 css 文件不要缓存那么久,因为我经常修改 css,希望第二天就能看到效果。

location ~* ^/static/(.+\.css)$ {
    alias /home/laike9m/static/$1;
    etag on; 
    expires 1d;
    add_header Pragma public;
    add_header Cache-Control "public";
    access_log off;
}

这个 alias 写不对很容易就 404 了。还有就是注意要把 css 的这个 location 放在 /static 之前,因为 Nginx 在正则匹配的时候是 sequential 的,即放在前面的会先匹配,而不是像前缀匹配取最长的那个。

效果

似乎加载时间反而变长了。。。不过缓存对初次加载本来就没啥用。

开启 HTTP/2
如果不是因为想尝试 HTTP/2,我也不会写这篇文章了。Nginx 从 1.9.5 版开始正式支持 HTTP/2

首先要重新编译 Nginx,在 configure 时加入 --with-http_v2_module
然后安装 OpenSSL 1.0.2。官方的说法是“This is required to support the ALPN extension to TLS that our HTTP/2 implementation uses.”。

然后就发现网站没法访问了_(:3」∠)_,出现错误 ERR_SPDY_INADEQUATE_TRANSPORT_SECURITY
找到了两篇相关文章:
Guide to Deploying Diffie-Hellman for TLS
Security/Server Side TLS
某论坛的一个回复里说

To use HTTP/2, you need to make sure cipher ECDHE-RSA-AES128-GCM-SHA256 is the first one in your cipher configuration.

首先按第一篇文章说的生成一个新的 "Diffie-Hellman group",作为 ssl_dhparam 的值。

openssl dhparam -out dhparams.pem 2048

然后复制了一个以 ECDHE-RSA-AES128-GCM-SHA256 为起始的值作为 ssl_ciphers,网站就可以工作了。

效果

HTTP/2 的 Multipexing 在这张图里体现得很明显,不会开多个连接去下载静态资源了。然而速度上似乎并没有提升。

可能的后续优化
Nginx Configuration Snippets
本博客 Nginx 配置之性能篇

另外,webpagetest 给我的 First Byte Time 这一项评分是 B。官方的解释是:

The First Byte time is the time from when the user started navigating to the page until the first bit of the server response arrived. The bulk of this time is usually referred to the "back-end time" and is the amount of time the server spent building the page for the user.

评分标准

The target time is the time needed for the DNS, socket and SSL negotiations + 100ms. A single letter grade will be deducted for every 100ms beyond the target.


这一项没有达标的原因大概是后端现在什么缓存都没开。其实原来是有用 Memcached 缓存全站,但是不够令人满意。 因为我在发布文章之后的一段时间可能会编辑文章,而加缓存之后总是要很慢才能生效。目前还在寻求更好的方法。 理想状况是最新的一篇文章永远不缓存,或者是更新文章之后让缓存失效。这些都需要更精细的代码层面的控制。

这篇文章之后应该还会更新。

2015.10.20
添加 OCSP Stapling
引用 JerryQu 的说法

浏览器可能会在建立 TLS 连接时在线验证证书有效性,从而阻塞 TLS 握手,拖慢整体速度。OCSP stapling 是一种优化措施,服务端通过它可以在证书链中封装证书颁发机构的 OCSP(Online Certificate Status Protocol)响应,从而让浏览器跳过在线查询。服务端获取 OCSP 一方面更快(因为服务端一般有更好的网络环境),另一方面可以更好地缓存。

nginx.conf 添加上这三行就可以了:

ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /home/laike9m/files/laike9m_com/laike9m_com.chain.crt;

ssl_trusted_certificate 所需要的 pem 文件其实就是 chained crt(问了 gogetssl 员工得知)。

2015.10.30
启用 Session Ticket

ssl_session_tickets  on;

Session Ticket 和 Session Cache 一样,也是简化 TLS 握手的方案。我不是很清楚在服务器都支持的情况下浏览器会选择哪一种。

修改 ssl_buffer_size
关于 ssl_buffer_sizeNginx 文档是这么解释的:

Sets the size of the buffer used for sending data.

By default, the buffer size is 16k, which corresponds to minimal overhead when sending big responses. To minimize Time To First Byte it may be beneficial to use smaller values, for example:

ssl_buffer_size 4k;

然而到底设成多少比较好,网上找不到相关的说明/测试。在 Is TLS fast yet 里 Ilya Grigorik 把这个值设成了 1400:

ssl_buffer_size 1400; # 1400 bytes to fit in one MTU

然后我也学着设成了 1400。不严谨的测试结果表明,TTFB 确实减小了,但是总的 download time 增大了一点。

开启 tcp_nopush + tcp_nodelay + sendfile

sendfile        on;
tcp_nopush      on;
tcp_nodelay     on;

这篇文章详细讲了为什么要这么配置。简单来说就是 sendfile on 启用了 sendfile(2) 这个系统调用,相比 read()/write() 有诸多优点;tcp_nopushtcp_nodelay 结合起来保证充分利用 MTU 的同时还能尽快地把数据包发出去而不等 0.2s。

2015.11.1
压缩 png/jpg
神器 tinypng.com

减小 favicon.ico 的大小
原来的 favicon.ico 竟然有 30KB。上网一查才知道,ico 其实就是多层未压缩的 bmp。本来想用 Photoshop 编辑,但是居然打不开。安了一个插件之后能打开了,然而编辑功能简直糟糕,多图层都没法显示。好在找到一枚神器 IcoFX,花钱买了正版。
按照这篇文章讲的,删掉了 64*64,24 * 24 两个图层。保留 32 * 32 是因为 Disqus 的站点缩略图可能要用。颜色也没有换成 4bit,仍然保留 8bit 256 色,毕竟已经减到 3KB 了。

2015.12.5
参照 sendfile 官方文档加上了 aio on

aio can be used to pre-load data for sendfile()

然后我无意之中发现,sendfile ongzip on 无法同时起作用,来自官方的《Tuning NGINX for Performance》 一文:

Note, however, that because data copied with sendfile() bypasses user space, it is not subject to the regular NGINX processing chain and filters that change content, such as gzip. When a configuration context includes both the sendfile directive and directives that activate a content-changing filter, NGINX automatically disables sendfile for that context.

因为 sendfile on 使用的是系统调用 sendfile(2),实现了 zero-copy,文件内根本就不会被拷贝到用户态(“bypasses user space”),因此 Nginx 无法进行压缩处理。

上网查了一下,唯一既能利用 zero-copy 又能利用 gzip 压缩的方案是启用 gzip_static。这个功能并非开箱即用,而是要求使用者先把文件压缩好,这样 Nginx 才能找到,如果没找到,就 fall back 回普通的 gzip。因为比较麻烦,这次先不弄了。计划是添加一个 Django command 实现原来指定 gzip 压缩的那些格式的文件。几个可能有用的链接:
Gzip per request vs static gzip 比较两种 gzip 方式
Nginx Performance Tuning 对于 gzip_static 讲得比官方文档清楚
Nginx中gzip_static模块的使用 使用 gzip_static 的一些注意事项

还有把 gzip_comp_level 从 6 改成了 9,提高压缩的程度,代价是更消耗 CPU,不过感觉 CPU 用的不多,所以无所谓。

博客迁移记录

之前在《决定转投 DigitalOcean》这篇文章中说了一下决定迁移网站的原因。 趁十一假期终于完成了博客的迁移工作,在此记录。

  1. Dump PostgreSQL data

    pg_dump -U laike9m -f dump.sql database1
    

    生成 425KB 的 dump.sql, 下载到本地,scp 到 DO machine。安装 PostgreSQL:

    sudo apt-get update
    sudo apt-get install postgresql postgresql-contrib
    

    创建用户和数据库,参考 how-to-install-and-use-postgresql-on-ubuntu-14-04。 还要给用户 laike9m 数据库的管理员权限。先切到 postgres 用户,然后参考 error-must-be-owner-of-language-plpgsql 给 laike9m 管理员权限。

  2. Load data

    psql -U laike9m database1 < dump.sql
    
  3. 配置虚拟环境
    安装 virtualenvwrapper,mkvirtualenv, 安装依赖。在安装之前先

    sudo apt-get install libpq-dev
    sudo apt-get install python3-dev
    

    因为要安装 psycopg2。

  4. 下载并修改代码
    在家目录创建 static, media 文件夹作为 STATIC_ROOTMEDIA_ROOT。 clone 代码,clone media 文件夹,按照新路径修改 settings.py, 下载 media 文件。设定密码环境变量(实际上这个用不到了)。
    之前的 Django 版本是 1.5,这次我想趁迁移升级成 1.8。参照官方的教程, 先把 south 从 INSTALLED_APPS 中去掉,然后在 css3two_blog 文件夹中

    mkdir migrations
    cd migrations
    touch __init__.py
    

    最后在根目录执行 python manage.py makemigrationspython manage.py migrate --fake-initial

    (blog)laike9m@laike9m1:~/Envs/blog/My_Blog$ python manage.py makemigrations
    Migrations for 'css3two_blog':
      0001_initial.py:
        - Create model BlogPost
        - Create model BlogPostImage
    (blog)laike9m@laike9m1:~/Envs/blog/My_Blog$ python manage.py migrate --fake-initial
    Operations to perform:
      Synchronize unmigrated apps: staticfiles, admindocs, messages, mytemplatetags, contact_form
      Apply all migrations: css3two_blog, contenttypes, taggit, admin, auth, sites, sessions
    Synchronizing apps without migrations:
      Creating tables...
        Running deferred SQL...
      Installing custom SQL...
    Running migrations:
      Rendering model states... DONE
      Applying contenttypes.0001_initial... FAKED
      Applying auth.0001_initial... FAKED
      Applying admin.0001_initial... FAKED
      Applying contenttypes.0002_remove_content_type_name... OK
      Applying auth.0002_alter_permission_name_max_length... OK
      Applying auth.0003_alter_user_email_max_length... OK
      Applying auth.0004_alter_user_username_opts... OK
      Applying auth.0005_alter_user_last_login_null... OK
      Applying auth.0006_require_contenttypes_0002... OK
      Applying taggit.0001_initial... FAKED
      Applying taggit.0002_auto_20150616_2121... OK
      Applying css3two_blog.0001_initial... FAKED
      Applying sessions.0001_initial... FAKED
      Applying sites.0001_initial... FAKED
    

    最后拷贝静态文件

    python manage.py collectstatic
    

    runserver 启动测试服务器,通过 ip 访问网站并执行各种操作,均没有出现问题。
    这里不得不赞一下 Django,原先以为 1.5 升级 1.8 将遇到各种问题,尤其是跨过了 1.7 这个内置 south 的重大改进版本,没想到居然顺利完成了。

  5. 安装配置 Nginx, uWSGI
    Nginx 我比较熟了,但是每次遇到 uWSGI 都很麻烦,这次也是。花了很长时间去解决 这个问题。后来发现在文档里已经有说明。
    uWSGI 配置文件 uwsgi.ini 如下

    [uwsgi]
    chdir           = /home/laike9m/Envs/blog/My_Blog
    module          = my_blog.wsgi:application
    home            = /home/laike9m/Envs/blog/
    master          = true
    processes       = 3
    socket          = /tmp/uwsgi.sock
    chmod-socket    = 777
    vacuum          = true
    logto           = /home/laike9m/uwsgi.log
    

    Nginx 配置在这里
    原本想直接开个 screen 来运行 uwsgi,后来发现用 upstart 更好。 下面是 uWSGI 的配置文件 /etc/init/uwsgi.conf:

    env DJANGO_DB_PASSWORD=
    env EMAIL_HOST_PASSWORD=
    env LC_ALL=en_US.UTF-8
    env LANG=en_US.UTF-8
    
    start on runlevel [2345]
    stop on runlevel [!2345]
    
    setuid laike9m
    setgid www-data
    
    exec /usr/local/bin/uwsgi --ini /home/laike9m/Envs/blog/My_Blog/uwsgi.ini
    

    这样之后用 sudo service uwsgi start/stop/restart 就能操作 uWSGI 了

  6. 添加 HTTPS
    证书还是用之前的,主要是修改 Nginx 配置。对 80 端口的访问要自动跳转。
    这里只贴关键部分的配置。

    server {
        listen 80;
        server_name  laike9m.com laike9m.com;
        return 301 https://$server_name$request_uri;
    }
    
    server {
        ...
        listen 443 ssl;
        server_name  laike9m.com laike9m.com;
        ssl_certificate /home/laike9m/files/laike9m_com/laike9m_com.crt;
        ssl_certificate_key /home/laike9m/files/laike9m_com/key;
        ...
    }
    


    基本就是这样了。


top