未来的Ruby 3.0

原文地址:http://hrnabi.com/2015/05/12/7035/
谁日语好的为大家做下贡献,把原文翻译一下吧。

反正我不会日语,根据文中的一些汉字连蒙带猜的,大致看了一遍,现在简单总结一下。
如果有总结得不对的,本人不负责哦。

看来要玩好Ruby不仅需要学英语,也还得学日语啊。


在2014年9月举行的 RubyKaigi 2014 大会上,Matz在演讲过程中首次提到了 Ruby 3.0。
文中说的Ruby 3.0的三个工作方向:

  • Concurrency (并行性)
  • JIT (即时编译)
  • Static typing (静态类型)

Ruby要引入静态类型检查?
Matz说,在20世纪出生的语言大多是脚本语言,如:Ruby、PHP和Perl、JavaScript,这些都不是静态类型的。
另一方面,最近推出的如Scala和Dart、Go是属于静态类型的。
在Ruby中可以考虑引入Python这种通过注释来进行检查的方法。

Ruby要引入并行计算?
Matz详细讨论了静态类型,但没有提到并行计算。详细的内容是由笹田氏来说的。
(这部分内容我没看懂,只是大概知道这位笹田氏的博士论文与这个相关。)
关于并行计算,Matz提到了Erlang和Scala的actor模型。

最后期待下一个Ruby开发者大会。


好了,我只看懂了这么多。其余的各位感兴趣的自己去看原文吧。
(怎么感觉好坑啊,这总结得跟没总结一样啊。没办法了我的日语水平有限。)

Ruby中一些重要的钩子方法

Ruby的哲学理念是基于一个基本的要素,那就是让程序员快乐。Ruby非常注重程序员的快乐,并且也提供了许多不同的方法来实现它。
它的元编程能力能够让程序员编写在运行时动态生成的代码。它的线程功能使得程序员有一种优雅的的方式编写多线程代码。
它的钩子方法能让程序员在程序运行时扩展它的行为。

上述的这些特性,以及一些其他很酷的语言方面,使得Ruby成为编写代码的优先选择之一。
本文将探讨Ruby中的一些重要的钩子方法。我们将从不同方面讨论钩子方法,如它们是什么,它们用于什么,以及我们如何使用它们来解决不同的问题。
我们同时也了解一下一些流行的Ruby框架/Gem包/库是如何使用它们来提供非常酷的特性的。

我们开始吧。

什么是钩子方法?

钩子方法提供了一种方式用于在程序运行时扩展程序的行为。
假设有这样的功能,可以在无论何时一个子类继承了一些特定的父类时收到通知,
或者是比较优雅地处理一个对象上的不可调用的方法而不是让编译器抛出异常。
这些情况就是使用钩子方法,但是它们的用法并不仅限于此。
不同的框架/库使用了不同的钩子方法来实现它们的功能。

在本文中我们将会讨论如下几个钩子方法:

  • included
  • extended
  • prepended
  • inherited
  • method_missing

included

Ruby给我们提供了一种方式使用 模块(modules) (在其他语言中被称作 混入类(mixins))来编写模块化的代码供其他的 模块/ 使用。
模块 的概念很简单,它就是一个可以在其他地方使用的独立代码块。

例如,如果我们想要编写一些代码在任何时候调用特定的方法都会返回一个静态字符串。
我们姑且将这个方法称作 name。你可能在其他地方也会想使用同一块代码。
这样最好是新建一个模块。让我们来创建一个:

1
2
3
4
5
module Person
def name
puts "My name is Person"
end
end

这是一个非常简单的模块,仅有一个 name 方法用于返回一个静态字符串。在我们的程序中使用这个模块:

1
2
3
class User
include Person
end

Ruby提供了一些不同的方法来使用模块include 是其中之一。include 所做的就是将在 module 内定义的方法在一个 class 的实例变量上可用。
在我们的例子中,是将 Person 模块中定义的方法变为一个 User 类实例对象的方法。
这就相当于我们是将 name 方法写在 User 类里一样,但是定义在 module 里的好处是可复用。
要调用 name 方法我们需要创建一个 User 的实例对象,然后再在这个对象上调用 name 方法。例如:

1
2
User.new.name
=> My name is Person

让我们看看基于 include 的钩子方法。included 是Ruby提供的一个钩子方法,当你在一些 module 或者 classinclude 了一个 module 时它会被调用。
更新 Person 模块:

1
2
3
4
5
6
7
8
9
module Person
def self.included(base)
puts "#{base} included #{self}"
end

def name
"My name is Person"
end
end

你可以看到一个新的方法 included 被定义为 Person 模块的类方法。当你在其他的模块或者类中执行 include Person 时,这个 included 方法会被调用。
该方法接收的一个参数是对包含该模块的类的引用。试试运行 User.new.name,你会看到如下的输出:

1
2
User included Person
My name is Person

正如你所见,base 返回的是包含该模块的类名。现在我们有了一个包含 Person 模块的类的引用,我们可以通过元编程来实现我们想要的功能。
让我们来看看 Devise是如何使用 included 钩子的。

Devise中的 included

Devise是Ruby中使用最广泛的身份验证gem包之一。它主要是由我喜欢的程序员 José Valim 开发的,现在是由一些了不起的贡献者在维护。
Devise为我们提供了从注册到登录,从忘记密码到找回密码等等完善的功能。它可以让我们在用户模型中使用简单的语法来配置各种模块:

1
devise :database_authenticatable, :registerable, :validatable

在我们模型中使用的 devise 方法在这里定义。
为了方便我将这段代码粘贴在下面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def devise(*modules)
options = modules.extract_options!.dup

selected_modules = modules.map(&:to_sym).uniq.sort_by do |s|
Devise::ALL.index(s) || -1 # follow Devise::ALL order
end

devise_modules_hook! do
include Devise::Models::Authenticatable

selected_modules.each do |m|
mod = Devise::Models.const_get(m.to_s.classify)

if mod.const_defined?("ClassMethods")
class_mod = mod.const_get("ClassMethods")
extend class_mod

if class_mod.respond_to?(:available_configs)
available_configs = class_mod.available_configs
available_configs.each do |config|
next unless options.key?(config)
send(:"#{config}=", options.delete(config))
end
end
end

include mod
end

self.devise_modules |= selected_modules
options.each { |key, value| send(:"#{key}=", value) }
end
end

在我们的模型中传给 devise 方法的模块名将会作为一个数组保存在 *modules 中。
对于传入的模块调用 extract_options! 方法提取可能传入的选项。
在11行中调用 each 方法,并且每个模块在代码块中用 m 表示。
在12行中 m 将会转化为一个常量(类名),因此使用 m.to.classify 一个例如 :validatable 这样的符号会变为 Validatable
随便说一下 classify 是ActiveSupport的方法。
Devise::Models.const_get(m.to_classify) 会获取该模块的引用,并赋值给 mod
在27行使用 include mod 包含该模块。
例子中的 Validatable 模块是定义在这里
Validatableincluded 钩子方法定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def self.included(base)
base.extend ClassMethods
assert_validations_api!(base)

base.class_eval do
validates_presence_of :email, if: :email_required?
validates_uniqueness_of :email, allow_blank: true, if: :email_changed?
validates_format_of :email, with: email_regexp, allow_blank: true, if: :email_changed?

validates_presence_of :password, if: :password_required?
validates_confirmation_of :password, if: :password_required?
validates_length_of :password, within: password_length, allow_blank: true
end
end

此时模型是 base。在第5行的 class_eval 代码块会以该类作为上下文进行求值运算。
通过 class_eval 编写的代码与直接打开该类的文件将代码粘贴进去效果是一样的。
Devise是通过 class_eval 将验证包含到我们的用户模型中的。

当我们试着使用Devise注册或者登录时,我们会看到这些验证,但是我们并没有编写这些验证代码。
Devise是利用了 included 钩子来实现这些的。非常的优雅吧。

extended

Ruby也允许开发者 扩展(extend) 一个模块,这与 包含(include) 有点不同。
extend 是将定义在 模块(module) 内的方法应用为类的方法,而不是实例的方法。
让我们来看一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
module Person
def name
"My name is Person"
end
end

class User
extend Person
end

puts User.name # => My name is Person

正如你所看到的,我们将 Person 模块内定义的 name 方法作为了 User 的类方法调用。
extendPerson 模块内的方法添加到了 User 类中。extend 同样也可以用于将模块内的方法作为单例方法(singleton methods)。
让我们再来看另外一个例子:

1
2
3
4
5
6
7
8
9
# We are using same Person module and User class from previous example.     

u1 = User.new
u2 = User.new

u1.extend Person

puts u1.name # => My name is Person
puts u2.name # => undefined method `name' for #<User:0x007fb8aaa2ab38> (NoMethodError)

我们创建了两个 User 的实例对象,并将 Person 作为参数在 u1 上调用 extend 方法。
使用这种调用方式,Personname 方法仅对 u1 有效,对于其他实例是无效的。

正如 included 一样,与 extend 相对应的钩子方法是 extended
当一个模块被其他模块或者类执行了 extend 操作时,该方法将会被调用。
让我们来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Modified version of Person module

module Person
def self.extended(base)
puts "#{base} extended #{self}"
end

def name
"My name is Person"
end
end

class User
extend Person
end

该代码的运行结果是输出 User extended Person

关于 extended 的介绍已经完了,让我们来看看 ActiveRecord 是如何使用它的。

ActiveRecord中的 extended

ActiveRecord 是在 Ruby 以及 Rails 中广泛使用的ORM框架。它具有许多酷的特性,
因此使用它在很多情况下成为了ORM的首选。让我们进入 ActiveRecord 内部看看 ActiveRecord 是如何使用回调的。
(我们使用的是 Rails v3.2.21)

ActiveRecord这里 extendActiveRecord::Models 模块。

1
extend ActiveModel::Callbacks

ActiveModel 提供了一套在模型类中使用的接口。它们允许 ActionPack 与不是 ActiveRecord 的模型进行交互。
这里ActiveModel::Callbacks 内部你将会看到如下代码:

1
2
3
4
5
def self.extended(base)
base.class_eval do
include ActiveSupport::Callbacks
end
end

ActiveModel::Callbacksbase 即就是 ActiveRecord::Callbacks 调用了 class_eval 方法,
并包含了 ActiveSupport::Callbacks 模块。我们前面已经提到过了,对一个类调用 class_eval 与手动地将代码写在这个类里是一样的。
ActiveSupport::CallbacksActiveRecord::Callbacks 提供了 Rails 中的回调方法。

这里我们讨论了 extend 方法,以及与之对应的钩子 extended。并且也了解了 ActiveRecord / ActiveModel
是如何使用上述方法为我们提供可用功能的。

prepended

另一个使用定义在模块内部方法的方式称为 prependprepend 是在Ruby 2.0中引入的,并且与 includeextend 很不一样。
使用 includeextend 引入的方法可以被目标模块/类重新定义覆盖。
例如,如果我们在某个模块中定义了一个名为 name 的方法,并且在目标模块/类中也定义同名的方法。
那么这个在我们类在定义的 name 方法将会覆盖模块中的。而 prepend 是不一样的,它会将 prepend 引入的模块
中的方法覆盖掉我们模块/类中定义的方法。让我们来看一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module Person
def name
"My name belongs to Person"
end
end

class User
include Person
def name
"My name belongs to User"
end
end

puts User.new.name
=> My name belongs to User

现在再来看看 prepend 的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module Person
def name
"My name belongs to Person"
end
end

class User
prepend Person
def name
"My name belongs to User"
end
end

puts User.new.name
=> My name belongs to Person

使用 prepend Person 会将 User 中的同名方法给覆盖掉,因此在终端输出的结果为 My name belongs to Person
prepend 实际上是将方法添加到方法链的前端。在调用 User 类内定义的 name 方法时,会调用 super 从而调用 Person 模块的 name

prepend 对应的回调名为(你应该猜到了) prepended。当一个模块被预置到另一个模块/类中时它会被调用。
我们来看下效果。更新 Person 模块的定义:

1
2
3
4
5
6
7
8
9
module Person
def self.prepended(base)
puts "#{self} prepended to #{base}"
end

def name
"My name belongs to Person"
end
end

你再运行这段代码应该会看到如下结果:

1
2
Person prepended to User
My name belongs to Person

prepend 的引入是为了去除 alias_method_chain hack的丑陋,它曾被Rails以及其他库广泛地使用以达到与 prepend 相同的功能。
因为 prepend 只有在 Ruby >= 2.0 的版本中才能使用,因此如果你打算使用 prepend 的话,那么你就应该升级你的Ruby版本。

inherited

继承是面向对象中一个最重要的概念。Ruby是一门面向对象的编程语言,并且提供了从基/父类继承一个子类的功能。
我们来看一个简单的例子:

1
2
3
4
5
6
7
8
9
10
class Person
def name
"My name is Person"
end
end

class User < Person
end

puts User.new.name # => My name is Person

我们创建了一个 Person 类和一个子类 User。在 Person 中定义的方法也成为了 User 的一部分。
这是非常简单的继承。你可能会好奇,是否有什么方法可以在一个类被其他类继承时收到通知呢?
是的,Ruby有一个名为 inherited 的钩子可以实现。我们再看看这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person
def self.inherited(child_class)
puts "#{child_class} inherits #{self}"
end

def name
"My name is Person"
end
end

class User < Person
end

puts User.new.name

正如你所见,当 Person 类被其他子类继承时 inherited 类方法将会被调用。
运行以上代码结果如下:

1
2
User inherits Person
My name is Person

让我们看看 Rails 在它的代码中是如何使用 inherited 的。

Rails中的 inherited

Rails应用中有一个重要的类名为 Application ,定义中 config/application.rb 文件内。
这个类执行了许多不同的任务,如运行所有的Railties,引擎以及插件的初始化。
关于 Application 类的一个有趣的事件是,在同一个进程中不能运行两个实例。
如果我们尝试修改这个行为,Rails将会抛出一个异常。让我们来看看Rails是如何实现这个特性的。

Application 类继承自 Rails::Application,它是在这里定义的。
在62行定义了 inherited 钩子,它会在我们的Rails应用 Application 类继承 Rails::Application 时被调用。
inherited 钩子的代码如下:

1
2
3
4
5
6
7
8
9
class << self
def inherited(base)
raise "You cannot have more than one Rails::Application" if Rails.application
super
Rails.application = base.instance
Rails.application.add_lib_to_load_path!
ActiveSupport.run_load_hooks(:before_configuration, base.instance)
end
end

class << self 是Ruby中的另一个定义类方法的方式。在 inherited 中的第1行是检查 Rails.application 是否已存在。
如果存在则抛出异常。第一次运行这段代码时 Rails.application 会返回false然后调用 super
在这里 super 即是 Rails::Engineinherited 钩子,因为 Rails::Application 继承自 Rails::Engine

在下一行,你会看到 Rails.application 被赋值为 base.instance 。其余就是设置Rails应用了。

这就是Rails如何巧妙地使用 inherited 钩子来实现我们的Rails Application 类的单实例。

method_missing

method_missing 可能是Ruby中使用最广的钩子。在许多流行的Ruby框架/gem包/库中都有使用它。
当我们试图访问一个对象上不存在的方法时则会调用这个钩子方法。
让我们来看一个简单的例子:

1
2
3
4
5
6
7
8
9
10
class Person
def name
"My name is Person"
end
end

p = Person.new

puts p.name # => My name is Person
puts p.address # => undefined method `address' for #<Person:0x007fb730a2b450> (NoMethodError)

我们定义了一个简单的 Person 类, 它只有一个 name 方法。然后创建一个 Person 的实例对象,
并分别调用 nameaddress 两个方法。因为 Person 中定义了 name,因此这个运行没问题。
然而 Person 并没有定义 address,这将会抛出一个异常。
method_missing 钩子可以优雅地捕捉到这些未定义的方法,避免此类异常。
让我们修改一下 Person 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person
def method_missing(sym, *args)
"#{sym} not defined on #{self}"
end

def name
"My name is Person"
end
end

p = Person.new

puts p.name # => My name is Person
puts p.address # => address not defined on #<Person:0x007fb2bb022fe0>

method_missing 接收两个参数:被调用的方法名和传递给该方法的参数。
首先Ruby会寻找我们试图调用的方法,如果方法没找到则会寻找 method_missing 方法。
现在我们重载了 Person 中的 method_missing,因此Ruby将会调用它而不是抛出异常。

让我们来看看 Rake 是如何使用 method_missing 的。

Rake中的 method_missing

Rake 是Ruby中使用最广泛的gem包之一。Rake 使用 method_missing 来提供访问传递给Rake任务的参数。
首先创建一个简单的rake任务:

1
2
3
task :hello do
puts "Hello"
end

如果你通过调用 rake hello 来执行这个任务,你会看到输出 Hello
让我们扩展这个rake任务,以便接收一个参数(一个人名)并向他打招呼:

1
2
3
task :hello, :name do |t, args|
puts "Hello #{args.name}"
end

t 是任务名,args 保存了传递过来的参数。正如你所见,我们调用 args.name 来获取传递给 hello 任务的 name 参数。
运行该任务,并传递一个参数:

1
2
rake hello["Imran Latif"]
=> Hello Imran Latif

让我们来看看 Rake 是如何使用 method_missing 为我们提供了传递给任务的参数的。

在上面任务中的 args 对象是一个 Rake::TaskArguments 实例,它是在这里所定义。
这个类负责管理传递给Rake任务的参数。查看 Rake::TaskArguments 的代码,你会发现并没有定义相关的方法将参数传给任务。
那么 Rake 是如何将参数提供给任务的呢?答案是 Rake 是使用了 method_missing 巧妙地实现了这个功能。
看看第64行 method_missing 的定义:

1
2
3
def method_missing(sym, *args)
lookup(sym.to_sym)
end

在这个类中定义 method_missing 是为了保证能够访问到那些未定义的方法,而不是由Ruby抛出异常。
method_missing 中它调用了 lookup 方法:

1
2
3
4
5
6
7
def lookup(name)
if @hash.has_key?(name)
@hash[name]
elsif @parent
@parent.lookup(name)
end
end

method_missing 调用 lookup,并将方法名以 Symbol(符号) 的形式传递给它。
lookup 方法将会在 @hash 中进行查找,它是在 Rake::TaskArguments 的构造函数中创建的。
如果 @hash 中包含该参数则返回,如果在 @hash 中没有则 Rake 会尝试调用 @parentlookup
如果该参数没有找到,则什么都不返回。

这就是 Rake 如何巧妙地使用 method_missing 提供了访问传递给Rake任务的参数的。
感谢Jim Weirich编写了Rake。

结束语

我们讨论了5个重要的Ruby钩子方法,探索了它们是如何工作的,以及一些流行的框架/gem包是如何使用它们来提供一些优雅的功能。
我希望你能喜欢这篇文章。请在评论中告诉我们你所喜欢的Ruby钩子,以及你使用它们所解决的问题。

原文地址: http://www.sitepoint.com/rubys-important-hook-methods/

使用Unicorn部署rails应用

玩 rails 也有段时间了,最近研究下怎么部署一个 rails 应用。在几年前的话要部署 rails 应用是件很麻烦的事,
但是近几年出现了一些比较好的工具可以方便的进行 rails 部署。如: Unicorn、thin、Passenger等。

Unicorn 是一个 Rack 应用的HTTP服务器。之前玩 Python 的时候也有一个 Gunicorn ,使用它来部署 Python 的 Web 应用
也很方便,可以参考我之前的那篇文件 《使用gunicorn部署Django》

接下来简单分享下使用 Nginx + Unicorn 来部署 rails 的配置。

安装

首先安装 unicorn 包: $ gem install unicorn

然后编译一下静态文件:

1
2
$ RAILS_ENV=production rake assets:clean
$ RAILS_ENV=production rake assets:precompile

下载配置文件: $ curl -o config/unicorn.rb https://raw.github.com/defunkt/unicorn/master/examples/unicorn.conf.rb

接着根据情况修改相关配置,如: working_directory、listen 等。
例如我的是需要同时监听网络端口和 sock 文件,那么我的 listen 设置如下:

1
2
listen "#{root_path}/tmp/sockets/unicorn.sock", :backlog => 64
listen 8081, :tcp_nopush => true

配置 Nginx

然后配置 Nginx 的反向代理,以下是我的 Nginx 配置示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
upstream rails_server {
server unix:/app_path/tmp/sockets/unicorn.sock fail_timeout=0;
}

server {
listen 80;
server_name webserver localhost;

root /app_path/public;

try_files $uri $uri @unicorn;

location @unicorn {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://rails_server;
}

location ~ /\.ht {
deny all;
}
}

启动服务

配置完成之后,最后启动服务。

1
2
$ bundle exec unicorn_rails -c config/unicorn.rb -D -E production
$ sudo service nginx start

然后再在浏览器中访问试试。

Vagrant使用笔记

Vgrant是一个基于 Ruby 使用 Virtualbox 进行创建和部署虚拟化环境的工具。
类似的工具之前有使用过 Docker。就我个人而言这两款工具之间,Docker是轻量级的VM,
因此性能应该会比较好,但是只能在64位的系统下使用。
而 Vgrant 是使用 Virtualbox 进行虚拟化,因此性能上不及 Docker,
不过它可以在32/64位的 Linux、Windows 等系统上运行。

我觉得 Vgrant 比较适合用于在开发环境中使用,而 Docker 比较适合用于生产环境。

安装

首先安装 virtualbox,然后再安装 Vgrant。

1.通过源代码安装

1
2
3
4
git clone https://github.com/mitchellh/vagrant
cd vagrant
bundle install
rake install

2.通过安装包安装
根据情况选择下载对应的安装包: http://www.vagrantup.com/downloads.html

注意:如果是 Windows 系统,可能还需要将 Vgrant 的路径添加到环境变量中,以便使用 vgrant 命令。

使用

Vagrant 的使用方法也很简单,基本如下:

1
2
3
4
5
6
7
8
# 这里我先添加一个 ArchLinux 的镜像
vagrant box add archlinux http://vagrant.srijn.net/archlinux-x64-2014-01-07.box
# 进行初始化
vagrant init archlinux
# 运行虚拟机
vagrant up
# 如果需要进行ssh连接到虚拟机中进行一些操作,可以执行该命令
vagrant ssh

其他的一些命令:

1
2
3
4
# 关闭虚拟机
vagrant halt
# 删除创建的虚拟机
vagrant destroy

vagrant的一些镜像: http://www.vagrantbox.es/

Redis集群配置实例

通过配置 redis 的主从集群可将请求的负荷分散到多台服务器上。

redis 的集群配置比较简单,以下是一个例子。
假设有如下三台主机:

  • 172.17.0.11 (主)
  • 172.17.0.12 (从)
  • 172.17.0.13 (从)

在从服务器上添加如下配置:

1
slaveof 172.17.0.11 6379

如果主服务器设置了认证密码,那么还需要再添加一条配置:

1
masterauth <password>

然后分别启动三台服务器的 redis 服务即可。

接下来连接主服务器添加一些数据测试一下。

1
2
3
$ redis-cli -h 172.17.0.11
172.17.0.11:6379> set foo1 bar1
OK

然后再连接到从服务器查询结果。

1
2
3
$ redis-cli -h 172.17.0.12
172.17.0.12:6379> get foo1
"bar1"

这时发现数据已经同步过来了。

注意:从服务器默认是只读的。如果需要设置为可写,可将 slave-read-only 设置项的值设为 no 即可。

Mongodb分片配置实例

数据分片即是从一个集合中选择一个片键(shard key)作为数据拆分的依据,原理与索引类似,然后将集合的数据拆分并保存到不同的服务器上。
以下通过一个例子来介绍一下Mongodb的分片配置。

有四台主机:

  • 172.17.0.6 (配置服务器)
  • 172.17.0.7 (mongos)
  • 172.17.0.8 (片服务器)
  • 172.17.0.9 (片服务器)

1.在 172.17.0.6 上启动 mongod 服务作为配置服务器;
修改配置,使其作为一个配置服务器,默认监听 27019 端口。

1
configsvr = true

启动服务 $ service mongodb start

2.在 172.17.0.7 上启动 mongos 服务作为路由服务;
建立mongos进程。(可以有多台配置服务器),用法如下:

1
$ mongos --configdb <config server hostnames>[,<config server hostnames>]

例如: $ mongos --configdb 172.17.0.6:27019

注意:在同一个分片集群中的每个 mongos 必须使用相同的 configDB 配置。

3.添加分片
一个片服务既可以是单个 mongod 实例,也可以是一个副本集。
1).先分别在 172.17.0.8 和 172.17.0.9 上启动片服务器,即就是一个普通的 mongod 服务。

1
$ service mongodb start

2).使用 mongo 客户端连接到 mongos 服务

1
$ mongo --host <hostname of machine running mongos> --port <port mongos listens on>

如: $ mongo --host 172.17.0.7 --port 27017

3).在 mongo 客户端上执行命令添加分片:

1
2
3
4
> use admin
> db.auth(<user>, <pswd>)
> sh.addShard("172.17.0.8:27017")
> sh.addShard("172.17.0.9:27017")

4.切片数据
1).首先对数据库进行切片
使用 mongo 客户端连接到 mongos ,执行命令打开数据库的分片功能,用法如下:

1
> sh.enableSharding("<database>")

例如要打开 mydb 数据库的分片功能: > sh.enableSharding("mydb")

2).然后对数据集合进行切片
命令用法如下:

1
> sh.shardCollection("<database>.<collection>", shard-key-pattern)

shard-key-pattern 与索引的用法一样,例如,要对 mydb 数据库的 test 集合按照 _id 字段进行分片: > sh.shardCollection("mydb.test", {"_id": "hashed"})

接下来通过一个程序来测试一下,向数据库中添加10000条数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env ruby
#-*- coding:utf-8 -*-

require "mongo"

begin
conn = Mongo::Connection.new '172.17.0.7'
db = conn['mydb']
rescue Exception=>e
p e
exit 1
end

i = 0
while i < 10000
d = {'no' => i}
d = db['test'].insert(d)
i += 1
puts d
end

然后分别查看 172.17.0.8 和 172.17.0.9 的状态:

1
2
3
4
5
6
7
172.17.0.8:
> db.test.count()
4952

172.17.0.9:
> db.test.count()
5048

数据基本上是平均的分布在两台服务器上。

参考: http://docs.mongodb.org/manual/tutorial/deploy-shard-cluster/

使用gunicorn部署Django

Gunicorn 是 Python的 一个 WSGI HTTP服务器,根据它的介绍说是它来自于 Ruby 的 Unicorn。可以方便的部署 Python 的 Web 程序,而且本身支持多种 Python 的框架,如 Django、Paster等。

通过介绍来看貌似很不错的样子,只可惜我现在不玩 Python 了,于是就简单体验一下。

简单应用

首先是安装,这个可以直接使用 pip 来完成:

1
$ pip install gunicorn

然后再根据官方文档的介绍部署一个简单的例子试试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ cd examples
$ cat test.py
# -*- coding: utf-8 -
#
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.

def app(environ, start_response):
"""Simplest possible application object"""
data = 'Hello, World!\n'
status = '200 OK'
response_headers = [
('Content-type','text/plain'),
('Content-Length', str(len(data)))
]
start_response(status, response_headers)
return iter([data])

$ gunicorn -b 0.0.0.0:8000 --workers=2 test:app

好的,现在程序运行起来了,可以访问 http://localhost:8000 看下效果。

gunicorn 也可以通过配置文件来设置一些内容, 一个配置文件是一个 python 脚本,格式类似 .ini 。通过 -c 参数指定要使用的配置文件。如:

1
2
3
# config.ini
bind = ["0.0.0.0:8000", "unix:///tmp/gunicorn.sock"]
workers = 3

gunicorn 还能与 Django 和 Paster 应用集成:

1
2
$ gunicorn --env DJANGO_SETTINGS_MODULE=myproject.settings myproject.wsgi:application
$ gunicorn --paste development.ini -b :8080 --chdir /path/to/project

与 Nginx 部署

gunicorn 本身也是一个 WSGI 应用,可以与 Nginx 一同使用。
以下是 Nginx + Gunicorn 部署 Django 的事例, Nginx 配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# nginx.conf
http {
include mime.types;
default_type application/octet-stream;
access_log /tmp/nginx.access.log combined;
sendfile on;

upstream app_server {
server unix:/tmp/gunicorn.sock fail_timeout=0;
# For a TCP configuration:
# server 192.168.0.7:8000 fail_timeout=0;
}

server {
listen 80 default;
client_max_body_size 4G;
server_name _;

keepalive_timeout 5;

# path for static files
root /path/to/app/current/public;

location / {
# checks for static file, if not found proxy to app
try_files $uri @proxy_to_app;
}

location @proxy_to_app {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;

proxy_pass http://app_server;
}

error_page 500 502 503 504 /500.html;
location = /500.html {
root /path/to/app/current/public;
}
}
}

Gunicorn 的配置文件:

1
2
3
4
5
6
7
8
9
10
11
# gunicorn.ini
import os

bind = ["0.0.0.0:8000", "unix:///tmp/gunicorn.sock"]
workers = 3
chdir = os.path.dirname(os.path.realpath(__file__))
raw_env = ["DJANGO_SETTINGS_MODULE=app.settings"]
accesslog = "/tmp/gunicorn-access.log"
errorlog = "/tmp/gunicorn.log"
daemon = True
pidfile = "/tmp/gunicorn.pid"

运行:

1
2
$ gunicorn -c gunicorn.ini myproject.wsgi:application
$ service nginx start

其他内容

与 WSGI 应用一样,如果之后配置有改动可以向 gunicorn 服务进程发送 HUP 信号让其重新加载配置:

1
$ kill -s HUP <pid>

Mongodb集群配置实例

Mongodb的集群有两种,一个是主从复制,另一种是副本集。

主从复制

根据 Mongodb 的官方文档说明,在生产环境中建议使用副本集代替主从复制。 http://docs.mongodb.org/manual/core/master-slave/

不过对于主从复制还是可以了解一下。假设有如下三台主机:

  • 172.17.0.4 (主)
  • 172.17.0.5 (从)
  • 172.17.0.6 (从)

要进行主从复制的配置,首先修改主服务器的配置信息:

1
2
master = true         # 以主服务器模式启动
bind_ip = 0.0.0.0

然后修改另两台从服务器配置信息:

1
2
3
slave = true
source = 172.17.0.4
bind_ip = 0.0.0.0

最后启动三台主机上的 Mongodb 服务,再通过一个简单的程序来测试一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env ruby
#-*- coding:utf-8 -*-

require "mongo"

begin
conn = Mongo::Connection.new '172.17.0.4'
db = conn['test']
rescue Exception=>e
p e
exit 1
end

i = 0
while i < 100
d = {'no' => i}
d = db['data'].insert(d)
i += 1
puts d
end

执行该脚本,向 172.17.0.4 主机的 Mongodb 中插入一些数据。然后发现数据被同步到了另外两台主机上。

主从之间安全认证:
如果启动了 auth 项,那么主从之间的认证需要使用 keyFile 选项。

执行如下命令生成 key 文件,并设置为只有 mongodb 的进程用户可读写:

1
2
3
$ openssl rand -base64 741 > /path/mongodb_keyFile
$ chmow 600 /path/mongodb_keyFile
$ chown mongodb:mongodb /path/mongodb_keyFile

将该文件复制到这三台主机中,然后分别修改主从的配置信息:

1
keyFile = /path/mongodb_keyFile

副本集

同样的对于这三台主机,我们重新修改配置设置为副本集的形式。

  • 172.17.0.4
  • 172.17.0.5
  • 172.17.0.6

首先修改配置文件,设置副本集的名字。
注意:副本集中所有主机设置的名字需要一样。这里我们设为 myrepl0
注意:设置副本集之前各个 mongodb 的数据目录必须都为空。

1
replSet = myrepl0

接着启动所有 mongodb 服务,然后对副本集进行初始化。
连接任意一台 mongodb 服务,执行如下操作:

1
2
3
4
5
> rs.initiate({'_id': 'myrepl0', 'members': [
{'_id': 1, 'host': '172.17.0.4:27017'},
{'_id': 2, 'host': '172.17.0.7:27017'},
{'_id': 3, 'host': '172.17.0.8:27017'}
]})

现在副本集的初始化已完成,可以通过如下命令查看状态:

1
> rs.status()

在运行过程中可以随时添加或移除一个节点,如:

1
2
rs.add("172.17.0.8:27017")
rs.remove("172.17.0.8:27017")

可以再通过上面的程序添加一些数据。然后再连接到任意一台主机进行查询,看看数据是否已同步。

详细内容可参考文档: http://docs.mongodb.org/manual/core/replication/

安全认证:

1.禁用 auth 选项和 replSet 选项再运行 mongodb

2.连接到该 mongodb 服务并创建用户

1
2
3
4
5
6
7
8
9
10
> use admin
switched to db admin
> db.addUser('root','root')
{
"user" : "root",
"readOnly" : false,
"pwd" : "2a8025f0885adad5a8ce0044070032b3",
"_id" : ObjectId("54745351f79804bd44b596fb")
}
>

3.重新以 auth、keyFile 和 replSet 模式启动 mongodb

4.连接到刚刚创建用户的 mongodb 服务

5.跟之前的步骤一样,配置副本集

1
2
3
4
5
> rs.initiate({'_id': 'myrepl0', 'members': [
{'_id': 1, 'host': '172.17.0.4:27017'},
{'_id': 2, 'host': '172.17.0.7:27017'},
{'_id': 3, 'host': '172.17.0.8:27017'}
]})

参考: http://docs.mongodb.org/manual/tutorial/deploy-replica-set-with-auth/

使用VIM进行PHP远程调试

最近在写 PHP 时感觉它的调试不是很方便,基本都是用的 var_dump 将信息输出到页面上进行调试。最终实现是受不了这种方式,就找了一下看看有没有什么简便的方法。
于是就找到了 vdebug 这个 vim 插件,使用它可以方便的进行远程调试。

根据 vdebug 的介绍,说是它可以用来调试基于 DBGP 协议的程序,比如: PHP、Python、Ruby等。
可能只是 DBGP 协议在 PHP 中用得比较多吧,因此看到的大部分介绍都是说的 PHP 调试。
关于 DBGP 协议的详细介绍可参考: http://xdebug.org/docs-dbgp.php

使用方法:

首先是在 vim 中安装这个插件,下载地址: https://github.com/joonty/vdebug

由于它的配置信息都写死在代码中的,因此我就 fork 了一份进行自定义的修改,如快捷键设置、远程主机名和端口号。

然后是安装 PHP 的 Xdebug 扩展,并配置一些参数信息。在 php.ini 配置文件中添加如下内容:

1
2
3
4
xdebug.remote_enable=on
xdebug.remote_handler=dbgp
xdebug.remote_host=127.0.0.1
xdebug.remote_port=9010

注意:这里的主机名和端口号要与 vdebug 中的一致。

接着使用 vim 打开一个 php 文件,按 <F5> 键启动该插件进行调试。然后在浏览器中访问该 php 程序,
并加上 XDEBUG_SESSION_START=1 参数,如: http://127.0.0.1/test.php?XDEBUG_SESSION_START=1

现在就可以在 vim 中对 php 程序进行单步调试了。

Nginx + uwsgi + Django环境配置

有段时间没折腾 Django 了,又有点生疏了。最近又部署了一下 Django 的环境,顺便作个笔记以便之后查阅。

首先安装 nginx、uwsgi 以及 uwsgi 的 python 插件。

然后新建一个 uwsgi 的配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
[uwsgi]
uid = www-data
chdir = /repo/django-blog
virtualenv = /repo/django-blog/pyenv2.7/ # python虚拟环境,没有可以不设置
env = DJANGO_SETTINGS_MODULE=blog.settings
module = blog.wsgi:application
master = true
plugin = python
pidfile = /tmp/blog-master.pid
socket = /tmp/blog.sock
enable-threads = true
post-buffering=1024000
post-buffering-busize=655360

这里我们的 Django 项目代码位于 /repo/django-blog ,项目的配置文件为: blog/settings.py

virtualenv 项表明我们使用的是 virtualenv 环境,也可以直接系统的 python 环境。不过还是建议使用虚拟环境,以免软件包版本冲突。

post-bufferingpost-buffering-busize 这两项设置了 POST 请求时缓冲区的大小,该值可根据自己的情况进行调整。之前遇到过由于缓冲区不足导致返回的内容不完整。

再安装对应的 python 依赖包,然后运行 uwsgi 服务。

接着修改 nginx 的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server {
listen 80;
server_name localhost;

client_max_body_size 50m;

access_log /var/log/nginx/blog-access.log;
error_log /var/log/nginx/blog-error.log;

location / {
uwsgi_pass unix:///tmp/blog.sock;
include uwsgi_params;
}

location /static {
alias /repo/django-blog/static/;
}
}

这个内容比较简单, client_max_body_size 项是用于设置 http 请求的 body 最大大小。如果你的程序中有文件上传的,那么就需要根据自身情况来设置允许上传文件的最大值。

最后再启动 nginx 服务。