Rails with_connection, reap 與 connection leak

栏目: Ruby · 发布时间: 7年前

内容简介:Rails with_connection, reap 與 connection leak

在之前遇到 疑似 rails connection leak 之後,就一直大概知道 ActiveRecord 的 with_connection 可以避免 connection leak。前陣子在設定 RabbitMQ 的時候,出現了 connection pool timeout 的 error,在 debug 的時候才發現原來我好像一直沒有親眼看過 Rails leak connection 的時候會長得怎樣 XD

所以我試圖用一些方式讓 Rails leak database connection,發現好像沒有很單純阿。 在這篇文章想要把一些嘗試的過程記錄下來和大家分享。

關於 connection leak 和 connection pool 的介紹可以看這邊。

重現問題

在另外開的 thread 裡面,如果有使用到 connection 而忘記 checkin 回去的話,理論上這個 connection 就會 leak 掉。

但是要怎麼重現這樣的狀況呢?

下面是概略的環境設定:

  • Rails 4.2.8
  • 首先我試著把測試的環境建立成一個 rake job
  • 並且把 connection pool 的 size 調成 3
task :test_conn => :environment do
  pool = ActiveRecord::Base.connection_pool

  threads = 3.times.map do
    Thread.new do
      pool.checkout
    end
  end

  threads.each(&:join)

  pool.checkout
end

在這邊,我開了 3 個 threads,並且在每一個 thread 裡面 checkout 一個 connection 出來。 在確定 connection 都被 checkout 出來了 ( threads.each(&:join) ) 之後, 理論上因為所有pool裡的 connection (3個) 都 leak 掉了,所以當我們要再 checkout 一個 connection 出來的時候,應該要出現 timeout 的 error。

但這個 rake job 跑下去的時候, 完全沒有任何的 error 出現 QQ

後來發現,connection 有一個 method 叫 in_use? 可以來幫我們確認 connection 有沒有正在被使用。 當 connection 沒有在被使用的時候,他會回傳 nil ,而當正在被使用的時候,他會回傳正在使用的那個 thread。

於是把剛才的 rake job 加上一些 debug 的資訊變成這樣:

def log_connections conns
  conns.each do |idx, conn|
    puts "#{idx}: #{conn.object_id}; #{conn.in_use?}"
  end
end

task :test_conn_w_log => :environment do
  pool = ActiveRecord::Base.connection_pool
  conns = {}

  threads = 3.times.map do |i|
    Thread.new(i) do |index|
      conns[index] = pool.checkout
    end
  end

  threads.each(&:join)
  puts "after joining"
  log_connections(conns)

  pool.checkout
  puts "after re-checkout"
  log_connections(conns)
end

得到的 output 是這樣:

after joining
0: 47195627544380; #<Thread:0x0055d92d3bd1a0>
1: 47195627073520; #<Thread:0x0055d92d3bc408>
2: 47195626859620; #<Thread:0x0055d92d367d90>

after re-checkout
0: 47195627544380; #<Thread:0x0055d92952a258>
1: 47195627073520;
2: 47195626859620;

我們發現在 join 之後,也就是在 thread 已經結束了之後,connection 都還是在 “使用中” 的狀態。 然後在這樣的狀況下 checkout,不僅沒有問題,反而其他的 connection 還變成 “非使用中” 的狀態了。

在實際看了 ActiveRecord connection pool 的 source code 之後, 發現在 每次企圖 checkout 的時候,如果 pool 裡面沒有可用的 connection 了,ActiveRecord 會試圖做一個叫作 reap 的行為,這個動作會把 “現在在 ‘使用中’ 但 owner 已經死掉” 的那些 connection 回收起來。 這也就是為什麼在重新 checkout 之後,另外兩個 connection 又變成 “非使用中” 的狀態了。

但是如果在執行 reap 的時候,所有正在使用的 connection 的 owner 都還沒死,那這時候 reap 就無法回收任何 connection。 即使在 timeout 的限制(5秒)之內,有其他的 thread 已經用完 connection 了,這些用完的 connection 還是無法被使用。

task :test_conn_cannot_reap => :environment do
  pool = ActiveRecord::Base.connection_pool
  conns = {}

  threads = 4.times.map do |i|
    Thread.new(i) do |index|
      conns[index] = pool.checkout
      puts "got connection in thread #{index}"
      sleep 1
    end
  end

  threads.each(&:join)
  puts "after joining"
  log_connections(conns)

  pool.checkout
  puts "after re-checkout"
  log_connections(conns)
end

這樣得到的 output 會是:

got connection in thread 0
got connection in thread 1
got connection in thread 2
rake aborted!
ActiveRecord::ConnectionTimeoutError: could not obtain a database connection within 5.000 seconds (waited 5.000 seconds)

但是這個時候, 如果配合上上面的 with_connection 就沒問題了:

task :test_conn_w_with_connection => :environment do
  pool = ActiveRecord::Base.connection_pool
  conns = {}

  threads = 4.times.map do |i|
    Thread.new(i) do |index|
      pool.with_connection do |conn|
        conns[index] = conn
        puts "got connection in thread #{index}"
        sleep 1
      end
    end
  end

  threads.each(&:join)
  puts "after joining"
  log_connections(conns)

  pool.checkout
  puts "after re-checkout"
  log_connections(conns)
end

output:

got connection in thread 0
got connection in thread 2
got connection in thread 1
got connection in thread 3
after joining
0: 47069386019060;
2: 47069385769900;
1: 47069385570280;
3: 47069386019060;
after re-checkout
0: 47069386019060;
2: 47069385769900; #<Thread:0x00559e5ee72258>
1: 47069385570280;
3: 47069386019060;

結論

Connection count 的限制是在各種時候都必須要小心的事情。再加上 thread 這類有”時間”性質的變因加入之後,會讓整個東西變得更複雜。 基本上我個人是比較偏好在寫 Rails 的時候,讓 library/framework 這層去控制 thread 比較令人安心一點。

也就是說, 如果可以讓 thread 的複雜度不要隨著 application feature 的增加而增加會是比較好的狀況

還有,雖然說難歸難,但是找答案的過程還真的是好有趣 der ㄎㄎ

Rails with_connection, reap 與 connection leak


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

我看电商3:零售的变革

我看电商3:零售的变革

黄若 / 电子工业出版社 / 2018-4 / 49

在《我看电商3:零售的变革》之前,黄若先生的“我看电商”系列图书《我看电商》《再看电商》《我看电商2》,均为行业畅销书。黄若先生的图书有两大特如一是干货满满,二是观点鲜明。 “新零售”是眼下的热门词。在2017年里,数以万计的企业以“新零售”作为标识进入市场。但是社会上对“新零售“存在着各种模糊的定义和不尽相同的解读。 《我看电商3:零售的变革》中明确提出:新零售不应过分关注于渠道形式......一起来看看 《我看电商3:零售的变革》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具