草庐IT

基于MogileFS+mysql+nginx实现分布文件存储与访问

onlywyn 2023-03-28 原文
一、MogileFS简介

   MogileFS是一个开源的分布式文件存储系统,由LiveJournal旗下的Danga Interactive公司开发。Danga团队开发了包括 Memcached、MogileFS、Perlbal 等多个知名的开源项目。目前使用MogileFS 的公司非常多,如日本排名先前的几个互联公司及国内的yupoo(又拍)、digg、豆瓣、1号店、大众点评、搜狗和安居客等,分别为所在的组织或公司管理着海量的图片。

MogileFS由3个部分组成:
(1) server:主要包括mogilefsd和mogstored两个应用程序。mogilefsd实现的是tracker,它通过数据库来保存元数据信息,包括站点domain、class、host等;mogstored是存储节点(store node),它其实是个WebDAV服务,默认监听在7500端口,接受客户端的文件存储请求。在MogileFS安装完后,要运行mogadm工具将所有的store node注册到mogilefsd的数据库里,mogilefsd会对这些节点进行管理和监控。
(2) utils(工具集):主要是MogileFS的一些管理工具,例如mogadm等。
(3) 客户端API:MogileFS的客户端API很多,例如Perl、PHP、Java、Python等,用这个模块可以编写客户端程序,实现文件的备份管理功能等。

   具体的应用层特点:    

       应用层——没有特殊的组件要求
   无单点失败——MogileFS启动的三个组件(存储节点、跟踪器、跟踪用的数据库),均可运行在多个 机器上,因此没有单点失败。(你也可以将跟踪器和存储节点运行在同一台机器上,这样你就没有必要用4台机器)推荐至少两台机器。
自动的文件复制——文件是基于他们的“类”,文件可以自动的在多个存储节点上复制,这是为了尽量少的复制,才使用“类”的。加入你有的图片站点有三份JPEG图片的拷贝,但实际只有1or2份拷贝,那么Mogile可以重新建立遗失的拷贝数。用这种办法,MogileFS(不做RAID)可以节约在磁盘,否则你将存储同样的拷贝多份,完全没有必要。
   “比RAID好多了”——在一个非存储区域网络的RAID(non-SAN RAID)的建立中,磁盘是冗余的,但主机不是,如果你整个机器坏了,那么文件也将不能访问。 MogileFS在不同的机器之间进行文件复制,因此文件始终是可用的。
传输中立,

     无特殊协议——MogileFS客户端可以通过NFS或HTTP来和MogileFS的存储节点来通信,但首先需要告知跟踪器一下。
     简单的命名空间——文件通过一个给定的key来确定,是一个全局的命名空间。你可以自己生成多个命名空间,只要你愿意,但是这样可能在同一MogileFS中,会造成冲突key。
   不用共享任何东西——MogileFS不需要依靠昂贵的SAN来共享磁盘,每个机器只用维护好自己的磁盘。
   不需要RAID——在MogileFS中的磁盘可以是做了RAID的也可以是没有,如果是为了安全性着想的话RAID没有必要买了,因为MogileFS已经提供了。
       不会碰到文件系统本身的不可知情况——在MogileFS中的存储节点的磁盘可以被格式化成多种格式(ext3,reiserFS等等)。MogilesFS会做自己内部目录的哈希,所以它不会碰到文件系统本身的一些限制,比如一个目录中的最大文件数。

http://bbs.chinaunix.net/thread-1235868-1-1.html(具体的应用层特点)thanks: happy_fish100 http://www.csource.org/    big niu(大牛) 其写的FastDFS

二,本次实验用到的架构图


node1 MariaDB10的解压安装这里不再描述,请自行google

为了与MogileFS与MySQL协同工作,需要在MySQL新建如下两个用户

grant all privileges on *.* to 'moguser'@'172.%.251.%' identified by 'mogpass'; grant all privileges on *.* to 'root'@'172.%.251.%' identified by 'root';这里着重介绍安装MogileFS

node2安装MogileFS安装

查看安装的软件信息

[root@node2 mogilefs]# rpm -qa|grep -i mogilefs MogileFS-Utils-2.19-1.el6.noarch MogileFS-Server-mogilefsd-2.46-2.el6.noarch MogileFS-Server-mogstored-2.46-2.el6.noarch perl-MogileFS-Client-1.14-1.el6.noarch MogileFS-Server-2.46-2.el6.noarch [root@node2 mogilefs]# rpm -ql MogileFS-Server-mogilefsd /etc/mogilefs/mogilefsd.conf /etc/rc.d/init.d/mogilefsd /usr/bin/mogdbsetup /usr/bin/mogilefsd /usr/share/man/man1/mogilefsd.1.gz /usr/share/man/man3/MogileFS::Checksum.3pm.gz /usr/share/man/man3/MogileFS::Class.3pm.gz 。。。。。。。。。。。。。。。。。。。。。。。。 [root@node2 mogilefs]# rpm -ql MogileFS-Server-mogstored /etc/mogilefs/mogstored.conf /etc/rc.d/init.d/mogstored /usr/bin/mogautomount /usr/bin/mogstored /usr/share/man/man1/mogautomount.1.gz /usr/share/man/man1/mogstored.1.gz /usr/share/perl5/vendor_perl/Mogstored/ChildProcess /usr/share/perl5/vendor_perl/Mogstored/ChildProcess.pm 。。。。。。。。。。。。。。。。。。。。。。。。。。。。 [root@node2 mogilefs]# rpm -ql MogileFS-Utils /usr/bin/mogadm /usr/bin/mogdelete /usr/bin/mogfetch /usr/bin/mogfiledebug /usr/bin/mogfileinfo /usr/bin/moglistfids /usr/bin/moglistkeys /usr/bin/mogrename /usr/bin/mogstats /usr/bin/mogtool /usr/bin/mogupload 。。。。。。。。。。。。。。。。。。。     修改目录权限,使用mogilefs用户能够有权限访问


[root@node2 mogilefs]# chown -R mogilefs.mogilefs /var/run/mogilefsd/ [root@node2 mogilefs]# mkdir -pv /dfsdata/mogdata/dev2 ## dev# 此#号尽量与主机所在的节点号一致,便于区分理解如:node1,dev1;node2,dev2 [root@node2 mogilefs]# chown -R mogilefs.mogilefs /dfsdata/mogdata 在node2建立与node1上MySQL的连接


mogdbsetup --dbhost=172.16.251.21 --dbport=3306 --dbname=mogdb --dbrootuser=root --dbrootpass=root --dbuser=moguser --dbpass=mogpass --yes

修改配置文件/etc/mogilefs/mogilefsd.conf ,mogsotred.conf (配置成功的文件,也要同步到其它两个节点上,node3,node4)

   启动mogilefsd mogstored 服务,并查看状态

[root@node2 mogilefs]#service mogilefsd start [root@node2 mogilefs]#service mogstored start [root@node2 mogilefs]# ss -tunlp |grep mog tcp LISTEN 0 128 *:7500 *:* users:(("mogstored",7221,4)) tcp LISTEN 0 128 *:7501 *:* users:(("mogstored",7221,9)) tcp LISTEN 0 128 *:7001 *:* users:(("mogilefsd",3773,6)) [root@node2 mogilefs]#    注意以上mogileFS安装步骤同样也要在node3,node4节点上安装一遍,切记不要再进行数据的初始化操作,原因是node2已经完成初始化,三个节点共享库中的信息

接下来是具体的配置阶段

增加自己本身为trackers 和mogstored

[root@node2 mogilefs]# mogadm --trackers=172.16.251.22:7001 host add 172.16.251.22 --ip=172.16.251.22 --status=alive [root@node2 mogilefs]# mogadm --trackers=172.16.251.22:7001 host list 172.16.251.22 [1]: alive IP: 172.16.251.22:7500 [root@node2 mogilefs]# mogadm --trackers=172.16.251.22:7001 host add 172.16.251.23 --ip=172.16.251.23 --status=alive [root@node2 mogilefs]# mogadm --trackers=172.16.251.22:7001 host list 172.16.251.22 [1]: alive IP: 172.16.251.22:7500 172.16.251.23 [2]: alive IP: 172.16.251.23:7500 [root@node2 mogilefs]#也可以在node3节点上,增加node4上的节点资源,如下


[root@node3 mogdata]# mogadm --trackers=172.16.251.23:7001 host list 172.16.251.22 [1]: alive IP: 172.16.251.22:7500 172.16.251.23 [2]: alive IP: 172.16.251.23:7500 [root@node3 mogdata]# mogadm --trackers=172.16.251.23:7001 host add 172.16.251.24 --ip=172.16.251.24 --status=alive [root@node3 mogdata]# mogadm --trackers=172.16.251.23:7001 host list 172.16.251.22 [1]: alive IP: 172.16.251.22:7500 172.16.251.23 [2]: alive IP: 172.16.251.23:7500 172.16.251.24 [3]: alive IP: 172.16.251.24:7500查看整体的节点信息:

[root@node2 ~]# mogadm --trackers=172.16.251.22:7001 host list

增加更节点的设备

[root@node2 ~]# mogadm --trackers=172.16.251.22:7001 device add 172.16.251.22 1 [root@node2 ~]# mogadm --trackers=172.16.251.22:7001 device add 172.16.251.23 2 [root@node2 ~]# mogadm --trackers=172.16.251.22:7001 device add 172.16.251.24 3 [root@node2 ~]# mogadm --trackers=172.16.251.22:7001 device list

介绍mogileFS系统中的几个概念

   https://code.google.com/p/mogilefs/wiki/Glossary

domain A domain is the top level separation of files. File keys are unique within domains. A domain consists of a set of classes that define the files within the domain. Examples of domains: fotobilder, livejournal.

域是顶层文件的分离。域内文件密钥是独一无二的。一个域由一组类定义文件的域。域的例子:fotobilder,livejournal

classEvery file is part of exactly one class. A class is part of exactly one domain. A class, in effect, specifies the minimum replica count of a file. Examples of classes: userpicture, userbackup, phonepost. Classes may have extra replication policies defined.

每个文件是一个类的一部分。一个类是一个域的一部分。实际上,类是指定文件的最小副本数。类的例子:userpicture、userbackup phonepost。类还可以定义额外的复制策略。

keyA key is a unique textual string that identifies a file. Keys are unique within domains. Examples of keys: userpicture:34:39, phonepost:93:3834, userbackup:15. Fake structures work too: /pics/hello.png, any string.

关键字是用唯一(unique)的,独特的文本字符串来标识一个文件(可以理解为变量名)。关键字领域内(全局)是独一无二的。

minimum replica count (mindevcount)最小副本(复制)数量This is a property of a class. This defines how many times the files in that class need to be replicated onto different devices in order to ensure redundancy among the data and prevent loss.

是类的属性。此数值定义了类中多少数量的文件需要复制到不同的设备,以确保数据之间的冗余和防止损失。

fileA file is a defined collection of bits uploaded to MogileFS to store. Files are replicated according to their minimum replica count. Each file has a key, is a part of one class, and is located in one domain. Files are the things that MogileFS stores for you.

文件上传到MogileFS存储。依据其最低副本数数量进行文件复制。每个文件都有一个关键字,是一个类的一部分,位于一个域。MogileFS提供文件

fid file identifier
A fid is an internal numerical representation of a file. Every file is assigned a unique fid. If a file is overwritten, it is given a new fid.

fid 是mogileFS内部表示一个文件的文件ID,且全局唯一,如果重写,则将生成一新的fid


增加域(domain),类(class)

   增加域(domain)

[root@node2 ~]# mogadm domain Help for 'domain' command: (enter any command prefix, leaving off options, for further help) mogadm domain add <domain> Add a domain (namespace) mogadm domain delete <domain> Delete a domain. mogadm domain list List all hosts. [root@node2 ~]# mogadm --trackers=172.16.251.22:7001 domain add p_w_picpaths [root@node2 ~]# mogadm --trackers=172.16.251.22:7001 domain add files [root@node2 ~]# mogadm --trackers=172.16.251.22:7001 domain list对应的域中增加类,并确定mindevcount

域中的默认类能删除么?答案是不能删除的

完成的域,类的添加(到这里可以理解为:为文件装备的存放position(domain),numbers (class)),

上传文件及显示

在MogileFS系统中文件显示

Nginx反向代理访问MogileFS文件系统的设置与安装

   前端nginx的安装与设置,要想Nginx代理访问mogileFS文件系统中的文件,要安装nginx-mogile插件,下面介绍安装的软件具体版本

解压文件并安装,注:安装时加载第三方插件的安装方式,

[root@node5 mogilefs]#useradd -r nginx [root@node5 mogilefs]#./configure --prefix=/usr --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx/nginx.pid --lock-path=/var/lock/nginx.lock --user=nginx --group=nginx --with-http_ssl_module --with-http_flv_module --with-http_stub_status_module --with-http_gzip_static_module --http-client-body-temp-path=/var/tmp/nginx/client/ --http-proxy-temp-path=/var/tmp/nginx/proxy/ --http-fastcgi-temp-path=/var/tmp/nginx/fcgi/ --http-uwsgi-temp-path=/var/tmp/nginx/uwsgi --http-scgi-temp-path=/var/tmp/nginx/scgi --with-pcre --with-debug --add-module=../nginx-mogilefs-module-master 安装服务配置脚本

[root@node5 mogilefs]#vim /etc/rc.d/init.d/nginx #!/bin/sh # # nginx - this script starts and stops the nginx daemon # # chkconfig: - 85 15 # description: Nginx is an HTTP(S) server, HTTP(S) reverse \ # proxy and IMAP/POP3 proxy server # processname: nginx # config: /etc/nginx/nginx.conf # config: /etc/sysconfig/nginx # pidfile: /var/run/nginx.pid # Source function library. . /etc/rc.d/init.d/functions # Source networking configuration. . /etc/sysconfig/network # Check that networking is up. [ "$NETWORKING" = "no" ] && exit 0 nginx="/usr/sbin/nginx" prog=$(basename $nginx) NGINX_CONF_FILE="/etc/nginx/nginx.conf" [ -f /etc/sysconfig/nginx ] && . /etc/sysconfig/nginx lockfile=/var/lock/subsys/nginx make_dirs() { # make required directories user=`nginx -V 2>&1 | grep "configure arguments:" | sed 's/[^*]*--user=\([^ ]*\).*/\1/g' -` options=`$nginx -V 2>&1 | grep 'configure arguments:'` for opt in $options; do if [ `echo $opt | grep '.*-temp-path'` ]; then value=`echo $opt | cut -d "=" -f 2` if [ ! -d "$value" ]; then # echo "creating" $value mkdir -p $value && chown -R $user $value fi fi done } start() { [ -x $nginx ] || exit 5 [ -f $NGINX_CONF_FILE ] || exit 6 make_dirs echo -n $"Starting $prog: " daemon $nginx -c $NGINX_CONF_FILE retval=$? echo [ $retval -eq 0 ] && touch $lockfile return $retval } stop() { echo -n $"Stopping $prog: " killproc $prog -QUIT retval=$? echo [ $retval -eq 0 ] && rm -f $lockfile return $retval } restart() { configtest || return $? stop sleep 1 start } reload() { configtest || return $? echo -n $"Reloading $prog: " killproc $nginx -HUP RETVAL=$? echo } force_reload() { restart } configtest() { $nginx -t -c $NGINX_CONF_FILE } rh_status() { status $prog } rh_status_q() { rh_status >/dev/null 2>&1 } case "$1" in start) rh_status_q && exit 0 $1 ;; stop) rh_status_q || exit 0 $1 ;; restart|configtest) $1 ;; reload) rh_status_q || exit 7 $1 ;; force-reload) force_reload ;; status) rh_status ;; condrestart|try-restart) rh_status_q || exit 0 ;; *) echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload|configtest}" exit 2 esac # chmod +x /etc/rc.d/init.d/nginx # chkconfig --add nginx # chkconfig nginx on # service nginx start编辑配置文件/etc/nginx/nginx.conf 如下所示

[root@node5 nginx]# grep -v "^[[:space:]].*#" nginx.conf |grep -v "^#" |grep -v "^$" worker_processes 1; error_log /var/log/nginx_error.log notice; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; upstream mogileserver { server 172.16.251.22:7001; server 172.16.251.23:7001; server 172.16.251.24:7001; } server { listen 80; server_name localhost; location / { root html; index index.html index.htm; } location ~* ^(/p_w_picpaths/.*)$ { mogilefs_tracker mogileserver; mogilefs_domain p_w_picpaths; mogilefs_pass $1 { proxy_pass $mogilefs_path; proxy_hide_header Content-Type; proxy_buffering off; } } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } }测试文件浏览

扩展以上的系统

之前的系统容易出现单点故障(nginx),现在增加一台nginx,生产中应该是增加一台物理机,

下面一一介绍具体配置

node4上安装nginx,具体安装,不再描述,node4,node5上的nginx监听端口修改为8080,nginx配置如下(node4,node5)一模一样

[root@node5 nginx]# grep -v "^[[:space:]].*#" nginx.conf |grep -v "^#" |grep -v "^$" worker_processes 1; error_log /var/log/nginx_error.log notice; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; upstream mogileserver { server 172.16.251.22:7001; server 172.16.251.23:7001; server 172.16.251.24:7001; } server { listen 8080; server_name localhost; location / { root html; index index.html index.htm; } location ~* ^(/p_w_picpaths/.*)$ { mogilefs_tracker mogileserver; mogilefs_domain p_w_picpaths; mogilefs_pass $1 { proxy_pass $mogilefs_path; proxy_hide_header Content-Type; proxy_buffering off; } } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } }node5上 haproxy配置文件如下;

[root@node5 haproxy]# grep -v "^[[:space:]].*#" haproxy.cfg |grep -v "^#" |grep -v "^$" global log 127.0.0.1 local2 chroot /var/lib/haproxy pidfile /var/run/haproxy.pid maxconn 4000 user haproxy group haproxy daemon stats socket /var/lib/haproxy/stats defaults mode http log global option httplog option dontlognull option http-server-close option forwardfor except 127.0.0.0/8 option redispatch retries 3 timeout http-request 10s timeout queue 1m timeout connect 10s timeout client 1m timeout server 1m timeout http-keep-alive 10s timeout check 10s maxconn 3000 frontend webservers bind *:80 acl url_static path_beg -i /p_w_picpaths acl url_static path_end -i .jpg .gif .png use_backend static if url_static backend static balance roundrobin server node4 172.16.251.25:8080 check server node5 172.16.251.24:8080 check启动node5上的haproxy,nginx,node4上的nginx 如下图:


接下来就可以测试

测试过程,停掉其中一台nginx服务,再进行图片的访问,成功!



有关基于MogileFS+mysql+nginx实现分布文件存储与访问的更多相关文章

  1. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  2. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  3. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  4. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  5. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  6. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  7. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  8. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  9. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  10. ruby-on-rails - 在混合/模块中覆盖模型的属性访问器 - 2

    我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah

随机推荐