正则表达式必知必会

匹配单个字符

匹配纯文本

正则:Ben

Hello, my name is Ben. Please visit my website at http://www.forta.com/.

匹配任意字符

. 匹配任意单个字符。在绝大多数的正则表达式实现里,. 只能匹配除换行符以外的任何单个字符。
正则:sales.

sales1.xls
orders3.xls
sales2.xls
sales3.xls
apac1.xls
europe2.xls
na1.xls
na2.xls
sa1.xls

Rails 多台服务器上 js 不一致的问题

发现问题

周二发布正式的时候,发生了匪夷所思的事情。
打开发布的网页,有一定的几率操作会没有反应。但在测试的时候却从没有这个问题发生。通过调试工具查看网页,发现一半的网页会加载 application-b1a 开头的文件,而另一半会加载 application-745 开头的文件。加载 applicaton-745 时会报 404,使得操作没有反应。

这里简单地说下发布的情况。该服务会发布到 A、B 两台服务器上,nginx 接收请求并转发到 A、B 上的实例。
查看正式服务器上的文件发现,A 上存在 application-b1a 文件,B 上存在 application-745 文件。看来 nginx 会查找 服务器 A 上的文件,由于找不到 application-745 而返回 404。先把 B 上的 applicaiton-b1a 复制到 A,临时修复这个问题。

排查原因

为什么两个服务器上生成的 js 文件名会不一致?我们先简单地回顾下 Asset Pipeline 生成 js 的过程。正式环境上, Asset Pipeline 会预编译文件,生成类似于 application-908e25f4bf641868d8683022a5b62f54.js 的文件。其中 908e 这串表示摘要,是根据文件的内容生成的 MD5,当 js 文件发生改变时生成的摘要也会发生变化。

1
2
3
4
5
// 引用 js
<%= javascript_include_tag "application" %>

// 生成的 html
<script src="/assets/application-908e25f4bf641868d8683022a5b62f54.js"></script>

Rails 如何在开发模式下重新加载源代码

在 Rails development 环境下,若更改了部分代码,只需要重新发起请求,就能看到最新代码的结果,不需要重启服务器。
下文简述 Rails 是如何做到这点的。

config/development.rb,也就是 development 的环境配置文件,我们可以看到不同于其他环境的一行:

1
2
3
4
# In the development environment your application's code is reloaded on
# every request. This slows down response time but is perfect for development
# since you don't have to restart the webserver when you make code changes.
config.cache_classes = false

正如注释所说的,它会使得 Rails 在每次接收请求时都重新加载源代码。

初始化

我们先从 Rails 的初始化说起,以 Rails 4.2.6 为例。

1
2
3
4
5
6
7
8
9
10
11
12
13
# rails/application/default_middleware_stack.rb

def build_stack
...
unless config.cache_classes
middleware.use ::ActionDispatch::Reloader, lambda { reload_dependencies? }
end
...
private
def reload_dependencies?
config.reload_classes_only_on_change != true || app.reloaders.map(&:updated?).any?
end
end

当 config.cache_classses 为 true 时,middleware 会增加 ActionDispatch::Reloader,而这正是重新加载源代码的关键。
ps: middleware 可以通过 rake middleware 来查看。

处理请求

当服务器接收到请求时,中间件 ActionDispatch::Reloader 使用回调 prepare、cleanup 来实现重载源代码。其中 prepare 在处理请求前被调用,cleanup 在处理请求后被调用。

MySQL EXPLAIN 解读

EXPLAIN 解释了 MySQL 是如何执行 SQL 语句的。使用的方法很简单,在 SQL 语句前加上 EXPLAIN 关键字就可以。
下面是一个简单的例子,测试数据在文章末尾。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 例 1
mysql> EXPLAIN SELECT name FROM users WHERE id = 1\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: users
type: const
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: const
rows: 1
Extra: NULL
1 row in set (0.00 sec)

EXPLAIN 列的解释:

  • id:SELECT 标识符,下面具体分析
  • select_type: SELECT 类型,下面会具体分析
  • table: 查询所使用的表
  • type: JOIN 的类型,下面会具体分析
  • possible_keys: 可能使用的索引,但不一定会真正使用
  • key: 真正使用的索引
  • key_len: 所使用的索引长度
  • ref: 与索引比较的列
  • rows: 预估需要扫描的行数
  • Extra: 额外信息

Rails Concern 源码研究

ActiveSupport::Concern 是为了更方便地 include 模块而推出的工具类。

使用方法

首先来看下它的使用方法。

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
# 传统的 Module 引入
module M
def self.included(base)
base.extend ClassMethods
base.class_eval do
scope :disabled, -> { where(disabled: true) }
end
end

module ClassMethods
...
end
end

# 使用 Concern
module M
extend ActiveSupport::Concern

included do
scope :disabled, -> { where(disabled: true) }
end

class_methods do
...
end
end

可见,通过 included 和 class_method 两个类方法使得 Module 的写法更加清晰。
你可能有些疑问,对比传统的引入,使用 Concern 虽然更加清晰了,但没什么巨大的优点。而传统的引入也可以通过将方法分成两个 module,如 InstanceMethods、ClassMethods,来达到同样的效果。
在这个简单的例子上,确实如此。但在一些嵌套的 include 上 Concern 的优势就体现出来了。

Ruby 常量查找

对 Ruby 中常量查找只有基础的认识,使用上还是有不少疑问,比如 “::A” 的含义、为什么有时使用 “A::B” 而有时直接用 “B”。
花时间查了资料来加深对此的理解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module Record
def self.method
puts 'outer'
end

module Music

Record.method # outer

module Record
def self.method
puts 'inner'
end
end

Record.method # inner
::Record.method # outer
Music::Record.method # inner
end
end

我们从简单的例子出发。在上文的代码中,共存在着两个名为 Record 的 module。一个是在最外层,一个是在 Music 内部。
当第 9 行调用 Record.method 时,由于 Music 内的 Record 还未被定义,因此调用的只能是最外层的 Record module。
而在第 17 行,当我们重新调用时,却发现调用的已经是内部的 Record 了。
可见常量的调用时是有一个先后顺序的,简单的说就是从近及远,先使用近的。虽然我们从直观上很容易理解近的含义,但严格上的顺序是通过 Module.nesting 得到的。在 Music 内部调用得到 [Record::Music, Record],可见 Record::Music 确实比 Record 有更高的优先级。

mac 上 MySQL system error 32 解决方案

不知为何,在升级了 macOS 系统之后,Rails 连接 MySQL 经常性会报 Mysql2::Error: Lost connection to MySQL server at 'sending authentication information', system error: 32 的错误。重启 MySQL 就好了,过段时间又会报错,烦不胜烦。下定决心解决下。

参考了这个帖子后,大致看到有两种解决方法。一种是调大系统 ulimit -n 的值,另一种是调小 table_open_cache 的值。
从帖子的回复以及 ulimit -n 表示系统的文件句柄限制来看,个人认为调节 ulimit -n 的值是一个治标不治本的方法。本机的值已经 4k+,应该足够了。

正巧有次出现问题时,MySQL 还连接着。查了下当时的 status,发现 open_tables 不过几十,而 open_files 却几千。
执行 flush tables 清除缓存后就可以重新连接了。确实和 table_open_cache 有一定的关系。

查看了下 table_open_cache 的值,默认值是 2000。通过在 my.cnf 中添加 table_open_cache = 500,MySQL 这几天就没有闹别扭,希望能一直安稳下去。

MySQL 相关命令

1
2
show status like '%open_files%'; # 查看 open_files 的值,open_tables 同理
show variables like '%table_open_cache%'; # 查看 table_open_cache 的值

参考

ActiveRecord select vs pluck

selectpluck 都可以从数据库读取指定的字段,但两者存在不小的差别。

1
2
3
4
5
6
7
Product.select(:id).to_a
# Product Load (0.5ms) SELECT `products`.`id` FROM `products`
# [#<Product id: 2>, #<Product id: 1>]

Product.pluck(:id)
# Product Load (0.4ms) SELECT `products`.`id` FROM `products`
# [2, 1]

select 返回的是仅含有 id 的 Product Model 数组,而 pluck 返回的是 id 的数组。
两者相比较,pluck 省却了构造 ActiveRecord 的过程,效率更优。我们可以通过 Benchmark.measure 来验证下。

1
2
puts Benchmark.measure {Product.select(:id).to_a}
puts Benchmark.measure {Product.pluck(:id)}

- user CPU time system CPU time sum CPU time elapsed real time
select 0.050000 0.020000 0.070000 0.095440
pluck 0.000000 0.000000 0.000000 0.001845

除此之外,两者还有一个区别,即查询时机的不同。

1
2
3
4
5
6
ProductOrder.where.not(id: SubOrder.where(sub_order_no: '001').pluck(:order_id))
# SELECT `orders`.* FROM `orders` WHERE `orders`.`type` IN ('ProductOrder') AND (`orders`.`id` NOT IN (SELECT `sub_orders`.`order_id` FROM `sub_orders` WHERE `sub_orders`.`sub_order_no` = '001'))

ProductOrder.where.not(id: SubOrder.where(sub_order_no: '001').pluck(:order_id))
# SELECT `sub_orders`.`order_id` FROM `sub_orders` WHERE `sub_orders`.`sub_order_no` = '001'
# SELECT `orders`.* FROM `orders` WHERE `orders`.`type` IN ('ProductOrder') AND (`orders`.`id` != 3)

在上面这个例子中,通过 pluck 的调用进行了两次查询,而 select 只进行了一次查询。可见调用 pluck 会立即进行数据库查询。

参考

Rails ETag 应用

使用 ETag 验证缓存

etag.png-27.9kB
来源:Browser Cache: How ETags Works in Rails 3 and Rails 4
可以把 ETag 看成是响应的 token,客户端在下一个同样的请求中将其发送给服务器。服务器通过 token 来判断资源是否进行修改。若未修改,则返回 304 Not Modified,使客户端不需再去下载与缓存中已有的完全相同的字节。

fresh_when

在 Rails 中,我们可以通过 stalefresh_when 方法来显式地使用 ETag。

1
2
3
4
5
6
7
8
9
10
class ProductsController < ApplicationController

# This will automatically send back a :not_modified if the request is fresh,
# and will render the default template (product.*) if it's stale.

def show
@product = Product.find(params[:id])
fresh_when last_modified: @product.published_at.utc, etag: @product
end
end

上例中通过 `@product.published_at.utc` 来判断资源是否过期。

默认开启的 ETag

最近才发现不用 fresh_when 返回的请求也会自带 ETag,查了下原来 Rails 框架默认使用 Rack::ETag middleware,会自动给无 ETag 的 response 根据其 body 添加上 ETag。

DOM 事件流

事件流

“DOM2级事件”规定的事件流包括三个阶段:

  1. 事件捕获阶段
  2. 处于目标阶段
  3. 事件冒泡阶段

事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。事件捕获的用意在于在事件到达预定目标之前捕获它。
事件冒泡指事件开始时由最具体的元素接收,然后逐级向上传播到较为不具体的节点。

event_flow.png-69.4kB
来源:W3C
以上图的 HTML 页面为例,单击 <td> 会按照上图所示顺序触发事件。

事件委托

事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。

1
2
3
4
5
<ul>
<li>TODO 1</li>
<li>TODO 2</li>
<li>TODO 3</li>
</ui>

以上面的 HTML 代码为例,按照传统的做法,若要使每个列表项单击后执行相关操作,则需要分别为它们各自添加 click 事件,共 3 个事件。若后续增加了更多的列表项,还需额外为它们添加相应的事件。
而使用事件委托,则只需在 <ul> 上添加 click 事件即可。由于所有列表项都是这个元素的子节点,而且它们的事件会冒泡,所以单击事件最终会被 <ul> 的事件处理。与前面的传统做法相比,事件委托占用的内存更小且更为便捷。

参考

  • JavaScript 高级程序设计 第 13 章