Ruby User.current 这种用法到底靠谱不靠谱?

ywencn · 2013年07月31日 · 最后由 lmorenbit 回复于 2013年09月27日 · 5868 次阅读
class User < ActiveRecord::Base
  def self.current
    Thread.current[:user]
  end
  def self.current=(user)
    Thread.current[:user] = user
  end
end

就是这种在 Model 里面拿 current_user 的方法。 最近似乎遇到了用户乱窜的情况。。获取错了似乎。

线程安全....

#1 楼 @jasl 设置一下 config.threadsafe! 的话管用么?

干嘛要这么弄啊,下层的就让上层传过去嘛

#2 楼 @ywencn 因为 current 是各自线程维护的,但是不一定这个用户每次都被某个线程 服务。。。

好不好用和项目有关, 小项目,无所谓, 大项目,不好。 我们之前就是用 User.current方式 导致,model 层代码,严重依赖 User.current,导致系统极度混乱。 后来,改成了Controller current_user模式,世界从此清净了!

为什么要这样用,能说说用的场景吗?

#6 楼 @small_fish__ 记录个操作记录什么的,这个最方便呀。 #5 楼 @ery 每次都从 controller 里面传过去么?

#7 楼 @ywencn controller: current_user.xxx.create ? 不知道这么弄有什么好不好。

线程是安全的。但是要注意 Thread.current[:xxx] 类似全局变量,小心在其他地方被修改了。起个不冲突的名字,应该没问题吧?

#7 楼 @ywencn 对,每次都从 Controller 传递过去。 举个例子

class BooksController < ApplicationController
  def index
    @books = current_user.books
  end

  def create_1
    @book = current_user.books.create
  end

  def create_2
    @book = Book.create
    @book.user = current_user
  end
end

其实 从 MVC 框架逻辑上分析 current_user 属于 会话层,属于一个临时的状态,而非持久性的状态。 所以 它更应该 属于 Controller 层 不应该属于 Model 层

User.current 这种用法,从 redmine 里来的吧 XD

传送门 https://github.com/redmine/redmine/blob/2.3.2/app/models/user.rb#L590

def self.current=(user)
  Thread.current[:current_user] = user
end

def self.current
  Thread.current[:current_user] ||= User.anonymous
end

用户乱窜有可能是处理当前的请求时没有重新赋值,结果继续使用上一个请求的数据。

每次请求都必须给线程变量赋值 这样是线程安全的, Rails4 还专门引入了 ActiveSupport::PerThreadRegistry.

#10 楼 @ery 恩,那不是想偷懒么。。

#14 楼 @hooopo +1 分析的很好

这个问题曾经在 rails bestpractices 上有激烈的争论: http://rails-bestpractices.com/posts/47-fetch-current-user-in-models

用户乱串 我们也遇到过,因为后面的用户复用了之前的线程变量。

所以应该在每次请求结束后清除线程变量,可以放到 application_controller 中

after_filter :clear_thread_variable

def clear_thread_variable
  Thread.current[:user] = nil
end

在 em-synchrony, celluloid, 或者线程池部署都行不通

另外 Thread local 在 Ruby 2.1 之前都是 Fiber local :

Fiber.new { Thread.current[:a] = 3 }.resume
Thread.current[:a] #=> nil

#20 楼 @wxianfeng 恩,我来试试看。后来你们搞定了么?

这种方式在 java 的项目中常用,因为 java 都是线程模型的,但在 ruby 上,如果是 unicorn 中中并发模型,还是可以的,记得设置完后清除掉 Thread 上的变量否则下次请求或者异常情况下被设置错误的变量。但在 Fiber 并发下,可能就不行了。

偶们把 session 存数据库里,然后就不会有线程问题啦

遵守 MVC 也好,不遵守 MVC 也好,都要付出代价。

#24 楼 @badboy 为什么把 session 存数据库里就没有线程问题了?

#26 楼 @ywencn 他说的是 Mnesia 吧

#27 楼 @hooopo 哈哈你妹,快说

@ywencn 就不能 Controller 传过去吗?

#26 楼 @ywencn session 存数据库不太靠谱吧

#29 楼 @ywencn 一提到 session 我就发现大多数人不懂 web 开发啊...

@ywencn session 存数据库怎么不靠谱,没什么区别啊

...session 到底是怎么和线程扯上关系的?

#34 楼 @tumayun 我哪里有说不靠谱。。。

@ywencn 额 回错了 应该是 @hbin 哈哈

你这个是 singleton 的问题的,你的 activerecord 是 singleton 的?

最近也同样遇到了这个问题,翻了几篇文章,觉得这篇讨论的挺详细的。 http://rails-bestpractices.com/posts/47-fetch-current-user-in-models 这里面有大段的关于 Thread 取 current_user 是否是好设计的讨论。@ywencn @hooopo 说得两种方式,其实比较像,第二种用 attr_accessor 比较巧妙的缓存了 session[:user_id](可以是 author, operator 等等随意)

flyerhzm 的其中之一的解释是 Thread.current is used to store and fetch the thread local variables. That means two threads have the same variables but these variables are referring to the different memory locations. If you see the rails 3.0.rc source codes, you will find rails also use Thread.current for time_zone, active_record_sql_runtime and etc.

我觉得 ywencn 说得是协作者修改创作者的操作记录的问题。所以 hp 说得第一种方案就不多说了。我的观点: 虽然 hp 的方式看起来更安全一点(也很巧妙,这是毋庸置疑的!)。我更喜欢 Thread 这种方式,从设计的角度来说,假如我有三处需要这个 session[:user_id](如 create, update, destroy),在 controller 每次都需要有一个{user_id: current_user.id},这实际上违反了 rails 的一些约定,另更多的重复会有更多的 bug,controller(有可能多个)跟 model 层耦合的更紧,扩展性也会差一些。我觉得如果真觉得有不对,可以多写一些 log 追踪一下。

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