庇护祝福的分享

Be worthy

Ruby 同步/异步/多线程模型测试

测试条件是对一个json接口进行1000次调用。

同步模型:

require 'json'
require 'open-uri'
require 'net/http'
1000.times do
  uri=URI("http://test.net/api/format/json/")
  req = Net::HTTP.get(uri)
  @slides = JSON.parse(req)["data"]["list"]
  p @slides[0]
end

接口延迟约15ms,执行总时间1分25秒。当接口延迟5ms左右时执行总时间约25秒

异步模型

异步回调模型:

require 'rubygems'
require 'eventmachine'
require 'em-http-request'
require 'json'
1000.times do
  EventMachine.run {
    http = EM::HttpRequest.new("http://test.net/api/format/json/").get
    http.errback { p 'Uh oh'; EM.stop }
    http.callback {
      req = http.response
      @slides = JSON.parse(req)["data"]["list"]
      p @slides[0]
      EM.stop
    }
  }
end

纤程模型:

require 'rubygems'
require 'eventmachine'
require 'em-http-request'
require 'em-synchrony'
require 'json'
require 'em-synchrony/em-http'   
1000.times do
  EM.synchrony do
    http = EM::HttpRequest.new("http://test.net/api/format/json/").get
    req = http.response
    @slides = JSON.parse(req)["data"]["list"]
    p @slides[0]
    EM.stop
  end
end

两种异步模型基本一样,在接口延迟15ms时测试均为50秒完成,接口延迟5ms时20+秒,和同步模型基本没有差别。

多线程模型

require 'json'
require 'open-uri'
require 'net/http'
def func1
  uri=URI("http://test.net/api/format/json/")
  req = Net::HTTP.get(uri)
  @slides = JSON.parse(req)["data"]["list"]
  p @slides[0]
end
200.times do
  threads = []
  5.times do 
    threads << Thread.new { func1 }
  end
  threads.each { |t| t.join }
end

接口延迟15ms时,5并发执行200次测试42秒完成,最快,接口延迟5ms时8秒完成,最快。不过在测试10并发的时候会出现不稳定的情况,50并发测试高达3分半。

Node.js模型

var http = require('http')
var options = {
  host: '117.79.93.206',
  port: 80,
  path: 'http://test.net/api/format/json/'
};
function get_message(){
    http.get(options, function(res){
        var body = '';
        res.on('data', function(chunk) {
            body += chunk;
        });
        res.on('end', function() {
            var content = JSON.parse(body)
            console.log(content.data.list[0]);
        });
        }).on('error', function(e) {
        console.log("Got error: " + e.message);
    });
}
for(i=0; i<1000; i++){
    get_message();
}

node.js果然名不虚传,接口延迟15ms,下约4秒完成,接口延迟5ms下平均2秒钟就可以完成,秒杀ruby。

Rails中间层解决方案

调用API构建前端

一般情况下,rails应用都是从数据库进行数据获取,而采用API调用的话,就不再需要model层了,只需要在控制器中用http库进行接口调用就可以了,大大简化了结构。 example:

require 'open-uri'
require 'json'
require 'net/http'
def index  
  uri = URI("http://test.csdn.net/api/format/json")
  req = Net::HTTP.get(uri)
  @slides = JSON.parse(req)["data"]["contents"]
end

问题:多个API调用时的阻塞问题

def index  
  uri_one = URI("http://test.csdn.net/api/1/format/json")
  uri_two = URI("http://test.csdn.net/api/2/format/json")
  uri_three = URI("http://test.csdn.net/api/3/format/json")
  ......
  req_one = Net::HTTP.get(uri_one)
  req_two = Net::HTTP.get(uri_two)
  req_three = Net::HTTP.get(uri_three)
  ......
  @slides = JSON.parse(req)["data"]["contents"]
  ......
end

如果有10个API调用,每个Net::HTTP.get(uri)通信耗时30ms,那么就是300ms的时间。

解决方案一:多线程处理

threads = []
threads << Thread.new {@req_one = Net::HTTP.get(uri_one)}
threads << Thread.new {@req_two = Net::HTTP.get(uri_two)}
threads << Thread.new {@req_three = Net::HTTP.get(uri_three)}
......
threads.each { |t| t.join }
data = JSON.parse(@req_one)["data"]

多进程是抢占资源的一个模型,并非并行计算,比如线程1执行一个http通信后中间有30ms的I/O时间,那么这段阻塞的时间就会被线程2抢占。 开启多线程并行处理,可以很大程度上提高处理速度。但是有两个问题: 一、开多个线程以及线程间的切换造成资源开销增加,实际上降低了应用的吞吐能力。 二、由于Ruby存在GIL (Global Interpreter Lock),并不能真正利用多线程进行并行计算。(JRuby 去除了 GIL,是真正意义的多线程,既能应付 IO Block,也能充分利用多核 CPU 加快整体运算速度。)

解决方案二:采用异步模型处理

Ruby的异步解决方案:Eventmachine

在Gemfile中添加:

gem 'eventmachine'
gem 'em-http-request', :require => 'em-http'

控制器中的代码:

EventMachine.run {
  http = EM::HttpRequest.new("http://test.csdn.net/api/1/format/json").get
  http.errback { 
    render :text "接口连接失败"; 
    EM.stop 
  }
  http.callback {
    req = http.response
    @slides = JSON.parse(req)["data"]["list"]
    @page_count = JSON.parse(req)["data"]["count"].to_i/18+1
    EM.stop
  }
}

这种方式不仅解决了阻塞问题,而且可以有效的提高系统的吞吐能力。但带来的问题是代码量增加,复杂逻辑下代码的复杂度成倍增加,不利于开发和维护。

引入纤程(Fiber),用同步代码编写异步程序

在Gemfile中添加gem 'em-synchrony'。 控制器代码:

EM.synchrony do
  http = EM::HttpRequest.new("http://test.csdn.net/api/1/format/json").get
  req = http.response
  @slides = JSON.parse(req)["data"]["list"]
  EM.stop
end

多个接口调用实例:

require "em-synchrony"
require "em-synchrony/em-http"
require "em-synchrony/fiber_iterator"
EM.synchrony do
  concurrency = 2
  urls = ['http://url.1.com', 'http://url2.com']
  results = []
  EM::Synchrony::FiberIterator.new(urls, concurrency).each do |url|
    res = EventMachine::HttpRequest.new(url).get
    results << res.response
  end
  puts results # all completed requests
  EventMachine.stop
end

Git Branch

创建操作

git branch

列出所有的分支, -r参数表示列出所有远程的分支

git branch branchname

建立一个名为的分支,使用当前版本作为这个新分支的版本

git branch

建立一个名为的分支, 使用指定的 作为新分支的版本,这个start-point可以是其他的分支名称或者tag名称。

删除操作

git branch -d

删除分支 ; 如果你要删除的分支并没有被merge到当前分支的话,将产生一个错误提示。

git branch -D

同-d,但是不管要删除的分支是否已经merge到当前分支,都会删除

切换操作

git checkout

切换当前分支到 , 更新 working directory 到 所对应的版本

git checkout -b

按照的版本建立一个新的分支,并checkout到这个新分支上。

git checkout -f

忽略差异部分,强制切换,例如: 修改branch test, 但是没有commit,这时如果切换到master,会提示错误。使用-f 则会丢弃test中的修改,强制切换,如果切回test分支,刚才的修改也没了

一个特殊符号 “HEAD” 总是指向当前分支. 实际上git使用一个在.git目录中叫 “HEAD”的文件来记录当前分支:

$ cat .git/HEAD
ref: refs/heads/master

示例:“master”分支是当初你执行git-clone时候,clone的一个远程repository的HEAD版本的一个copy.

$ git branch -r
  origin/HEAD
  origin/html
  origin/maint
  origin/man
  origin/master
  origin/next
  origin/pu
  origin/todo

列出所有远程版本,origin是用来表示你当初执行git-clone时候的repository,你不可以直接checkout远程repository,但是你可以根据远程repository生成一个本地repository:

$ git checkout -b my-todo-copy origin/todo

Mysqldump及用户权限设置

新建用户

我们查看mysql的root用户的port字段,其中有值为’%’的,代表除本地外的其他地址的链接,但是port = ‘%’的行在root用户组中排在最下面,因此远程链接数据库就会因权限不足而失败。 我们选择新建一个用户来进行远程数据备份:

mysql> insert into mysql.user(Host,User,Password) values("%","yourname",password("1234"));

这样就创建了一个新用户,然后是给用户授权:

mysql> GRANT ALL PRIVILEGES ON *.* TO 'yourname'@'%'IDENTIFIED BY '1234';

刷新用户权限:

mysql> flush privileges;

mysqldump

用于将远程数据库导出到本地:

mysqldump -h192.125.117.89 -uyourname -p1234 databasename > /home/xieyu/databasename.sql

Ruby元编程五:编写代码的代码

Kernel#eval

kernel#eval会直接 执行字符串中的代码,并返回执行结果,和javascript中的eval()一样。

钩子方法

Class#inherited

当类被继承时,调用该方法,平时它什么也不做。,可以通过覆写它来形成类似回调函数的用法。 example:

class MyClass                         #inherited是Class的一个实例方法,对于一个特定的类,则是它的类方法。
    def self.inherited(param)         #注意inherited方法需要一个参数,参数是继承者的类名
        p "lala"
    end
end

MySonClass < MyClass; end       #=>"lala"

更多钩子方法

Class#inherited是最常用的,还有其他一些: Module#includeed Module#method_added Module#method_removed Module#method_undefined

Ruby元编程四:类定义

类定义

在ruby中,类定义和其他语言有所不同,当使用class关键字时,并非是在指定对象未来的行为方式,实际上是在运行代码。 类定义会返回最后一条语句的值。 每当通过class关键字打开一个类,这个类就成为当前类。在类定义中,当前对象self就是正在定义的类。

类实例变量

class MyClass
    @var = 0
    def self.method_one
        @var = 1
    end

    def method_two
        @var = 2
    end

    def method_three
        @var = 3
    end

    class<<self 
        def method_four
            x=4
        end

        def method_five
            x=5
        end
    end
end

obj = MyClass.new
obj.method_two           #=>2
obj.method_threee        #=>3
MyClass.method_one       #=>1
类实例变量只有类可以调用它,而类的实例则不行,实例变量则正好相反。
class<<self 用于一次性定义多个类方法

类变量

以@@开头,它们可以被子类或者类的实例所使用 后定义的同名类变量会覆盖之前的,不论它的位置,example:

@@v = 1
class A
  @@v = 2
end
@@v                        #=>2

扁平化作用域

使用class, def, module关键字会产生作用域门.但是如果想在一个模块中共享变量,就需要用到扁平化作用域. 使用ClassName = Class.new , ModuleName = module.new , define_method: method_name do ….end example:

    my_var = "success"
    MyClass = Class.new do
      puts "#{my_var} is get"
      define_method :printvar do
        puts "#{my_var} is in the method"
      end
    end
    MyClass.new.printvar

单件方法与类方法

Ruby允许给单个对象添加方法,这就是单件方法。example:

str = "hahaha"
def str.title?
    self.upcase == self
end

而类方法实际就是一种单件方法,因为类也是对象,它针对这个类生效。

类宏

Ruby对象没有属性,如果想要定义属性,就需要定义两个拟态方法,一个读方法和一个写方法。

class MyClass
    def height=(val)
        @height = val
    end

    def height
        @height
    end
end
obj = MyClass.new
obj.height = "160cm"
obj.height     #=>"160cm"

这样非常麻烦,我们可以使用module#attr_*系列方法,其中attr_reader()可以生成一个读方法;attr_writer()可以生成写方法;attr_accessor()则可以生成读写方法。
那么上面的例子就可以改写为:

class MyClass
    attr_accessor :height
end

环绕别名

从一个重新定义的方法中调用原始的,被重命名的版本。 三个步骤: 1. 通过alias 对原有方法定义一个别名 2. 覆写原有方法 3. 在该方法中调用别名方法 这样做的好处,可以改写原有方法,又不破坏原有功能 。

    class String  
        alias :old_reverse :reverse
        def reverse 
            "x#{old_reverse}x"
        end
    end 
    "abc".reverse  =>xcbax

Ruby元编程三:代码块

闭包

块可以作为一个闭包. example:

def my_method
    x="Goodbye"
    yield("cruel")
end
x = "Hello"
my_method {|y| "#{x} , #{y} world"}   #=>Hello, cruel world

块获得了局部绑定的变量x,并一直带着它。块并不能接受方法中定义的x=”Goodbye”

作用域与作用域门

程序会在三个地方关闭前一个作用域,同时打开一个新的作用域: 类定义 模块定义 方法 它们分别用关键字class,module,def为标志每一个关键字都充当了一个作用域门。

Proc类,proc,lambda , yield , &操作符

将块转化为对象

使用块需要分两步:1.把块的逻辑写好打包备用。2.调用块
块不是对象,如果想要存储一个块供以后执行,需要一个对象。
Ruby标准库提供Proc类,它接受一个块作为参数,从而将这个块转化为对象:

inc = Proc.new { |x| x+1}
inc.call(2)                       #=>3

而lambda , proc 本质也是将块转化为Proc类的实例化对象

lambda,proc的区别

1.使用return关键字的时候,lambda从它本身返回,而proc则会在定义它的作用域中返回。

def double(param)
    param.call*2
end
l=lambda {return 10}
double(l)                         #=>20
p=proc {return 10}
double(p)                         #=>报错LocalJumpError

2.参数数量 调用lambda的时候,如果参数数量不对则会报错,而proc会自动调整,忽略多余的,如果少了则会将其赋为nil

&操作符

它可以实现: 1.把这个块传递给另一个方法 2.把一个块转化为proc,只需要去掉&操作符即可。

example1:

def math(a, b)
    yield(a , b)
end
def teach_math(a , b , &block)
    puts math(a , b , &black)
end
teach_math(2,3) {|x ,y| x*y}         #=>6

example2:

def test(&block)
    block
end
t=test {|name| "Hello,#{name}"}
puts t.call("Tom")                         #=>Hello , Tom

Ruby_file

检验存在

File.file? "test.txt"                  #检验文件存在性
File.exists? "test.txt"                #只检验存在性,无论是文件夹还是其他的文件类型
File.directory? "test.txt"             #检验文件夹存在性

检验权限

File.readable?
File.writable?
File.executable?
File.owned?                         #文件是否属于当前用户组,windows下总是为true

文件属性

File.stat                    # 文件的详细属性
<File::Stat dev=0x700, ino=670451, mode=0100664, nlink=1, uid=1000, gid=1000, rdev=0x0, size=8, blksize=4096, blocks=8, atime=2013-06-05 09:37:38 +0800, mtime=2013-06-05 09:37:27 +0800, ctime=2013-06-05 09:37:27 +0800>

atime :最后读取的时间 mtime :最后修改内容的时间 ctime :最后修改的时间(包括修改权限等)

文件列表

列出某路径下的文件列表
Dir.getwd                           #获取工作路径
Dir.chdir '/bin'                    #改变工作路径
Dir.mkdir 'haha'                    #创建工作路径
Dir.entries "/"
Dir.foreach("/") do |file|
  puts file
end

文件写入

‘w’:写模式,覆盖写入
‘a’:附加模式,不会覆盖原有内容
open("test.txt" , "a") do |f|
  f.puts("aaaaaaaa")
end

ruby的HTTP类库

HTTP

NET::HTTP.get

    require "net/http"
    Net::HTTP.get('example.com', '/index.html')      # => String

    uri = URI('http://example.com/index.html?count=10')
    Net::HTTP.get(uri)        # => String

可以使用get方法进行下载:

    File.open("test.jpg" , "w") do |f|
        f.write Net::HTTP.get(uri) 
    end

接收动态参数:

    uri = URI('http://bbs.csdn.net/forums/ROR')
    params = { :limit => 10, :page => 3 }
    uri.query = URI.encode_www_form(params)
    res = Net::HTTP.get_response(uri)                         #get_response()方返回一个对象
    puts res.body if res.is_a?(Net::HTTPSuccess)

URI

Basic

    require 'uri'
    uri = URI("http://foo.com/posts?id=30&limit=5#time=1305298413")
    #=> #<URI::HTTP:0x00000000b14880
          URL:http://foo.com/posts?id=30&limit=5#time=1305298413>
    uri.scheme                                       #=> "http"
    uri.host                                            #=> "foo.com"
    uri.path                                            #=> "/posts"
    uri.query                                          #=> "id=30&limit=5"
    uri.fragment                                     #=> "time=1305298413"
    uri.to_s                                             #=> "http://foo.com/posts?id=30&limit=5#time=1305298413"

Joins URIs.

    require 'uri'
    p URI.join("http://example.com/","main.rbx")                 # => #< URL:http://localhost/main.rbx>
    p URI.join('http://example.com', 'foo')                            # => #< URL:http://example.com/foo>
    p URI.join('http://example.com', '/foo', '/bar')                 # => #< URL:http://example.com/bar>
    p URI.join('http://example.com', '/foo', 'bar')                  # => #< URL:http://example.com/bar>
    p URI.join('http://example.com', '/foo/', 'bar')                 # => #< URL:http://example.com/foo/bar>

Ruby_array类常用方法

取值

索引取值

    arr  = [1, 2, 3, 4, 5]
    arr[0]  = 1
    arr.at(0) = 1

take & drop

    arr.take 3              #=>[1, 2, 3]
    arr.drop 3              #=>[4, 5]

插值

pop, push && shift , unshift

    arr = [1, 2, 3, 4]
    arr.push(5)              #=> [1, 2, 3, 4, 5]
    arr << 6                  #=> [1, 2, 3, 4, 5, 6]
    arr.pop                    #=> 6
    arr                          #=> [1, 2, 3, 4, 5]
    arr.unshift 0             #=>[0, 1, 2, 3, 4, 5]
    arr.shift                   #=> 0 
    arr                           #=>[1, 2, 3, 4, 5]
    arr.push [1, 2, 3]      #=>[1, 2, 3, 4, 5, [1, 2, 3] ]

insert

    arr = [1, 2, 3, 4]
    arr.insert(3 , 5)        #=>[1, 2, 3, 5, 4]              #第一个参是索引值

删除

delete

    arr = [1, 2, 2, 3, 4]
    arr.delete 2            #=>2
    arr                         #=>[1, 3, 4]                       #delete根据实际值删除全部
    arr.delete_at 2        #=>4 
    arr                         #=>[1, 3]                           #delete_at根据索引值删除

compact(去nil)

    arr  = [1, nil, 2 , nil, 3 , 4]
    arr.compact             #=>[1, 2, 3, 4]
    arr                           #=>[1, nil, 2 , nil, 3 , 4]
    arr.compact!            #=>[1, 2, 3, 4]
    arr                           #=>[1, 2, 3, 4]
                                  #compact: v, adj 紧凑的,使紧凑

uniq(去重)

    arr = [2, 5, 6, 556, 6, 6, 8, 9, 0, 123, 556]
    arr.uniq                  #=> [2, 5, 6, 556, 8, 9, 0, 123]
    arr.uniq!

迭代

each

    arr = [1, 2, 3, 4]
    arr.each {|a| puts a+1}               执行结果:1234 #=>[1, 2, 3, 4]   #返回原数组

map&map!

    arr.map {|a| a+1}                            #=>[2, 3, 4, 5]           # 返回block处理过后的结果
    arr.map! {|a| a+1}                          #=>[2, 3, 4, 5]         # 返回block处理过后的结果,并改变原数组

选择

不改变原数组

    arr = [1, 2, 3, 4, 5, 6]
    arr.select { |a| a > 3 }     #=> [4, 5, 6]
    arr.reject { |a| a < 3 }     #=> [3, 4, 5, 6]                     #可以理解为!select
    arr.drop_while { |a| a < 4 } #=> [4, 5, 6]
    arr                          #=> [1, 2, 3, 4, 5, 6]       

修改原数组

    arr = [1, 2, 3, 4, 5, 6]
    arr.delete_if { |a| a < 4 } #=> [4, 5, 6]
    arr                         #=> [4, 5, 6]

    arr = [1, 2, 3, 4, 5, 6]
    arr.keep_if { |a| a < 4 } #=> [1, 2, 3]
    arr                       #=> [1, 2, 3]

连接

判断

存在性

    arr = [1, 2, 3, 4, 5, 6]
    arr.include? 1                 #=>true

为空?

    arr.empty?                     #=>false

相等

    a=[1, 2]
    b=[1, 2]
    a.eql? b                          #=>true 
    a.equal? b                       #=>false    equal?只有当二者是同一个对象的时候才会为true