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


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

查看所有标签

猜你喜欢:

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

算法学

算法学

哈雷尔 / 霍红卫 / 高等教育 / 2007-6 / 39.00元

本书主要论述计算机科学的基本概念、思想、方法和结果。全书内容由 5个部分组成。“预备知识”部分包括算法学中的基本概念、算法结构、算法所操纵的数据以及描述算法所用的程序设计语言。“方法和分析”部分包括算法设计的方法、算法的正确性和效率、评价算法的方法。“局限性和健壮性”部分包括可执行算法的固有局限性以及实现这些算法的计算机的固有局限性、不可计算性和不可判定性、算法学的通用性及其健壮性。此外,还讨论了......一起来看看 《算法学》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

随机密码生成器
随机密码生成器

多种字符组合密码