数据库 PostgreSQL 的异步操作接口,以及 Ruby 中对应实现

chrisloong · 2014年09月03日 · 最后由 zhongxiao37 回复于 2022年03月18日 · 7678 次阅读
本帖已被管理员设置为精华贴

我们团队一直在用 padrino 框架做开发,数据库操作,是用 sequel 这个轻量级库。 在开发中遇到数据 IO 的瓶颈,就想把数据库操作改为异步的,来提升代码性能。

原以为 sequel 默认都是同步方式来操作数据库,但看了源码和说明,才发现 sequel 默认都是在用异步接口: 那为啥在使用过程中,一点都没感到异步的快感?

于是就扒了一下 sequel 依赖的那些库: libpq,是 Pg 官方提供的操作接口代码库,C 写的; ruby-pg,是用 ruby 和 C 写的 gem,实现对 libpq 的拓展,从而实现 ruby 对 Pg 的操作接口; sequel,是 ruby 写的轻量级 ORM,它实现了数据库连接管理,以及 sql 的生成。涉及数据库接口方面工作,都是依赖 ruby-pg。

libpq 本身,确实支持同步和异步两种操作方式: http://www.postgresql.org/docs/current/static/libpq-async.html 一个数据库连接,就是一条 TCP 连接,libpq 是客户端,Pg 是服务端, 同步方式,libpq 将指令发送到 Pg,Pg 指令执行完成,将结果发回,libpq 收到结果后,才继续执行下面的代码: 异步方式,libpq 只是把指令发送到 Pg,返回发送结果的标志,想要获取执行结果,需要自己去轮询:

下面看看 ruby-pg:https://github.com/ged/ruby-pg ruby-pg 对 Pg 的操作,是依赖 libpq 实现的,其中也包含异步操作接口。 但是,经过 ruby-pg 的封装,异步接口,已经和同步效果一样,看代码: https://github.com/ged/ruby-pg/blob/master/ext/pg_connection.c#L3034 https://github.com/ged/ruby-pg/blob/master/ext/pg_connection.c#L2997 可以看到,在 send_query 之后,紧接着,就实现了一个轮询方法,去 get_result,这个轮询会导致调用程序阻塞,直到获得返回结果。 使用 Pg 中的 pg_sleep(seconds) 来模拟指令执行,通过代码来验证: 结果 exec 和 async_exec 都在 10 秒后结束,异步和同步的函数,确实都会阻塞 ruby 的解释器。

也就是说,想要在 ruby 实现对 Pg 的异步操作数据库,ruby-pg 本身不支持。 还是要自己实现个 libpq 的拓展,就把 ruby-pg 中那个轮询的循环去掉就行。

精华!喜欢这样有理有据,寻根问底的帖子。

请问什么时候实现 libpq 的拓展?

异步这种东西,最佳的方式就是用回调来处理,主程序也得是回调结构的。

#1 楼 @Peter 发现这个问题后,就没打算在 ruby 中实现,遇到的问题,改用队列(sidekiq 和 redis)来解决了。 如果哪天真有人把 ruby-pg 里异步函数改了,那可能会引起很多问题。 毕竟 ruby 开发者习惯了,执行完操作就有返回结果。

#2 楼 @jimrokliu 在 nodejs 这种平台,回调结构很自然。 但要在 ruby,尤其是 MRI 上实现回调,比较折腾,还是算了。

应该说 pg 对异步支持不太好,然后只能这样实现了。pgconn_send_query 只是返回是否 dispatch 成功,After successfully calling PQsendQuery, call PQgetResult ** one or more times ** to obtain the results.

#5 楼 @fake_whythelukystiff libpq 提供的是基本的异步支持,可以给开发者提供多一种选择,足够了。 只不过,在 ruby-pg 中,把异步接口实现成同步了,容易给上层开发者带来误解。 sequel 的维护者,就认为 async_exec 不会阻塞 ruby 解释器。 nodejs 这种回调优先的平台,在 pg 接口:node-postgres 的实现中,都是用 libpq 的异步函数。

干货贴,pg 应该会逐步完善对异步的支持

#6 楼 @ChrisLoong 这个问题今天老在我心头萦绕不绝...我觉得即便专门用 PQsendpreapred 这些专门用于异步的函数都避免不了阻塞。如果你用 ruby 的话,会怎么实现这个异步?

#8 楼 @fake_whythelukystiff libpq 里面提供的异步函数,就是方便客户端程序,在独立的线程上阻塞等待结果,而不影响主线程。 伪代码:

def async_exec(sql)
  Thread.new do
    conn.PQsendQuery(sql)
    while true
      res = PQgetResult(conn)
      if res
        yield res
        break
      end
    end
  end
end

async_exec("select...") {|res| #process result }

nodejs 里面的回调,主要就是靠 libuv 这个库实现的,就是把 IO 的等待,都放在单独的线程池里面阻塞,主线程还是可以继续处理 request。 ruby 的 web 应用里,都是单进程执行,一个 IO 阻塞了,那就无法处理后面的 request。 我们为了产品稳固,涉及这种耗时操作的,都放队列去执行了,不会去用线程,否则带来的问题更多。 ruby 里面也有个libuv 的拓展,但是如果要用这个库做东西,还不如直接用 nodejs 😄

楼主说的 数据IO的瓶颈 具体是什么?没有其他的优化方法吗?

#10 楼 @hz_qiuyuanxin 有些 sql 执行太慢,导致 app 处理请求的性能下降,还没时间做 DB 层面优化, 我们就是用队列来临时解决了。

#9 楼 @ChrisLoong 学习了,很好的思路~

#11 楼 @ChrisLoong

SQL执行太慢,对应的表的数据大概有多大,其增长速度有多快?不能先简单地通过索引的方式来优化执行性能吗?

在这里问题的本质就是在当前的状态下,做这种事情本身就需要那么多时间,除非你在同一个进程里,有好几个事情可以并发做,这样才有可能带来性能的提升,但是响应最慢的事情也决定了整个进程的响应的时间。

所以要么优化 SQL 执行,可以用空间来换取时间;要么将一些事情放在 background job 里面去做;要么...,哈,我也不知道了。

#13 楼 @hz_qiuyuanxin 😄 数据规模不大,电商的业务逻辑,略烦,表很多,数据库函数很长...等产品功能完善了,再慢慢优化吧。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号