Monthly Review 2014-10

工作:

  1. 本月的 git commits 还不足上个月的一半。
  2. Redmine 上关掉 42 issues,当然有部分是无法复现或拒绝需求。
  3. 完成拖延一年的运营管理界面,这部分一周可能都用不了一次,一直没动力去做。
  4. 捡起 Docker 想做一下 CI,发现最大的问题不是环境,而是没有写测试用例的习惯。
  5. 除了没有测试,大部分项目都没有文档,或者更新不及时,接下来一个月先把自己手上的项目文档补全。
  6. 暴漏出来的问题就是自己的开发模式还处于比较原始的小作坊形式。VCS+Code Review 全靠自觉+代码强迫症,测试和部署上线全手工操作,没有流程,而这却是最重要的。

生活:

  1. 想六六。
  2. 考虑把娃带北京自己带,然后就没有然后了,哎。
  3. 如果独生子女有什么好处的话,孩子在需要帮忙的时候父母没有太多其他牵挂算一个。
  4. 晚上买菜做饭,对自己的厨艺还算满意,就是刀工太差。

Jekyll in Docker

最近又捡起 Docker,打算用在团队内做一些 CI 工作。拿 Jekyll 练手,记一下笔记:

FROM ruby:2.1.3
MAINTAINER Heyward Fann <[email protected]>

RUN gem install github-pages
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*

ENV NODE_VERSION 0.10.33
RUN curl -SLO "http://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.gz" \
        && tar -xzf "node-v$NODE_VERSION-linux-x64.tar.gz" -C /usr/local --strip-components=1 \
        && rm "node-v$NODE_VERSION-linux-x64.tar.gz"

WORKDIR /blog
EXPOSE 4000

ENTRYPOINT ["jekyll"]
CMD ["serve"]

Then:

  1. docker build --rm=true --tag="blog:0.0.2" .
  2. docker run --rm -p 4000:4000 -v /ABSOLUTE/PATH:/blog blog:0.0.2
  3. boot2docker ip then http://IP:4000
  4. OR docker run --rm -v /ABSOLUTE/PATH:/blog blog:0.0.2 build

笔记:

  1. Base image 建议选用 debian:wheezy,如果需要编译环境可用 buildpack-deps:wheezy|jessie,相对 Ubuntu image 要小很多。
  2. 尽量不安装编译环境,直接包管理工具或二进制文件,注意清理缓存文件。
  3. 尽量少的 RUN 命令,减少 layers 数量,尽可能在一个 RUN 组合完成多个操作,比如 ruby
  4. 配合 .dockerignore 忽略不需要的文件。
  5. build 或 run 的时候加上 --rm=true 自动删除中间容器。
  6. CMDENTRYPOINT 都是 docker run 的入口,只是在参数处理上不同。CMD 可以被 run 后面的命令替换,而 ENTRYPOINT 是把 run 后面的作为参数传入。
  7. CMD 配合 ENTRYPOINT 一起用很不错,如果没有参数,执行的就是 ENTRYPOINT+CMD 组合起来的功能,加上参数就会把 CMD 替换掉,执行另外的命令。
  8. OS X 下用 boot2docker 要注意 IP 不是本机或 127.0.0.1,而是 boot2docker ip.
  9. 如果是 Golang 二进制程序,完全可以构建一个空 image 执行,比如 Building Docker Images for Static Go Binaries.

参考:


就目前的情况,用 Docker 构建测试环境很方便,麻烦的是测试用例和测试脚本整理,小团队基本以业务为主,很少有时间或没有意识的去写测试用例,只是在完成具体业务后针对该功能进行测试,没法系统的进行测试,这个需要在后面工作中重视起来。

Tweet

Make every detail perfect and limit the number of details to perfect.

via Jack

What Apple has done

Zsh Shared History

You can share every commands between all terminals with Zsh shared history. In your .zshrc:

# enable shared history
setopt inc_append_history
setopt share_history

# disable shared history
unsetopt inc_append_history
unsetopt share_history

Two Hard Things

There are only two hard things in Computer Science: cache invalidation and naming things. – Phil Karlton

Monthly Review 2014-09

选择

月初一个项目计划是客户端+服务端都由我负责,项目进行中我把客户端交了出去,保证进度是一个原因,毕竟是完全新的业务功能,服务端要做东西还挺多,主要原因是面对客户端开发,忽然手生,表现在这个东西我知道,着手代码的时候要愣一愣。

算下来有一年多没有 系统 的写 iOS,正好 iOS 7 一代。这一年 iDev 相关的学习一直没落下,但缺乏实际项目锻炼,解决问题的思路还有,具体到某一个技术点,比如写一个毛玻璃模糊效果,就要愣一下,需要查文档了。

出现这种情况也在意料之中,虽说要做全栈开发,终究是要有些侧重,目前还是以服务端为主。服务端开发现在主要是业务驱动,技术长进有限,最近在看 nsq,争取在业务和技术上都能有进步。客户端方面还是技术关注+学习,具体开发上给我一点时间还是有信心回到之前的熟练水平 :D


最后还是选择把六六留在老家。走这一步我和老婆俩人没少哭,就现阶段条件,执意带六六到北京的结果可能更糟,北京的居住条件六六和妈是否适应,爸一个人在家的生活,妹妹也还没毕业,就算狠狠心带过来也不能长久,这些都让人头大。六六在家爸妈肯定能照顾好,就是想孩子受不了,尤其是大了越来越好玩,真心舍不得。每个月多往家跑跑吧。

六六健康成长。

租房小事

因为自己的犹豫,错过了一套条件非常不错的房子。房东同时把房子挂在链家和我爱我家,链家带我看房的时候我在想看看别的房子再说,等一天应该没问题,结果就被我爱我家出手。所以说:你越担心某种情况发生,那么它往往就是会发生,墨菲定律:

Anything that can possibly go wrong, does. via

这事对我来说是个教训,做事太瞻前顾后,不够果断。不过我想记的不是这件事,是下面这件,不知道我做的是对是错还是很傻逼的事。

带我看房的中介哥们,90 年的,人特老实,不像其他中介满嘴跑火车。在确定租房意向后,房东很直接的提议私单,就是我和房东直接签,然后出一半中介费给哥们作为辛苦费,这样他能拿到比提成多一倍的钱,我也能省一半中介费。我当然是愿意,不过哥们支吾半天说不行,说知道这样是来钱快,但是他不想这么干,圈子里这么做的人很多,但他不喜欢,要按原则来。当时房东还说我俩可以把他踢开直接签,这样连一半中介费我都不用出。中介哥们当时很憋屈,感觉眼圈都是红的,又很无力。房东离开后我俩单聊,他说理解,只是自己不喜欢。然后,有那么一瞬间,我决定按照正规流程来办,当然我要出全部中介费。签合同的时候才发现,这是他入行一个多月的第一单。能看出他很紧张,写字手都在抖,我不知道这第一单对他有多深的意义,也许若干年后他回想起来依然会激动?或者骂自己傻逼为什么没有直接拿钱?或者笑话有个傻逼居然答应出全部中介费?我只记得当时那一瞬间我的想法:

不要做让自己讨厌自己的事情。 1 2

Monthly Review 2014-08

工作上把一个去年就想过的设计码成并上线,目前状况良好,说明当初的设计思路是没有问题的。之所以拖了这么久是因为项目时间比较紧(是我比较懒),最主要的原因是一直没有下狠心去重构。这部分功能运行正常,可能在某些情况下会有性能问题,但绝大数情况下完全不用担心性能,而新的设计和之前的实现不同,作为一个线上服务首先要考虑的是服务的稳定性,其次是新设计的兼容性,所以一直拖到现在。现在回头看开发要有点魄力,有时候自己过于小心了。

服务端开发的架构设计很重要,前期要多做思考工作,不能上来就去代码,要考虑可能出现的功能需求,思考性能瓶颈,有了好的设计再去代码效率也会更好。

生活上给自己买了一份商业保险,30年寿险,是自己对家庭的责任。核保的时候因为 BMI 超标,每年保费多了 150,体重又一次打击了我,减肥正式开始,从 19 号开始每天晚上锻炼,根据 Nike+ Running 统计已经 66 公里,继续坚持。

由于工作原因老婆打算提前回北京,这几天抽空一直在看房子,打算整租一套,不再合租,不管六六来不来北京。以前老感觉整租花钱多,将就一下也就过了,不要太过于奢侈。现在看对我们自己太辛苦了,自己都过不好拿什么承担其他责任。老婆回去这两三个月,每天也不做饭,周末都在单间里窝着,就算出去转转也是一个人,没人说话,行尸走肉一般,再这样下去怀疑自己就要抑郁症了。

要好好的对待自己,好好生活,好好工作。

Nginx proxy_cache

Nginx proxy_cache 可以将后端动态请求的返回内容进行缓存,原理是 URL 作为 cache_key,将内容缓存到磁盘,新请求符合缓存规则的话直接读取缓存内容返回。

proxy_cache_path /tmp/ngx_cache/proxy_cache_dir levels=1:2 keys_zone=ngx_cache:10m inactive=30m max_size=500m;
proxy_temp_path  /tmp/ngx_cache/proxy_temp_dir;

server {
    proxy_cache ngx_cache;
    proxy_cache_valid 10m;
    add_header  Nginx-Cache "$upstream_cache_status";

    set $no_cache '';
    set_by_lua $cache_key "
        local no_cache = false
        if ngx.var.http_cookie and string.find(ngx.var.http_cookie, 'user') then
            # 带 cookie 的请求(比如登录用户)忽略缓存
            no_cache = true
        end

        if ngx.var.uri == '/api/test' then
        	  #某些 URL 的请求强制缓存,不管是否有 cookie
            no_cache = false
        end

        if no_cache then
            #确定忽略缓存就不再计算 cache_key
            ngx.var.no_cache = 'true'

            return ngx.var.uri
        end

        local uri_args = ngx.req.get_uri_args()
        local args = {}
        for k, v in pairs(uri_args) do
            if k and v and type(v) == 'string' then
                if k == 'count' or k == 'sort' or k == 'page' then
                    #过滤掉非法请求参数
                    args[#args+1] = k .. '=' .. v
                end
            end
        end

        if #args > 0 then
            table.sort(args)
            return ngx.var.uri .. '?' .. table.concat(args, '&')
        else
            return ngx.var.uri
        end
    ";
    
    proxy_cache_key $cache_key;
    proxy_no_cache $no_cache;
    proxy_cache_bypass $no_cache;
    
    location / {
	     proxy_pass http://localhost:8080;
    }
}

配置 proxy_cache 很简单,建议先通读 NGINX Content Caching 文档。记几点笔记:

  1. 用 OpenResty(ngx_lua) 作为前端 Nginx 代理和缓存服务器,好处是可以用 set_by_lua 计算赋值变量,原生 set 语法不够灵活。
  2. proxy_cache_path 指定缓存文件目录,和 proxy_temp_path 最好设置在同一文件分区下,缓存内容是先写在 temp_path,然后移动到 cache_path,不同文件分区会影响性能。
  3. keys_zone 命名并设置缓存的内存空间大小,要注意的是这个内存空间并不保存缓存文件,而是缓存文件的元信息(meta information),所以不必太大,根据文档 1M 大小可保存 8000 文件的元信息,可以根据缓存文件数量进行设置。
  4. inactive=30m 表示 30 分钟没有被访问的文件会被 cache manager 删除,max_size=500m 表示缓存目录最大限制 500M 磁盘空间。
  5. proxy_cache 指明用哪个缓存空间,proxy_cache_valid 是缓存的有效时间,可以针对不同响应状态设置不同的有效时间,比如 proxy_cache_valid 404 1m;,默认只对 200/301/302 响应进行缓存。
  6. 缓存文件数量过多会影响 proxy_cache 性能,Nginx 在启动时 cache manager 会检查并读取缓存文件的元信息到内存,这个读取是有限制的,默认情况下 cache manager 每次读取 100 个文件的元信息,每次读取限时 200ms,间隔 50ms 进行下次读取。
  7. 缓存文件并不是越多越好,所以 cache_key 的设计非常关键。代理或 URL 跳转常常会添加的无用请求参数,这就会出现不同的 cache_key 保存了多份相同的缓存内容,这对缓存效果影响很大。通过 ngx_lua 可以对 URL 参数进行过滤,保证 cache_key 唯一。
  8. table.sort(args) 对 URL 参数重排序,避免 /api?page=1&count=10 /api?count=10&page=1 生成两份缓存的情况。
  9. $upstream_cache_status 可以获取缓存状态,包括 HIT/BYPASS/MISS/EXPIRED,可以记录到 access_log 和 response header,用以计算缓存命中率。
  10. proxy_no_cache 如果有值且不为 ‘0’,该请求的 response 就不会生成缓存。
  11. proxy_cache_bypass 如有有值且不为 ‘0’,该请求会忽略缓存。
  12. proxy_cache 不支持手动清除缓存,可以通过第三方模块 ngx_cache_purge 来清除指定 URL 的缓存。

proxy_cache 非常的简单高效,合理使用可以有效的减轻后端服务压力,提升服务访问速度。