0%

最近在使用 sqlhooks 进行 sql 执行过程中的错误
指标收集,上线后发现错误数很高,基本和执行成功数一致。为此对 sqlhooksonError 增加了日志,
发现错误:driver: skip fast-path; continue as if unimplemented

经过搜索引擎查询后,定位到了这个错误定义在:driver.go

这里对该错误有具体的说明:

1
2
3
4
5
ErrSkip may be returned by some optional interfaces' methods to
indicate at runtime that the fast path is unavailable and the sql
package should continue as if the optional interface was not
implemented. ErrSkip is only supported where explicitly
documented.

大意是:ErrSkip 可以被可选 interface 的方法返回,表面在运行时 fast path 不可用。如果 interface 未实现,sql 包应该继续执行。

SQL 执行经过了 prepare -> exec -> close 这三个步骤。

这里已 golang 的 mysql driverquery 为例:

sql package 在执行 query 的时候,会调用对应 driver 实现的 QueryContext 进行执行,mysqlQueryContext 会调用 query 方法,该方法的代码如下:

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
func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error) {
if mc.closed.Load() {
errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn
}
if len(args) != 0 {
if !mc.cfg.InterpolateParams {
return nil, driver.ErrSkip
}
// try client-side prepare to reduce roundtrip
prepared, err := mc.interpolateParams(query, args)
if err != nil {
return nil, err
}
query = prepared
}
// Send command
err := mc.writeCommandPacketStr(comQuery, query)
if err == nil {
// Read Result
var resLen int
resLen, err = mc.readResultSetHeaderPacket()
if err == nil {
rows := new(textRows)
rows.mc = mc

if resLen == 0 {
rows.rs.done = true

switch err := rows.NextResultSet(); err {
case nil, io.EOF:
return rows, nil
default:
return nil, err
}
}

// Columns
rows.rs.columns, err = mc.readColumns(resLen)
return rows, err
}
}
return nil, mc.markBadConn(err)
}

当 SQL 执行的是待有参数的,如:SELECT * FROM foo WHERE id = ?,会先判断 driver 是否启用了
InterpolateParams,如果没有启用,则会调用 mysql 执行 prepare 逻辑;如果开启,则会在 client 侧进行 prepare,减少一次 roundtrip

因此本人对于 fast path 理解是指是否允许在 client 侧进行 prepare 减少一次对 mysql 的调用。

这里有一段简单的 perf 对比:https://github.com/fatelei/go-benchmark-result/issues/3

如果开发者希望在 document 完成 unload 前向服务器发送数据,通常不能直接发起 asynchronous XMLHttpRequest
unload 事件响应的回调函数中,这里因为浏览器会 abort 这样的请求。

通常为了实现这样的需求,有以下几种方式可供选择:

  • synchronous XMLHttpRequest

既然浏览器会 abortasynchronous XMLHttpRequest,那我们可以考虑发起 synchronous XMLHttpRequest 请求,在
unload 或者 beforeunload 时间的回调函数中,这样的同步请求会阻塞住 document unload 的进程,直到请求的数据发送完成。

1
2
3
4
5
6
7
8
window.addEventListener('unload', sendData, false);

function sendData() {
var xhr = new XMLHttpRequest();
xhr.open("POST", "/collect_data", false); // third parameter indicates sync xhr
xhr.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
xhr.send('test');
}

效果如下:

sync_xhr_xmlhttprequest

  • fake img element

实现上述需求,也可以通过在 unload 或者 beforeunload 时间的回调函数中创建一个 img element,并将数据通过指定图片 src 的方式将数据发送到后端。这是因为大多数浏览器会延迟 documentunload 进程直到完成所有未完成加载的图片完成加载。

1
2
3
4
5
6
7
window.addEventListener('unload', sendData, false);

function sendData() {
var img = document.createElement('img');
img.src = 'http://example.com/collect_data.jpg?data=test'
document.body.append(img);
}

效果如下:

fake_img

  • navigator.sendBeacon

前两种方式,都存在着一个问题:由于阻塞了 documentunload 进程,这样就会影响到下一个页面的加载(相关数据收集 API 出现性能问题 or 网络时延)。

navigator.sendBeacon 的出现,正好解决了这个问题。其定义如下:

1
can be used to asynchronously transfer a small amount of data over HTTP to a web server.(可以异步地通过 `HTTP` 向服务端发送小数量的数据。)

navigator.sendBeacon 的调用,并不是直接将数据发给后端,而是将待发送的数据放入浏览器待传输队列,浏览器再将待传输的数据发送到
后端,这样就不会阻塞 documentunload 进程,用户也不会长时间等待。

1
2
3
4
5
window.addEventListener('unload', sendData, false);

function sendData() {
navigator.sendBeacon('/collect_data', 'test');
}

效果如下:

fake_img

PS: 这里虽然 chromenetwork 面板显示请求处于 pending 状态,但是请求已经发送到了后端,只是还未得到响应,作者没有深入探究,猜测和在 unload 回调中执行有关。

参考链接
相关代码

家里 WiFi 密码过长,每次家里来人都要帮忙输一堆字符。看了下目前 androidiOS 11+ 都支持使用二维码扫码连接 WiFI

具体信息如下:

1
WIFI:T:{加密方式};S:{ssid};P:{密码};;
  • 加密方式:WPA、WEP

在网络编程,无论哪种编程语言,本身对于学习这门语言的用户,都有着很强的吸引力,那今天来看看 Go 的网络编程是个什么样的。
这里先给出一个 ping-pong 的 tcp server & tcp client 的例子。

Read more »

tabindex 作为可以作用于所有 Html 元素上面的属性,其值的类型为整型,其作用是表明一个元素是否
能够 focusable(通常 div 是不能被 focus 的,但是可以通过 tabindex,让其可以 focusable)、
可以通过键盘进行导航,以及通过键盘进行导航时候的顺序。

Read more »

最初部署 cassandra 时,采用 cassandra 的 2.2.1 版本,但是在实际使用中遇到了,如下 BUG:

对线上有很严重的影响,特别是最后一个,直接导致每次新加节点到已有的集群时,都会出现加入不了的情况,基本上就不能横向扩展,T T。

既然那就升级吧,分布式数据有个好处就是,做 rolling update 特别的容易,如果不升级主版本,那就更容易了。

插曲

虽然 apache cassandra 2.2.x 系列的支持是到今年 11 月就截止的,但是不想一次升级太大,就选择了 2.2.x 最新的一个版本 2.2.7 不过一升级就出问题,最后不得不只升级到 2.2.5。

升级步骤

  • A. 升级某个节点的 cassandra 版本时,需要先执行

nodetool drain

该命令表示该节点不再接受写的请求,并将 memtable 进行 flush 到磁盘;

  • B. 进行数据的备份,可以使用

nodetool snapshot

进行 keyspace 的快照。由于目前所在的公司使用的是 AWS,这里没有使用 cassandra 自己提供的工具进行快照,而是使用的 AWS 提供的工具,对整块 EBS 进行的快照。

  • C. 数据备份完之后,stop cassandra 进程,可以用

cassandra_install_location/bin/stop-server

停止掉服务

  • D. 将老版本的中必要的配置,拷贝到新版本的目录中,比如:cassandra.yml

  • E. 以上完成之后,便可以直接启动新版本的 cassandra 啦,然后观察一下 cassandra 服务日志,如果没有问题,那就大功告成啦。注意,如果你升级的是主版本(1.x - 2.x)或者主版本下的一个大版本(2.1 - 2.2),在启动 cassandra 之后,还需要执行

`nodetool upgradesstables

  • F. 然后再各个节点重复 A - E 的步骤。

  • G. 升级完之后,需要重启你的应用。

是不是很简单呢……

参考资料