Nginx/OpenResty 指令的执行顺序

  1. http, 可以通过 init_by_lua 加载公共函数,比如 lua-resty-core.
  2. server selection,listen,server_name.
  3. post read, ngx_realip.
  4. server rewrite, set, rewrite, return, set_by_lua.
  5. server rewrite tail, rewrite_by_lua.
  6. server access, allow, deny.
  7. server access tail, access_by_lua.
  8. server try_files.
  9. location:
    1. prefix strings 遵循 最长子串匹配原则
    2. regular expressions 遵循 先定义优先匹配原则
    3. location = {exact_url} 精准匹配
    4. location ~ {case-sensitive regex} 区分大小写
    5. location ~* {case-insensitive regex} 不区分大小写
    6. location ^~ {prefix_string_if_any} 一旦字符匹配成功,就不再正则匹配
    7. 尽量不要 if,换用 try_files
    8. -f 检测文件是否存在,-d 目录,-e 文件/目录/符号链接,-x 可执行文件
  10. location rewrite, set, rewrite, return, set_by_lua.
  11. location rewrite tail, rewrite_by_lua.
  12. preaccess, degradation, limit_zone, limit req, ngx_realip.
  13. location access, allow, deny, auth_basic.
  14. location access tail, access_by_lua.
  15. content, ngx_echo, proxy_pass, content_by_lua.
    1. 请求具体处理阶段,只能有一个 内容处理程序(content handler)
    2. 多个 echo 可以共存,因为同属于 ngx_echo 模块,但 ngx_lua 限制只能有一个 content_by_lua.
    3. ngx_echo 的 echo_before_body/echo_after_body 可以和其他模块共存
    4. 如果没有 ngx_echo, proxy_pass, content_lua 这些 content handler,Nginx 会根据 URL 将请求映射到静态资源服务模块,依次是 ngx_index, ngx_autoindex, ngx_static.
    5. ngx_index/ngx_autoindex 处理以 / 结尾的请求,ngx_static 正好相反。
  16. output header filter, more_set_headers 输出 Headers.
  17. output filter echo_before_body, echo_after_body, body_filter_by_lua.
  18. log, access_log, error_log, log_by_lua.
  19. post action.

Nginx log to InfluxDB

InfluxDB 是一个支持时间序列的数据库,自带 SQL-like 查询语言,很适合用作日志存储。配合 Grafana 面板展示,非常方便。

接下来要做的就是将 Nginx 日志写入 InfluxDB。常见的方法是用 Logstash 等工具收集 access.log/error.log 通过 filter 处理后写入 InfluxDB。这种方法对服务没有任何侵入,数据完全从 log 获取,缺点就是数据源单一,nginx.log 能纪录的东西比较有限。

再一个方式就是通过 ngx_lua 的 log_by_lua。log 阶段在 content 后,请求已完成,这时候做一些处理不会拖累服务。相对 nginx.log 可以通过 ngx_lua 获取更多信息,比如 ngx.req 获取请求信息,过滤 ngx.var.uri 将同一类请求合并,ngx.var.http_cookie 读取 cookie 针对登录用户做特殊纪录等。然后通过 InfluxDB 的 HTTP API 写入存储。

需要注意的是在 log_by_lua 里不能直接用 Cosocket,需要做一些特殊处理:创建 0 延时的 ngx.timer,在 timer 回调中用 Cosocket 发请求,参考 lua-resty-logger-socket 的实现,文档里也是建议这个方法 Cosockets Not Available Everywhere.

InfluxDB 初步用下来还不错,HTTP API 方便不同服务接入,拿来做数据存储分析挺好。现在的问题是看 InfluxDB 的性能、稳定性如何。

Monthly Review 2015-04

  1. 维护开发外,一个小功能模块尝试用网页代替 native 实现。在体验可接受的前提下开发效率确实比原生要好,而且现在前端开发框架+辅助工具井喷,可以多做尝试。
  2. 前端水平 JS 刚刚够用,还不时需要 Dash 查文档,ES6 什么的不懂,CSS 是硬伤。
  3. 又十个 iOS 面试,不行,我得多写几句:
    • 遇到硬件网络转型互联网开发,似乎硬件现在日子不太好过?
    • “用大众点评 API 实现了一个美团应用”,似乎是一个培训机构的题目?
    • 现在的移动开发过火,整个行情都被抬高。基础一般,又看不到学习能力,没办法上手项目的开口就是 15K+,这钱真好赚。
    • 做产品开发首先是产品的热爱,基础差没问题,展示出学习能力,很多时候公司更愿意内部培训。
  4. 感兴趣的东西过多,同时并行的效果不好,需要有计划的去学习。
  5. 这次回去六六对爸爸的依赖更多了,基本上自己一个人带一天都没问题。
  6. 牛牛:哞…… 六六:eng……

Cache Pattern

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

Read-Through/Write-Through

App-Cache-DB 结构,App 不直接访问 DB,由缓存间接操作。读的时候先从缓存取数据,有就直接返回,没有的话由缓存负责从 DB 读取并更新到 Cache,然后返回数据。写的时候先写缓存,然后由缓存负责更新到 DB,只有 DB 更新完成才算写成功,返回操作结果。好处是缓存数据更新及时,适合读多写少,缺点就是写操作慢。

Write-Around

跳过缓存直接写数据到 DB。相比 Write-Through 避免了写数据时候对缓存数据的冲洗,缺点是缓存数据不能及时更新。

Write-Back/Write-Behind

数据写到缓存后操作立即返回结果,然后缓存系统延时+异步的将数据更新到 DB,一般配合队列处理。这种写操作是最快的,也能避免大量写数据对 DB 的压力。

Cache-Aside

App 读的时候检查数据是否在缓存中,有就返回,没有的话 App 直接读 DB 返回,同时将数据写入缓存。写操作的时候直接写入 DB,如果缓存中有对应数据,将缓存设置无效或删除,如果数据读取频繁的话也可以直接更新缓存中的数据,保证数据一致性。这种模式更多是有 App 进行数据检查,缓存只做存储。

一些参考:

  • https://msdn.microsoft.com/en-us/library/dn589799.aspx
  • http://www.computerweekly.com/feature/Write-through-write-around-write-back-Cache-explained
  • http://www.infoq.com/cn/articles/write-behind-caching/
  • http://docs.oracle.com/cd/E15357_01/coh.360/e15723/cache_rtwtwbra.htm#COHDG5177
  • https://www.v2ex.com/t/180474

Monthly Review 2015-03

  1. 依然是业务维护型开发,由于在新产品还在调研阶段,并没有太多代码产出,commit 只有 30.
  2. 抽空整理 API 文档,Gitbook 写文档挺方便。
  3. 面试了十来个 iOS 开发,要想考核别人,首先自己要知道,面试很考验技术的。

Monthly Review 2015-02

  1. 从 2.14 休假到月底,等于这个月只工作了半个月。
  2. 个人习惯,长假前三五天代码库冻结,除了 typo 级别的修改,其他全部延迟安排到假期后,这样算下来代码的时间就更少了。
  3. 然后就有了更多时间阅读学习,看了不少 Go 相关的,结合之前项目加深理解。
  4. 休假前推进完成服务迁移,长假期间没有出现一次问题,省心很多,更放心的玩耍。
  5. 算下来今年春节是这几年休假时间最长的,狠狠的陪六六半个月。
  6. 小孩子一天一个样,刚回去还没长牙,一周后露头,等走的时候已经很明显能看到小嫩牙。
  7. 车子成了现在人的身份象征,甚至高于房子。没人关心你做什么,追求什么,累不累,单纯的根据车子判断你的成功,大家似乎也很高兴用车子来证明自己。

Nginx If Is Evil

官方文档 IfIsEvil.

简单说在 location 中要尽量避免使用 if,如果一定要用,确保 if body 中只包含 return or rewrite,其他指令可能会出现莫名错误。

解决方案就是用 try_files 替换:

location / {
    try_files $uri $uri/index.html $uri.html =404;
}

如果想 if 和 try_files 一起用,可以把 if 放在 location 外:

set $APP 'unknown';
if ($query_string ~ "app=([^&]+)") {
   set $APP $1;
}
location = /api {
   try_files /$APP/data.json /data.json =404;
}

Nginx Pitfalls 列举了一些 nginx 陷阱,值得学习。


春哥这篇 How nginx “location if” works 做了逐步分析,学习。

Middleware

(这块内容属于个人理解,可能会不对)

最近学习中又一次接触 middleware 概念,一直对这个东西都比较模糊,似乎 Ruby 界用的比较多,比如 Rack。middleware 给我的感觉就是在请求与 App 之间对请求进行一层或多层处理,然后将处理后的请求对象交由 App;同理,在 App 和响应之间也可以有。

一个常见的业务场景:

请求 -> [cache.get -> 有-返回|无 -> 服务处理生成数据 -> cache.set] -> 响应

其中 cache 读写都是在服务内处理请求时进行。套用 middleware 似乎是这样的流程:

请求 -> [middleware.cache.get -> 有-返回|无-请求交由下一步处理] -> [服务处理生成数据] -> [middleware.cache.set] -> 响应

去掉 middleware.cache 整个服务不受影响,流程变成了这样:

请求 -> [服务处理生成数据] -> 响应

middleware 的好处是可多层组合,让流程有层次,服务更专一。接下来要在实际项目中实践一下:

  • Negroni, Idiomatic HTTP Middleware for Golang. Martini 作者开发。
  • lua-resty-rack, A minimalistic rack implementation for Openresty.

Go 初体验

用 Go 写了第一个线上服务,简单记录一些。

  1. 直接 net/http,没有用 Web 框架。之前用过 Beego,强大但过于黑盒,很多细节不理解,其实 Go 已经提供了 web 开发所需要的东西,这个服务只需对外 API,没有页面等,直接 net/http 反而更简单。
  2. 强制代码风格,大爱,包括定义但不使用直接报错,刚开始会有不适应,但是对整体代码质量很有帮助。
  3. database/sql 提供了统一的数据库操作接口,配上不同的 driver 即可。
  4. Golang 的 error handlling 是个特色,但作为 web service 有些繁琐,需要再学习看有没有更为简洁的处理方式。
  5. 无痛热更新比较麻烦,还没有找到类似 Nginx 的实现。
  6. 性能上简单的 ab 压测和 ngx_lua 差距不大,开发效率相对高一些,毕竟自带库更丰富。
  7. gin 可以监控代码变化并自动重新编译,代理方式,不错的开发辅助工具。

Monthly Review 2015-01

  1. 参加一次线下的 Golang 技术聚会,收获不少。
  2. 有些业务功能很难 cover 全部情况,要有所取舍,满足最主要的用户需求。敢于放弃。
  3. Android 项目启动,简单调研了一下,对这个生态系统还是没兴趣,尤其是国内乱七八糟的市场。
  4. Golang 有了实际线上服务产出。
  5. 视频时六六已经会自己找爸妈了,真快。